aboutsummaryrefslogtreecommitdiffstats
path: root/tests/hazmat/primitives/test_x25519.py
blob: 0f83eb6e82ec2fa5fec12b442c6ed334acd4acef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.

from __future__ import absolute_import, division, print_function

import binascii
import os

import pytest

from cryptography.exceptions import _Reasons
from cryptography.hazmat.backends.interfaces import DHBackend
from cryptography.hazmat.primitives.asymmetric.x25519 import (
    X25519PrivateKey, X25519PublicKey
)

from ...utils import (
    load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm
)


@pytest.mark.supported(
    only_if=lambda backend: not backend.x25519_supported(),
    skip_message="Requires OpenSSL without X25519 support"
)
@pytest.mark.requires_backend_interface(interface=DHBackend)
def test_x25519_unsupported(backend):
    with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM):
        X25519PublicKey.from_public_bytes(b"0" * 32)

    with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM):
        X25519PrivateKey.generate()


@pytest.mark.supported(
    only_if=lambda backend: backend.x25519_supported(),
    skip_message="Requires OpenSSL with X25519 support"
)
@pytest.mark.requires_backend_interface(interface=DHBackend)
class TestX25519Exchange(object):
    @pytest.mark.parametrize(
        "vector",
        load_vectors_from_file(
            os.path.join("asymmetric", "X25519", "rfc7748.txt"),
            load_nist_vectors
        )
    )
    def test_rfc7748(self, vector, backend):
        private = binascii.unhexlify(vector["input_scalar"])
        public = binascii.unhexlify(vector["input_u"])
        shared_key = binascii.unhexlify(vector["output_u"])
        private_key = X25519PrivateKey._from_private_bytes(private)
        public_key = X25519PublicKey.from_public_bytes(public)
        computed_shared_key = private_key.exchange(public_key)
        assert computed_shared_key == shared_key

    def test_rfc7748_1000_iteration(self, backend):
        old_private = private = public = binascii.unhexlify(
            b"090000000000000000000000000000000000000000000000000000000000"
            b"0000"
        )
        shared_key = binascii.unhexlify(
            b"684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d9953"
            b"2c51"
        )
        private_key = X25519PrivateKey._from_private_bytes(private)
        public_key = X25519PublicKey.from_public_bytes(public)
        for _ in range(1000):
            computed_shared_key = private_key.exchange(public_key)
            private_key = X25519PrivateKey._from_private_bytes(
                computed_shared_key
            )
            public_key = X25519PublicKey.from_public_bytes(old_private)
            old_private = computed_shared_key

        assert computed_shared_key == shared_key

    def test_null_shared_key_raises_error(self, backend):
        """
        The vector used here is taken from wycheproof's x25519 test vectors
        """
        public = binascii.unhexlify(
            "5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157"
        )
        private = binascii.unhexlify(
            "78f1e8edf14481b389448dac8f59c70b038e7cf92ef2c7eff57a72466e115296"
        )
        private_key = X25519PrivateKey._from_private_bytes(
            private
        )
        public_key = X25519PublicKey.from_public_bytes(public)
        with pytest.raises(ValueError):
            private_key.exchange(public_key)

    # These vectors are also from RFC 7748
    # https://tools.ietf.org/html/rfc7748#section-6.1
    @pytest.mark.parametrize(
        ("private_bytes", "public_bytes"),
        [
            (
                binascii.unhexlify(
                    b"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba"
                    b"51db92c2a"
                ),
                binascii.unhexlify(
                    b"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98"
                    b"eaa9b4e6a"
                )
            ),
            (
                binascii.unhexlify(
                    b"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b2"
                    b"7ff88e0eb"
                ),
                binascii.unhexlify(
                    b"de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e1"
                    b"46f882b4f"
                )
            )
        ]
    )
    def test_public_bytes(self, private_bytes, public_bytes, backend):
        private_key = X25519PrivateKey._from_private_bytes(private_bytes)
        assert private_key.public_key().public_bytes() == public_bytes
        public_key = X25519PublicKey.from_public_bytes(public_bytes)
        assert public_key.public_bytes() == public_bytes

    def test_generate(self, backend):
        key = X25519PrivateKey.generate()
        assert key
        assert key.public_key()

    def test_invalid_type_exchange(self, backend):
        key = X25519PrivateKey.generate()
        with pytest.raises(TypeError):
            key.exchange(object())

    def test_invalid_length_from_public_bytes(self, backend):
        with pytest.raises(ValueError):
            X25519PublicKey.from_public_bytes(b"a" * 31)

        with pytest.raises(ValueError):
            X25519PublicKey.from_public_bytes(b"a" * 33)