diff options
Diffstat (limited to 'tests/wycheproof')
| -rw-r--r-- | tests/wycheproof/__init__.py | 0 | ||||
| -rw-r--r-- | tests/wycheproof/test_aes.py | 144 | ||||
| -rw-r--r-- | tests/wycheproof/test_chacha20poly1305.py | 47 | ||||
| -rw-r--r-- | tests/wycheproof/test_cmac.py | 36 | ||||
| -rw-r--r-- | tests/wycheproof/test_dsa.py | 53 | ||||
| -rw-r--r-- | tests/wycheproof/test_ecdh.py | 115 | ||||
| -rw-r--r-- | tests/wycheproof/test_ecdsa.py | 93 | ||||
| -rw-r--r-- | tests/wycheproof/test_eddsa.py | 67 | ||||
| -rw-r--r-- | tests/wycheproof/test_hkdf.py | 50 | ||||
| -rw-r--r-- | tests/wycheproof/test_hmac.py | 66 | ||||
| -rw-r--r-- | tests/wycheproof/test_keywrap.py | 61 | ||||
| -rw-r--r-- | tests/wycheproof/test_rsa.py | 260 | ||||
| -rw-r--r-- | tests/wycheproof/test_utils.py | 21 | ||||
| -rw-r--r-- | tests/wycheproof/test_x25519.py | 42 | ||||
| -rw-r--r-- | tests/wycheproof/test_x448.py | 48 |
15 files changed, 1103 insertions, 0 deletions
diff --git a/tests/wycheproof/__init__.py b/tests/wycheproof/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/wycheproof/__init__.py diff --git a/tests/wycheproof/test_aes.py b/tests/wycheproof/test_aes.py new file mode 100644 index 00000000..55e45454 --- /dev/null +++ b/tests/wycheproof/test_aes.py @@ -0,0 +1,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 pytest + +from cryptography.exceptions import InvalidTag +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives import padding +from cryptography.hazmat.primitives.ciphers import ( + Cipher, algorithms, modes +) +from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESGCM + +from ..hazmat.primitives.test_aead import _aead_supported + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +@pytest.mark.wycheproof_tests("aes_cbc_pkcs5_test.json") +def test_aes_cbc_pkcs5(backend, wycheproof): + key = binascii.unhexlify(wycheproof.testcase["key"]) + iv = binascii.unhexlify(wycheproof.testcase["iv"]) + msg = binascii.unhexlify(wycheproof.testcase["msg"]) + ct = binascii.unhexlify(wycheproof.testcase["ct"]) + + padder = padding.PKCS7(128).padder() + + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend) + enc = cipher.encryptor() + computed_ct = enc.update( + padder.update(msg) + padder.finalize()) + enc.finalize() + dec = cipher.decryptor() + padded_msg = dec.update(ct) + dec.finalize() + unpadder = padding.PKCS7(128).unpadder() + if wycheproof.valid or wycheproof.acceptable: + assert computed_ct == ct + computed_msg = unpadder.update(padded_msg) + unpadder.finalize() + assert computed_msg == msg + else: + assert computed_ct != ct + with pytest.raises(ValueError): + unpadder.update(padded_msg) + unpadder.finalize() + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +@pytest.mark.wycheproof_tests("aes_gcm_test.json") +def test_aes_gcm(backend, wycheproof): + key = binascii.unhexlify(wycheproof.testcase["key"]) + iv = binascii.unhexlify(wycheproof.testcase["iv"]) + aad = binascii.unhexlify(wycheproof.testcase["aad"]) + msg = binascii.unhexlify(wycheproof.testcase["msg"]) + ct = binascii.unhexlify(wycheproof.testcase["ct"]) + tag = binascii.unhexlify(wycheproof.testcase["tag"]) + if wycheproof.valid or wycheproof.acceptable: + enc = Cipher(algorithms.AES(key), modes.GCM(iv), backend).encryptor() + enc.authenticate_additional_data(aad) + computed_ct = enc.update(msg) + enc.finalize() + computed_tag = enc.tag + assert computed_ct == ct + assert computed_tag == tag + dec = Cipher( + algorithms.AES(key), + modes.GCM(iv, tag, min_tag_length=len(tag)), + backend + ).decryptor() + dec.authenticate_additional_data(aad) + computed_msg = dec.update(ct) + dec.finalize() + assert computed_msg == msg + elif len(iv) == 0: + with pytest.raises(ValueError): + Cipher(algorithms.AES(key), modes.GCM(iv), backend) + else: + dec = Cipher( + algorithms.AES(key), + modes.GCM(iv, tag, min_tag_length=len(tag)), + backend + ).decryptor() + dec.authenticate_additional_data(aad) + dec.update(ct) + with pytest.raises(InvalidTag): + dec.finalize() + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +@pytest.mark.wycheproof_tests("aes_gcm_test.json") +def test_aes_gcm_aead_api(backend, wycheproof): + key = binascii.unhexlify(wycheproof.testcase["key"]) + iv = binascii.unhexlify(wycheproof.testcase["iv"]) + aad = binascii.unhexlify(wycheproof.testcase["aad"]) + msg = binascii.unhexlify(wycheproof.testcase["msg"]) + ct = binascii.unhexlify(wycheproof.testcase["ct"]) + tag = binascii.unhexlify(wycheproof.testcase["tag"]) + aesgcm = AESGCM(key) + if wycheproof.valid or wycheproof.acceptable: + computed_ct = aesgcm.encrypt(iv, msg, aad) + assert computed_ct == ct + tag + computed_msg = aesgcm.decrypt(iv, ct + tag, aad) + assert computed_msg == msg + elif len(iv) == 0: + with pytest.raises(ValueError): + aesgcm.encrypt(iv, msg, aad) + else: + with pytest.raises(InvalidTag): + aesgcm.decrypt(iv, ct + tag, aad) + + +@pytest.mark.skipif( + not _aead_supported(AESCCM), + reason="Requires OpenSSL with AES-CCM support", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +@pytest.mark.wycheproof_tests("aes_ccm_test.json") +def test_aes_ccm_aead_api(backend, wycheproof): + key = binascii.unhexlify(wycheproof.testcase["key"]) + iv = binascii.unhexlify(wycheproof.testcase["iv"]) + aad = binascii.unhexlify(wycheproof.testcase["aad"]) + msg = binascii.unhexlify(wycheproof.testcase["msg"]) + ct = binascii.unhexlify(wycheproof.testcase["ct"]) + tag = binascii.unhexlify(wycheproof.testcase["tag"]) + + if ( + wycheproof.invalid and + wycheproof.testcase["comment"] == "Invalid tag size" + ): + with pytest.raises(ValueError): + AESCCM(key, tag_length=wycheproof.testgroup["tagSize"] // 8) + return + + aesccm = AESCCM(key, tag_length=wycheproof.testgroup["tagSize"] // 8) + if wycheproof.valid or wycheproof.acceptable: + computed_ct = aesccm.encrypt(iv, msg, aad) + assert computed_ct == ct + tag + computed_msg = aesccm.decrypt(iv, ct + tag, aad) + assert computed_msg == msg + elif not 7 <= len(iv) <= 13: + with pytest.raises(ValueError): + aesccm.decrypt(iv, ct + tag, aad) + else: + with pytest.raises(InvalidTag): + aesccm.decrypt(iv, ct + tag, aad) diff --git a/tests/wycheproof/test_chacha20poly1305.py b/tests/wycheproof/test_chacha20poly1305.py new file mode 100644 index 00000000..deef5a0a --- /dev/null +++ b/tests/wycheproof/test_chacha20poly1305.py @@ -0,0 +1,47 @@ +# 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 pytest + +from cryptography.exceptions import InvalidTag +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 + +from ..hazmat.primitives.test_aead import _aead_supported + + +@pytest.mark.skipif( + not _aead_supported(ChaCha20Poly1305), + reason="Requires OpenSSL with ChaCha20Poly1305 support" +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +@pytest.mark.wycheproof_tests("chacha20_poly1305_test.json") +def test_chacha2poly1305(wycheproof): + key = binascii.unhexlify(wycheproof.testcase["key"]) + iv = binascii.unhexlify(wycheproof.testcase["iv"]) + aad = binascii.unhexlify(wycheproof.testcase["aad"]) + msg = binascii.unhexlify(wycheproof.testcase["msg"]) + ct = binascii.unhexlify(wycheproof.testcase["ct"]) + tag = binascii.unhexlify(wycheproof.testcase["tag"]) + + if wycheproof.valid: + chacha = ChaCha20Poly1305(key) + computed_ct = chacha.encrypt(iv, msg, aad) + assert computed_ct == ct + tag + computed_msg = chacha.decrypt(iv, ct + tag, aad) + assert computed_msg == msg + elif len(iv) != 12: + chacha = ChaCha20Poly1305(key) + with pytest.raises(ValueError): + chacha.encrypt(iv, msg, aad) + with pytest.raises(ValueError): + chacha.decrypt(iv, ct + tag, aad) + else: + chacha = ChaCha20Poly1305(key) + with pytest.raises(InvalidTag): + chacha.decrypt(iv, msg + tag, aad) diff --git a/tests/wycheproof/test_cmac.py b/tests/wycheproof/test_cmac.py new file mode 100644 index 00000000..bef85839 --- /dev/null +++ b/tests/wycheproof/test_cmac.py @@ -0,0 +1,36 @@ +# 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 pytest + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.backends.interfaces import CMACBackend +from cryptography.hazmat.primitives.ciphers.algorithms import AES +from cryptography.hazmat.primitives.cmac import CMAC + + +@pytest.mark.requires_backend_interface(interface=CMACBackend) +@pytest.mark.wycheproof_tests("aes_cmac_test.json") +def test_aes_cmac(backend, wycheproof): + key = binascii.unhexlify(wycheproof.testcase["key"]) + msg = binascii.unhexlify(wycheproof.testcase["msg"]) + tag = binascii.unhexlify(wycheproof.testcase["tag"]) + + # skip truncated tags, which we don't support in the API + if wycheproof.valid and len(tag) == 16: + ctx = CMAC(AES(key), backend) + ctx.update(msg) + ctx.verify(tag) + elif len(key) not in [16, 24, 32]: + with pytest.raises(ValueError): + CMAC(AES(key), backend) + else: + ctx = CMAC(AES(key), backend) + ctx.update(msg) + with pytest.raises(InvalidSignature): + ctx.verify(tag) diff --git a/tests/wycheproof/test_dsa.py b/tests/wycheproof/test_dsa.py new file mode 100644 index 00000000..5019dc6a --- /dev/null +++ b/tests/wycheproof/test_dsa.py @@ -0,0 +1,53 @@ +# 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 pytest + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.backends.interfaces import DSABackend +from cryptography.hazmat.primitives import hashes, serialization + + +_DIGESTS = { + "SHA-1": hashes.SHA1(), + "SHA-224": hashes.SHA224(), + "SHA-256": hashes.SHA256(), +} + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +@pytest.mark.wycheproof_tests( + "dsa_test.json", + "dsa_2048_224_sha224_test.json", + "dsa_2048_224_sha256_test.json", + "dsa_2048_256_sha256_test.json", + "dsa_3072_256_sha256_test.json", +) +def test_dsa_signature(backend, wycheproof): + key = serialization.load_der_public_key( + binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend + ) + digest = _DIGESTS[wycheproof.testgroup["sha"]] + + if ( + wycheproof.valid or ( + wycheproof.acceptable and not wycheproof.has_flag("NoLeadingZero") + ) + ): + key.verify( + binascii.unhexlify(wycheproof.testcase["sig"]), + binascii.unhexlify(wycheproof.testcase["msg"]), + digest, + ) + else: + with pytest.raises(InvalidSignature): + key.verify( + binascii.unhexlify(wycheproof.testcase["sig"]), + binascii.unhexlify(wycheproof.testcase["msg"]), + digest, + ) diff --git a/tests/wycheproof/test_ecdh.py b/tests/wycheproof/test_ecdh.py new file mode 100644 index 00000000..b89dc68c --- /dev/null +++ b/tests/wycheproof/test_ecdh.py @@ -0,0 +1,115 @@ +# 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 pytest + +from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat.backends.interfaces import EllipticCurveBackend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec + +from ..hazmat.primitives.test_ec import _skip_exchange_algorithm_unsupported + + +_CURVES = { + "secp224r1": ec.SECP224R1(), + "secp256r1": ec.SECP256R1(), + "secp384r1": ec.SECP384R1(), + "secp521r1": ec.SECP521R1(), + "secp224k1": None, + "secp256k1": ec.SECP256K1(), + "brainpoolP224r1": None, + "brainpoolP256r1": ec.BrainpoolP256R1(), + "brainpoolP320r1": None, + "brainpoolP384r1": ec.BrainpoolP384R1(), + "brainpoolP512r1": ec.BrainpoolP512R1(), + "brainpoolP224t1": None, + "brainpoolP256t1": None, + "brainpoolP320t1": None, + "brainpoolP384t1": None, + "brainpoolP512t1": None, +} + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +@pytest.mark.wycheproof_tests( + "ecdh_test.json", + "ecdh_brainpoolP224r1_test.json", + "ecdh_brainpoolP256r1_test.json", + "ecdh_brainpoolP320r1_test.json", + "ecdh_brainpoolP384r1_test.json", + "ecdh_brainpoolP512r1_test.json", + "ecdh_secp224r1_test.json", + "ecdh_secp256k1_test.json", + "ecdh_secp256r1_test.json", + "ecdh_secp384r1_test.json", + "ecdh_secp521r1_test.json", +) +def test_ecdh(backend, wycheproof): + curve = _CURVES[wycheproof.testgroup["curve"]] + if curve is None: + pytest.skip( + "Unsupported curve ({})".format(wycheproof.testgroup["curve"]) + ) + _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) + + private_key = ec.derive_private_key( + int(wycheproof.testcase["private"], 16), curve, backend + ) + + try: + public_key = serialization.load_der_public_key( + binascii.unhexlify(wycheproof.testcase["public"]), backend + ) + except NotImplementedError: + assert wycheproof.has_flag("UnnamedCurve") + return + except ValueError: + assert wycheproof.invalid or wycheproof.acceptable + return + except UnsupportedAlgorithm: + return + + if wycheproof.valid or wycheproof.acceptable: + computed_shared = private_key.exchange(ec.ECDH(), public_key) + expected_shared = binascii.unhexlify(wycheproof.testcase["shared"]) + assert computed_shared == expected_shared + else: + with pytest.raises(ValueError): + private_key.exchange(ec.ECDH(), public_key) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +@pytest.mark.wycheproof_tests( + "ecdh_secp224r1_ecpoint_test.json", + "ecdh_secp256r1_ecpoint_test.json", + "ecdh_secp384r1_ecpoint_test.json", + "ecdh_secp521r1_ecpoint_test.json", +) +def test_ecdh_ecpoint(backend, wycheproof): + curve = _CURVES[wycheproof.testgroup["curve"]] + _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) + + private_key = ec.derive_private_key( + int(wycheproof.testcase["private"], 16), curve, backend + ) + + if wycheproof.invalid: + with pytest.raises(ValueError): + ec.EllipticCurvePublicKey.from_encoded_point( + curve, binascii.unhexlify(wycheproof.testcase["public"]) + ) + return + + assert wycheproof.valid or wycheproof.acceptable + public_key = ec.EllipticCurvePublicKey.from_encoded_point( + curve, binascii.unhexlify(wycheproof.testcase["public"]) + ) + computed_shared = private_key.exchange(ec.ECDH(), public_key) + expected_shared = binascii.unhexlify(wycheproof.testcase["shared"]) + assert computed_shared == expected_shared diff --git a/tests/wycheproof/test_ecdsa.py b/tests/wycheproof/test_ecdsa.py new file mode 100644 index 00000000..49a3388d --- /dev/null +++ b/tests/wycheproof/test_ecdsa.py @@ -0,0 +1,93 @@ +# 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 pytest + +from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm +from cryptography.hazmat.backends.interfaces import EllipticCurveBackend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec + + +_DIGESTS = { + "SHA-1": hashes.SHA1(), + "SHA-224": hashes.SHA224(), + "SHA-256": hashes.SHA256(), + "SHA-384": hashes.SHA384(), + "SHA-512": hashes.SHA512(), + "SHA3-224": hashes.SHA3_224(), + "SHA3-256": hashes.SHA3_256(), + "SHA3-384": hashes.SHA3_384(), + "SHA3-512": hashes.SHA3_512(), +} + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +@pytest.mark.wycheproof_tests( + "ecdsa_test.json", + "ecdsa_brainpoolP224r1_sha224_test.json", + "ecdsa_brainpoolP256r1_sha256_test.json", + "ecdsa_brainpoolP320r1_sha384_test.json", + "ecdsa_brainpoolP384r1_sha384_test.json", + "ecdsa_brainpoolP512r1_sha512_test.json", + "ecdsa_secp224r1_sha224_test.json", + "ecdsa_secp224r1_sha256_test.json", + "ecdsa_secp224r1_sha512_test.json", + "ecdsa_secp224r1_sha3_224_test.json", + "ecdsa_secp224r1_sha3_256_test.json", + "ecdsa_secp224r1_sha3_512_test.json", + "ecdsa_secp256k1_sha256_test.json", + "ecdsa_secp256k1_sha512_test.json", + "ecdsa_secp256k1_sha3_256_test.json", + "ecdsa_secp256k1_sha3_512_test.json", + "ecdsa_secp256r1_sha256_test.json", + "ecdsa_secp256r1_sha512_test.json", + "ecdsa_secp256r1_sha3_256_test.json", + "ecdsa_secp256r1_sha3_512_test.json", + "ecdsa_secp384r1_sha384_test.json", + "ecdsa_secp384r1_sha512_test.json", + "ecdsa_secp384r1_sha3_384_test.json", + "ecdsa_secp384r1_sha3_512_test.json", + "ecdsa_secp521r1_sha512_test.json", + "ecdsa_secp521r1_sha3_512_test.json", +) +def test_ecdsa_signature(backend, wycheproof): + try: + key = serialization.load_der_public_key( + binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend + ) + except (UnsupportedAlgorithm, ValueError): + # In some OpenSSL 1.0.2s, some keys fail to load with ValueError, + # instead of Unsupported Algorithm. We can remove handling for that + # exception when we drop support. + pytest.skip( + "unable to load key (curve {})".format( + wycheproof.testgroup["key"]["curve"] + ) + ) + digest = _DIGESTS[wycheproof.testgroup["sha"]] + + if not backend.hash_supported(digest): + pytest.skip("Hash {} not supported".format(digest)) + + if ( + wycheproof.valid or + (wycheproof.acceptable and not wycheproof.has_flag("MissingZero")) + ): + key.verify( + binascii.unhexlify(wycheproof.testcase["sig"]), + binascii.unhexlify(wycheproof.testcase["msg"]), + ec.ECDSA(digest), + ) + else: + with pytest.raises(InvalidSignature): + key.verify( + binascii.unhexlify(wycheproof.testcase["sig"]), + binascii.unhexlify(wycheproof.testcase["msg"]), + ec.ECDSA(digest), + ) diff --git a/tests/wycheproof/test_eddsa.py b/tests/wycheproof/test_eddsa.py new file mode 100644 index 00000000..5beca130 --- /dev/null +++ b/tests/wycheproof/test_eddsa.py @@ -0,0 +1,67 @@ +# 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 pytest + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey +from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" +) +@pytest.mark.wycheproof_tests( + "eddsa_test.json", +) +def test_ed25519_signature(backend, wycheproof): + # We want to fail if/when wycheproof adds more edwards curve tests + # so we can add them as well. + assert wycheproof.testgroup["key"]["curve"] == "edwards25519" + + key = Ed25519PublicKey.from_public_bytes( + binascii.unhexlify(wycheproof.testgroup["key"]["pk"]) + ) + + if wycheproof.valid or wycheproof.acceptable: + key.verify( + binascii.unhexlify(wycheproof.testcase["sig"]), + binascii.unhexlify(wycheproof.testcase["msg"]), + ) + else: + with pytest.raises(InvalidSignature): + key.verify( + binascii.unhexlify(wycheproof.testcase["sig"]), + binascii.unhexlify(wycheproof.testcase["msg"]), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" +) +@pytest.mark.wycheproof_tests( + "ed448_test.json", +) +def test_ed448_signature(backend, wycheproof): + key = Ed448PublicKey.from_public_bytes( + binascii.unhexlify(wycheproof.testgroup["key"]["pk"]) + ) + + if wycheproof.valid or wycheproof.acceptable: + key.verify( + binascii.unhexlify(wycheproof.testcase["sig"]), + binascii.unhexlify(wycheproof.testcase["msg"]), + ) + else: + with pytest.raises(InvalidSignature): + key.verify( + binascii.unhexlify(wycheproof.testcase["sig"]), + binascii.unhexlify(wycheproof.testcase["msg"]), + ) diff --git a/tests/wycheproof/test_hkdf.py b/tests/wycheproof/test_hkdf.py new file mode 100644 index 00000000..7038d623 --- /dev/null +++ b/tests/wycheproof/test_hkdf.py @@ -0,0 +1,50 @@ +# 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 pytest + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.hkdf import HKDF + + +_HASH_ALGORITHMS = { + "HKDF-SHA-1": hashes.SHA1(), + "HKDF-SHA-256": hashes.SHA256(), + "HKDF-SHA-384": hashes.SHA384(), + "HKDF-SHA-512": hashes.SHA512(), +} + + +@pytest.mark.wycheproof_tests( + "hkdf_sha1_test.json", + "hkdf_sha256_test.json", + "hkdf_sha384_test.json", + "hkdf_sha512_test.json", +) +def test_hkdf(backend, wycheproof): + hash_algo = _HASH_ALGORITHMS[wycheproof.testfiledata["algorithm"]] + if wycheproof.invalid: + with pytest.raises(ValueError): + HKDF( + algorithm=hash_algo, + length=wycheproof.testcase["size"], + salt=binascii.unhexlify(wycheproof.testcase["salt"]), + info=binascii.unhexlify(wycheproof.testcase["info"]), + backend=backend + ) + return + + h = HKDF( + algorithm=hash_algo, + length=wycheproof.testcase["size"], + salt=binascii.unhexlify(wycheproof.testcase["salt"]), + info=binascii.unhexlify(wycheproof.testcase["info"]), + backend=backend + ) + result = h.derive(binascii.unhexlify(wycheproof.testcase["ikm"])) + assert result == binascii.unhexlify(wycheproof.testcase["okm"]) diff --git a/tests/wycheproof/test_hmac.py b/tests/wycheproof/test_hmac.py new file mode 100644 index 00000000..0cf908fe --- /dev/null +++ b/tests/wycheproof/test_hmac.py @@ -0,0 +1,66 @@ +# 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 pytest + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes, hmac + + +_HMAC_ALGORITHMS = { + "HMACSHA1": hashes.SHA1(), + "HMACSHA224": hashes.SHA224(), + "HMACSHA256": hashes.SHA256(), + "HMACSHA384": hashes.SHA384(), + "HMACSHA512": hashes.SHA512(), + "HMACSHA3-224": hashes.SHA3_224(), + "HMACSHA3-256": hashes.SHA3_256(), + "HMACSHA3-384": hashes.SHA3_384(), + "HMACSHA3-512": hashes.SHA3_512(), +} + + +@pytest.mark.wycheproof_tests( + "hmac_sha1_test.json", + "hmac_sha224_test.json", + "hmac_sha256_test.json", + "hmac_sha384_test.json", + "hmac_sha3_224_test.json", + "hmac_sha3_256_test.json", + "hmac_sha3_384_test.json", + "hmac_sha3_512_test.json", + "hmac_sha512_test.json", +) +def test_hmac(backend, wycheproof): + hash_algo = _HMAC_ALGORITHMS[wycheproof.testfiledata["algorithm"]] + if wycheproof.testgroup["tagSize"] // 8 != hash_algo.digest_size: + pytest.skip("Truncated HMAC not supported") + if not backend.hash_supported(hash_algo): + pytest.skip("Hash {} not supported".format(hash_algo.name)) + + h = hmac.HMAC( + key=binascii.unhexlify(wycheproof.testcase["key"]), + algorithm=hash_algo, + backend=backend, + ) + h.update(binascii.unhexlify(wycheproof.testcase["msg"])) + + if wycheproof.invalid: + with pytest.raises(InvalidSignature): + h.verify(binascii.unhexlify(wycheproof.testcase["tag"])) + else: + tag = h.finalize() + assert tag == binascii.unhexlify(wycheproof.testcase["tag"]) + + h = hmac.HMAC( + key=binascii.unhexlify(wycheproof.testcase["key"]), + algorithm=hash_algo, + backend=backend, + ) + h.update(binascii.unhexlify(wycheproof.testcase["msg"])) + h.verify(binascii.unhexlify(wycheproof.testcase["tag"])) diff --git a/tests/wycheproof/test_keywrap.py b/tests/wycheproof/test_keywrap.py new file mode 100644 index 00000000..5f694e4d --- /dev/null +++ b/tests/wycheproof/test_keywrap.py @@ -0,0 +1,61 @@ +# 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 pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives import keywrap + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +@pytest.mark.wycheproof_tests("kwp_test.json") +def test_keywrap_with_padding(backend, wycheproof): + wrapping_key = binascii.unhexlify(wycheproof.testcase["key"]) + key_to_wrap = binascii.unhexlify(wycheproof.testcase["msg"]) + expected = binascii.unhexlify(wycheproof.testcase["ct"]) + + result = keywrap.aes_key_wrap_with_padding( + wrapping_key, key_to_wrap, backend + ) + if wycheproof.valid or wycheproof.acceptable: + assert result == expected + + if wycheproof.valid or (wycheproof.acceptable and not len(expected) < 16): + result = keywrap.aes_key_unwrap_with_padding( + wrapping_key, expected, backend + ) + assert result == key_to_wrap + else: + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap_with_padding( + wrapping_key, expected, backend + ) + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +@pytest.mark.wycheproof_tests("kw_test.json") +def test_keywrap(backend, wycheproof): + wrapping_key = binascii.unhexlify(wycheproof.testcase["key"]) + key_to_wrap = binascii.unhexlify(wycheproof.testcase["msg"]) + expected = binascii.unhexlify(wycheproof.testcase["ct"]) + + if ( + wycheproof.valid or ( + wycheproof.acceptable and + wycheproof.testcase["comment"] != "invalid size of wrapped key" + ) + ): + result = keywrap.aes_key_wrap(wrapping_key, key_to_wrap, backend) + assert result == expected + + if wycheproof.valid or wycheproof.acceptable: + result = keywrap.aes_key_unwrap(wrapping_key, expected, backend) + assert result == key_to_wrap + else: + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap(wrapping_key, expected, backend) diff --git a/tests/wycheproof/test_rsa.py b/tests/wycheproof/test_rsa.py new file mode 100644 index 00000000..a7c26e6a --- /dev/null +++ b/tests/wycheproof/test_rsa.py @@ -0,0 +1,260 @@ +# 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 pytest + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.backends.interfaces import RSABackend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding + + +_DIGESTS = { + "SHA-1": hashes.SHA1(), + "SHA-224": hashes.SHA224(), + "SHA-256": hashes.SHA256(), + "SHA-384": hashes.SHA384(), + "SHA-512": hashes.SHA512(), + # Not supported by OpenSSL for RSA signing + "SHA-512/224": None, + "SHA-512/256": None, + "SHA3-224": hashes.SHA3_224(), + "SHA3-256": hashes.SHA3_256(), + "SHA3-384": hashes.SHA3_384(), + "SHA3-512": hashes.SHA3_512(), +} + + +def should_verify(backend, wycheproof): + if wycheproof.valid: + return True + + if wycheproof.acceptable: + if ( + ( + backend._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER or + backend._lib.CRYPTOGRAPHY_IS_LIBRESSL + ) and wycheproof.has_flag("MissingNull") + ): + return False + return True + + return False + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.wycheproof_tests( + "rsa_signature_test.json", + "rsa_signature_2048_sha224_test.json", + "rsa_signature_2048_sha256_test.json", + "rsa_signature_2048_sha384_test.json", + "rsa_signature_2048_sha512_test.json", + "rsa_signature_2048_sha512_224_test.json", + "rsa_signature_2048_sha512_256_test.json", + "rsa_signature_2048_sha3_224_test.json", + "rsa_signature_2048_sha3_256_test.json", + "rsa_signature_2048_sha3_384_test.json", + "rsa_signature_2048_sha3_512_test.json", + "rsa_signature_3072_sha256_test.json", + "rsa_signature_3072_sha384_test.json", + "rsa_signature_3072_sha512_test.json", + "rsa_signature_3072_sha512_256_test.json", + "rsa_signature_3072_sha3_256_test.json", + "rsa_signature_3072_sha3_384_test.json", + "rsa_signature_3072_sha3_512_test.json", + "rsa_signature_4096_sha384_test.json", + "rsa_signature_4096_sha512_test.json", + "rsa_signature_4096_sha512_256_test.json", +) +def test_rsa_pkcs1v15_signature(backend, wycheproof): + key = serialization.load_der_public_key( + binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend + ) + digest = _DIGESTS[wycheproof.testgroup["sha"]] + + if digest is None or not backend.hash_supported(digest): + pytest.skip( + "Hash {} not supported".format(wycheproof.testgroup["sha"]) + ) + + if should_verify(backend, wycheproof): + key.verify( + binascii.unhexlify(wycheproof.testcase["sig"]), + binascii.unhexlify(wycheproof.testcase["msg"]), + padding.PKCS1v15(), + digest, + ) + else: + with pytest.raises(InvalidSignature): + key.verify( + binascii.unhexlify(wycheproof.testcase["sig"]), + binascii.unhexlify(wycheproof.testcase["msg"]), + padding.PKCS1v15(), + digest, + ) + + +@pytest.mark.wycheproof_tests( + "rsa_sig_gen_misc_test.json" +) +def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): + key = serialization.load_pem_private_key( + wycheproof.testgroup["privateKeyPem"].encode(), + password=None, + backend=backend, + ) + digest = _DIGESTS[wycheproof.testgroup["sha"]] + + sig = key.sign( + binascii.unhexlify(wycheproof.testcase["msg"]), + padding.PKCS1v15(), + digest, + ) + assert sig == binascii.unhexlify(wycheproof.testcase["sig"]) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.wycheproof_tests( + "rsa_pss_2048_sha1_mgf1_20_test.json", + "rsa_pss_2048_sha256_mgf1_0_test.json", + "rsa_pss_2048_sha256_mgf1_32_test.json", + "rsa_pss_2048_sha512_256_mgf1_28_test.json", + "rsa_pss_2048_sha512_256_mgf1_32_test.json", + "rsa_pss_3072_sha256_mgf1_32_test.json", + "rsa_pss_4096_sha256_mgf1_32_test.json", + "rsa_pss_4096_sha512_mgf1_32_test.json", + "rsa_pss_misc_test.json", +) +def test_rsa_pss_signature(backend, wycheproof): + key = serialization.load_der_public_key( + binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend + ) + digest = _DIGESTS[wycheproof.testgroup["sha"]] + mgf_digest = _DIGESTS[wycheproof.testgroup["mgfSha"]] + + if digest is None or mgf_digest is None: + pytest.skip( + "PSS with digest={} and MGF digest={} not supported".format( + wycheproof.testgroup["sha"], wycheproof.testgroup["mgfSha"], + ) + ) + + if wycheproof.valid or wycheproof.acceptable: + key.verify( + binascii.unhexlify(wycheproof.testcase["sig"]), + binascii.unhexlify(wycheproof.testcase["msg"]), + padding.PSS( + mgf=padding.MGF1(mgf_digest), + salt_length=wycheproof.testgroup["sLen"] + ), + digest + ) + else: + with pytest.raises(InvalidSignature): + key.verify( + binascii.unhexlify(wycheproof.testcase["sig"]), + binascii.unhexlify(wycheproof.testcase["msg"]), + padding.PSS( + mgf=padding.MGF1(mgf_digest), + salt_length=wycheproof.testgroup["sLen"] + ), + digest + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.supported( + only_if=lambda backend: ( + backend._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER or + backend._lib.CRYPTOGRAPHY_IS_LIBRESSL + ), + skip_message=( + "A handful of these tests fail on OpenSSL 1.0.2 and since upstream " + "isn't maintaining it, they'll never be fixed." + ), +) +@pytest.mark.wycheproof_tests( + "rsa_oaep_2048_sha1_mgf1sha1_test.json", + "rsa_oaep_2048_sha224_mgf1sha1_test.json", + "rsa_oaep_2048_sha224_mgf1sha224_test.json", + "rsa_oaep_2048_sha256_mgf1sha1_test.json", + "rsa_oaep_2048_sha256_mgf1sha256_test.json", + "rsa_oaep_2048_sha384_mgf1sha1_test.json", + "rsa_oaep_2048_sha384_mgf1sha384_test.json", + "rsa_oaep_2048_sha512_mgf1sha1_test.json", + "rsa_oaep_2048_sha512_mgf1sha512_test.json", + "rsa_oaep_3072_sha256_mgf1sha1_test.json", + "rsa_oaep_3072_sha256_mgf1sha256_test.json", + "rsa_oaep_3072_sha512_mgf1sha1_test.json", + "rsa_oaep_3072_sha512_mgf1sha512_test.json", + "rsa_oaep_4096_sha256_mgf1sha1_test.json", + "rsa_oaep_4096_sha256_mgf1sha256_test.json", + "rsa_oaep_4096_sha512_mgf1sha1_test.json", + "rsa_oaep_4096_sha512_mgf1sha512_test.json", + "rsa_oaep_misc_test.json", +) +def test_rsa_oaep_encryption(backend, wycheproof): + key = serialization.load_pem_private_key( + wycheproof.testgroup["privateKeyPem"].encode("ascii"), + password=None, + backend=backend, + ) + digest = _DIGESTS[wycheproof.testgroup["sha"]] + mgf_digest = _DIGESTS[wycheproof.testgroup["mgfSha"]] + + padding_algo = padding.OAEP( + mgf=padding.MGF1(algorithm=mgf_digest), + algorithm=digest, + label=binascii.unhexlify(wycheproof.testcase["label"]) + ) + + if not backend.rsa_padding_supported(padding_algo): + pytest.skip( + "OAEP with digest={} and MGF digest={} not supported".format( + wycheproof.testgroup["sha"], wycheproof.testgroup["mgfSha"], + ) + ) + + if wycheproof.valid or wycheproof.acceptable: + pt = key.decrypt( + binascii.unhexlify(wycheproof.testcase["ct"]), + padding_algo + ) + assert pt == binascii.unhexlify(wycheproof.testcase["msg"]) + else: + with pytest.raises(ValueError): + key.decrypt( + binascii.unhexlify(wycheproof.testcase["ct"]), + padding_algo + ) + + +@pytest.mark.wycheproof_tests( + "rsa_pkcs1_2048_test.json", + "rsa_pkcs1_3072_test.json", + "rsa_pkcs1_4096_test.json", +) +def test_rsa_pkcs1_encryption(backend, wycheproof): + key = serialization.load_pem_private_key( + wycheproof.testgroup["privateKeyPem"].encode("ascii"), + password=None, + backend=backend, + ) + + if wycheproof.valid: + pt = key.decrypt( + binascii.unhexlify(wycheproof.testcase["ct"]), + padding.PKCS1v15() + ) + assert pt == binascii.unhexlify(wycheproof.testcase["msg"]) + else: + with pytest.raises(ValueError): + key.decrypt( + binascii.unhexlify(wycheproof.testcase["ct"]), + padding.PKCS1v15() + ) diff --git a/tests/wycheproof/test_utils.py b/tests/wycheproof/test_utils.py new file mode 100644 index 00000000..2cf3be08 --- /dev/null +++ b/tests/wycheproof/test_utils.py @@ -0,0 +1,21 @@ +# 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 pytest + +from ..utils import WycheproofTest, skip_if_wycheproof_none + + +def test_wycheproof_test_repr(): + wycheproof = WycheproofTest({}, {}, {"tcId": 3}) + assert repr(wycheproof) == "<WycheproofTest({}, {}, {'tcId': 3}, tcId=3)>" + + +def test_skip_if_wycheproof_none(): + with pytest.raises(pytest.skip.Exception): + skip_if_wycheproof_none(None) + + skip_if_wycheproof_none("abc") diff --git a/tests/wycheproof/test_x25519.py b/tests/wycheproof/test_x25519.py new file mode 100644 index 00000000..29579467 --- /dev/null +++ b/tests/wycheproof/test_x25519.py @@ -0,0 +1,42 @@ +# 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 pytest + +from cryptography.hazmat.primitives.asymmetric.x25519 import ( + X25519PrivateKey, X25519PublicKey +) + + +@pytest.mark.supported( + only_if=lambda backend: backend.x25519_supported(), + skip_message="Requires OpenSSL with X25519 support" +) +@pytest.mark.wycheproof_tests("x25519_test.json") +def test_x25519(backend, wycheproof): + assert set(wycheproof.testgroup.items()) == { + ("curve", "curve25519"), ("type", "XdhComp") + } + + private_key = X25519PrivateKey.from_private_bytes( + binascii.unhexlify(wycheproof.testcase["private"]) + ) + public_key = X25519PublicKey.from_public_bytes( + binascii.unhexlify(wycheproof.testcase["public"]) + ) + + assert wycheproof.valid or wycheproof.acceptable + + expected = binascii.unhexlify(wycheproof.testcase["shared"]) + if expected == b"\x00" * 32: + assert wycheproof.acceptable + # OpenSSL returns an error on all zeros shared key + with pytest.raises(ValueError): + private_key.exchange(public_key) + else: + assert private_key.exchange(public_key) == expected diff --git a/tests/wycheproof/test_x448.py b/tests/wycheproof/test_x448.py new file mode 100644 index 00000000..094bf57c --- /dev/null +++ b/tests/wycheproof/test_x448.py @@ -0,0 +1,48 @@ +# 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 pytest + +from cryptography.hazmat.primitives.asymmetric.x448 import ( + X448PrivateKey, X448PublicKey +) + + +@pytest.mark.supported( + only_if=lambda backend: backend.x448_supported(), + skip_message="Requires OpenSSL with X448 support" +) +@pytest.mark.wycheproof_tests("x448_test.json") +def test_x448(backend, wycheproof): + assert set(wycheproof.testgroup.items()) == { + ("curve", "curve448"), ("type", "XdhComp") + } + + private_key = X448PrivateKey.from_private_bytes( + binascii.unhexlify(wycheproof.testcase["private"]) + ) + public_key_bytes = binascii.unhexlify(wycheproof.testcase["public"]) + if len(public_key_bytes) == 57: + assert wycheproof.acceptable + assert wycheproof.has_flag("NonCanonicalPublic") + with pytest.raises(ValueError): + X448PublicKey.from_public_bytes(public_key_bytes) + return + + public_key = X448PublicKey.from_public_bytes(public_key_bytes) + + assert wycheproof.valid or wycheproof.acceptable + + expected = binascii.unhexlify(wycheproof.testcase["shared"]) + if expected == b"\x00" * 56: + assert wycheproof.acceptable + # OpenSSL returns an error on all zeros shared key + with pytest.raises(ValueError): + private_key.exchange(public_key) + else: + assert private_key.exchange(public_key) == expected |
