1# This file is dual licensed under the terms of the Apache License, Version 2# 2.0, and the BSD License. See the LICENSE file in the root of this repository 3# for complete details. 4 5from __future__ import absolute_import, division, print_function 6 7import binascii 8import os 9 10import pytest 11 12from cryptography.exceptions import _Reasons 13from cryptography.hazmat.primitives import serialization 14from cryptography.hazmat.primitives.asymmetric.x25519 import ( 15 X25519PrivateKey, 16 X25519PublicKey, 17) 18 19from ...utils import ( 20 load_nist_vectors, 21 load_vectors_from_file, 22 raises_unsupported_algorithm, 23) 24 25 26@pytest.mark.supported( 27 only_if=lambda backend: not backend.x25519_supported(), 28 skip_message="Requires OpenSSL without X25519 support", 29) 30def test_x25519_unsupported(backend): 31 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM): 32 X25519PublicKey.from_public_bytes(b"0" * 32) 33 34 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM): 35 X25519PrivateKey.from_private_bytes(b"0" * 32) 36 37 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM): 38 X25519PrivateKey.generate() 39 40 41@pytest.mark.supported( 42 only_if=lambda backend: backend.x25519_supported(), 43 skip_message="Requires OpenSSL with X25519 support", 44) 45class TestX25519Exchange(object): 46 @pytest.mark.parametrize( 47 "vector", 48 load_vectors_from_file( 49 os.path.join("asymmetric", "X25519", "rfc7748.txt"), 50 load_nist_vectors, 51 ), 52 ) 53 def test_rfc7748(self, vector, backend): 54 private = binascii.unhexlify(vector["input_scalar"]) 55 public = binascii.unhexlify(vector["input_u"]) 56 shared_key = binascii.unhexlify(vector["output_u"]) 57 private_key = X25519PrivateKey.from_private_bytes(private) 58 public_key = X25519PublicKey.from_public_bytes(public) 59 computed_shared_key = private_key.exchange(public_key) 60 assert computed_shared_key == shared_key 61 62 def test_rfc7748_1000_iteration(self, backend): 63 old_private = private = public = binascii.unhexlify( 64 b"090000000000000000000000000000000000000000000000000000000000" 65 b"0000" 66 ) 67 shared_key = binascii.unhexlify( 68 b"684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d9953" 69 b"2c51" 70 ) 71 private_key = X25519PrivateKey.from_private_bytes(private) 72 public_key = X25519PublicKey.from_public_bytes(public) 73 for _ in range(1000): 74 computed_shared_key = private_key.exchange(public_key) 75 private_key = X25519PrivateKey.from_private_bytes( 76 computed_shared_key 77 ) 78 public_key = X25519PublicKey.from_public_bytes(old_private) 79 old_private = computed_shared_key 80 81 assert computed_shared_key == shared_key 82 83 def test_null_shared_key_raises_error(self, backend): 84 """ 85 The vector used here is taken from wycheproof's x25519 test vectors 86 """ 87 public = binascii.unhexlify( 88 "5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157" 89 ) 90 private = binascii.unhexlify( 91 "78f1e8edf14481b389448dac8f59c70b038e7cf92ef2c7eff57a72466e115296" 92 ) 93 private_key = X25519PrivateKey.from_private_bytes(private) 94 public_key = X25519PublicKey.from_public_bytes(public) 95 with pytest.raises(ValueError): 96 private_key.exchange(public_key) 97 98 def test_public_bytes_bad_args(self, backend): 99 key = X25519PrivateKey.generate().public_key() 100 with pytest.raises(ValueError): 101 key.public_bytes(None, serialization.PublicFormat.Raw) 102 with pytest.raises(TypeError): 103 key.public_bytes(serialization.Encoding.Raw) 104 105 # These vectors are also from RFC 7748 106 # https://tools.ietf.org/html/rfc7748#section-6.1 107 @pytest.mark.parametrize( 108 ("private_bytes", "public_bytes"), 109 [ 110 ( 111 binascii.unhexlify( 112 b"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba" 113 b"51db92c2a" 114 ), 115 binascii.unhexlify( 116 b"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98" 117 b"eaa9b4e6a" 118 ), 119 ), 120 ( 121 binascii.unhexlify( 122 b"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b2" 123 b"7ff88e0eb" 124 ), 125 binascii.unhexlify( 126 b"de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e1" 127 b"46f882b4f" 128 ), 129 ), 130 ], 131 ) 132 def test_pub_priv_bytes_raw(self, private_bytes, public_bytes, backend): 133 private_key = X25519PrivateKey.from_private_bytes(private_bytes) 134 assert ( 135 private_key.private_bytes( 136 serialization.Encoding.Raw, 137 serialization.PrivateFormat.Raw, 138 serialization.NoEncryption(), 139 ) 140 == private_bytes 141 ) 142 assert ( 143 private_key.public_key().public_bytes( 144 serialization.Encoding.Raw, serialization.PublicFormat.Raw 145 ) 146 == public_bytes 147 ) 148 public_key = X25519PublicKey.from_public_bytes(public_bytes) 149 assert ( 150 public_key.public_bytes( 151 serialization.Encoding.Raw, serialization.PublicFormat.Raw 152 ) 153 == public_bytes 154 ) 155 156 def test_generate(self, backend): 157 key = X25519PrivateKey.generate() 158 assert key 159 assert key.public_key() 160 161 def test_invalid_type_exchange(self, backend): 162 key = X25519PrivateKey.generate() 163 with pytest.raises(TypeError): 164 key.exchange(object()) 165 166 def test_invalid_length_from_public_bytes(self, backend): 167 with pytest.raises(ValueError): 168 X25519PublicKey.from_public_bytes(b"a" * 31) 169 170 with pytest.raises(ValueError): 171 X25519PublicKey.from_public_bytes(b"a" * 33) 172 173 def test_invalid_length_from_private_bytes(self, backend): 174 with pytest.raises(ValueError): 175 X25519PrivateKey.from_private_bytes(b"a" * 31) 176 177 with pytest.raises(ValueError): 178 X25519PrivateKey.from_private_bytes(b"a" * 33) 179 180 def test_invalid_private_bytes(self, backend): 181 key = X25519PrivateKey.generate() 182 with pytest.raises(ValueError): 183 key.private_bytes( 184 serialization.Encoding.Raw, 185 serialization.PrivateFormat.Raw, 186 None, 187 ) 188 189 with pytest.raises(ValueError): 190 key.private_bytes( 191 serialization.Encoding.Raw, 192 serialization.PrivateFormat.PKCS8, 193 None, 194 ) 195 196 with pytest.raises(ValueError): 197 key.private_bytes( 198 serialization.Encoding.PEM, 199 serialization.PrivateFormat.Raw, 200 serialization.NoEncryption(), 201 ) 202 203 def test_invalid_public_bytes(self, backend): 204 key = X25519PrivateKey.generate().public_key() 205 with pytest.raises(ValueError): 206 key.public_bytes( 207 serialization.Encoding.Raw, 208 serialization.PublicFormat.SubjectPublicKeyInfo, 209 ) 210 211 with pytest.raises(ValueError): 212 key.public_bytes( 213 serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 214 ) 215 216 with pytest.raises(ValueError): 217 key.public_bytes( 218 serialization.Encoding.PEM, serialization.PublicFormat.Raw 219 ) 220 221 @pytest.mark.parametrize( 222 ("encoding", "fmt", "encryption", "passwd", "load_func"), 223 [ 224 ( 225 serialization.Encoding.PEM, 226 serialization.PrivateFormat.PKCS8, 227 serialization.BestAvailableEncryption(b"password"), 228 b"password", 229 serialization.load_pem_private_key, 230 ), 231 ( 232 serialization.Encoding.DER, 233 serialization.PrivateFormat.PKCS8, 234 serialization.BestAvailableEncryption(b"password"), 235 b"password", 236 serialization.load_der_private_key, 237 ), 238 ( 239 serialization.Encoding.PEM, 240 serialization.PrivateFormat.PKCS8, 241 serialization.NoEncryption(), 242 None, 243 serialization.load_pem_private_key, 244 ), 245 ( 246 serialization.Encoding.DER, 247 serialization.PrivateFormat.PKCS8, 248 serialization.NoEncryption(), 249 None, 250 serialization.load_der_private_key, 251 ), 252 ], 253 ) 254 def test_round_trip_private_serialization( 255 self, encoding, fmt, encryption, passwd, load_func, backend 256 ): 257 key = X25519PrivateKey.generate() 258 serialized = key.private_bytes(encoding, fmt, encryption) 259 loaded_key = load_func(serialized, passwd, backend) 260 assert isinstance(loaded_key, X25519PrivateKey) 261 262 def test_buffer_protocol(self, backend): 263 private_bytes = bytearray(os.urandom(32)) 264 key = X25519PrivateKey.from_private_bytes(private_bytes) 265 assert ( 266 key.private_bytes( 267 serialization.Encoding.Raw, 268 serialization.PrivateFormat.Raw, 269 serialization.NoEncryption(), 270 ) 271 == private_bytes 272 ) 273