diff options
Diffstat (limited to 'tests')
87 files changed, 22433 insertions, 6231 deletions
diff --git a/tests/conftest.py b/tests/conftest.py index c4d6b9c1..fd690ce7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,39 +6,47 @@ from __future__ import absolute_import, division, print_function import pytest -from cryptography.hazmat.backends import _available_backends +from cryptography.hazmat.backends.openssl import backend as openssl_backend -from .utils import check_backend_support, select_backends, skip_if_empty +from .utils import ( + check_backend_support, load_wycheproof_tests, skip_if_wycheproof_none +) -def pytest_generate_tests(metafunc): - names = metafunc.config.getoption("--backend") - selected_backends = select_backends(names, _available_backends()) - - if "backend" in metafunc.fixturenames: - filtered_backends = [] - required = metafunc.function.requires_backend_interface - required_interfaces = tuple( - mark.kwargs["interface"] for mark in required - ) - for backend in selected_backends: - if isinstance(backend, required_interfaces): - filtered_backends.append(backend) +def pytest_report_header(config): + return "OpenSSL: {}".format(openssl_backend.openssl_version_text()) - # If you pass an empty list to parametrize Bad Things(tm) happen - # as of pytest 2.6.4 when the test also has a parametrize decorator - skip_if_empty(filtered_backends, required_interfaces) - - metafunc.parametrize("backend", filtered_backends) +def pytest_addoption(parser): + parser.addoption("--wycheproof-root", default=None) -@pytest.mark.trylast -def pytest_runtest_setup(item): - check_backend_support(item) +def pytest_generate_tests(metafunc): + if "wycheproof" in metafunc.fixturenames: + wycheproof = metafunc.config.getoption("--wycheproof-root") + skip_if_wycheproof_none(wycheproof) + + testcases = [] + marker = metafunc.definition.get_closest_marker("wycheproof_tests") + for path in marker.args: + testcases.extend(load_wycheproof_tests(wycheproof, path)) + metafunc.parametrize("wycheproof", testcases) + + +@pytest.fixture() +def backend(request): + required_interfaces = [ + mark.kwargs["interface"] + for mark in request.node.iter_markers("requires_backend_interface") + ] + if not all( + isinstance(openssl_backend, iface) for iface in required_interfaces + ): + pytest.skip( + "OpenSSL doesn't implement required interfaces: {}".format( + required_interfaces + ) + ) -def pytest_addoption(parser): - parser.addoption( - "--backend", action="store", metavar="NAME", - help="Only run tests matching the backend NAME." - ) + check_backend_support(openssl_backend, request) + return openssl_backend diff --git a/tests/doubles.py b/tests/doubles.py new file mode 100644 index 00000000..2ff1942f --- /dev/null +++ b/tests/doubles.py @@ -0,0 +1,43 @@ +# 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 + +from cryptography import utils +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.ciphers import CipherAlgorithm +from cryptography.hazmat.primitives.ciphers.modes import Mode + + +@utils.register_interface(CipherAlgorithm) +class DummyCipherAlgorithm(object): + name = "dummy-cipher" + block_size = 128 + key_size = None + + +@utils.register_interface(Mode) +class DummyMode(object): + name = "dummy-mode" + + def validate_for_algorithm(self, algorithm): + pass + + +@utils.register_interface(hashes.HashAlgorithm) +class DummyHashAlgorithm(object): + name = "dummy-hash" + block_size = None + digest_size = None + + +@utils.register_interface(serialization.KeySerializationEncryption) +class DummyKeySerializationEncryption(object): + pass + + +@utils.register_interface(padding.AsymmetricPadding) +class DummyAsymmetricPadding(object): + name = "dummy-padding" diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py deleted file mode 100644 index f7200016..00000000 --- a/tests/hazmat/backends/test_commoncrypto.py +++ /dev/null @@ -1,61 +0,0 @@ -# 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 cryptography import utils -from cryptography.exceptions import InternalError, _Reasons -from cryptography.hazmat.backends import _available_backends -from cryptography.hazmat.primitives.ciphers import Cipher, CipherAlgorithm -from cryptography.hazmat.primitives.ciphers.algorithms import AES -from cryptography.hazmat.primitives.ciphers.modes import CBC, GCM - -from ...utils import raises_unsupported_algorithm - - -@utils.register_interface(CipherAlgorithm) -class DummyCipher(object): - name = "dummy-cipher" - block_size = None - key_size = None - - -@pytest.mark.skipif("commoncrypto" not in - [i.name for i in _available_backends()], - reason="CommonCrypto not available") -class TestCommonCrypto(object): - def test_supports_cipher(self): - from cryptography.hazmat.backends.commoncrypto.backend import backend - assert backend.cipher_supported(None, None) is False - - def test_register_duplicate_cipher_adapter(self): - from cryptography.hazmat.backends.commoncrypto.backend import backend - with pytest.raises(ValueError): - backend._register_cipher_adapter( - AES, backend._lib.kCCAlgorithmAES128, - CBC, backend._lib.kCCModeCBC - ) - - def test_handle_response(self): - from cryptography.hazmat.backends.commoncrypto.backend import backend - - with pytest.raises(ValueError): - backend._check_cipher_response(backend._lib.kCCAlignmentError) - - with pytest.raises(InternalError): - backend._check_cipher_response(backend._lib.kCCMemoryFailure) - - with pytest.raises(InternalError): - backend._check_cipher_response(backend._lib.kCCDecodeError) - - def test_nonexistent_aead_cipher(self): - from cryptography.hazmat.backends.commoncrypto.backend import Backend - b = Backend() - cipher = Cipher( - DummyCipher(), GCM(b"fake_iv_here"), backend=b, - ) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): - cipher.encryptor() diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py deleted file mode 100644 index 3c05cdfa..00000000 --- a/tests/hazmat/backends/test_multibackend.py +++ /dev/null @@ -1,498 +0,0 @@ -# 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 - -from cryptography import utils -from cryptography.exceptions import ( - UnsupportedAlgorithm, _Reasons -) -from cryptography.hazmat.backends.interfaces import ( - CMACBackend, CipherBackend, DERSerializationBackend, DSABackend, - EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, - PEMSerializationBackend, RSABackend, X509Backend -) -from cryptography.hazmat.backends.multibackend import MultiBackend -from cryptography.hazmat.primitives import cmac, hashes, hmac -from cryptography.hazmat.primitives.asymmetric import ec, padding -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes - -from ...utils import raises_unsupported_algorithm - - -@utils.register_interface(CipherBackend) -class DummyCipherBackend(object): - def __init__(self, supported_ciphers): - self._ciphers = supported_ciphers - - def cipher_supported(self, cipher, mode): - return (type(cipher), type(mode)) in self._ciphers - - def create_symmetric_encryption_ctx(self, cipher, mode): - if not self.cipher_supported(cipher, mode): - raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_CIPHER) - - def create_symmetric_decryption_ctx(self, cipher, mode): - if not self.cipher_supported(cipher, mode): - raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_CIPHER) - - -@utils.register_interface(HashBackend) -class DummyHashBackend(object): - def __init__(self, supported_algorithms): - self._algorithms = supported_algorithms - - def hash_supported(self, algorithm): - return type(algorithm) in self._algorithms - - def create_hash_ctx(self, algorithm): - if not self.hash_supported(algorithm): - raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_HASH) - - -@utils.register_interface(HMACBackend) -class DummyHMACBackend(object): - def __init__(self, supported_algorithms): - self._algorithms = supported_algorithms - - def hmac_supported(self, algorithm): - return type(algorithm) in self._algorithms - - def create_hmac_ctx(self, key, algorithm): - if not self.hmac_supported(algorithm): - raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_HASH) - - -@utils.register_interface(PBKDF2HMACBackend) -class DummyPBKDF2HMACBackend(object): - def __init__(self, supported_algorithms): - self._algorithms = supported_algorithms - - def pbkdf2_hmac_supported(self, algorithm): - return type(algorithm) in self._algorithms - - def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, - key_material): - if not self.pbkdf2_hmac_supported(algorithm): - raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_HASH) - - -@utils.register_interface(RSABackend) -class DummyRSABackend(object): - def generate_rsa_private_key(self, public_exponent, key_size): - pass - - def rsa_padding_supported(self, padding): - pass - - def generate_rsa_parameters_supported(self, public_exponent, key_size): - pass - - def load_rsa_private_numbers(self, numbers): - pass - - def load_rsa_public_numbers(self, numbers): - pass - - -@utils.register_interface(DSABackend) -class DummyDSABackend(object): - def generate_dsa_parameters(self, key_size): - pass - - def generate_dsa_private_key(self, parameters): - pass - - def generate_dsa_private_key_and_parameters(self, key_size): - pass - - def dsa_hash_supported(self, algorithm): - pass - - def dsa_parameters_supported(self, p, q, g): - pass - - def load_dsa_private_numbers(self, numbers): - pass - - def load_dsa_public_numbers(self, numbers): - pass - - def load_dsa_parameter_numbers(self, numbers): - pass - - -@utils.register_interface(CMACBackend) -class DummyCMACBackend(object): - def __init__(self, supported_algorithms): - self._algorithms = supported_algorithms - - def cmac_algorithm_supported(self, algorithm): - return type(algorithm) in self._algorithms - - def create_cmac_ctx(self, algorithm): - if not self.cmac_algorithm_supported(algorithm): - raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_CIPHER) - - -@utils.register_interface(EllipticCurveBackend) -class DummyEllipticCurveBackend(object): - def __init__(self, supported_curves): - self._curves = supported_curves - - def elliptic_curve_supported(self, curve): - return any( - isinstance(curve, curve_type) - for curve_type in self._curves - ) - - def elliptic_curve_signature_algorithm_supported( - self, signature_algorithm, curve - ): - return ( - isinstance(signature_algorithm, ec.ECDSA) and - any( - isinstance(curve, curve_type) - for curve_type in self._curves - ) - ) - - def generate_elliptic_curve_private_key(self, curve): - if not self.elliptic_curve_supported(curve): - raise UnsupportedAlgorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE) - - def load_elliptic_curve_private_numbers(self, numbers): - if not self.elliptic_curve_supported(numbers.public_numbers.curve): - raise UnsupportedAlgorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE) - - def load_elliptic_curve_public_numbers(self, numbers): - if not self.elliptic_curve_supported(numbers.curve): - raise UnsupportedAlgorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE) - - -@utils.register_interface(PEMSerializationBackend) -class DummyPEMSerializationBackend(object): - def load_pem_private_key(self, data, password): - pass - - def load_pem_public_key(self, data): - pass - - -@utils.register_interface(DERSerializationBackend) -class DummyDERSerializationBackend(object): - def load_der_private_key(self, data, password): - pass - - def load_der_public_key(self, data): - pass - - -@utils.register_interface(X509Backend) -class DummyX509Backend(object): - def load_pem_x509_certificate(self, data): - pass - - def load_der_x509_certificate(self, data): - pass - - def load_pem_x509_csr(self, data): - pass - - def load_der_x509_csr(self, data): - pass - - def create_x509_csr(self, builder, private_key, algorithm): - pass - - -class TestMultiBackend(object): - def test_ciphers(self): - backend = MultiBackend([ - DummyHashBackend([]), - DummyCipherBackend([ - (algorithms.AES, modes.CBC), - ]) - ]) - assert backend.cipher_supported( - algorithms.AES(b"\x00" * 16), modes.CBC(b"\x00" * 16) - ) - - cipher = Cipher( - algorithms.AES(b"\x00" * 16), - modes.CBC(b"\x00" * 16), - backend=backend - ) - cipher.encryptor() - cipher.decryptor() - - cipher = Cipher( - algorithms.Camellia(b"\x00" * 16), - modes.CBC(b"\x00" * 16), - backend=backend - ) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): - cipher.encryptor() - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): - cipher.decryptor() - - def test_hashes(self): - backend = MultiBackend([ - DummyHashBackend([hashes.MD5]) - ]) - assert backend.hash_supported(hashes.MD5()) - - hashes.Hash(hashes.MD5(), backend=backend) - - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - hashes.Hash(hashes.SHA1(), backend=backend) - - def test_hmac(self): - backend = MultiBackend([ - DummyHMACBackend([hashes.MD5]) - ]) - assert backend.hmac_supported(hashes.MD5()) - - hmac.HMAC(b"", hashes.MD5(), backend=backend) - - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - hmac.HMAC(b"", hashes.SHA1(), backend=backend) - - def test_pbkdf2(self): - backend = MultiBackend([ - DummyPBKDF2HMACBackend([hashes.MD5]) - ]) - assert backend.pbkdf2_hmac_supported(hashes.MD5()) - - backend.derive_pbkdf2_hmac(hashes.MD5(), 10, b"", 10, b"") - - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - backend.derive_pbkdf2_hmac(hashes.SHA1(), 10, b"", 10, b"") - - def test_rsa(self): - backend = MultiBackend([ - DummyRSABackend() - ]) - - backend.generate_rsa_private_key( - key_size=1024, public_exponent=65537 - ) - - backend.rsa_padding_supported(padding.PKCS1v15()) - - backend.generate_rsa_parameters_supported(65537, 1024) - - backend.load_rsa_private_numbers("private_numbers") - - backend.load_rsa_public_numbers("public_numbers") - - backend = MultiBackend([]) - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.generate_rsa_private_key(key_size=1024, public_exponent=3) - - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.rsa_padding_supported(padding.PKCS1v15()) - - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.generate_rsa_parameters_supported(65537, 1024) - - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.load_rsa_private_numbers("private_numbers") - - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.load_rsa_public_numbers("public_numbers") - - def test_dsa(self): - backend = MultiBackend([ - DummyDSABackend() - ]) - - backend.generate_dsa_parameters(key_size=1024) - - parameters = object() - backend.generate_dsa_private_key(parameters) - backend.generate_dsa_private_key_and_parameters(key_size=1024) - - backend.dsa_hash_supported(hashes.SHA1()) - backend.dsa_parameters_supported(1, 2, 3) - backend.load_dsa_private_numbers("numbers") - backend.load_dsa_public_numbers("numbers") - backend.load_dsa_parameter_numbers("numbers") - - backend = MultiBackend([]) - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.generate_dsa_parameters(key_size=1024) - - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.generate_dsa_private_key(parameters) - - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.generate_dsa_private_key_and_parameters(key_size=1024) - - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.dsa_hash_supported(hashes.SHA1()) - - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.dsa_parameters_supported('p', 'q', 'g') - - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.load_dsa_private_numbers("numbers") - - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.load_dsa_public_numbers("numbers") - - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): - backend.load_dsa_parameter_numbers("numbers") - - def test_cmac(self): - backend = MultiBackend([ - DummyCMACBackend([algorithms.AES]) - ]) - - fake_key = b"\x00" * 16 - - assert backend.cmac_algorithm_supported( - algorithms.AES(fake_key)) is True - - cmac.CMAC(algorithms.AES(fake_key), backend) - - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): - cmac.CMAC(algorithms.TripleDES(fake_key), backend) - - def test_elliptic_curve(self): - backend = MultiBackend([ - DummyEllipticCurveBackend([ - ec.SECT283K1 - ]) - ]) - - assert backend.elliptic_curve_supported(ec.SECT283K1()) is True - - assert backend.elliptic_curve_signature_algorithm_supported( - ec.ECDSA(hashes.SHA256()), - ec.SECT283K1() - ) is True - - backend.generate_elliptic_curve_private_key(ec.SECT283K1()) - - backend.load_elliptic_curve_private_numbers( - ec.EllipticCurvePrivateNumbers( - 1, - ec.EllipticCurvePublicNumbers( - 2, - 3, - ec.SECT283K1() - ) - ) - ) - - backend.load_elliptic_curve_public_numbers( - ec.EllipticCurvePublicNumbers( - 2, - 3, - ec.SECT283K1() - ) - ) - - assert backend.elliptic_curve_supported(ec.SECT163K1()) is False - - assert backend.elliptic_curve_signature_algorithm_supported( - ec.ECDSA(hashes.SHA256()), - ec.SECT163K1() - ) is False - - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE): - backend.generate_elliptic_curve_private_key(ec.SECT163K1()) - - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE): - backend.load_elliptic_curve_private_numbers( - ec.EllipticCurvePrivateNumbers( - 1, - ec.EllipticCurvePublicNumbers( - 2, - 3, - ec.SECT163K1() - ) - ) - ) - - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE): - backend.load_elliptic_curve_public_numbers( - ec.EllipticCurvePublicNumbers( - 2, - 3, - ec.SECT163K1() - ) - ) - - def test_pem_serialization_backend(self): - backend = MultiBackend([DummyPEMSerializationBackend()]) - - backend.load_pem_private_key(b"keydata", None) - backend.load_pem_public_key(b"keydata") - - backend = MultiBackend([]) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): - backend.load_pem_private_key(b"keydata", None) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): - backend.load_pem_public_key(b"keydata") - - def test_der_serialization_backend(self): - backend = MultiBackend([DummyDERSerializationBackend()]) - - backend.load_der_private_key(b"keydata", None) - backend.load_der_public_key(b"keydata") - - backend = MultiBackend([]) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): - backend.load_der_private_key(b"keydata", None) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): - backend.load_der_public_key(b"keydata") - - def test_x509_backend(self): - backend = MultiBackend([DummyX509Backend()]) - - backend.load_pem_x509_certificate(b"certdata") - backend.load_der_x509_certificate(b"certdata") - backend.load_pem_x509_csr(b"reqdata") - backend.load_der_x509_csr(b"reqdata") - backend.create_x509_csr(object(), b"privatekey", hashes.SHA1()) - - backend = MultiBackend([]) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): - backend.load_pem_x509_certificate(b"certdata") - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): - backend.load_der_x509_certificate(b"certdata") - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): - backend.load_pem_x509_csr(b"reqdata") - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): - backend.load_der_x509_csr(b"reqdata") - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): - backend.create_x509_csr(object(), b"privatekey", hashes.SHA1()) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 6a2e8a77..44fd3db4 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -4,60 +4,49 @@ from __future__ import absolute_import, division, print_function +import itertools import os import subprocess import sys import textwrap -import pretend - import pytest -from cryptography import utils +from cryptography import x509 from cryptography.exceptions import InternalError, _Reasons -from cryptography.hazmat.backends.interfaces import RSABackend +from cryptography.hazmat.backends.interfaces import DHBackend, RSABackend from cryptography.hazmat.backends.openssl.backend import ( Backend, backend ) from cryptography.hazmat.backends.openssl.ec import _sn_to_elliptic_curve from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import dsa, ec, padding -from cryptography.hazmat.primitives.ciphers import ( - BlockCipherAlgorithm, Cipher, CipherAlgorithm -) +from cryptography.hazmat.primitives.asymmetric import dh, dsa, padding +from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES -from cryptography.hazmat.primitives.ciphers.modes import CBC, CTR, Mode +from cryptography.hazmat.primitives.ciphers.modes import CBC -from ..primitives.fixtures_dsa import DSA_KEY_2048 from ..primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 -from ..primitives.test_ec import _skip_curve_unsupported -from ...utils import load_vectors_from_file, raises_unsupported_algorithm - - -@utils.register_interface(Mode) -class DummyMode(object): - name = "dummy-mode" - - def validate_for_algorithm(self, algorithm): - pass - +from ...doubles import ( + DummyAsymmetricPadding, DummyCipherAlgorithm, DummyHashAlgorithm, DummyMode +) +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) +from ...x509.test_x509 import _load_cert -@utils.register_interface(CipherAlgorithm) -class DummyCipher(object): - name = "dummy-cipher" - key_size = None +def skip_if_libre_ssl(openssl_version): + if u'LibreSSL' in openssl_version: + pytest.skip("LibreSSL hard-codes RAND_bytes to use arc4random.") -@utils.register_interface(padding.AsymmetricPadding) -class DummyPadding(object): - name = "dummy-cipher" +class TestLibreSkip(object): + def test_skip_no(self): + assert skip_if_libre_ssl(u"OpenSSL 1.0.2h 3 May 2016") is None -@utils.register_interface(hashes.HashAlgorithm) -class DummyHash(object): - name = "dummy-hash" - block_size = None - digest_size = None + def test_skip_yes(self): + with pytest.raises(pytest.skip.Exception): + skip_if_libre_ssl(u"LibreSSL 2.1.6") class DummyMGF(object): @@ -82,14 +71,12 @@ class TestOpenSSL(object): backend.openssl_version_text().startswith("LibreSSL") ) + def test_openssl_version_number(self): + assert backend.openssl_version_number() > 0 + def test_supports_cipher(self): assert backend.cipher_supported(None, None) is False - def test_aes_ctr_always_available(self): - # AES CTR should always be available in both 0.9.8 and 1.0.0+ - assert backend.cipher_supported(AES(b"\x00" * 16), - CTR(b"\x00" * 16)) is True - def test_register_duplicate_cipher_adapter(self): with pytest.raises(ValueError): backend.register_cipher_adapter(AES, CBC, None) @@ -98,16 +85,21 @@ class TestOpenSSL(object): def test_nonexistent_cipher(self, mode): b = Backend() b.register_cipher_adapter( - DummyCipher, + DummyCipherAlgorithm, type(mode), lambda backend, cipher, mode: backend._ffi.NULL ) cipher = Cipher( - DummyCipher(), mode, backend=b, + DummyCipherAlgorithm(), mode, backend=b, ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): cipher.encryptor() + def test_openssl_assert(self): + backend.openssl_assert(True) + with pytest.raises(InternalError): + backend.openssl_assert(False) + def test_consume_errors(self): for i in range(10): backend._lib.ERR_put_error(backend._lib.ERR_LIB_EVP, 0, 0, @@ -120,25 +112,8 @@ class TestOpenSSL(object): assert backend._lib.ERR_peek_error() == 0 assert len(errors) == 10 - def test_openssl_error_string(self): - backend._lib.ERR_put_error( - backend._lib.ERR_LIB_EVP, - backend._lib.EVP_F_EVP_DECRYPTFINAL_EX, - 0, - b"test_openssl.py", - -1 - ) - - errors = backend._consume_errors() - exc = backend._unknown_error(errors[0]) - - assert ( - "digital envelope routines:" - "EVP_DecryptFinal_ex:digital envelope routines" in str(exc) - ) - def test_ssl_ciphers_registered(self): - meth = backend._lib.TLSv1_method() + meth = backend._lib.SSLv23_method() ctx = backend._lib.SSL_CTX_new(meth) assert ctx != backend._ffi.NULL backend._lib.SSL_CTX_free(ctx) @@ -148,12 +123,9 @@ class TestOpenSSL(object): assert cipher != backend._ffi.NULL def test_error_strings_loaded(self): - # returns a value in a static buffer - err = backend._lib.ERR_error_string(101183626, backend._ffi.NULL) - assert backend._ffi.string(err) == ( - b"error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:" - b"data not multiple of block length" - ) + buf = backend._ffi.new("char[]", 256) + backend._lib.ERR_error_string_n(101183626, buf, len(buf)) + assert b"data not multiple of block length" in backend._ffi.string(buf) def test_unknown_error_in_cipher_finalize(self): cipher = Cipher(AES(b"\0" * 16), CBC(b"\0" * 16), backend=backend) @@ -164,40 +136,19 @@ class TestOpenSSL(object): with pytest.raises(InternalError): enc.finalize() - def test_derive_pbkdf2_raises_unsupported_on_old_openssl(self): - if backend.pbkdf2_hmac_supported(hashes.SHA256()): - pytest.skip("Requires an older OpenSSL") - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - backend.derive_pbkdf2_hmac(hashes.SHA256(), 10, b"", 1000, b"") - - @pytest.mark.skipif( - backend._lib.OPENSSL_VERSION_NUMBER >= 0x1000000f, - reason="Requires an older OpenSSL. Must be < 1.0.0" - ) - def test_large_key_size_on_old_openssl(self): - with pytest.raises(ValueError): - dsa.generate_parameters(2048, backend=backend) - - with pytest.raises(ValueError): - dsa.generate_parameters(3072, backend=backend) - - @pytest.mark.skipif( - backend._lib.OPENSSL_VERSION_NUMBER < 0x1000000f, - reason="Requires a newer OpenSSL. Must be >= 1.0.0" - ) def test_large_key_size_on_new_openssl(self): parameters = dsa.generate_parameters(2048, backend) param_num = parameters.parameter_numbers() - assert utils.bit_length(param_num.p) == 2048 + assert param_num.p.bit_length() == 2048 parameters = dsa.generate_parameters(3072, backend) param_num = parameters.parameter_numbers() - assert utils.bit_length(param_num.p) == 3072 + assert param_num.p.bit_length() == 3072 def test_int_to_bn(self): value = (2 ** 4242) - 4242 bn = backend._int_to_bn(value) assert bn != backend._ffi.NULL - bn = backend._ffi.gc(bn, backend._lib.BN_free) + bn = backend._ffi.gc(bn, backend._lib.BN_clear_free) assert bn assert backend._bn_to_int(bn) == value @@ -217,15 +168,29 @@ class TestOpenSSL(object): assert backend._bn_to_int(bn) == 0 +@pytest.mark.skipif( + backend._lib.Cryptography_HAS_ENGINE == 0, + reason="Requires OpenSSL with ENGINE support") class TestOpenSSLRandomEngine(object): - def teardown_method(self, method): + def setup(self): + # The default RAND engine is global and shared between + # tests. We make sure that the default engine is osrandom + # before we start each test and restore the global state to + # that engine in teardown. + current_default = backend._lib.ENGINE_get_default_RAND() + name = backend._lib.ENGINE_get_name(current_default) + assert name == backend._lib.Cryptography_osrandom_engine_name + + def teardown(self): # we need to reset state to being default. backend is a shared global # for all these tests. backend.activate_osrandom_engine() current_default = backend._lib.ENGINE_get_default_RAND() name = backend._lib.ENGINE_get_name(current_default) - assert name == backend._binding._osrandom_engine_name + assert name == backend._lib.Cryptography_osrandom_engine_name + @pytest.mark.skipif(sys.executable is None, + reason="No Python interpreter available.") def test_osrandom_engine_is_default(self, tmpdir): engine_printer = textwrap.dedent( """ @@ -254,18 +219,19 @@ class TestOpenSSLRandomEngine(object): subprocess.check_call( [sys.executable, "-c", engine_printer], env=env, - stdout=out + stdout=out, + stderr=subprocess.PIPE, ) osrandom_engine_name = backend._ffi.string( - backend._binding._osrandom_engine_name + backend._lib.Cryptography_osrandom_engine_name ) assert engine_name.read().encode('ascii') == osrandom_engine_name def test_osrandom_sanity_check(self): # This test serves as a check against catastrophic failure. - buf = backend._ffi.new("char[]", 500) + buf = backend._ffi.new("unsigned char[]", 500) res = backend._lib.RAND_bytes(buf, 500) assert res == 1 assert backend._ffi.buffer(buf)[:] != "\x00" * 500 @@ -277,7 +243,7 @@ class TestOpenSSLRandomEngine(object): backend.activate_osrandom_engine() e = backend._lib.ENGINE_get_default_RAND() name = backend._lib.ENGINE_get_name(e) - assert name == backend._binding._osrandom_engine_name + assert name == backend._lib.Cryptography_osrandom_engine_name res = backend._lib.ENGINE_free(e) assert res == 1 @@ -285,7 +251,7 @@ class TestOpenSSLRandomEngine(object): e = backend._lib.ENGINE_get_default_RAND() assert e != backend._ffi.NULL name = backend._lib.ENGINE_get_name(e) - assert name == backend._binding._osrandom_engine_name + assert name == backend._lib.Cryptography_osrandom_engine_name res = backend._lib.ENGINE_free(e) assert res == 1 backend.activate_builtin_random() @@ -300,20 +266,50 @@ class TestOpenSSLRandomEngine(object): e = backend._lib.ENGINE_get_default_RAND() assert e == backend._ffi.NULL + def test_osrandom_engine_implementation(self): + name = backend.osrandom_engine_implementation() + assert name in ['/dev/urandom', 'CryptGenRandom', 'getentropy', + 'getrandom'] + if sys.platform.startswith('linux'): + assert name in ['getrandom', '/dev/urandom'] + if sys.platform == 'darwin': + assert name in ['getentropy', '/dev/urandom'] + if sys.platform == 'win32': + assert name == 'CryptGenRandom' + def test_activate_osrandom_already_default(self): e = backend._lib.ENGINE_get_default_RAND() name = backend._lib.ENGINE_get_name(e) - assert name == backend._binding._osrandom_engine_name + assert name == backend._lib.Cryptography_osrandom_engine_name res = backend._lib.ENGINE_free(e) assert res == 1 backend.activate_osrandom_engine() e = backend._lib.ENGINE_get_default_RAND() name = backend._lib.ENGINE_get_name(e) - assert name == backend._binding._osrandom_engine_name + assert name == backend._lib.Cryptography_osrandom_engine_name res = backend._lib.ENGINE_free(e) assert res == 1 +@pytest.mark.skipif( + backend._lib.Cryptography_HAS_ENGINE == 1, + reason="Requires OpenSSL without ENGINE support") +class TestOpenSSLNoEngine(object): + def test_no_engine_support(self): + assert backend._ffi.string( + backend._lib.Cryptography_osrandom_engine_id + ) == b"no-engine-support" + assert backend._ffi.string( + backend._lib.Cryptography_osrandom_engine_name + ) == b"osrandom_engine disabled due to no engine support" + + def test_activate_builtin_random_does_nothing(self): + backend.activate_builtin_random() + + def test_activate_osrandom_does_nothing(self): + backend.activate_osrandom_engine() + + class TestOpenSSLRSA(object): def test_generate_rsa_parameters_supported(self): assert backend.generate_rsa_parameters_supported(1, 1024) is False @@ -337,42 +333,13 @@ class TestOpenSSLRSA(object): backend.generate_rsa_private_key(public_exponent=65537, key_size=256) - @pytest.mark.skipif( - backend._lib.OPENSSL_VERSION_NUMBER >= 0x1000100f, - reason="Requires an older OpenSSL. Must be < 1.0.1" - ) - def test_non_sha1_pss_mgf1_hash_algorithm_on_old_openssl(self): - private_key = RSA_KEY_512.private_key(backend) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - private_key.signer( - padding.PSS( - mgf=padding.MGF1( - algorithm=hashes.SHA256(), - ), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA1() - ) - public_key = private_key.public_key() - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - public_key.verifier( - b"sig", - padding.PSS( - mgf=padding.MGF1( - algorithm=hashes.SHA256(), - ), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA1() - ) - def test_rsa_padding_unsupported_pss_mgf1_hash(self): assert backend.rsa_padding_supported( - padding.PSS(mgf=padding.MGF1(DummyHash()), salt_length=0) + padding.PSS(mgf=padding.MGF1(DummyHashAlgorithm()), salt_length=0) ) is False def test_rsa_padding_unsupported(self): - assert backend.rsa_padding_supported(DummyPadding()) is False + assert backend.rsa_padding_supported(DummyAsymmetricPadding()) is False def test_rsa_padding_supported_pkcs1v15(self): assert backend.rsa_padding_supported(padding.PKCS1v15()) is True @@ -391,6 +358,27 @@ class TestOpenSSLRSA(object): ), ) is True + @pytest.mark.skipif( + backend._lib.Cryptography_HAS_RSA_OAEP_MD == 0, + reason="Requires OpenSSL with rsa_oaep_md (1.0.2+)" + ) + def test_rsa_padding_supported_oaep_sha2_combinations(self): + hashalgs = [ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), + ] + for mgf1alg, oaepalg in itertools.product(hashalgs, hashalgs): + assert backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=mgf1alg), + algorithm=oaepalg, + label=None + ), + ) is True + def test_rsa_padding_unsupported_mgf(self): assert backend.rsa_padding_supported( padding.OAEP( @@ -404,9 +392,13 @@ class TestOpenSSLRSA(object): padding.PSS(mgf=DummyMGF(), salt_length=0) ) is False + @pytest.mark.skipif( + backend._lib.Cryptography_HAS_RSA_OAEP_MD == 1, + reason="Requires OpenSSL without rsa_oaep_md (< 1.0.2)" + ) def test_unsupported_mgf1_hash_algorithm_decrypt(self): private_key = RSA_KEY_512.private_key(backend) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): private_key.decrypt( b"0" * 64, padding.OAEP( @@ -416,9 +408,13 @@ class TestOpenSSLRSA(object): ) ) + @pytest.mark.skipif( + backend._lib.Cryptography_HAS_RSA_OAEP_MD == 1, + reason="Requires OpenSSL without rsa_oaep_md (< 1.0.2)" + ) def test_unsupported_oaep_hash_algorithm_decrypt(self): private_key = RSA_KEY_512.private_key(backend) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): private_key.decrypt( b"0" * 64, padding.OAEP( @@ -428,70 +424,96 @@ class TestOpenSSLRSA(object): ) ) - def test_unsupported_oaep_label_decrypt(self): + def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self): private_key = RSA_KEY_512.private_key(backend) - with pytest.raises(ValueError): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): private_key.decrypt( b"0" * 64, padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), - label=b"label" + mgf=padding.MGF1(algorithm=hashes.MD5()), + algorithm=hashes.MD5(), + label=None ) ) -@pytest.mark.skipif( - backend._lib.OPENSSL_VERSION_NUMBER <= 0x10001000, - reason="Requires an OpenSSL version >= 1.0.1" -) class TestOpenSSLCMAC(object): def test_unsupported_cipher(self): - @utils.register_interface(BlockCipherAlgorithm) - class FakeAlgorithm(object): - block_size = 64 - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): - backend.create_cmac_ctx(FakeAlgorithm()) + backend.create_cmac_ctx(DummyCipherAlgorithm()) -class TestOpenSSLCreateX509CSR(object): - @pytest.mark.skipif( - backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000, - reason="Requires an older OpenSSL. Must be < 1.0.1" - ) - def test_unsupported_dsa_keys(self): - private_key = DSA_KEY_2048.private_key(backend) +class TestOpenSSLSignX509Certificate(object): + def test_requires_certificate_builder(self): + private_key = RSA_KEY_2048.private_key(backend) - with pytest.raises(NotImplementedError): - backend.create_x509_csr(object(), private_key, hashes.SHA1()) + with pytest.raises(TypeError): + backend.create_x509_certificate( + object(), private_key, DummyHashAlgorithm() + ) + + +class TestOpenSSLSignX509CSR(object): + def test_requires_csr_builder(self): + private_key = RSA_KEY_2048.private_key(backend) + + with pytest.raises(TypeError): + backend.create_x509_csr( + object(), private_key, DummyHashAlgorithm() + ) - @pytest.mark.skipif( - backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000, - reason="Requires an older OpenSSL. Must be < 1.0.1" - ) - def test_unsupported_ec_keys(self): - _skip_curve_unsupported(backend, ec.SECP256R1()) - private_key = ec.generate_private_key(ec.SECP256R1(), backend) - with pytest.raises(NotImplementedError): - backend.create_x509_csr(object(), private_key, hashes.SHA1()) +class TestOpenSSLSignX509CertificateRevocationList(object): + def test_invalid_builder(self): + private_key = RSA_KEY_2048.private_key(backend) + with pytest.raises(TypeError): + backend.create_x509_crl(object(), private_key, hashes.SHA256()) -class TestOpenSSLSerialisationWithOpenSSL(object): - def test_pem_password_cb_buffer_too_small(self): - ffi_cb, cb = backend._pem_password_cb(b"aa") - assert cb(None, 1, False, None) == 0 + +class TestOpenSSLCreateRevokedCertificate(object): + def test_invalid_builder(self): + with pytest.raises(TypeError): + backend.create_x509_revoked_certificate(object()) + + +class TestOpenSSLSerializationWithOpenSSL(object): + def test_pem_password_cb(self): + userdata = backend._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") + pw = b"abcdefg" + password = backend._ffi.new("char []", pw) + userdata.password = password + userdata.length = len(pw) + buflen = 10 + buf = backend._ffi.new("char []", buflen) + res = backend._lib.Cryptography_pem_password_cb( + buf, buflen, 0, userdata + ) + assert res == len(pw) + assert userdata.called == 1 + assert backend._ffi.buffer(buf, len(pw))[:] == pw + assert userdata.maxsize == buflen + assert userdata.error == 0 + + def test_pem_password_cb_no_password(self): + userdata = backend._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") + buflen = 10 + buf = backend._ffi.new("char []", buflen) + res = backend._lib.Cryptography_pem_password_cb( + buf, buflen, 0, userdata + ) + assert res == 0 + assert userdata.error == -1 def test_unsupported_evp_pkey_type(self): - key = pretend.stub(type="unsupported") + key = backend._create_evp_pkey_gc() with raises_unsupported_algorithm(None): backend._evp_pkey_to_private_key(key) with raises_unsupported_algorithm(None): backend._evp_pkey_to_public_key(key) def test_very_long_pem_serialization_password(self): - password = "x" * 1024 + password = b"x" * 1024 with pytest.raises(ValueError): load_vectors_from_file( @@ -507,23 +529,7 @@ class TestOpenSSLSerialisationWithOpenSSL(object): ) -class DummyLibrary(object): - Cryptography_HAS_EC = 0 - - class TestOpenSSLEllipticCurve(object): - def test_elliptic_curve_supported(self, monkeypatch): - monkeypatch.setattr(backend, "_lib", DummyLibrary()) - - assert backend.elliptic_curve_supported(None) is False - - def test_elliptic_curve_signature_algorithm_supported(self, monkeypatch): - monkeypatch.setattr(backend, "_lib", DummyLibrary()) - - assert backend.elliptic_curve_signature_algorithm_supported( - None, None - ) is False - def test_sn_to_elliptic_curve_not_supported(self): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE): _sn_to_elliptic_curve(backend, b"fake") @@ -540,3 +546,101 @@ class TestRSAPEMSerialization(object): serialization.PrivateFormat.PKCS8, serialization.BestAvailableEncryption(password) ) + + +class TestGOSTCertificate(object): + def test_numeric_string_x509_name_entry(self): + cert = _load_cert( + os.path.join("x509", "e-trust.ru.der"), + x509.load_der_x509_certificate, + backend + ) + if backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_102I: + with pytest.raises(ValueError) as exc: + cert.subject + + # We assert on the message in this case because if the certificate + # fails to load it will also raise a ValueError and this test could + # erroneously pass. + assert str(exc.value) == "Unsupported ASN1 string type. Type: 18" + else: + assert cert.subject.get_attributes_for_oid( + x509.ObjectIdentifier("1.2.643.3.131.1.1") + )[0].value == "007710474375" + + +@pytest.mark.skipif( + backend._lib.Cryptography_HAS_EVP_PKEY_DHX == 1, + reason="Requires OpenSSL without EVP_PKEY_DHX (< 1.0.2)") +@pytest.mark.requires_backend_interface(interface=DHBackend) +class TestOpenSSLDHSerialization(object): + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "RFC5114.txt"), + load_nist_vectors)) + def test_dh_serialization_with_q_unsupported(self, backend, vector): + parameters = dh.DHParameterNumbers(int(vector["p"], 16), + int(vector["g"], 16), + int(vector["q"], 16)) + public = dh.DHPublicNumbers(int(vector["ystatcavs"], 16), parameters) + private = dh.DHPrivateNumbers(int(vector["xstatcavs"], 16), public) + private_key = private.private_key(backend) + public_key = private_key.public_key() + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): + private_key.private_bytes(serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): + public_key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): + parameters.parameters(backend).parameter_bytes( + serialization.Encoding.PEM, + serialization.ParameterFormat.PKCS3) + + @pytest.mark.parametrize( + ("key_path", "loader_func"), + [ + ( + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.pem"), + serialization.load_pem_private_key, + ), + ( + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.der"), + serialization.load_der_private_key, + ) + ] + ) + def test_private_load_dhx_unsupported(self, key_path, loader_func, + backend): + key_bytes = load_vectors_from_file( + key_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + with pytest.raises(ValueError): + loader_func(key_bytes, None, backend) + + @pytest.mark.parametrize( + ("key_path", "loader_func"), + [ + ( + os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.pem"), + serialization.load_pem_public_key, + ), + ( + os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.der"), + serialization.load_der_public_key, + ) + ] + ) + def test_public_load_dhx_unsupported(self, key_path, loader_func, + backend): + key_bytes = load_vectors_from_file( + key_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + with pytest.raises(ValueError): + loader_func(key_bytes, backend) diff --git a/tests/hazmat/backends/test_openssl_memleak.py b/tests/hazmat/backends/test_openssl_memleak.py new file mode 100644 index 00000000..935ea3df --- /dev/null +++ b/tests/hazmat/backends/test_openssl_memleak.py @@ -0,0 +1,451 @@ +# 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 json +import os +import subprocess +import sys +import textwrap + +import pytest + +from cryptography.hazmat.bindings.openssl.binding import Binding + + +MEMORY_LEAK_SCRIPT = """ +import sys + + +def main(argv): + import gc + import json + + import cffi + + from cryptography.hazmat.bindings._openssl import ffi, lib + + heap = {} + + BACKTRACE_ENABLED = False + if BACKTRACE_ENABLED: + backtrace_ffi = cffi.FFI() + backtrace_ffi.cdef(''' + int backtrace(void **, int); + char **backtrace_symbols(void *const *, int); + ''') + backtrace_lib = backtrace_ffi.dlopen(None) + + def backtrace(): + buf = backtrace_ffi.new("void*[]", 24) + length = backtrace_lib.backtrace(buf, len(buf)) + return (buf, length) + + def symbolize_backtrace(trace): + (buf, length) = trace + symbols = backtrace_lib.backtrace_symbols(buf, length) + stack = [ + backtrace_ffi.string(symbols[i]).decode() + for i in range(length) + ] + lib.Cryptography_free_wrapper(symbols, backtrace_ffi.NULL, 0) + return stack + else: + def backtrace(): + return None + + def symbolize_backtrace(trace): + return None + + @ffi.callback("void *(size_t, const char *, int)") + def malloc(size, path, line): + ptr = lib.Cryptography_malloc_wrapper(size, path, line) + heap[ptr] = (size, path, line, backtrace()) + return ptr + + @ffi.callback("void *(void *, size_t, const char *, int)") + def realloc(ptr, size, path, line): + if ptr != ffi.NULL: + del heap[ptr] + new_ptr = lib.Cryptography_realloc_wrapper(ptr, size, path, line) + heap[new_ptr] = (size, path, line, backtrace()) + return new_ptr + + @ffi.callback("void(void *, const char *, int)") + def free(ptr, path, line): + if ptr != ffi.NULL: + del heap[ptr] + lib.Cryptography_free_wrapper(ptr, path, line) + + result = lib.Cryptography_CRYPTO_set_mem_functions(malloc, realloc, free) + assert result == 1 + + # Trigger a bunch of initialization stuff. + import cryptography.hazmat.backends.openssl + + start_heap = set(heap) + + func(*argv[1:]) + gc.collect() + gc.collect() + gc.collect() + + if lib.Cryptography_HAS_OPENSSL_CLEANUP: + lib.OPENSSL_cleanup() + + # Swap back to the original functions so that if OpenSSL tries to free + # something from its atexit handle it won't be going through a Python + # function, which will be deallocated when this function returns + result = lib.Cryptography_CRYPTO_set_mem_functions( + ffi.addressof(lib, "Cryptography_malloc_wrapper"), + ffi.addressof(lib, "Cryptography_realloc_wrapper"), + ffi.addressof(lib, "Cryptography_free_wrapper"), + ) + assert result == 1 + + remaining = set(heap) - start_heap + + if remaining: + sys.stdout.write(json.dumps(dict( + (int(ffi.cast("size_t", ptr)), { + "size": heap[ptr][0], + "path": ffi.string(heap[ptr][1]).decode(), + "line": heap[ptr][2], + "backtrace": symbolize_backtrace(heap[ptr][3]), + }) + for ptr in remaining + ))) + sys.stdout.flush() + sys.exit(255) + +main(sys.argv) +""" + + +def assert_no_memory_leaks(s, argv=[]): + env = os.environ.copy() + env["PYTHONPATH"] = os.pathsep.join(sys.path) + argv = [ + sys.executable, "-c", "{}\n\n{}".format(s, MEMORY_LEAK_SCRIPT) + ] + argv + # Shell out to a fresh Python process because OpenSSL does not allow you to + # install new memory hooks after the first malloc/free occurs. + proc = subprocess.Popen( + argv, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + try: + proc.wait() + if proc.returncode == 255: + # 255 means there was a leak, load the info about what mallocs + # weren't freed. + out = json.loads(proc.stdout.read().decode()) + raise AssertionError(out) + elif proc.returncode != 0: + # Any exception type will do to be honest + raise ValueError(proc.stdout.read(), proc.stderr.read()) + finally: + proc.stdout.close() + proc.stderr.close() + + +def skip_if_memtesting_not_supported(): + return pytest.mark.skipif( + not Binding().lib.Cryptography_HAS_MEM_FUNCTIONS, + reason="Requires OpenSSL memory functions (>=1.1.0)" + ) + + +@skip_if_memtesting_not_supported() +class TestAssertNoMemoryLeaks(object): + def test_no_leak_no_malloc(self): + assert_no_memory_leaks(textwrap.dedent(""" + def func(): + pass + """)) + + def test_no_leak_free(self): + assert_no_memory_leaks(textwrap.dedent(""" + def func(): + from cryptography.hazmat.bindings.openssl.binding import Binding + b = Binding() + name = b.lib.X509_NAME_new() + b.lib.X509_NAME_free(name) + """)) + + def test_no_leak_gc(self): + assert_no_memory_leaks(textwrap.dedent(""" + def func(): + from cryptography.hazmat.bindings.openssl.binding import Binding + b = Binding() + name = b.lib.X509_NAME_new() + b.ffi.gc(name, b.lib.X509_NAME_free) + """)) + + def test_leak(self): + with pytest.raises(AssertionError): + assert_no_memory_leaks(textwrap.dedent(""" + def func(): + from cryptography.hazmat.bindings.openssl.binding import ( + Binding + ) + b = Binding() + b.lib.X509_NAME_new() + """)) + + def test_errors(self): + with pytest.raises(ValueError): + assert_no_memory_leaks(textwrap.dedent(""" + def func(): + raise ZeroDivisionError + """)) + + +@skip_if_memtesting_not_supported() +class TestOpenSSLMemoryLeaks(object): + @pytest.mark.parametrize("path", [ + "x509/PKITS_data/certs/ValidcRLIssuerTest28EE.crt", + ]) + def test_der_x509_certificate_extensions(self, path): + assert_no_memory_leaks(textwrap.dedent(""" + def func(path): + from cryptography import x509 + from cryptography.hazmat.backends.openssl import backend + + import cryptography_vectors + + with cryptography_vectors.open_vector_file(path, "rb") as f: + cert = x509.load_der_x509_certificate( + f.read(), backend + ) + + cert.extensions + """), [path]) + + @pytest.mark.parametrize("path", [ + "x509/cryptography.io.pem", + ]) + def test_pem_x509_certificate_extensions(self, path): + assert_no_memory_leaks(textwrap.dedent(""" + def func(path): + from cryptography import x509 + from cryptography.hazmat.backends.openssl import backend + + import cryptography_vectors + + with cryptography_vectors.open_vector_file(path, "rb") as f: + cert = x509.load_pem_x509_certificate( + f.read(), backend + ) + + cert.extensions + """), [path]) + + def test_x509_csr_extensions(self): + assert_no_memory_leaks(textwrap.dedent(""" + def func(): + from cryptography import x509 + from cryptography.hazmat.backends.openssl import backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import rsa + + private_key = rsa.generate_private_key( + key_size=2048, public_exponent=65537, backend=backend + ) + cert = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([]) + ).add_extension( + x509.OCSPNoCheck(), critical=False + ).sign(private_key, hashes.SHA256(), backend) + + cert.extensions + """)) + + def test_ec_private_numbers_private_key(self): + assert_no_memory_leaks(textwrap.dedent(""" + def func(): + from cryptography.hazmat.backends.openssl import backend + from cryptography.hazmat.primitives.asymmetric import ec + + ec.EllipticCurvePrivateNumbers( + private_value=int( + '280814107134858470598753916394807521398239633534281633982576099083' + '35787109896602102090002196616273211495718603965098' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP384R1(), + x=int( + '10036914308591746758780165503819213553101287571902957054148542' + '504671046744460374996612408381962208627004841444205030' + ), + y=int( + '17337335659928075994560513699823544906448896792102247714689323' + '575406618073069185107088229463828921069465902299522926' + ) + ) + ).private_key(backend) + """)) + + def test_ec_derive_private_key(self): + assert_no_memory_leaks(textwrap.dedent(""" + def func(): + from cryptography.hazmat.backends.openssl import backend + from cryptography.hazmat.primitives.asymmetric import ec + ec.derive_private_key(1, ec.SECP256R1(), backend) + """)) + + def test_x25519_pubkey_from_private_key(self): + assert_no_memory_leaks(textwrap.dedent(""" + def func(): + from cryptography.hazmat.primitives.asymmetric import x25519 + private_key = x25519.X25519PrivateKey.generate() + private_key.public_key() + """)) + + def test_create_ocsp_request(self): + assert_no_memory_leaks(textwrap.dedent(""" + def func(): + from cryptography import x509 + from cryptography.hazmat.backends.openssl import backend + from cryptography.hazmat.primitives import hashes + from cryptography.x509 import ocsp + import cryptography_vectors + + path = "x509/PKITS_data/certs/ValidcRLIssuerTest28EE.crt" + with cryptography_vectors.open_vector_file(path, "rb") as f: + cert = x509.load_der_x509_certificate( + f.read(), backend + ) + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_certificate( + cert, cert, hashes.SHA1() + ).add_extension(x509.OCSPNonce(b"0000"), False) + req = builder.build() + """)) + + @pytest.mark.parametrize("path", [ + "pkcs12/cert-aes256cbc-no-key.p12", + "pkcs12/cert-key-aes256cbc.p12", + ]) + def test_load_pkcs12_key_and_certificates(self, path): + assert_no_memory_leaks(textwrap.dedent(""" + def func(path): + from cryptography import x509 + from cryptography.hazmat.backends.openssl import backend + from cryptography.hazmat.primitives.serialization import pkcs12 + import cryptography_vectors + + with cryptography_vectors.open_vector_file(path, "rb") as f: + pkcs12.load_key_and_certificates( + f.read(), b"cryptography", backend + ) + """), [path]) + + def test_create_crl_with_idp(self): + assert_no_memory_leaks(textwrap.dedent(""" + def func(): + import datetime + from cryptography import x509 + from cryptography.hazmat.backends.openssl import backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.x509.oid import NameOID + + key = ec.generate_private_key(ec.SECP256R1(), backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + idp = x509.IssuingDistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA") + ]), + only_contains_user_certs=False, + only_contains_ca_certs=True, + only_some_reasons=None, + indirect_crl=False, + only_contains_attribute_certs=False, + ) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography.io CA" + ) + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_extension( + idp, True + ) + + crl = builder.sign(key, hashes.SHA256(), backend) + crl.extensions.get_extension_for_class( + x509.IssuingDistributionPoint + ) + """)) + + def test_create_certificate_with_extensions(self): + assert_no_memory_leaks(textwrap.dedent(""" + def func(): + import datetime + + from cryptography import x509 + from cryptography.hazmat.backends.openssl import backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.x509.oid import ( + AuthorityInformationAccessOID, ExtendedKeyUsageOID, NameOID + ) + + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + + not_valid_before = datetime.datetime.now() + not_valid_after = not_valid_before + datetime.timedelta(days=365) + + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + sans = [u'*.example.org', u'foobar.example.net'] + san = x509.SubjectAlternativeName(list(map(x509.DNSName, sans))) + + ski = x509.SubjectKeyIdentifier.from_public_key( + private_key.public_key() + ) + eku = x509.ExtendedKeyUsage([ + ExtendedKeyUsageOID.CLIENT_AUTH, + ExtendedKeyUsageOID.SERVER_AUTH, + ExtendedKeyUsageOID.CODE_SIGNING, + ]) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + private_key.public_key() + ).add_extension( + aia, critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(private_key, hashes.SHA256(), backend) + cert.extensions + """)) diff --git a/tests/hazmat/bindings/test_commoncrypto.py b/tests/hazmat/bindings/test_commoncrypto.py deleted file mode 100644 index b0a2dc43..00000000 --- a/tests/hazmat/bindings/test_commoncrypto.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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 - - -ccbinding = pytest.importorskip( - "cryptography.hazmat.bindings.commoncrypto.binding" -) - - -class TestCommonCrypto(object): - def test_binding_loads(self): - binding = ccbinding.Binding() - assert binding - assert binding.lib - assert binding.ffi - - def test_binding_returns_same_lib(self): - binding = ccbinding.Binding() - binding2 = ccbinding.Binding() - assert binding.lib == binding2.lib - assert binding.ffi == binding2.ffi diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index fe78b0ba..29a1c459 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -6,7 +6,10 @@ from __future__ import absolute_import, division, print_function import pytest -from cryptography.hazmat.bindings.openssl.binding import Binding +from cryptography.exceptions import InternalError +from cryptography.hazmat.bindings.openssl.binding import ( + Binding, _consume_errors, _openssl_assert, _verify_package_version +) class TestOpenSSL(object): @@ -18,85 +21,27 @@ class TestOpenSSL(object): def test_crypto_lock_init(self): b = Binding() - b.init_static_locks() - lock_cb = b.lib.CRYPTO_get_locking_callback() - assert lock_cb != b.ffi.NULL - - def _skip_if_not_fallback_lock(self, b): - # only run this test if we are using our locking cb - original_cb = b.lib.CRYPTO_get_locking_callback() - if original_cb != b._lock_cb_handle: - pytest.skip( - "Not using the fallback Python locking callback " - "implementation. Probably because import _ssl set one" - ) - - def test_fallback_crypto_lock_via_openssl_api(self): - b = Binding() - b.init_static_locks() - - self._skip_if_not_fallback_lock(b) - - # check that the lock state changes appropriately - lock = b._locks[b.lib.CRYPTO_LOCK_SSL] - - # starts out unlocked - assert lock.acquire(False) - lock.release() - - b.lib.CRYPTO_lock( - b.lib.CRYPTO_LOCK | b.lib.CRYPTO_READ, - b.lib.CRYPTO_LOCK_SSL, b.ffi.NULL, 0 - ) - - # becomes locked - assert not lock.acquire(False) - - b.lib.CRYPTO_lock( - b.lib.CRYPTO_UNLOCK | b.lib.CRYPTO_READ, - b.lib.CRYPTO_LOCK_SSL, b.ffi.NULL, 0 - ) - # then unlocked - assert lock.acquire(False) - lock.release() - - def test_fallback_crypto_lock_via_binding_api(self): - b = Binding() b.init_static_locks() - - self._skip_if_not_fallback_lock(b) - - lock = b._locks[b.lib.CRYPTO_LOCK_SSL] - - with pytest.raises(RuntimeError): - b._lock_cb(0, b.lib.CRYPTO_LOCK_SSL, "<test>", 1) - - # errors shouldn't cause locking - assert lock.acquire(False) - lock.release() - - b._lock_cb(b.lib.CRYPTO_LOCK | b.lib.CRYPTO_READ, - b.lib.CRYPTO_LOCK_SSL, "<test>", 1) - # locked - assert not lock.acquire(False) - - b._lock_cb(b.lib.CRYPTO_UNLOCK | b.lib.CRYPTO_READ, - b.lib.CRYPTO_LOCK_SSL, "<test>", 1) - # unlocked - assert lock.acquire(False) - lock.release() + lock_cb = b.lib.CRYPTO_get_locking_callback() + if b.lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER: + assert lock_cb == b.ffi.NULL + assert b.lib.Cryptography_HAS_LOCKING_CALLBACKS == 0 + else: + assert lock_cb != b.ffi.NULL + assert b.lib.Cryptography_HAS_LOCKING_CALLBACKS == 1 def test_add_engine_more_than_once(self): b = Binding() - res = b._register_osrandom_engine() - assert res == 2 + b._register_osrandom_engine() + assert b.lib.ERR_get_error() == 0 def test_ssl_ctx_options(self): # Test that we're properly handling 32-bit unsigned on all platforms. b = Binding() assert b.lib.SSL_OP_ALL > 0 - ctx = b.lib.SSL_CTX_new(b.lib.TLSv1_method()) + ctx = b.lib.SSL_CTX_new(b.lib.SSLv23_method()) + assert ctx != b.ffi.NULL ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free) current_options = b.lib.SSL_CTX_get_options(ctx) resp = b.lib.SSL_CTX_set_options(ctx, b.lib.SSL_OP_ALL) @@ -108,7 +53,8 @@ class TestOpenSSL(object): # Test that we're properly handling 32-bit unsigned on all platforms. b = Binding() assert b.lib.SSL_OP_ALL > 0 - ctx = b.lib.SSL_CTX_new(b.lib.TLSv1_method()) + ctx = b.lib.SSL_CTX_new(b.lib.SSLv23_method()) + assert ctx != b.ffi.NULL ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free) ssl = b.lib.SSL_new(ctx) ssl = b.ffi.gc(ssl, b.lib.SSL_free) @@ -122,7 +68,8 @@ class TestOpenSSL(object): # Test that we're properly handling 32-bit unsigned on all platforms. b = Binding() assert b.lib.SSL_OP_ALL > 0 - ctx = b.lib.SSL_CTX_new(b.lib.TLSv1_method()) + ctx = b.lib.SSL_CTX_new(b.lib.SSLv23_method()) + assert ctx != b.ffi.NULL ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free) ssl = b.lib.SSL_new(ctx) ssl = b.ffi.gc(ssl, b.lib.SSL_free) @@ -131,3 +78,47 @@ class TestOpenSSL(object): expected_options = current_options | b.lib.SSL_OP_ALL assert resp == expected_options assert b.lib.SSL_get_mode(ssl) == expected_options + + def test_conditional_removal(self): + b = Binding() + + if b.lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER: + assert b.lib.TLS_ST_OK + else: + with pytest.raises(AttributeError): + b.lib.TLS_ST_OK + + def test_openssl_assert_error_on_stack(self): + b = Binding() + b.lib.ERR_put_error( + b.lib.ERR_LIB_EVP, + b.lib.EVP_F_EVP_ENCRYPTFINAL_EX, + b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH, + b"", + -1 + ) + with pytest.raises(InternalError) as exc_info: + _openssl_assert(b.lib, False) + + error = exc_info.value.err_code[0] + assert error.code == 101183626 + assert error.lib == b.lib.ERR_LIB_EVP + assert error.func == b.lib.EVP_F_EVP_ENCRYPTFINAL_EX + assert error.reason == b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH + assert b"data not multiple of block length" in error.reason_text + + def test_check_startup_errors_are_allowed(self): + b = Binding() + b.lib.ERR_put_error( + b.lib.ERR_LIB_EVP, + b.lib.EVP_F_EVP_ENCRYPTFINAL_EX, + b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH, + b"", + -1 + ) + b._register_osrandom_engine() + assert _consume_errors(b.lib) == [] + + def test_version_mismatch(self): + with pytest.raises(ImportError): + _verify_package_version("nottherightversion") diff --git a/tests/hazmat/primitives/fixtures_ec.py b/tests/hazmat/primitives/fixtures_ec.py new file mode 100644 index 00000000..21c69031 --- /dev/null +++ b/tests/hazmat/primitives/fixtures_ec.py @@ -0,0 +1,296 @@ +# 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 + +from cryptography.hazmat.primitives.asymmetric import ec + + +EC_KEY_SECT571R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '213997069697108634621868251335076179190383272087548888968788698953' + '131928375431570122753130054966269038244076049869476736547896549201' + '7388482714521707824160638375437887802901' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT571R1(), + x=int( + '42585672410900520895287019432267514156432686681290164230262278' + '54789182447139054594501570747809649335533486119017169439209005' + '883737780433424425566023654583165324498640038089' + ), + y=int( + '13822523320209387572500458104799806851658024537477228250738334' + '46977851514777531296572763848253279034733550774927720436494321' + '97281333379623823457479233585424800362717541750' + ) + ) +) + +EC_KEY_SECT409R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '604993237916498765317587097853603474519114726157206838874832379003' + '281871982139714656205843929472002062791572217653118715727' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT409R1(), + x=int( + '76237701339268928039087238870073679814646664010783544301589269' + '2272579213400205907766385199643053767195204247826349822350081' + ), + y=int( + '10056668929618383045204866060110626563392345494925302478351744' + '01475129090774493235522729123877384838835703483224447476728811' + ) + ) +) + +EC_KEY_SECT283R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '589705077255658434962118789801402573495547207239917043241273753671' + '0603230261342427657' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT283R1(), + x=int( + '10694213430317013187241490088760888472172922291550831393222973' + '531614941756901942108493' + ), + y=int( + '11461553100313943515373601367527399649593366728262918214942116' + '4359557613202950705170' + ) + ) +) + +EC_KEY_SECT233R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '343470067105388144757135261232658742142830154753739648095101899829' + '8288' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT233R1(), + x=int( + '74494951569151557692195071465128140646140765188698294062550374' + '71118267' + ), + y=int( + '48699150823022962508544923825876164485917001162461401797511748' + '44872205' + ) + ) +) + +EC_KEY_SECT163R2 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '11788436193853888218177032687141056784083668635' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT163R2(), + x=int( + '5247234453330640212490501030772203801908103222463' + ), + y=int( + '3172513801099088785224248292142866317754124455206' + ) + ) +) + +EC_KEY_SECT571K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '592811051234886966121888758661314648311634839499582476726008738218' + '165015048237934517672316204181933804884636855291118594744334592153' + '883208936227914544246799490897169723387' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT571K1(), + x=int( + '81362471461936552203898455874182916939857774872643607884250052' + '29301336524105230729653881789373412990921493551253481866317181' + '50644729351721577822595637058949405764944491655' + ), + y=int( + '14058041260812945396067821061063618047896814719828637241661260' + '31235681542401975593036630733881695595289523801041910183736211' + '587294494888450327374439795428519848065589000434' + ) + ) +) + +EC_KEY_SECT409K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '110321743150399087059465162400463719641470113494908091197354523708' + '934106732952992153105338671368548199643686444619485307877' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT409K1(), + x=int( + '62280214209410363493525178797944995742119600145953755916426161' + '0790364158569265348038207313261547476506319796469776797725796' + ), + y=int( + '46653883749102474289095010108777579907422472804577185369332018' + '7318642669590280811057512951467298158275464566214288556375885' + ) + ) +) + +EC_KEY_SECT283K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '182508394415444014156574733141549331538128234395356466108310015130' + '3868915489347291850' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT283K1(), + x=int( + '31141647206111886426350703123670451554123180910379592764773885' + '2959123367428352287032' + ), + y=int( + '71787460144483665964585187837283963089964760704065205376175384' + '58957627834444017112582' + ) + ) +) + +EC_KEY_SECT233K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '172670089647474613734091436081960550801254775902629891892394471086' + '2070' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT233K1(), + x=int( + '55693911474339510991521579392202889561373678973929426354737048' + '68129172' + ), + y=int( + '11025856248546376145959939911850923631416718241836051344384802' + '737277815' + ) + ) +) + +EC_KEY_SECT163K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '3699303791425402204035307605170569820290317991287' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT163K1(), + x=int( + '4479755902310063321544063130576409926980094120721' + ), + y=int( + '3051218481937171839039826690648109285113977745779' + ) + ) +) + +EC_KEY_SECP521R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '662751235215460886290293902658128847495347691199214706697089140769' + '672273950767961331442265530524063943548846724348048614239791498442' + '5997823106818915698960565' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP521R1(), + x=int( + '12944742826257420846659527752683763193401384271391513286022917' + '29910013082920512632908350502247952686156279140016049549948975' + '670668730618745449113644014505462' + ), + y=int( + '10784108810271976186737587749436295782985563640368689081052886' + '16296815984553198866894145509329328086635278430266482551941240' + '591605833440825557820439734509311' + ) + ) +) + +EC_KEY_SECP384R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '280814107134858470598753916394807521398239633534281633982576099083' + '35787109896602102090002196616273211495718603965098' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP384R1(), + x=int( + '10036914308591746758780165503819213553101287571902957054148542' + '504671046744460374996612408381962208627004841444205030' + ), + y=int( + '17337335659928075994560513699823544906448896792102247714689323' + '575406618073069185107088229463828921069465902299522926' + ) + ) +) + +EC_KEY_SECP256R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '271032978511595617649844168316234344656921218699414461240502635010' + '25776962849' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP256R1(), + x=int( + '49325986169170464532722748935508337546545346352733747948730305' + '442770101441241' + ), + y=int( + '51709162888529903487188595007092772817469799707382623884187518' + '455962250433661' + ) + ) +) + +EC_KEY_SECP256K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '683341569008473593765879222774207677458810362976327530563215318048' + '64380736732' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP256K1(), + x=int( + '59251322975795306609293064274738085741081547489119277536110995' + '120127593127884' + ), + y=int( + '10334192001480392039227801832201340147605940717841294644187071' + '8261641142297801' + ) + ) +) + +EC_KEY_SECP224R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '234854340492774342642505519082413233282383066880756900834047566251' + '50' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP224R1(), + x=int( + '51165676638271204691095081341581621487998422645261573824239666' + '1214' + ), + y=int( + '14936601450555711309158397172719963843891926209168533453717969' + '1265' + ) + ) +) + +EC_KEY_SECP192R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '4534766128536179420071447168915990251715442361606049349869' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP192R1(), + x=int( + '5415069751170397888083674339683360671310515485781457536999' + ), + y=int( + '18671605334415960797751252911958331304288357195986572776' + ) + ) +) diff --git a/tests/hazmat/primitives/fixtures_rsa.py b/tests/hazmat/primitives/fixtures_rsa.py index f93361de..a531783e 100644 --- a/tests/hazmat/primitives/fixtures_rsa.py +++ b/tests/hazmat/primitives/fixtures_rsa.py @@ -529,3 +529,75 @@ RSA_KEY_2048 = RSAPrivateNumbers( "de04fd053846ca10a223b10cc841cc80fdebee44f3114c13e886af583", 16), ) ) + +RSA_KEY_2048_ALT = RSAPrivateNumbers( + d=int( + "7522768467449591813737881904131688860626637897199391200040629" + "8641018746450502628484395471408986929218353894683769457466923" + "3079369551423094451013669595729568593462009746342148367797495" + "5529909313614750246672441810743580455199636293179539903480635" + "3091286716112931976896334411287175213124504134181121011488550" + "5290054443979198998564749640800633368957384058700741073997703" + "8877364695937023906368630297588990131009278072614118207348356" + "4640244134189285070202534488517371577359510236833464698189075" + "5160693085297816063285814039518178249628112908466649245545732" + "5791532385553960363601827996980725025898649392004494256400884" + "092073" + ), + dmp1=int( + "5847872614112935747739644055317429405973942336206460017493394" + "9737607778799766591021036792892472774720417920838206576785118" + "8889624058962939702950175807073343659386156232294197300491647" + "1029508414050591959344812347424476498076532682798598325230069" + "0925827594762920534235575029199380552228825468180187156871965" + "973" + ), + dmq1=int( + "2949536259161239302081155875068405238857801001054083407704879" + "8210876832264504685327766351157044892283801611558399025326793" + "4131638001934454489864437565651739832511702151461257267169691" + "6611992398459006200708626815153304591390855807749769768978152" + "9854112656599931724820610358669306523835327459478374630794532" + "167" + ), + iqmp=int( + "7331180989818931535458916053540252830484856703208982675535284" + "4613815808798190559315018094080936347757336989616401164752221" + "8101156529898067044923499386460167055405998646366011838018441" + "3678947694258190172377716154009305082091341215866326061721180" + "3836418654472188816187630316821692982783286322262994892003058" + "782" + ), + p=int( + "1460007723851883695617573533155574746587863843382715314919865" + "2434108956187429726002840717317310431378483921058946835896252" + "7109559207437158778332364464259678946305487699031865937075508" + "8616612925453842458055546540240601585731206561647892336916583" + "0023641764106581040198845259766246869529221084602380669333021" + "0819" + ), + q=int( + "1433897765867889178402883410610177836503402597775250087462018" + "4617952933433119527945447840336616357136736935069377619782227" + "2822380830300262175671282877680573202309319960687756231128996" + "9764855320953993690199846269451095044922353809602378616938811" + "7513900906279873343591486841303392490561500301994171338761080" + "4439" + ), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "209350181338107812610165420955871971489973659392253291327" + "839812910252466502190690572476688311285621239204212139711" + "207388949164851984253143698667018532039612470954223918242" + "145976986600705122576087630525229796950722166468064721258" + "490916138706756006902066136471049807637157890128560592039" + "941717275079733754782848729566190631725183735944031456237" + "089928120178187552521649483240599003240074352860189285952" + "078970127554801074176375499583703254849309993132931268013" + "715070507278514207864914944621214574162116786377990456375" + "964817771730371110612100247262908550409785456157505694419" + "00451152778245269283276012328748538414051025541" + ) + ) +) diff --git a/tests/hazmat/primitives/test_3des.py b/tests/hazmat/primitives/test_3des.py index 0197353e..0f0f1470 100644 --- a/tests/hazmat/primitives/test_3des.py +++ b/tests/hazmat/primitives/test_3des.py @@ -22,13 +22,13 @@ from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.TripleDES("\x00" * 8), modes.CBC("\x00" * 8) + algorithms.TripleDES(b"\x00" * 8), modes.CBC(b"\x00" * 8) ), skip_message="Does not support TripleDES CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestTripleDESModeCBC(object): - test_KAT = generate_encrypt_test( + test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CBC"), [ @@ -42,7 +42,7 @@ class TestTripleDESModeCBC(object): lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) - test_MMT = generate_encrypt_test( + test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CBC"), [ @@ -59,13 +59,13 @@ class TestTripleDESModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.TripleDES("\x00" * 8), modes.OFB("\x00" * 8) + algorithms.TripleDES(b"\x00" * 8), modes.OFB(b"\x00" * 8) ), skip_message="Does not support TripleDES OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestTripleDESModeOFB(object): - test_KAT = generate_encrypt_test( + test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "OFB"), [ @@ -79,7 +79,7 @@ class TestTripleDESModeOFB(object): lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) - test_MMT = generate_encrypt_test( + test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "OFB"), [ @@ -96,13 +96,13 @@ class TestTripleDESModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.TripleDES("\x00" * 8), modes.CFB("\x00" * 8) + algorithms.TripleDES(b"\x00" * 8), modes.CFB(b"\x00" * 8) ), skip_message="Does not support TripleDES CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestTripleDESModeCFB(object): - test_KAT = generate_encrypt_test( + test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), [ @@ -116,7 +116,7 @@ class TestTripleDESModeCFB(object): lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) - test_MMT = generate_encrypt_test( + test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), [ @@ -133,13 +133,13 @@ class TestTripleDESModeCFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.TripleDES("\x00" * 8), modes.CFB8("\x00" * 8) + algorithms.TripleDES(b"\x00" * 8), modes.CFB8(b"\x00" * 8) ), skip_message="Does not support TripleDES CFB8", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestTripleDESModeCFB8(object): - test_KAT = generate_encrypt_test( + test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), [ @@ -153,7 +153,7 @@ class TestTripleDESModeCFB8(object): lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), ) - test_MMT = generate_encrypt_test( + test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), [ @@ -170,13 +170,13 @@ class TestTripleDESModeCFB8(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.TripleDES("\x00" * 8), modes.ECB() + algorithms.TripleDES(b"\x00" * 8), modes.ECB() ), skip_message="Does not support TripleDES ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestTripleDESModeECB(object): - test_KAT = generate_encrypt_test( + test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "ECB"), [ @@ -190,7 +190,7 @@ class TestTripleDESModeECB(object): lambda **kwargs: modes.ECB(), ) - test_MMT = generate_encrypt_test( + test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "ECB"), [ diff --git a/tests/hazmat/primitives/test_aead.py b/tests/hazmat/primitives/test_aead.py new file mode 100644 index 00000000..4f6bc7f4 --- /dev/null +++ b/tests/hazmat/primitives/test_aead.py @@ -0,0 +1,446 @@ +# 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 InvalidTag, UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers.aead import ( + AESCCM, AESGCM, ChaCha20Poly1305 +) + +from .utils import _load_all_params +from ...utils import ( + load_nist_ccm_vectors, load_nist_vectors, load_vectors_from_file, + raises_unsupported_algorithm +) + + +class FakeData(object): + def __len__(self): + return 2 ** 32 + 1 + + +def _aead_supported(cls): + try: + cls(b"0" * 32) + return True + except UnsupportedAlgorithm: + return False + + +@pytest.mark.skipif( + _aead_supported(ChaCha20Poly1305), + reason="Requires OpenSSL without ChaCha20Poly1305 support" +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +def test_chacha20poly1305_unsupported_on_older_openssl(backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + ChaCha20Poly1305(ChaCha20Poly1305.generate_key()) + + +@pytest.mark.skipif( + not _aead_supported(ChaCha20Poly1305), + reason="Does not support ChaCha20Poly1305" +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestChaCha20Poly1305(object): + def test_data_too_large(self): + key = ChaCha20Poly1305.generate_key() + chacha = ChaCha20Poly1305(key) + nonce = b"0" * 12 + + with pytest.raises(OverflowError): + chacha.encrypt(nonce, FakeData(), b"") + + with pytest.raises(OverflowError): + chacha.encrypt(nonce, b"", FakeData()) + + def test_generate_key(self): + key = ChaCha20Poly1305.generate_key() + assert len(key) == 32 + + def test_bad_key(self, backend): + with pytest.raises(TypeError): + ChaCha20Poly1305(object()) + + with pytest.raises(ValueError): + ChaCha20Poly1305(b"0" * 31) + + @pytest.mark.parametrize( + ("nonce", "data", "associated_data"), + [ + [object(), b"data", b""], + [b"0" * 12, object(), b""], + [b"0" * 12, b"data", object()] + ] + ) + def test_params_not_bytes_encrypt(self, nonce, data, associated_data, + backend): + key = ChaCha20Poly1305.generate_key() + chacha = ChaCha20Poly1305(key) + with pytest.raises(TypeError): + chacha.encrypt(nonce, data, associated_data) + + with pytest.raises(TypeError): + chacha.decrypt(nonce, data, associated_data) + + def test_nonce_not_12_bytes(self, backend): + key = ChaCha20Poly1305.generate_key() + chacha = ChaCha20Poly1305(key) + with pytest.raises(ValueError): + chacha.encrypt(b"00", b"hello", b"") + + with pytest.raises(ValueError): + chacha.decrypt(b"00", b"hello", b"") + + def test_decrypt_data_too_short(self, backend): + key = ChaCha20Poly1305.generate_key() + chacha = ChaCha20Poly1305(key) + with pytest.raises(InvalidTag): + chacha.decrypt(b"0" * 12, b"0", None) + + def test_associated_data_none_equal_to_empty_bytestring(self, backend): + key = ChaCha20Poly1305.generate_key() + chacha = ChaCha20Poly1305(key) + nonce = os.urandom(12) + ct1 = chacha.encrypt(nonce, b"some_data", None) + ct2 = chacha.encrypt(nonce, b"some_data", b"") + assert ct1 == ct2 + pt1 = chacha.decrypt(nonce, ct1, None) + pt2 = chacha.decrypt(nonce, ct2, b"") + assert pt1 == pt2 + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("ciphers", "ChaCha20Poly1305", "openssl.txt"), + load_nist_vectors + ) + ) + def test_openssl_vectors(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["iv"]) + aad = binascii.unhexlify(vector["aad"]) + tag = binascii.unhexlify(vector["tag"]) + pt = binascii.unhexlify(vector["plaintext"]) + ct = binascii.unhexlify(vector["ciphertext"]) + chacha = ChaCha20Poly1305(key) + if vector.get("result") == b"CIPHERFINAL_ERROR": + with pytest.raises(InvalidTag): + chacha.decrypt(nonce, ct + tag, aad) + else: + computed_pt = chacha.decrypt(nonce, ct + tag, aad) + assert computed_pt == pt + computed_ct = chacha.encrypt(nonce, pt, aad) + assert computed_ct == ct + tag + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("ciphers", "ChaCha20Poly1305", "boringssl.txt"), + load_nist_vectors + ) + ) + def test_boringssl_vectors(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["nonce"]) + if vector["ad"].startswith(b'"'): + aad = vector["ad"][1:-1] + else: + aad = binascii.unhexlify(vector["ad"]) + tag = binascii.unhexlify(vector["tag"]) + if vector["in"].startswith(b'"'): + pt = vector["in"][1:-1] + else: + pt = binascii.unhexlify(vector["in"]) + ct = binascii.unhexlify(vector["ct"].strip(b'"')) + chacha = ChaCha20Poly1305(key) + computed_pt = chacha.decrypt(nonce, ct + tag, aad) + assert computed_pt == pt + computed_ct = chacha.encrypt(nonce, pt, aad) + assert computed_ct == ct + tag + + def test_buffer_protocol(self, backend): + key = ChaCha20Poly1305.generate_key() + chacha = ChaCha20Poly1305(key) + pt = b"encrypt me" + ad = b"additional" + nonce = os.urandom(12) + ct = chacha.encrypt(nonce, pt, ad) + computed_pt = chacha.decrypt(nonce, ct, ad) + assert computed_pt == pt + chacha2 = ChaCha20Poly1305(bytearray(key)) + ct2 = chacha2.encrypt(bytearray(nonce), pt, ad) + assert ct2 == ct + computed_pt2 = chacha2.decrypt(bytearray(nonce), ct2, ad) + assert computed_pt2 == pt + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESCCM(object): + def test_data_too_large(self): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + nonce = b"0" * 12 + + with pytest.raises(OverflowError): + aesccm.encrypt(nonce, FakeData(), b"") + + with pytest.raises(OverflowError): + aesccm.encrypt(nonce, b"", FakeData()) + + def test_default_tag_length(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + nonce = os.urandom(12) + pt = b"hello" + ct = aesccm.encrypt(nonce, pt, None) + assert len(ct) == len(pt) + 16 + + def test_invalid_tag_length(self, backend): + key = AESCCM.generate_key(128) + with pytest.raises(ValueError): + AESCCM(key, tag_length=7) + + with pytest.raises(ValueError): + AESCCM(key, tag_length=2) + + with pytest.raises(TypeError): + AESCCM(key, tag_length="notanint") + + def test_invalid_nonce_length(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + pt = b"hello" + nonce = os.urandom(14) + with pytest.raises(ValueError): + aesccm.encrypt(nonce, pt, None) + + with pytest.raises(ValueError): + aesccm.encrypt(nonce[:6], pt, None) + + @pytest.mark.parametrize( + "vector", + _load_all_params( + os.path.join("ciphers", "AES", "CCM"), + [ + "DVPT128.rsp", "DVPT192.rsp", "DVPT256.rsp", + "VADT128.rsp", "VADT192.rsp", "VADT256.rsp", + "VNT128.rsp", "VNT192.rsp", "VNT256.rsp", + "VPT128.rsp", "VPT192.rsp", "VPT256.rsp", + ], + load_nist_ccm_vectors + ) + ) + def test_vectors(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["nonce"]) + adata = binascii.unhexlify(vector["adata"])[:vector["alen"]] + ct = binascii.unhexlify(vector["ct"]) + pt = binascii.unhexlify(vector["payload"])[:vector["plen"]] + aesccm = AESCCM(key, vector["tlen"]) + if vector.get('fail'): + with pytest.raises(InvalidTag): + aesccm.decrypt(nonce, ct, adata) + else: + computed_pt = aesccm.decrypt(nonce, ct, adata) + assert computed_pt == pt + assert aesccm.encrypt(nonce, pt, adata) == ct + + def test_roundtrip(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + pt = b"encrypt me" + ad = b"additional" + nonce = os.urandom(12) + ct = aesccm.encrypt(nonce, pt, ad) + computed_pt = aesccm.decrypt(nonce, ct, ad) + assert computed_pt == pt + + def test_nonce_too_long(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + pt = b"encrypt me" * 6600 + # pt can be no more than 65536 bytes when nonce is 13 bytes + nonce = os.urandom(13) + with pytest.raises(ValueError): + aesccm.encrypt(nonce, pt, None) + + @pytest.mark.parametrize( + ("nonce", "data", "associated_data"), + [ + [object(), b"data", b""], + [b"0" * 12, object(), b""], + [b"0" * 12, b"data", object()], + ] + ) + def test_params_not_bytes(self, nonce, data, associated_data, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + with pytest.raises(TypeError): + aesccm.encrypt(nonce, data, associated_data) + + def test_bad_key(self, backend): + with pytest.raises(TypeError): + AESCCM(object()) + + with pytest.raises(ValueError): + AESCCM(b"0" * 31) + + def test_bad_generate_key(self, backend): + with pytest.raises(TypeError): + AESCCM.generate_key(object()) + + with pytest.raises(ValueError): + AESCCM.generate_key(129) + + def test_associated_data_none_equal_to_empty_bytestring(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + nonce = os.urandom(12) + ct1 = aesccm.encrypt(nonce, b"some_data", None) + ct2 = aesccm.encrypt(nonce, b"some_data", b"") + assert ct1 == ct2 + pt1 = aesccm.decrypt(nonce, ct1, None) + pt2 = aesccm.decrypt(nonce, ct2, b"") + assert pt1 == pt2 + + def test_decrypt_data_too_short(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + with pytest.raises(InvalidTag): + aesccm.decrypt(b"0" * 12, b"0", None) + + def test_buffer_protocol(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + pt = b"encrypt me" + ad = b"additional" + nonce = os.urandom(12) + ct = aesccm.encrypt(nonce, pt, ad) + computed_pt = aesccm.decrypt(nonce, ct, ad) + assert computed_pt == pt + aesccm2 = AESCCM(bytearray(key)) + ct2 = aesccm2.encrypt(bytearray(nonce), pt, ad) + assert ct2 == ct + computed_pt2 = aesccm2.decrypt(bytearray(nonce), ct2, ad) + assert computed_pt2 == pt + + +def _load_gcm_vectors(): + vectors = _load_all_params( + os.path.join("ciphers", "AES", "GCM"), + [ + "gcmDecrypt128.rsp", + "gcmDecrypt192.rsp", + "gcmDecrypt256.rsp", + "gcmEncryptExtIV128.rsp", + "gcmEncryptExtIV192.rsp", + "gcmEncryptExtIV256.rsp", + ], + load_nist_vectors + ) + return [x for x in vectors if len(x["tag"]) == 32] + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESGCM(object): + def test_data_too_large(self): + key = AESGCM.generate_key(128) + aesgcm = AESGCM(key) + nonce = b"0" * 12 + + with pytest.raises(OverflowError): + aesgcm.encrypt(nonce, FakeData(), b"") + + with pytest.raises(OverflowError): + aesgcm.encrypt(nonce, b"", FakeData()) + + @pytest.mark.parametrize("vector", _load_gcm_vectors()) + def test_vectors(self, vector): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["iv"]) + aad = binascii.unhexlify(vector["aad"]) + ct = binascii.unhexlify(vector["ct"]) + pt = binascii.unhexlify(vector.get("pt", b"")) + tag = binascii.unhexlify(vector["tag"]) + aesgcm = AESGCM(key) + if vector.get("fail") is True: + with pytest.raises(InvalidTag): + aesgcm.decrypt(nonce, ct + tag, aad) + else: + computed_ct = aesgcm.encrypt(nonce, pt, aad) + assert computed_ct[:-16] == ct + assert computed_ct[-16:] == tag + computed_pt = aesgcm.decrypt(nonce, ct + tag, aad) + assert computed_pt == pt + + @pytest.mark.parametrize( + ("nonce", "data", "associated_data"), + [ + [object(), b"data", b""], + [b"0" * 12, object(), b""], + [b"0" * 12, b"data", object()] + ] + ) + def test_params_not_bytes(self, nonce, data, associated_data, backend): + key = AESGCM.generate_key(128) + aesgcm = AESGCM(key) + with pytest.raises(TypeError): + aesgcm.encrypt(nonce, data, associated_data) + + with pytest.raises(TypeError): + aesgcm.decrypt(nonce, data, associated_data) + + def test_invalid_nonce_length(self, backend): + key = AESGCM.generate_key(128) + aesgcm = AESGCM(key) + with pytest.raises(ValueError): + aesgcm.encrypt(b"", b"hi", None) + + def test_bad_key(self, backend): + with pytest.raises(TypeError): + AESGCM(object()) + + with pytest.raises(ValueError): + AESGCM(b"0" * 31) + + def test_bad_generate_key(self, backend): + with pytest.raises(TypeError): + AESGCM.generate_key(object()) + + with pytest.raises(ValueError): + AESGCM.generate_key(129) + + def test_associated_data_none_equal_to_empty_bytestring(self, backend): + key = AESGCM.generate_key(128) + aesgcm = AESGCM(key) + nonce = os.urandom(12) + ct1 = aesgcm.encrypt(nonce, b"some_data", None) + ct2 = aesgcm.encrypt(nonce, b"some_data", b"") + assert ct1 == ct2 + pt1 = aesgcm.decrypt(nonce, ct1, None) + pt2 = aesgcm.decrypt(nonce, ct2, b"") + assert pt1 == pt2 + + def test_buffer_protocol(self, backend): + key = AESGCM.generate_key(128) + aesgcm = AESGCM(key) + pt = b"encrypt me" + ad = b"additional" + nonce = os.urandom(12) + ct = aesgcm.encrypt(nonce, pt, ad) + computed_pt = aesgcm.decrypt(nonce, ct, ad) + assert computed_pt == pt + aesgcm2 = AESGCM(bytearray(key)) + ct2 = aesgcm2.encrypt(bytearray(nonce), pt, ad) + assert ct2 == ct + computed_pt2 = aesgcm2.decrypt(bytearray(nonce), ct2, ad) + assert computed_pt2 == pt diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 4d48e8ad..d99ba406 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -12,19 +12,54 @@ import pytest from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import algorithms, base, modes -from .utils import generate_aead_test, generate_encrypt_test +from .utils import _load_all_params, generate_aead_test, generate_encrypt_test +from ...doubles import DummyMode from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.CBC("\x00" * 16) + algorithms.AES(b"\x00" * 32), modes.XTS(b"\x00" * 16) + ), + skip_message="Does not support AES XTS", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESModeXTS(object): + @pytest.mark.parametrize( + "vector", + # This list comprehension excludes any vector that does not have a + # data unit length that is divisible by 8. The NIST vectors include + # tests for implementations that support encryption of data that is + # not divisible modulo 8, but OpenSSL is not such an implementation. + [x for x in _load_all_params( + os.path.join("ciphers", "AES", "XTS", "tweak-128hexstr"), + ["XTSGenAES128.rsp", "XTSGenAES256.rsp"], + load_nist_vectors + ) if int(x["dataunitlen"]) / 8.0 == int(x["dataunitlen"]) // 8] + ) + def test_xts_vectors(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + tweak = binascii.unhexlify(vector["i"]) + pt = binascii.unhexlify(vector["pt"]) + ct = binascii.unhexlify(vector["ct"]) + cipher = base.Cipher(algorithms.AES(key), modes.XTS(tweak), backend) + enc = cipher.encryptor() + computed_ct = enc.update(pt) + enc.finalize() + assert computed_ct == ct + dec = cipher.decryptor() + computed_pt = dec.update(ct) + dec.finalize() + assert computed_pt == pt + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.CBC(b"\x00" * 16) ), skip_message="Does not support AES CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeCBC(object): - test_CBC = generate_encrypt_test( + test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CBC"), [ @@ -51,13 +86,13 @@ class TestAESModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.ECB() + algorithms.AES(b"\x00" * 16), modes.ECB() ), skip_message="Does not support AES ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeECB(object): - test_ECB = generate_encrypt_test( + test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "ECB"), [ @@ -84,13 +119,13 @@ class TestAESModeECB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.OFB("\x00" * 16) + algorithms.AES(b"\x00" * 16), modes.OFB(b"\x00" * 16) ), skip_message="Does not support AES OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeOFB(object): - test_OFB = generate_encrypt_test( + test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "OFB"), [ @@ -117,13 +152,13 @@ class TestAESModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.CFB("\x00" * 16) + algorithms.AES(b"\x00" * 16), modes.CFB(b"\x00" * 16) ), skip_message="Does not support AES CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeCFB(object): - test_CFB = generate_encrypt_test( + test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CFB"), [ @@ -150,13 +185,13 @@ class TestAESModeCFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.CFB8("\x00" * 16) + algorithms.AES(b"\x00" * 16), modes.CFB8(b"\x00" * 16) ), skip_message="Does not support AES CFB8", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeCFB8(object): - test_CFB8 = generate_encrypt_test( + test_cfb8 = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CFB"), [ @@ -183,13 +218,13 @@ class TestAESModeCFB8(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.CTR("\x00" * 16) + algorithms.AES(b"\x00" * 16), modes.CTR(b"\x00" * 16) ), skip_message="Does not support AES CTR", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeCTR(object): - test_CTR = generate_encrypt_test( + test_ctr = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CTR"), ["aes-128-ctr.txt", "aes-192-ctr.txt", "aes-256-ctr.txt"], @@ -200,13 +235,13 @@ class TestAESModeCTR(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.GCM("\x00" * 12) + algorithms.AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) ), skip_message="Does not support AES GCM", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeGCM(object): - test_GCM = generate_aead_test( + test_gcm = generate_aead_test( load_nist_vectors, os.path.join("ciphers", "AES", "GCM"), [ @@ -253,3 +288,174 @@ class TestAESModeGCM(object): computed_ct = encryptor.update(pt) + encryptor.finalize() assert computed_ct == ct assert encryptor.tag == tag + + def test_gcm_ciphertext_limit(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor._bytes_processed = modes.GCM._MAX_ENCRYPTED_BYTES - 16 + encryptor.update(b"0" * 16) + assert ( + encryptor._bytes_processed == modes.GCM._MAX_ENCRYPTED_BYTES + ) + with pytest.raises(ValueError): + encryptor.update(b"0") + + def test_gcm_aad_limit(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor._aad_bytes_processed = modes.GCM._MAX_AAD_BYTES - 16 + encryptor.authenticate_additional_data(b"0" * 16) + assert encryptor._aad_bytes_processed == modes.GCM._MAX_AAD_BYTES + with pytest.raises(ValueError): + encryptor.authenticate_additional_data(b"0") + + def test_gcm_ciphertext_increments(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor.update(b"0" * 8) + assert encryptor._bytes_processed == 8 + encryptor.update(b"0" * 7) + assert encryptor._bytes_processed == 15 + encryptor.update(b"0" * 18) + assert encryptor._bytes_processed == 33 + + def test_gcm_aad_increments(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor.authenticate_additional_data(b"0" * 8) + assert encryptor._aad_bytes_processed == 8 + encryptor.authenticate_additional_data(b"0" * 18) + assert encryptor._aad_bytes_processed == 26 + + def test_gcm_tag_decrypt_none(self, backend): + key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") + iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") + aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193") + + encryptor = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ).encryptor() + encryptor.authenticate_additional_data(aad) + encryptor.finalize() + + decryptor = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ).decryptor() + decryptor.authenticate_additional_data(aad) + with pytest.raises(ValueError): + decryptor.finalize() + + def test_gcm_tag_decrypt_mode(self, backend): + key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") + iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") + aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193") + + encryptor = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ).encryptor() + encryptor.authenticate_additional_data(aad) + encryptor.finalize() + tag = encryptor.tag + + decryptor = base.Cipher( + algorithms.AES(key), + modes.GCM(iv, tag), + backend=backend + ).decryptor() + decryptor.authenticate_additional_data(aad) + decryptor.finalize() + + def test_gcm_tag_decrypt_finalize(self, backend): + key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") + iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") + aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193") + + encryptor = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ).encryptor() + encryptor.authenticate_additional_data(aad) + encryptor.finalize() + tag = encryptor.tag + + decryptor = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ).decryptor() + decryptor.authenticate_additional_data(aad) + + decryptor.finalize_with_tag(tag) + + def test_gcm_tag_decrypt_finalize_tag_length(self, backend): + decryptor = base.Cipher( + algorithms.AES(b"0" * 16), + modes.GCM(b"0" * 12), + backend=backend + ).decryptor() + with pytest.raises(ValueError): + decryptor.finalize_with_tag(b"tagtooshort") + + def test_buffer_protocol(self, backend): + data = bytearray(b"helloworld") + enc = base.Cipher( + algorithms.AES(bytearray(b"\x00" * 16)), + modes.GCM(bytearray(b"\x00" * 12)), + backend + ).encryptor() + enc.authenticate_additional_data(bytearray(b"foo")) + ct = enc.update(data) + enc.finalize() + dec = base.Cipher( + algorithms.AES(bytearray(b"\x00" * 16)), + modes.GCM(bytearray(b"\x00" * 12), enc.tag), + backend + ).decryptor() + dec.authenticate_additional_data(bytearray(b"foo")) + pt = dec.update(ct) + dec.finalize() + assert pt == data + + +@pytest.mark.parametrize( + "mode", + [ + modes.CBC(bytearray(b"\x00" * 16)), + modes.CTR(bytearray(b"\x00" * 16)), + modes.OFB(bytearray(b"\x00" * 16)), + modes.CFB(bytearray(b"\x00" * 16)), + modes.CFB8(bytearray(b"\x00" * 16)), + modes.XTS(bytearray(b"\x00" * 16)), + # Add a dummy mode for coverage of the cipher_supported check. + DummyMode(), + ] +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +def test_buffer_protocol_alternate_modes(mode, backend): + data = bytearray(b"sixteen_byte_msg") + key = algorithms.AES(bytearray(os.urandom(32))) + if not backend.cipher_supported(key, mode): + pytest.skip("AES in {} mode not supported".format(mode.name)) + cipher = base.Cipher(key, mode, backend) + enc = cipher.encryptor() + ct = enc.update(data) + enc.finalize() + dec = cipher.decryptor() + pt = dec.update(ct) + dec.finalize() + assert pt == data diff --git a/tests/hazmat/primitives/test_arc4.py b/tests/hazmat/primitives/test_arc4.py index acd306b2..1a173444 100644 --- a/tests/hazmat/primitives/test_arc4.py +++ b/tests/hazmat/primitives/test_arc4.py @@ -18,7 +18,7 @@ from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.ARC4("\x00" * 16), None + algorithms.ARC4(b"\x00" * 16), None ), skip_message="Does not support ARC4", ) @@ -35,6 +35,7 @@ class TestARC4(object): "rfc-6229-128.txt", "rfc-6229-192.txt", "rfc-6229-256.txt", + "arc4.txt" ], lambda key, **kwargs: algorithms.ARC4(binascii.unhexlify(key)), ) diff --git a/tests/hazmat/primitives/test_asym_utils.py b/tests/hazmat/primitives/test_asym_utils.py index 35b77ca4..b49ca3f2 100644 --- a/tests/hazmat/primitives/test_asym_utils.py +++ b/tests/hazmat/primitives/test_asym_utils.py @@ -7,64 +7,69 @@ from __future__ import absolute_import, division, print_function import pytest from cryptography.hazmat.primitives.asymmetric.utils import ( - decode_rfc6979_signature, encode_rfc6979_signature + Prehashed, decode_dss_signature, encode_dss_signature ) -def test_rfc6979_signature(): - sig = encode_rfc6979_signature(1, 1) +def test_dss_signature(): + sig = encode_dss_signature(1, 1) assert sig == b"0\x06\x02\x01\x01\x02\x01\x01" - assert decode_rfc6979_signature(sig) == (1, 1) + assert decode_dss_signature(sig) == (1, 1) r_s1 = ( 1037234182290683143945502320610861668562885151617, 559776156650501990899426031439030258256861634312 ) - sig2 = encode_rfc6979_signature(*r_s1) + sig2 = encode_dss_signature(*r_s1) assert sig2 == ( b'0-\x02\x15\x00\xb5\xaf0xg\xfb\x8bT9\x00\x13\xccg\x02\r\xdf\x1f,\x0b' b'\x81\x02\x14b\r;"\xabP1D\x0c>5\xea\xb6\xf4\x81)\x8f\x9e\x9f\x08' ) - assert decode_rfc6979_signature(sig2) == r_s1 + assert decode_dss_signature(sig2) == r_s1 - sig3 = encode_rfc6979_signature(0, 0) + sig3 = encode_dss_signature(0, 0) assert sig3 == b"0\x06\x02\x01\x00\x02\x01\x00" - assert decode_rfc6979_signature(sig3) == (0, 0) + assert decode_dss_signature(sig3) == (0, 0) - sig4 = encode_rfc6979_signature(-1, 0) - assert sig4 == b"0\x06\x02\x01\xFF\x02\x01\x00" - assert decode_rfc6979_signature(sig4) == (-1, 0) +def test_encode_dss_non_integer(): + with pytest.raises(ValueError): + encode_dss_signature("h", 3) -def test_encode_rfc6979_non_integer(): with pytest.raises(ValueError): - encode_rfc6979_signature("h", 3) + encode_dss_signature("3", "2") with pytest.raises(ValueError): - encode_rfc6979_signature("3", "2") + encode_dss_signature(3, "h") with pytest.raises(ValueError): - encode_rfc6979_signature(3, "h") + encode_dss_signature(3.3, 1.2) with pytest.raises(ValueError): - encode_rfc6979_signature(3.3, 1.2) + encode_dss_signature("hello", "world") + +def test_encode_dss_negative(): with pytest.raises(ValueError): - encode_rfc6979_signature("hello", "world") + encode_dss_signature(-1, 0) -def test_decode_rfc6979_trailing_bytes(): +def test_decode_dss_trailing_bytes(): with pytest.raises(ValueError): - decode_rfc6979_signature(b"0\x06\x02\x01\x01\x02\x01\x01\x00\x00\x00") + decode_dss_signature(b"0\x06\x02\x01\x01\x02\x01\x01\x00\x00\x00") -def test_decode_rfc6979_invalid_asn1(): +def test_decode_dss_invalid_asn1(): with pytest.raises(ValueError): # This byte sequence has an invalid ASN.1 sequence length as well as # an invalid integer length for the second integer. - decode_rfc6979_signature(b"0\x07\x02\x01\x01\x02\x02\x01") + decode_dss_signature(b"0\x07\x02\x01\x01\x02\x02\x01") with pytest.raises(ValueError): - # This is the BER "end-of-contents octets," which older versions of - # pyasn1 are wrongly willing to return from top-level DER decoding. - decode_rfc6979_signature(b"\x00\x00") + # This is the BER "end-of-contents octets". + decode_dss_signature(b"\x00\x00") + + +def test_pass_invalid_prehashed_arg(): + with pytest.raises(TypeError): + Prehashed(object()) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index 1b3fc1cb..37158f15 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -8,7 +8,6 @@ import binascii import pytest -from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, _Reasons ) @@ -20,23 +19,10 @@ from cryptography.hazmat.primitives.ciphers import ( from .utils import ( generate_aead_exception_test, generate_aead_tag_exception_test ) +from ...doubles import DummyCipherAlgorithm, DummyMode from ...utils import raises_unsupported_algorithm -@utils.register_interface(modes.Mode) -class DummyMode(object): - name = "dummy-mode" - - def validate_for_algorithm(self, algorithm): - pass - - -@utils.register_interface(base.CipherAlgorithm) -class DummyCipher(object): - name = "dummy-cipher" - key_size = None - - @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCipher(object): def test_creates_encryptor(self, backend): @@ -84,6 +70,19 @@ class TestCipherContext(object): with pytest.raises(AlreadyFinalized): decryptor.finalize() + def test_use_update_into_after_finalize(self, backend): + cipher = Cipher( + algorithms.AES(binascii.unhexlify(b"0" * 32)), + modes.CBC(binascii.unhexlify(b"0" * 32)), + backend + ) + encryptor = cipher.encryptor() + encryptor.update(b"a" * 16) + encryptor.finalize() + with pytest.raises(AlreadyFinalized): + buf = bytearray(31) + encryptor.update_into(b"b" * 16, buf) + def test_unaligned_block_encryption(self, backend): cipher = Cipher( algorithms.AES(binascii.unhexlify(b"0" * 32)), @@ -107,7 +106,7 @@ class TestCipherContext(object): @pytest.mark.parametrize("mode", [DummyMode(), None]) def test_nonexistent_cipher(self, backend, mode): cipher = Cipher( - DummyCipher(), mode, backend + DummyCipherAlgorithm(), mode, backend ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): cipher.encryptor() @@ -134,7 +133,7 @@ class TestCipherContext(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.GCM("\x00" * 12) + algorithms.AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) ), skip_message="Does not support AES GCM", ) @@ -191,3 +190,37 @@ class TestModeValidation(object): modes.CTR(b"abc"), backend, ) + + def test_gcm(self): + with pytest.raises(ValueError): + modes.GCM(b"") + + +class TestModesRequireBytes(object): + def test_cbc(self): + with pytest.raises(TypeError): + modes.CBC([1] * 16) + + def test_cfb(self): + with pytest.raises(TypeError): + modes.CFB([1] * 16) + + def test_cfb8(self): + with pytest.raises(TypeError): + modes.CFB8([1] * 16) + + def test_ofb(self): + with pytest.raises(TypeError): + modes.OFB([1] * 16) + + def test_ctr(self): + with pytest.raises(TypeError): + modes.CTR([1] * 16) + + def test_gcm_iv(self): + with pytest.raises(TypeError): + modes.GCM([1] * 16) + + def test_gcm_tag(self): + with pytest.raises(TypeError): + modes.GCM(b"\x00" * 16, [1] * 16) diff --git a/tests/hazmat/primitives/test_blowfish.py b/tests/hazmat/primitives/test_blowfish.py index de49e86d..5f7480ec 100644 --- a/tests/hazmat/primitives/test_blowfish.py +++ b/tests/hazmat/primitives/test_blowfish.py @@ -18,13 +18,13 @@ from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Blowfish("\x00" * 56), modes.ECB() + algorithms.Blowfish(b"\x00" * 56), modes.ECB() ), skip_message="Does not support Blowfish ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestBlowfishModeECB(object): - test_ECB = generate_encrypt_test( + test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-ecb.txt"], @@ -35,13 +35,13 @@ class TestBlowfishModeECB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Blowfish("\x00" * 56), modes.CBC("\x00" * 8) + algorithms.Blowfish(b"\x00" * 56), modes.CBC(b"\x00" * 8) ), skip_message="Does not support Blowfish CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestBlowfishModeCBC(object): - test_CBC = generate_encrypt_test( + test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-cbc.txt"], @@ -52,13 +52,13 @@ class TestBlowfishModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Blowfish("\x00" * 56), modes.OFB("\x00" * 8) + algorithms.Blowfish(b"\x00" * 56), modes.OFB(b"\x00" * 8) ), skip_message="Does not support Blowfish OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestBlowfishModeOFB(object): - test_OFB = generate_encrypt_test( + test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-ofb.txt"], @@ -69,13 +69,13 @@ class TestBlowfishModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Blowfish("\x00" * 56), modes.CFB("\x00" * 8) + algorithms.Blowfish(b"\x00" * 56), modes.CFB(b"\x00" * 8) ), skip_message="Does not support Blowfish CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestBlowfishModeCFB(object): - test_CFB = generate_encrypt_test( + test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-cfb.txt"], diff --git a/tests/hazmat/primitives/test_camellia.py b/tests/hazmat/primitives/test_camellia.py index 8bdcb309..07a735ad 100644 --- a/tests/hazmat/primitives/test_camellia.py +++ b/tests/hazmat/primitives/test_camellia.py @@ -20,13 +20,13 @@ from ...utils import ( @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Camellia("\x00" * 16), modes.ECB() + algorithms.Camellia(b"\x00" * 16), modes.ECB() ), skip_message="Does not support Camellia ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCamelliaModeECB(object): - test_ECB = generate_encrypt_test( + test_ecb = generate_encrypt_test( load_cryptrec_vectors, os.path.join("ciphers", "Camellia"), [ @@ -41,13 +41,13 @@ class TestCamelliaModeECB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Camellia("\x00" * 16), modes.CBC("\x00" * 16) + algorithms.Camellia(b"\x00" * 16), modes.CBC(b"\x00" * 16) ), skip_message="Does not support Camellia CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCamelliaModeCBC(object): - test_CBC = generate_encrypt_test( + test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Camellia"), ["camellia-cbc.txt"], @@ -58,13 +58,13 @@ class TestCamelliaModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Camellia("\x00" * 16), modes.OFB("\x00" * 16) + algorithms.Camellia(b"\x00" * 16), modes.OFB(b"\x00" * 16) ), skip_message="Does not support Camellia OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCamelliaModeOFB(object): - test_OFB = generate_encrypt_test( + test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Camellia"), ["camellia-ofb.txt"], @@ -75,13 +75,13 @@ class TestCamelliaModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Camellia("\x00" * 16), modes.CFB("\x00" * 16) + algorithms.Camellia(b"\x00" * 16), modes.CFB(b"\x00" * 16) ), skip_message="Does not support Camellia CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCamelliaModeCFB(object): - test_CFB = generate_encrypt_test( + test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Camellia"), ["camellia-cfb.txt"], diff --git a/tests/hazmat/primitives/test_cast5.py b/tests/hazmat/primitives/test_cast5.py index 0e4f879c..87e12a33 100644 --- a/tests/hazmat/primitives/test_cast5.py +++ b/tests/hazmat/primitives/test_cast5.py @@ -18,13 +18,13 @@ from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5("\x00" * 16), modes.ECB() + algorithms.CAST5(b"\x00" * 16), modes.ECB() ), skip_message="Does not support CAST5 ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCAST5ModeECB(object): - test_ECB = generate_encrypt_test( + test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-ecb.txt"], @@ -35,13 +35,13 @@ class TestCAST5ModeECB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5("\x00" * 16), modes.CBC("\x00" * 8) + algorithms.CAST5(b"\x00" * 16), modes.CBC(b"\x00" * 8) ), skip_message="Does not support CAST5 CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCAST5ModeCBC(object): - test_CBC = generate_encrypt_test( + test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-cbc.txt"], @@ -52,13 +52,13 @@ class TestCAST5ModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5("\x00" * 16), modes.OFB("\x00" * 8) + algorithms.CAST5(b"\x00" * 16), modes.OFB(b"\x00" * 8) ), skip_message="Does not support CAST5 OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCAST5ModeOFB(object): - test_OFB = generate_encrypt_test( + test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-ofb.txt"], @@ -69,33 +69,16 @@ class TestCAST5ModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5("\x00" * 16), modes.CFB("\x00" * 8) + algorithms.CAST5(b"\x00" * 16), modes.CFB(b"\x00" * 8) ), skip_message="Does not support CAST5 CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCAST5ModeCFB(object): - test_CFB = generate_encrypt_test( + test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-cfb.txt"], lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)) ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5("\x00" * 16), modes.CTR("\x00" * 8) - ), - skip_message="Does not support CAST5 CTR", -) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCAST5ModeCTR(object): - test_CTR = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "CAST5"), - ["cast5-ctr.txt"], - lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), - lambda iv, **kwargs: modes.CTR(binascii.unhexlify(iv)) - ) diff --git a/tests/hazmat/primitives/test_chacha20.py b/tests/hazmat/primitives/test_chacha20.py new file mode 100644 index 00000000..7c475c0f --- /dev/null +++ b/tests/hazmat/primitives/test_chacha20.py @@ -0,0 +1,76 @@ +# 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 struct + +import pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms + +from .utils import _load_all_params +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.ChaCha20(b"\x00" * 32, b"0" * 16), None + ), + skip_message="Does not support ChaCha20", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestChaCha20(object): + @pytest.mark.parametrize( + "vector", + _load_all_params( + os.path.join("ciphers", "ChaCha20"), + ["rfc7539.txt"], + load_nist_vectors + ) + ) + def test_vectors(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["nonce"]) + ibc = struct.pack("<i", int(vector["initial_block_counter"])) + pt = binascii.unhexlify(vector["plaintext"]) + encryptor = Cipher( + algorithms.ChaCha20(key, ibc + nonce), None, backend + ).encryptor() + computed_ct = encryptor.update(pt) + encryptor.finalize() + assert binascii.hexlify(computed_ct) == vector["ciphertext"] + + def test_buffer_protocol(self, backend): + key = bytearray(os.urandom(32)) + nonce = bytearray(os.urandom(16)) + cipher = Cipher( + algorithms.ChaCha20(key, nonce), None, backend + ) + enc = cipher.encryptor() + ct = enc.update(bytearray(b"hello")) + enc.finalize() + dec = cipher.decryptor() + pt = dec.update(ct) + dec.finalize() + assert pt == b"hello" + + def test_key_size(self): + chacha = algorithms.ChaCha20(b"0" * 32, b"0" * 16) + assert chacha.key_size == 256 + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + algorithms.ChaCha20(b"wrongsize", b"0" * 16) + + def test_invalid_nonce(self): + with pytest.raises(ValueError): + algorithms.ChaCha20(b"0" * 32, b"0") + + with pytest.raises(TypeError): + algorithms.ChaCha20(b"0" * 32, object()) + + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + algorithms.ChaCha20(u"0" * 32, b"0" * 16) diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py index d9a07ff6..f29ba9a9 100644 --- a/tests/hazmat/primitives/test_ciphers.py +++ b/tests/hazmat/primitives/test_ciphers.py @@ -5,17 +5,21 @@ from __future__ import absolute_import, division, print_function import binascii +import os import pytest -from cryptography.exceptions import _Reasons +from cryptography.exceptions import AlreadyFinalized, _Reasons +from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives.ciphers import modes from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, Blowfish, CAST5, Camellia, IDEA, SEED, TripleDES ) -from cryptography.hazmat.primitives.ciphers.modes import ECB -from ...utils import raises_unsupported_algorithm +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) class TestAES(object): @@ -32,6 +36,34 @@ class TestAES(object): with pytest.raises(ValueError): AES(binascii.unhexlify(b"0" * 12)) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + AES(u"0" * 32) + + +class TestAESXTS(object): + @pytest.mark.requires_backend_interface(interface=CipherBackend) + @pytest.mark.parametrize( + "mode", + (modes.CBC, modes.CTR, modes.CFB, modes.CFB8, modes.OFB) + ) + def test_invalid_key_size_with_mode(self, mode, backend): + with pytest.raises(ValueError): + ciphers.Cipher(AES(b"0" * 64), mode(b"0" * 16), backend) + + def test_xts_tweak_not_bytes(self): + with pytest.raises(TypeError): + modes.XTS(32) + + def test_xts_tweak_too_small(self): + with pytest.raises(ValueError): + modes.XTS(b"0") + + @pytest.mark.requires_backend_interface(interface=CipherBackend) + def test_xts_wrong_key_size(self, backend): + with pytest.raises(ValueError): + ciphers.Cipher(AES(b"0" * 16), modes.XTS(b"0" * 16), backend) + class TestCamellia(object): @pytest.mark.parametrize(("key", "keysize"), [ @@ -47,6 +79,10 @@ class TestCamellia(object): with pytest.raises(ValueError): Camellia(binascii.unhexlify(b"0" * 12)) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + Camellia(u"0" * 32) + class TestTripleDES(object): @pytest.mark.parametrize("key", [ @@ -62,6 +98,10 @@ class TestTripleDES(object): with pytest.raises(ValueError): TripleDES(binascii.unhexlify(b"0" * 12)) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + TripleDES(u"0" * 16) + class TestBlowfish(object): @pytest.mark.parametrize(("key", "keysize"), [ @@ -75,6 +115,10 @@ class TestBlowfish(object): with pytest.raises(ValueError): Blowfish(binascii.unhexlify(b"0" * 6)) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + Blowfish(u"0" * 8) + class TestCAST5(object): @pytest.mark.parametrize(("key", "keysize"), [ @@ -88,6 +132,10 @@ class TestCAST5(object): with pytest.raises(ValueError): CAST5(binascii.unhexlify(b"0" * 34)) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + CAST5(u"0" * 10) + class TestARC4(object): @pytest.mark.parametrize(("key", "keysize"), [ @@ -107,6 +155,10 @@ class TestARC4(object): with pytest.raises(ValueError): ARC4(binascii.unhexlify(b"0" * 34)) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + ARC4(u"0" * 10) + class TestIDEA(object): def test_key_size(self): @@ -117,6 +169,10 @@ class TestIDEA(object): with pytest.raises(ValueError): IDEA(b"\x00" * 17) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + IDEA(u"0" * 16) + class TestSEED(object): def test_key_size(self): @@ -127,9 +183,129 @@ class TestSEED(object): with pytest.raises(ValueError): SEED(b"\x00" * 17) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + SEED(u"0" * 16) + def test_invalid_backend(): pretend_backend = object() with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - ciphers.Cipher(AES(b"AAAAAAAAAAAAAAAA"), ECB, pretend_backend) + ciphers.Cipher(AES(b"AAAAAAAAAAAAAAAA"), modes.ECB, pretend_backend) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES ECB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCipherUpdateInto(object): + @pytest.mark.parametrize( + "params", + load_vectors_from_file( + os.path.join("ciphers", "AES", "ECB", "ECBGFSbox128.rsp"), + load_nist_vectors + ) + ) + def test_update_into(self, params, backend): + key = binascii.unhexlify(params["key"]) + pt = binascii.unhexlify(params["plaintext"]) + ct = binascii.unhexlify(params["ciphertext"]) + c = ciphers.Cipher(AES(key), modes.ECB(), backend) + encryptor = c.encryptor() + buf = bytearray(len(pt) + 15) + res = encryptor.update_into(pt, buf) + assert res == len(pt) + assert bytes(buf)[:res] == ct + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.GCM(b"0" * 12) + ), + skip_message="Does not support AES GCM", + ) + def test_update_into_gcm(self, backend): + key = binascii.unhexlify(b"e98b72a9881a84ca6b76e0f43e68647a") + iv = binascii.unhexlify(b"8b23299fde174053f3d652ba") + ct = binascii.unhexlify(b"5a3c1cf1985dbb8bed818036fdd5ab42") + pt = binascii.unhexlify(b"28286a321293253c3e0aa2704a278032") + c = ciphers.Cipher(AES(key), modes.GCM(iv), backend) + encryptor = c.encryptor() + buf = bytearray(len(pt) + 15) + res = encryptor.update_into(pt, buf) + assert res == len(pt) + assert bytes(buf)[:res] == ct + encryptor.finalize() + c = ciphers.Cipher(AES(key), modes.GCM(iv, encryptor.tag), backend) + decryptor = c.decryptor() + res = decryptor.update_into(ct, buf) + decryptor.finalize() + assert res == len(pt) + assert bytes(buf)[:res] == pt + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.GCM(b"0" * 12) + ), + skip_message="Does not support AES GCM", + ) + def test_finalize_with_tag_already_finalized(self, backend): + key = binascii.unhexlify(b"e98b72a9881a84ca6b76e0f43e68647a") + iv = binascii.unhexlify(b"8b23299fde174053f3d652ba") + encryptor = ciphers.Cipher( + AES(key), modes.GCM(iv), backend + ).encryptor() + ciphertext = encryptor.update(b"abc") + encryptor.finalize() + + decryptor = ciphers.Cipher( + AES(key), modes.GCM(iv, tag=encryptor.tag), backend + ).decryptor() + decryptor.update(ciphertext) + decryptor.finalize() + with pytest.raises(AlreadyFinalized): + decryptor.finalize_with_tag(encryptor.tag) + + @pytest.mark.parametrize( + "params", + load_vectors_from_file( + os.path.join("ciphers", "AES", "ECB", "ECBGFSbox128.rsp"), + load_nist_vectors + ) + ) + def test_update_into_multiple_calls(self, params, backend): + key = binascii.unhexlify(params["key"]) + pt = binascii.unhexlify(params["plaintext"]) + ct = binascii.unhexlify(params["ciphertext"]) + c = ciphers.Cipher(AES(key), modes.ECB(), backend) + encryptor = c.encryptor() + buf = bytearray(len(pt) + 15) + res = encryptor.update_into(pt[:3], buf) + assert res == 0 + res = encryptor.update_into(pt[3:], buf) + assert res == len(pt) + assert bytes(buf)[:res] == ct + + def test_update_into_buffer_too_small(self, backend): + key = b"\x00" * 16 + c = ciphers.Cipher(AES(key), modes.ECB(), backend) + encryptor = c.encryptor() + buf = bytearray(16) + with pytest.raises(ValueError): + encryptor.update_into(b"testing", buf) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) + ), + skip_message="Does not support AES GCM", + ) + def test_update_into_buffer_too_small_gcm(self, backend): + key = b"\x00" * 16 + c = ciphers.Cipher(AES(key), modes.GCM(b"\x00" * 12), backend) + encryptor = c.encryptor() + buf = bytearray(5) + with pytest.raises(ValueError): + encryptor.update_into(b"testing", buf) diff --git a/tests/hazmat/primitives/test_cmac.py b/tests/hazmat/primitives/test_cmac.py index 08b6bf5d..e319396d 100644 --- a/tests/hazmat/primitives/test_cmac.py +++ b/tests/hazmat/primitives/test_cmac.py @@ -6,8 +6,6 @@ from __future__ import absolute_import, division, print_function import binascii -import pretend - import pytest from cryptography.exceptions import ( @@ -19,11 +17,11 @@ from cryptography.hazmat.primitives.ciphers.algorithms import ( ) from cryptography.hazmat.primitives.cmac import CMAC -from ..backends.test_multibackend import DummyCMACBackend from ...utils import ( load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm ) + vectors_aes128 = load_vectors_from_file( "CMAC/nist-800-38b-aes128.txt", load_nist_vectors) @@ -185,16 +183,18 @@ class TestCMAC(object): copy_cmac = cmac.copy() assert cmac.finalize() == copy_cmac.finalize() - -def test_copy(): - backend = DummyCMACBackend([AES]) - copied_ctx = pretend.stub() - pretend_ctx = pretend.stub(copy=lambda: copied_ctx) - key = b"2b7e151628aed2a6abf7158809cf4f3c" - cmac = CMAC(AES(key), backend=backend, ctx=pretend_ctx) - - assert cmac._backend is backend - assert cmac.copy()._backend is backend + @pytest.mark.supported( + only_if=lambda backend: backend.cmac_algorithm_supported( + AES(fake_key)), + skip_message="Does not support CMAC." + ) + def test_buffer_protocol(self, backend): + key = bytearray(b"2b7e151628aed2a6abf7158809cf4f3c") + cmac = CMAC(AES(key), backend) + cmac.update(b"6bc1bee22e409f96e93d7e117393172a") + assert cmac.finalize() == binascii.unhexlify( + b"a21e6e647bfeaf5ca0a5e1bcd957dfad" + ) def test_invalid_backend(): diff --git a/tests/hazmat/primitives/test_concatkdf.py b/tests/hazmat/primitives/test_concatkdf.py index 27e5460e..67315099 100644 --- a/tests/hazmat/primitives/test_concatkdf.py +++ b/tests/hazmat/primitives/test_concatkdf.py @@ -52,6 +52,22 @@ class TestConcatKDFHash(object): assert ckdf.derive(prk) == okm + def test_buffer_protocol(self, backend): + prk = binascii.unhexlify( + b"52169af5c485dcc2321eb8d26d5efa21fb9b93c98e38412ee2484cf14f0d0d23" + ) + + okm = binascii.unhexlify(b"1c3bc9e7c4547c5191c0d478cccaed55") + + oinfo = binascii.unhexlify( + b"a1b2c3d4e53728157e634612c12d6d5223e204aeea4341565369647bd184bcd2" + b"46f72971f292badaa2fe4124612cba" + ) + + ckdf = ConcatKDFHash(hashes.SHA256(), 16, oinfo, backend) + + assert ckdf.derive(bytearray(prk)) == okm + def test_verify(self, backend): prk = binascii.unhexlify( b"52169af5c485dcc2321eb8d26d5efa21fb9b93c98e38412ee2484cf14f0d0d23" @@ -158,6 +174,46 @@ class TestConcatKDFHMAC(object): assert ckdf.derive(prk) == okm + def test_buffer_protocol(self, backend): + prk = binascii.unhexlify( + b"013951627c1dea63ea2d7702dd24e963eef5faac6b4af7e4" + b"b831cde499dff1ce45f6179f741c728aa733583b02409208" + b"8f0af7fce1d045edbc5790931e8d5ca79c73" + ) + + okm = binascii.unhexlify(b"64ce901db10d558661f10b6836a122a7" + b"605323ce2f39bf27eaaac8b34cf89f2f") + + oinfo = binascii.unhexlify( + b"a1b2c3d4e55e600be5f367e0e8a465f4bf2704db00c9325c" + b"9fbd216d12b49160b2ae5157650f43415653696421e68e" + ) + + ckdf = ConcatKDFHMAC(hashes.SHA512(), 32, None, oinfo, backend) + + assert ckdf.derive(bytearray(prk)) == okm + + def test_derive_explicit_salt(self, backend): + prk = binascii.unhexlify( + b"013951627c1dea63ea2d7702dd24e963eef5faac6b4af7e4" + b"b831cde499dff1ce45f6179f741c728aa733583b02409208" + b"8f0af7fce1d045edbc5790931e8d5ca79c73" + ) + + okm = binascii.unhexlify(b"64ce901db10d558661f10b6836a122a7" + b"605323ce2f39bf27eaaac8b34cf89f2f") + + oinfo = binascii.unhexlify( + b"a1b2c3d4e55e600be5f367e0e8a465f4bf2704db00c9325c" + b"9fbd216d12b49160b2ae5157650f43415653696421e68e" + ) + + ckdf = ConcatKDFHMAC( + hashes.SHA512(), 32, b"\x00" * 128, oinfo, backend + ) + + assert ckdf.derive(prk) == okm + def test_verify(self, backend): prk = binascii.unhexlify( b"013951627c1dea63ea2d7702dd24e963eef5faac6b4af7e4" diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index d8869de9..43f2ce5c 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -4,22 +4,42 @@ from __future__ import absolute_import, division, print_function +import binascii +import itertools +import os + import pytest +from cryptography.hazmat.backends.interfaces import ( + DERSerializationBackend, DHBackend, PEMSerializationBackend) +from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import dh +from cryptography.utils import int_from_bytes + +from ...doubles import DummyKeySerializationEncryption +from ...utils import load_nist_vectors, load_vectors_from_file + + +def _skip_dhx_unsupported(backend, is_dhx): + if not is_dhx: + return + if not backend.dh_x942_serialization_supported(): + pytest.skip( + "DH x9.42 serialization is not supported" + ) def test_dh_parameternumbers(): params = dh.DHParameterNumbers( - 65537, 3 + 65537, 2 ) assert params.p == 65537 - assert params.g == 3 + assert params.g == 2 with pytest.raises(TypeError): dh.DHParameterNumbers( - None, 3 + None, 2 ) with pytest.raises(TypeError): @@ -32,10 +52,28 @@ def test_dh_parameternumbers(): None, None ) + with pytest.raises(ValueError): + dh.DHParameterNumbers( + 65537, 1 + ) + + params = dh.DHParameterNumbers( + 65537, 7, 1245 + ) + + assert params.p == 65537 + assert params.g == 7 + assert params.q == 1245 + + with pytest.raises(TypeError): + dh.DHParameterNumbers( + 65537, 2, "hello" + ) + def test_dh_numbers(): params = dh.DHParameterNumbers( - 65537, 3 + 65537, 2 ) public = dh.DHPublicNumbers( @@ -74,14 +112,18 @@ def test_dh_numbers(): def test_dh_parameter_numbers_equality(): - assert dh.DHParameterNumbers(65537, 3) == dh.DHParameterNumbers(65537, 3) - assert dh.DHParameterNumbers(6, 3) != dh.DHParameterNumbers(65537, 3) - assert dh.DHParameterNumbers(65537, 0) != dh.DHParameterNumbers(65537, 3) - assert dh.DHParameterNumbers(65537, 0) != object() + assert dh.DHParameterNumbers(65537, 2) == dh.DHParameterNumbers(65537, 2) + assert dh.DHParameterNumbers(65537, 7, 12345) == dh.DHParameterNumbers( + 65537, 7, 12345) + assert dh.DHParameterNumbers(6, 2) != dh.DHParameterNumbers(65537, 2) + assert dh.DHParameterNumbers(65537, 2, 123) != dh.DHParameterNumbers( + 65537, 2, 456) + assert dh.DHParameterNumbers(65537, 5) != dh.DHParameterNumbers(65537, 2) + assert dh.DHParameterNumbers(65537, 2) != object() def test_dh_private_numbers_equality(): - params = dh.DHParameterNumbers(65537, 3) + params = dh.DHParameterNumbers(65537, 2) public = dh.DHPublicNumbers(1, params) private = dh.DHPrivateNumbers(2, public) @@ -89,16 +131,752 @@ def test_dh_private_numbers_equality(): assert private != dh.DHPrivateNumbers(0, public) assert private != dh.DHPrivateNumbers(2, dh.DHPublicNumbers(0, params)) assert private != dh.DHPrivateNumbers( - 2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 0)) + 2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 5)) ) assert private != object() def test_dh_public_numbers_equality(): - params = dh.DHParameterNumbers(65537, 3) + params = dh.DHParameterNumbers(65537, 2) public = dh.DHPublicNumbers(1, params) assert public == dh.DHPublicNumbers(1, params) assert public != dh.DHPublicNumbers(0, params) - assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 0)) + assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 5)) assert public != object() + + +@pytest.mark.requires_backend_interface(interface=DHBackend) +class TestDH(object): + def test_small_key_generate_dh(self, backend): + with pytest.raises(ValueError): + dh.generate_parameters(2, 511, backend) + + def test_unsupported_generator_generate_dh(self, backend): + with pytest.raises(ValueError): + dh.generate_parameters(7, 512, backend) + + def test_dh_parameters_supported(self, backend): + valid_p = int( + b"907c7211ae61aaaba1825ff53b6cb71ac6df9f1a424c033f4a0a41ac42fad3a9" + b"bcfc7f938a269710ed69e330523e4039029b7900977c740990d46efed79b9bbe" + b"73505ae878808944ce4d9c6c52daecc0a87dc889c53499be93db8551ee685f30" + b"349bf1b443d4ebaee0d5e8b441a40d4e8178f8f612f657a5eb91e0a8e" + b"107755f", 16 + ) + assert backend.dh_parameters_supported(valid_p, 5) + assert not backend.dh_parameters_supported(23, 22) + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "rfc3526.txt"), + load_nist_vectors + ) + ) + def test_dh_parameters_allows_rfc3526_groups(self, backend, vector): + p = int_from_bytes(binascii.unhexlify(vector["p"]), 'big') + params = dh.DHParameterNumbers(p, int(vector["g"])) + param = params.parameters(backend) + key = param.generate_private_key() + # This confirms that a key generated with this group + # will pass DH_check when we serialize and de-serialize it via + # the Numbers path. + roundtripped_key = key.private_numbers().private_key(backend) + assert key.private_numbers() == roundtripped_key.private_numbers() + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "RFC5114.txt"), + load_nist_vectors)) + def test_dh_parameters_supported_with_q(self, backend, vector): + assert backend.dh_parameters_supported(int(vector["p"], 16), + int(vector["g"], 16), + int(vector["q"], 16)) + + @pytest.mark.parametrize("with_q", [False, True]) + def test_convert_to_numbers(self, backend, with_q): + if with_q: + vector = load_vectors_from_file( + os.path.join("asymmetric", "DH", "RFC5114.txt"), + load_nist_vectors)[0] + p = int(vector["p"], 16) + g = int(vector["g"], 16) + q = int(vector["q"], 16) + else: + parameters = backend.generate_dh_private_key_and_parameters(2, 512) + + private = parameters.private_numbers() + + p = private.public_numbers.parameter_numbers.p + g = private.public_numbers.parameter_numbers.g + q = None + + params = dh.DHParameterNumbers(p, g, q) + public = dh.DHPublicNumbers(1, params) + private = dh.DHPrivateNumbers(2, public) + + deserialized_params = params.parameters(backend) + deserialized_public = public.public_key(backend) + deserialized_private = private.private_key(backend) + + assert isinstance(deserialized_params, + dh.DHParametersWithSerialization) + assert isinstance(deserialized_public, + dh.DHPublicKeyWithSerialization) + assert isinstance(deserialized_private, + dh.DHPrivateKeyWithSerialization) + + def test_numbers_unsupported_parameters(self, backend): + # p is set to 21 because when calling private_key we want it to + # fail the DH_check call OpenSSL does. Originally this was 23, but + # we are allowing p % 24 to == 23 with this PR (see #3768 for more) + # By setting it to 21 it fails later in DH_check in a primality check + # which triggers the code path we want to test + params = dh.DHParameterNumbers(21, 2) + public = dh.DHPublicNumbers(1, params) + private = dh.DHPrivateNumbers(2, public) + + with pytest.raises(ValueError): + private.private_key(backend) + + @pytest.mark.parametrize("with_q", [False, True]) + def test_generate_dh(self, backend, with_q): + if with_q: + vector = load_vectors_from_file( + os.path.join("asymmetric", "DH", "RFC5114.txt"), + load_nist_vectors)[0] + p = int(vector["p"], 16) + g = int(vector["g"], 16) + q = int(vector["q"], 16) + parameters = dh.DHParameterNumbers(p, g, q).parameters(backend) + key_size = 1024 + else: + generator = 2 + key_size = 512 + + parameters = dh.generate_parameters(generator, key_size, backend) + assert isinstance(parameters, dh.DHParameters) + + key = parameters.generate_private_key() + assert isinstance(key, dh.DHPrivateKey) + assert key.key_size == key_size + + public = key.public_key() + assert isinstance(public, dh.DHPublicKey) + assert public.key_size == key_size + + assert isinstance(parameters, dh.DHParametersWithSerialization) + parameter_numbers = parameters.parameter_numbers() + assert isinstance(parameter_numbers, dh.DHParameterNumbers) + assert parameter_numbers.p.bit_length() == key_size + + assert isinstance(public, dh.DHPublicKeyWithSerialization) + assert isinstance(public.public_numbers(), dh.DHPublicNumbers) + assert isinstance(public.parameters(), dh.DHParameters) + + assert isinstance(key, dh.DHPrivateKeyWithSerialization) + assert isinstance(key.private_numbers(), dh.DHPrivateNumbers) + assert isinstance(key.parameters(), dh.DHParameters) + + def test_exchange(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + assert isinstance(parameters, dh.DHParameters) + + key1 = parameters.generate_private_key() + key2 = parameters.generate_private_key() + + symkey1 = key1.exchange(key2.public_key()) + assert symkey1 + assert len(symkey1) == 512 // 8 + + symkey2 = key2.exchange(key1.public_key()) + assert symkey1 == symkey2 + + def test_exchange_algorithm(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + + key1 = parameters.generate_private_key() + key2 = parameters.generate_private_key() + + shared_key_bytes = key2.exchange(key1.public_key()) + symkey = int_from_bytes(shared_key_bytes, 'big') + + symkey_manual = pow(key1.public_key().public_numbers().y, + key2.private_numbers().x, + parameters.parameter_numbers().p) + + assert symkey == symkey_manual + + def test_symmetric_key_padding(self, backend): + """ + This test has specific parameters that produce a symmetric key + In length 63 bytes instead 64. We make sure here that we add + padding to the key. + """ + p = int("11859949538425015739337467917303613431031019140213666" + "129025407300654026585086345323066284800963463204246390" + "256567934582260424238844463330887962689642467123") + g = 2 + y = int("32155788395534640648739966373159697798396966919821525" + "72238852825117261342483718574508213761865276905503199" + "969908098203345481366464874759377454476688391248") + x = int("409364065449673443397833358558926598469347813468816037" + "268451847116982490733450463194921405069999008617231539" + "7147035896687401350877308899732826446337707128") + parameters = dh.DHParameterNumbers(p, g) + public = dh.DHPublicNumbers(y, parameters) + private = dh.DHPrivateNumbers(x, public) + key = private.private_key(backend) + symkey = key.exchange(public.public_key(backend)) + assert len(symkey) == 512 // 8 + assert symkey[:1] == b'\x00' + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "bad_exchange.txt"), + load_nist_vectors)) + def test_bad_exchange(self, backend, vector): + parameters1 = dh.DHParameterNumbers(int(vector["p1"]), + int(vector["g"])) + public1 = dh.DHPublicNumbers(int(vector["y1"]), parameters1) + private1 = dh.DHPrivateNumbers(int(vector["x1"]), public1) + key1 = private1.private_key(backend) + pub_key1 = key1.public_key() + + parameters2 = dh.DHParameterNumbers(int(vector["p2"]), + int(vector["g"])) + public2 = dh.DHPublicNumbers(int(vector["y2"]), parameters2) + private2 = dh.DHPrivateNumbers(int(vector["x2"]), public2) + key2 = private2.private_key(backend) + pub_key2 = key2.public_key() + + if pub_key2.public_numbers().y >= parameters1.p: + with pytest.raises(ValueError): + key1.exchange(pub_key2) + else: + symkey1 = key1.exchange(pub_key2) + assert symkey1 + + symkey2 = key2.exchange(pub_key1) + + assert symkey1 != symkey2 + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "vec.txt"), + load_nist_vectors)) + def test_dh_vectors(self, backend, vector): + parameters = dh.DHParameterNumbers(int(vector["p"]), + int(vector["g"])) + public = dh.DHPublicNumbers(int(vector["y"]), parameters) + private = dh.DHPrivateNumbers(int(vector["x"]), public) + key = private.private_key(backend) + symkey = key.exchange(public.public_key(backend)) + + assert int_from_bytes(symkey, 'big') == int(vector["k"], 16) + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "RFC5114.txt"), + load_nist_vectors)) + def test_dh_vectors_with_q(self, backend, vector): + parameters = dh.DHParameterNumbers(int(vector["p"], 16), + int(vector["g"], 16), + int(vector["q"], 16)) + public1 = dh.DHPublicNumbers(int(vector["ystatcavs"], 16), parameters) + private1 = dh.DHPrivateNumbers(int(vector["xstatcavs"], 16), public1) + public2 = dh.DHPublicNumbers(int(vector["ystatiut"], 16), parameters) + private2 = dh.DHPrivateNumbers(int(vector["xstatiut"], 16), public2) + key1 = private1.private_key(backend) + key2 = private2.private_key(backend) + symkey1 = key1.exchange(public2.public_key(backend)) + symkey2 = key2.exchange(public1.public_key(backend)) + + assert int_from_bytes(symkey1, 'big') == int(vector["z"], 16) + assert int_from_bytes(symkey2, 'big') == int(vector["z"], 16) + + +@pytest.mark.requires_backend_interface(interface=DHBackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) +class TestDHPrivateKeySerialization(object): + + @pytest.mark.parametrize( + ("encoding", "loader_func"), + [ + [ + serialization.Encoding.PEM, + serialization.load_pem_private_key + ], + [ + serialization.Encoding.DER, + serialization.load_der_private_key + ], + ] + ) + def test_private_bytes_unencrypted(self, backend, encoding, + loader_func): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + serialized = key.private_bytes( + encoding, serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + loaded_key = loader_func(serialized, None, backend) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), + (serialization.Encoding.DER, serialization.PrivateFormat.Raw), + (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), + ] + ) + def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(ValueError): + key.private_bytes(encoding, fmt, serialization.NoEncryption()) + + @pytest.mark.parametrize( + ("key_path", "loader_func", "encoding", "is_dhx"), + [ + ( + os.path.join("asymmetric", "DH", "dhkey.pem"), + serialization.load_pem_private_key, + serialization.Encoding.PEM, + False, + ), ( + os.path.join("asymmetric", "DH", "dhkey.der"), + serialization.load_der_private_key, + serialization.Encoding.DER, + False, + ), ( + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.pem"), + serialization.load_pem_private_key, + serialization.Encoding.PEM, + True, + ), ( + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.der"), + serialization.load_der_private_key, + serialization.Encoding.DER, + True, + ) + ] + ) + def test_private_bytes_match(self, key_path, loader_func, + encoding, is_dhx, backend): + _skip_dhx_unsupported(backend, is_dhx) + key_bytes = load_vectors_from_file( + key_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + key = loader_func(key_bytes, None, backend) + serialized = key.private_bytes( + encoding, serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + assert serialized == key_bytes + + @pytest.mark.parametrize( + ("key_path", "loader_func", "vec_path", "is_dhx"), + [ + ( + os.path.join("asymmetric", "DH", "dhkey.pem"), + serialization.load_pem_private_key, + os.path.join("asymmetric", "DH", "dhkey.txt"), + False, + ), ( + os.path.join("asymmetric", "DH", "dhkey.der"), + serialization.load_der_private_key, + os.path.join("asymmetric", "DH", "dhkey.txt"), + False, + ), ( + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.pem"), + serialization.load_pem_private_key, + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"), + True, + ), ( + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.der"), + serialization.load_der_private_key, + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"), + True, + ) + ] + ) + def test_private_bytes_values(self, key_path, loader_func, + vec_path, is_dhx, backend): + _skip_dhx_unsupported(backend, is_dhx) + key_bytes = load_vectors_from_file( + key_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + vec = load_vectors_from_file(vec_path, load_nist_vectors)[0] + key = loader_func(key_bytes, None, backend) + private_numbers = key.private_numbers() + assert private_numbers.x == int(vec["x"], 16) + assert private_numbers.public_numbers.y == int(vec["y"], 16) + assert private_numbers.public_numbers.parameter_numbers.g == int( + vec["g"], 16) + assert private_numbers.public_numbers.parameter_numbers.p == int( + vec["p"], 16) + if "q" in vec: + assert private_numbers.public_numbers.parameter_numbers.q == int( + vec["q"], 16) + else: + assert private_numbers.public_numbers.parameter_numbers.q is None + + def test_private_bytes_traditional_openssl_invalid(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_encoding(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(TypeError): + key.private_bytes( + "notencoding", + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_format(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + "invalidformat", + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_encryption_algorithm(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + "notanencalg" + ) + + def test_private_bytes_unsupported_encryption_type(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + DummyKeySerializationEncryption() + ) + + +@pytest.mark.requires_backend_interface(interface=DHBackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) +class TestDHPublicKeySerialization(object): + + @pytest.mark.parametrize( + ("encoding", "loader_func"), + [ + [ + serialization.Encoding.PEM, + serialization.load_pem_public_key + ], + [ + serialization.Encoding.DER, + serialization.load_der_public_key + ], + ] + ) + def test_public_bytes(self, backend, encoding, + loader_func): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key().public_key() + serialized = key.public_bytes( + encoding, serialization.PublicFormat.SubjectPublicKeyInfo + ) + loaded_key = loader_func(serialized, backend) + loaded_pub_num = loaded_key.public_numbers() + pub_num = key.public_numbers() + assert loaded_pub_num == pub_num + + @pytest.mark.parametrize( + ("key_path", "loader_func", "encoding", "is_dhx"), + [ + ( + os.path.join("asymmetric", "DH", "dhpub.pem"), + serialization.load_pem_public_key, + serialization.Encoding.PEM, + False, + ), ( + os.path.join("asymmetric", "DH", "dhpub.der"), + serialization.load_der_public_key, + serialization.Encoding.DER, + False, + ), ( + os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.pem"), + serialization.load_pem_public_key, + serialization.Encoding.PEM, + True, + ), ( + os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.der"), + serialization.load_der_public_key, + serialization.Encoding.DER, + True, + ) + ] + ) + def test_public_bytes_match(self, key_path, loader_func, + encoding, is_dhx, backend): + _skip_dhx_unsupported(backend, is_dhx) + key_bytes = load_vectors_from_file( + key_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + pub_key = loader_func(key_bytes, backend) + serialized = pub_key.public_bytes( + encoding, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + assert serialized == key_bytes + + @pytest.mark.parametrize( + ("key_path", "loader_func", "vec_path", "is_dhx"), + [ + ( + os.path.join("asymmetric", "DH", "dhpub.pem"), + serialization.load_pem_public_key, + os.path.join("asymmetric", "DH", "dhkey.txt"), + False, + ), ( + os.path.join("asymmetric", "DH", "dhpub.der"), + serialization.load_der_public_key, + os.path.join("asymmetric", "DH", "dhkey.txt"), + False, + ), ( + os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.pem"), + serialization.load_pem_public_key, + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"), + True, + ), ( + os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.der"), + serialization.load_der_public_key, + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"), + True, + ) + ] + ) + def test_public_bytes_values(self, key_path, loader_func, + vec_path, is_dhx, backend): + _skip_dhx_unsupported(backend, is_dhx) + key_bytes = load_vectors_from_file( + key_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + vec = load_vectors_from_file(vec_path, load_nist_vectors)[0] + pub_key = loader_func(key_bytes, backend) + public_numbers = pub_key.public_numbers() + assert public_numbers.y == int(vec["y"], 16) + assert public_numbers.parameter_numbers.g == int(vec["g"], 16) + assert public_numbers.parameter_numbers.p == int(vec["p"], 16) + if "q" in vec: + assert public_numbers.parameter_numbers.q == int(vec["q"], 16) + else: + assert public_numbers.parameter_numbers.q is None + + def test_public_bytes_invalid_encoding(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key().public_key() + with pytest.raises(TypeError): + key.public_bytes( + "notencoding", + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + def test_public_bytes_pkcs1_unsupported(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key().public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 + ) + + +@pytest.mark.requires_backend_interface(interface=DHBackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) +class TestDHParameterSerialization(object): + + @pytest.mark.parametrize( + ("encoding", "loader_func"), + [ + [ + serialization.Encoding.PEM, + serialization.load_pem_parameters + ], + [ + serialization.Encoding.DER, + serialization.load_der_parameters + ], + ] + ) + def test_parameter_bytes(self, backend, encoding, + loader_func): + parameters = dh.generate_parameters(2, 512, backend) + serialized = parameters.parameter_bytes( + encoding, serialization.ParameterFormat.PKCS3 + ) + loaded_key = loader_func(serialized, backend) + loaded_param_num = loaded_key.parameter_numbers() + assert loaded_param_num == parameters.parameter_numbers() + + @pytest.mark.parametrize( + ("param_path", "loader_func", "encoding", "is_dhx"), + [ + ( + os.path.join("asymmetric", "DH", "dhp.pem"), + serialization.load_pem_parameters, + serialization.Encoding.PEM, + False, + ), ( + os.path.join("asymmetric", "DH", "dhp.der"), + serialization.load_der_parameters, + serialization.Encoding.DER, + False, + ), ( + os.path.join("asymmetric", "DH", "dhp_rfc5114_2.pem"), + serialization.load_pem_parameters, + serialization.Encoding.PEM, + True, + ), ( + os.path.join("asymmetric", "DH", "dhp_rfc5114_2.der"), + serialization.load_der_parameters, + serialization.Encoding.DER, + True, + ) + ] + ) + def test_parameter_bytes_match(self, param_path, loader_func, + encoding, backend, is_dhx): + _skip_dhx_unsupported(backend, is_dhx) + param_bytes = load_vectors_from_file( + param_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + parameters = loader_func(param_bytes, backend) + serialized = parameters.parameter_bytes( + encoding, + serialization.ParameterFormat.PKCS3, + ) + assert serialized == param_bytes + + @pytest.mark.parametrize( + ("param_path", "loader_func", "vec_path", "is_dhx"), + [ + ( + os.path.join("asymmetric", "DH", "dhp.pem"), + serialization.load_pem_parameters, + os.path.join("asymmetric", "DH", "dhkey.txt"), + False, + ), ( + os.path.join("asymmetric", "DH", "dhp.der"), + serialization.load_der_parameters, + os.path.join("asymmetric", "DH", "dhkey.txt"), + False, + ), ( + os.path.join("asymmetric", "DH", "dhp_rfc5114_2.pem"), + serialization.load_pem_parameters, + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"), + True, + ), ( + os.path.join("asymmetric", "DH", "dhp_rfc5114_2.der"), + serialization.load_der_parameters, + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"), + True, + ) + ] + ) + def test_public_bytes_values(self, param_path, loader_func, + vec_path, backend, is_dhx): + _skip_dhx_unsupported(backend, is_dhx) + key_bytes = load_vectors_from_file( + param_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + vec = load_vectors_from_file(vec_path, load_nist_vectors)[0] + parameters = loader_func(key_bytes, backend) + parameter_numbers = parameters.parameter_numbers() + assert parameter_numbers.g == int(vec["g"], 16) + assert parameter_numbers.p == int(vec["p"], 16) + if "q" in vec: + assert parameter_numbers.q == int(vec["q"], 16) + else: + assert parameter_numbers.q is None + + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + ( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ), + (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), + ] + list(itertools.product( + [ + serialization.Encoding.Raw, + serialization.Encoding.X962, + serialization.Encoding.PEM, + serialization.Encoding.DER + ], + [ + serialization.PublicFormat.Raw, + serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint + ] + )) + ) + def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key().public_key() + with pytest.raises(ValueError): + key.public_bytes(encoding, fmt) + + def test_parameter_bytes_invalid_encoding(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + with pytest.raises(TypeError): + parameters.parameter_bytes( + "notencoding", + serialization.ParameterFormat.PKCS3 + ) + + def test_parameter_bytes_invalid_format(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + with pytest.raises(ValueError): + parameters.parameter_bytes( + serialization.Encoding.PEM, + "notformat" + ) + + def test_parameter_bytes_openssh_unsupported(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + with pytest.raises(TypeError): + parameters.parameter_bytes( + serialization.Encoding.OpenSSH, + serialization.ParameterFormat.PKCS3 + ) diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index 5c83d5c7..9e1acf93 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -9,7 +9,6 @@ import os import pytest -from cryptography import utils from cryptography.exceptions import AlreadyFinalized, InvalidSignature from cryptography.hazmat.backends.interfaces import ( DSABackend, PEMSerializationBackend @@ -17,37 +16,34 @@ from cryptography.hazmat.backends.interfaces import ( from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.hazmat.primitives.asymmetric.utils import ( - encode_rfc6979_signature + Prehashed, encode_dss_signature ) -from cryptography.utils import bit_length +from cryptography.utils import CryptographyDeprecationWarning from .fixtures_dsa import ( DSA_KEY_1024, DSA_KEY_2048, DSA_KEY_3072 ) +from ...doubles import DummyHashAlgorithm, DummyKeySerializationEncryption from ...utils import ( load_fips_dsa_key_pair_vectors, load_fips_dsa_sig_vectors, load_vectors_from_file, ) -def _skip_if_no_serialization(key, backend): - if not isinstance( - key, - (dsa.DSAPrivateKeyWithSerialization, dsa.DSAPublicKeyWithSerialization) +def _skip_if_dsa_not_supported(backend, algorithm, p, q, g): + if ( + not backend.dsa_parameters_supported(p, q, g) or + not backend.dsa_hash_supported(algorithm) ): pytest.skip( - "{0} does not support DSA key serialization".format(backend) + "{} does not support the provided parameters".format(backend) ) -def test_skip_if_no_serialization(): +@pytest.mark.requires_backend_interface(interface=DSABackend) +def test_skip_if_dsa_not_supported(backend): with pytest.raises(pytest.skip.Exception): - _skip_if_no_serialization("notakeywithserialization", "backend") - - -@utils.register_interface(serialization.KeySerializationEncryption) -class DummyKeyEncryption(object): - pass + _skip_if_dsa_not_supported(backend, DummyHashAlgorithm(), 1, 1, 1) @pytest.mark.requires_backend_interface(interface=DSABackend) @@ -75,476 +71,282 @@ class TestDSA(object): g=vector['g'] ).parameters(backend) skey = parameters.generate_private_key() - if isinstance(skey, dsa.DSAPrivateKeyWithSerialization): - numbers = skey.private_numbers() - skey_parameters = numbers.public_numbers.parameter_numbers - pkey = skey.public_key() - parameters = pkey.parameters() - parameter_numbers = parameters.parameter_numbers() - assert parameter_numbers.p == skey_parameters.p - assert parameter_numbers.q == skey_parameters.q - assert parameter_numbers.g == skey_parameters.g - assert skey_parameters.p == vector['p'] - assert skey_parameters.q == vector['q'] - assert skey_parameters.g == vector['g'] - assert skey.key_size == bit_length(vector['p']) - assert pkey.key_size == skey.key_size - public_numbers = pkey.public_numbers() - assert numbers.public_numbers.y == public_numbers.y - assert numbers.public_numbers.y == pow( - skey_parameters.g, numbers.x, skey_parameters.p - ) + numbers = skey.private_numbers() + skey_parameters = numbers.public_numbers.parameter_numbers + pkey = skey.public_key() + parameters = pkey.parameters() + parameter_numbers = parameters.parameter_numbers() + assert parameter_numbers.p == skey_parameters.p + assert parameter_numbers.q == skey_parameters.q + assert parameter_numbers.g == skey_parameters.g + assert skey_parameters.p == vector['p'] + assert skey_parameters.q == vector['q'] + assert skey_parameters.g == vector['g'] + assert skey.key_size == vector['p'].bit_length() + assert pkey.key_size == skey.key_size + public_numbers = pkey.public_numbers() + assert numbers.public_numbers.y == public_numbers.y + assert numbers.public_numbers.y == pow( + skey_parameters.g, numbers.x, skey_parameters.p + ) def test_generate_dsa_private_key_and_parameters(self, backend): skey = dsa.generate_private_key(1024, backend) assert skey - if isinstance(skey, dsa.DSAPrivateKeyWithSerialization): - numbers = skey.private_numbers() - skey_parameters = numbers.public_numbers.parameter_numbers - assert numbers.public_numbers.y == pow( - skey_parameters.g, numbers.x, skey_parameters.p - ) - - def test_invalid_parameters_values(self, backend): - # Test a p < 1024 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=2 ** 1000, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ).parameters(backend) - - # Test a p < 2048 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=2 ** 2000, - q=DSA_KEY_2048.public_numbers.parameter_numbers.q, - g=DSA_KEY_2048.public_numbers.parameter_numbers.g, - ).parameters(backend) - - # Test a p < 3072 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=2 ** 3000, - q=DSA_KEY_3072.public_numbers.parameter_numbers.q, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ).parameters(backend) - - # Test a p > 3072 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=2 ** 3100, - q=DSA_KEY_3072.public_numbers.parameter_numbers.q, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ).parameters(backend) - - # Test a q < 160 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=2 ** 150, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ).parameters(backend) - - # Test a q < 256 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=DSA_KEY_2048.public_numbers.parameter_numbers.p, - q=2 ** 250, - g=DSA_KEY_2048.public_numbers.parameter_numbers.g - ).parameters(backend) - - # Test a q > 256 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=DSA_KEY_3072.public_numbers.parameter_numbers.p, - q=2 ** 260, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ).parameters(backend) - - # Test a g < 1 - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=0 - ).parameters(backend) - - # Test a g = 1 - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=1 - ).parameters(backend) - - # Test a g > p - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=2 ** 1200 - ).parameters(backend) - - def test_invalid_dsa_private_key_arguments(self, backend): - # Test a p < 1024 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 1000, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=DSA_KEY_1024.x - ).private_key(backend) - - # Test a p < 2048 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 2000, - q=DSA_KEY_2048.public_numbers.parameter_numbers.q, - g=DSA_KEY_2048.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_2048.public_numbers.y - ), - x=DSA_KEY_2048.x, - ).private_key(backend) - - # Test a p < 3072 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 3000, - q=DSA_KEY_3072.public_numbers.parameter_numbers.q, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_3072.public_numbers.y - ), - x=DSA_KEY_3072.x, - ).private_key(backend) - - # Test a p > 3072 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 3100, - q=DSA_KEY_3072.public_numbers.parameter_numbers.q, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_3072.public_numbers.y - ), - x=DSA_KEY_3072.x, - ).private_key(backend) - - # Test a q < 160 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=2 ** 150, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=DSA_KEY_1024.x, - ).private_key(backend) - - # Test a q < 256 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_2048.public_numbers.parameter_numbers.p, - q=2 ** 250, - g=DSA_KEY_2048.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_2048.public_numbers.y - ), - x=DSA_KEY_2048.x, - ).private_key(backend) - - # Test a q > 256 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_3072.public_numbers.parameter_numbers.p, - q=2 ** 260, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_3072.public_numbers.y - ), - x=DSA_KEY_3072.x, - ).private_key(backend) - - # Test a g < 1 - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=0, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=DSA_KEY_1024.x, - ).private_key(backend) - - # Test a g = 1 - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=1, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=DSA_KEY_1024.x, - ).private_key(backend) - - # Test a g > p - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=2 ** 1200, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=DSA_KEY_1024.x, - ).private_key(backend) - - # Test x = 0 - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=0, - ).private_key(backend) - - # Test x < 0 - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=-2, - ).private_key(backend) - - # Test x = q - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=2 ** 159, - ).private_key(backend) + numbers = skey.private_numbers() + skey_parameters = numbers.public_numbers.parameter_numbers + assert numbers.public_numbers.y == pow( + skey_parameters.g, numbers.x, skey_parameters.p + ) - # Test x > q + @pytest.mark.parametrize( + ("p", "q", "g"), + [ + ( + 2 ** 1000, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + ( + 2 ** 2000, + DSA_KEY_2048.public_numbers.parameter_numbers.q, + DSA_KEY_2048.public_numbers.parameter_numbers.g, + ), + ( + 2 ** 3000, + DSA_KEY_3072.public_numbers.parameter_numbers.q, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + ), + ( + 2 ** 3100, + DSA_KEY_3072.public_numbers.parameter_numbers.q, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + 2 ** 150, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + ( + DSA_KEY_2048.public_numbers.parameter_numbers.p, + 2 ** 250, + DSA_KEY_2048.public_numbers.parameter_numbers.g + ), + ( + DSA_KEY_3072.public_numbers.parameter_numbers.p, + 2 ** 260, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 0 + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 1 + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 2 ** 1200 + ), + ] + ) + def test_invalid_parameters_values(self, p, q, g, backend): with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=2 ** 200, - ).private_key(backend) + dsa.DSAParameterNumbers(p, q, g).parameters(backend) - # Test y != (g ** x) % p + @pytest.mark.parametrize( + ("p", "q", "g", "y", "x"), + [ + ( + 2 ** 1000, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + DSA_KEY_1024.x, + ), + ( + 2 ** 2000, + DSA_KEY_2048.public_numbers.parameter_numbers.q, + DSA_KEY_2048.public_numbers.parameter_numbers.g, + DSA_KEY_2048.public_numbers.y, + DSA_KEY_2048.x, + ), + ( + 2 ** 3000, + DSA_KEY_3072.public_numbers.parameter_numbers.q, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + DSA_KEY_3072.public_numbers.y, + DSA_KEY_3072.x, + ), + ( + 2 ** 3100, + DSA_KEY_3072.public_numbers.parameter_numbers.q, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + DSA_KEY_3072.public_numbers.y, + DSA_KEY_3072.x, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + 2 ** 150, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + DSA_KEY_1024.x, + ), + ( + DSA_KEY_2048.public_numbers.parameter_numbers.p, + 2 ** 250, + DSA_KEY_2048.public_numbers.parameter_numbers.g, + DSA_KEY_2048.public_numbers.y, + DSA_KEY_2048.x, + ), + ( + DSA_KEY_3072.public_numbers.parameter_numbers.p, + 2 ** 260, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + DSA_KEY_3072.public_numbers.y, + DSA_KEY_3072.x, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 0, + DSA_KEY_1024.public_numbers.y, + DSA_KEY_1024.x, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 1, + DSA_KEY_1024.public_numbers.y, + DSA_KEY_1024.x, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 2 ** 1200, + DSA_KEY_1024.public_numbers.y, + DSA_KEY_1024.x, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + 0, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + -2, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + 2 ** 159, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + 2 ** 200, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + 2 ** 100, + DSA_KEY_1024.x + ), + ] + ) + def test_invalid_dsa_private_key_arguments(self, p, q, g, y, x, backend): with pytest.raises(ValueError): dsa.DSAPrivateNumbers( public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=2 ** 100 - ), - x=DSA_KEY_1024.x, + parameter_numbers=dsa.DSAParameterNumbers(p=p, q=q, g=g), + y=y + ), x=x ).private_key(backend) - # Test a non-integer y value - with pytest.raises(TypeError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=None - ), - x=DSA_KEY_1024.x, - ).private_key(backend) - - # Test a non-integer x value - with pytest.raises(TypeError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=None, - ).private_key(backend) - - def test_invalid_dsa_public_key_arguments(self, backend): - # Test a p < 1024 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 1000, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ).public_key(backend) - - # Test a p < 2048 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 2000, - q=DSA_KEY_2048.public_numbers.parameter_numbers.q, - g=DSA_KEY_2048.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_2048.public_numbers.y - ).public_key(backend) - - # Test a p < 3072 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 3000, - q=DSA_KEY_3072.public_numbers.parameter_numbers.q, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_3072.public_numbers.y - ).public_key(backend) - - # Test a p > 3072 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 3100, - q=DSA_KEY_3072.public_numbers.parameter_numbers.q, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_3072.public_numbers.y - ).public_key(backend) - - # Test a q < 160 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=2 ** 150, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ).public_key(backend) - - # Test a q < 256 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_2048.public_numbers.parameter_numbers.p, - q=2 ** 250, - g=DSA_KEY_2048.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_2048.public_numbers.y - ).public_key(backend) - - # Test a q > 256 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_3072.public_numbers.parameter_numbers.p, - q=2 ** 260, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_3072.public_numbers.y - ).public_key(backend) - - # Test a g < 1 - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=0, - ), - y=DSA_KEY_1024.public_numbers.y - ).public_key(backend) - - # Test a g = 1 - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=1, - ), - y=DSA_KEY_1024.public_numbers.y - ).public_key(backend) - - # Test a g > p + @pytest.mark.parametrize( + ("p", "q", "g", "y"), + [ + ( + 2 ** 1000, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + ), + ( + 2 ** 2000, + DSA_KEY_2048.public_numbers.parameter_numbers.q, + DSA_KEY_2048.public_numbers.parameter_numbers.g, + DSA_KEY_2048.public_numbers.y, + ), + ( + 2 ** 3000, + DSA_KEY_3072.public_numbers.parameter_numbers.q, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + DSA_KEY_3072.public_numbers.y, + ), + ( + 2 ** 3100, + DSA_KEY_3072.public_numbers.parameter_numbers.q, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + DSA_KEY_3072.public_numbers.y, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + 2 ** 150, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + ), + ( + DSA_KEY_2048.public_numbers.parameter_numbers.p, + 2 ** 250, + DSA_KEY_2048.public_numbers.parameter_numbers.g, + DSA_KEY_2048.public_numbers.y, + ), + ( + DSA_KEY_3072.public_numbers.parameter_numbers.p, + 2 ** 260, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + DSA_KEY_3072.public_numbers.y, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 0, + DSA_KEY_1024.public_numbers.y, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 1, + DSA_KEY_1024.public_numbers.y, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 2 ** 1200, + DSA_KEY_1024.public_numbers.y, + ), + ] + ) + def test_invalid_dsa_public_key_arguments(self, p, q, g, y, backend): with pytest.raises(ValueError): dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=2 ** 1200, - ), - y=DSA_KEY_1024.public_numbers.y - ).public_key(backend) - - # Test a non-integer y value - with pytest.raises(TypeError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=None + parameter_numbers=dsa.DSAParameterNumbers(p=p, q=q, g=g), + y=y ).public_key(backend) @@ -569,14 +371,10 @@ class TestDSAVerification(object): def test_dsa_verification(self, vector, backend): digest_algorithm = vector['digest_algorithm'].replace("-", "") algorithm = self._algorithms_dict[digest_algorithm] - if ( - not backend.dsa_parameters_supported( - vector['p'], vector['q'], vector['g'] - ) or not backend.dsa_hash_supported(algorithm) - ): - pytest.skip( - "{0} does not support the provided parameters".format(backend) - ) + + _skip_if_dsa_not_supported( + backend, algorithm, vector['p'], vector['q'], vector['g'] + ) public_key = dsa.DSAPublicNumbers( parameter_numbers=dsa.DSAParameterNumbers( @@ -584,25 +382,29 @@ class TestDSAVerification(object): ), y=vector['y'] ).public_key(backend) - sig = encode_rfc6979_signature(vector['r'], vector['s']) - verifier = public_key.verifier(sig, algorithm()) - verifier.update(vector['msg']) + sig = encode_dss_signature(vector['r'], vector['s']) + if vector['result'] == "F": with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify(sig, vector['msg'], algorithm()) else: - verifier.verify() + public_key.verify(sig, vector['msg'], algorithm()) def test_dsa_verify_invalid_asn1(self, backend): public_key = DSA_KEY_1024.public_numbers.public_key(backend) - verifier = public_key.verifier(b'fakesig', hashes.SHA1()) - verifier.update(b'fakesig') with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify(b'fakesig', b'fakemsg', hashes.SHA1()) + + def test_signature_not_bytes(self, backend): + public_key = DSA_KEY_1024.public_numbers.public_key(backend) + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + public_key.verifier(1234, hashes.SHA1()) def test_use_after_finalize(self, backend): public_key = DSA_KEY_1024.public_numbers.public_key(backend) - verifier = public_key.verifier(b'fakesig', hashes.SHA1()) + with pytest.warns(CryptographyDeprecationWarning): + verifier = public_key.verifier(b'fakesig', hashes.SHA1()) verifier.update(b'irrelevant') with pytest.raises(InvalidSignature): verifier.verify() @@ -611,6 +413,50 @@ class TestDSAVerification(object): with pytest.raises(AlreadyFinalized): verifier.update(b"more data") + def test_verify(self, backend): + message = b"one little message" + algorithm = hashes.SHA1() + private_key = DSA_KEY_1024.private_key(backend) + signature = private_key.sign(message, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, algorithm) + + def test_prehashed_verify(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = Prehashed(hashes.SHA1()) + signature = private_key.sign(message, hashes.SHA1()) + public_key = private_key.public_key() + public_key.verify(signature, digest, prehashed_alg) + + def test_prehashed_digest_mismatch(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + public_key = private_key.public_key() + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = Prehashed(hashes.SHA224()) + with pytest.raises(ValueError): + public_key.verify(b"\x00" * 128, digest, prehashed_alg) + + def test_prehashed_unsupported_in_signer_ctx(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + private_key.signer(Prehashed(hashes.SHA1())) + + def test_prehashed_unsupported_in_verifier_ctx(self, backend): + public_key = DSA_KEY_1024.private_key(backend).public_key() + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + public_key.verifier( + b"0" * 64, Prehashed(hashes.SHA1()) + ) + @pytest.mark.requires_backend_interface(interface=DSABackend) class TestDSASignature(object): @@ -632,14 +478,10 @@ class TestDSASignature(object): def test_dsa_signing(self, vector, backend): digest_algorithm = vector['digest_algorithm'].replace("-", "") algorithm = self._algorithms_dict[digest_algorithm] - if ( - not backend.dsa_parameters_supported( - vector['p'], vector['q'], vector['g'] - ) or not backend.dsa_hash_supported(algorithm) - ): - pytest.skip( - "{0} does not support the provided parameters".format(backend) - ) + + _skip_if_dsa_not_supported( + backend, algorithm, vector['p'], vector['q'], vector['g'] + ) private_key = dsa.DSAPrivateNumbers( public_numbers=dsa.DSAPublicNumbers( @@ -650,19 +492,15 @@ class TestDSASignature(object): ), x=vector['x'] ).private_key(backend) - signer = private_key.signer(algorithm()) - signer.update(vector['msg']) - signature = signer.finalize() + signature = private_key.sign(vector['msg'], algorithm()) assert signature - public_key = private_key.public_key() - verifier = public_key.verifier(signature, algorithm()) - verifier.update(vector['msg']) - verifier.verify() + private_key.public_key().verify(signature, vector['msg'], algorithm()) def test_use_after_finalize(self, backend): private_key = DSA_KEY_1024.private_key(backend) - signer = private_key.signer(hashes.SHA1()) + with pytest.warns(CryptographyDeprecationWarning): + signer = private_key.signer(hashes.SHA1()) signer.update(b"data") signer.finalize() with pytest.raises(AlreadyFinalized): @@ -670,6 +508,35 @@ class TestDSASignature(object): with pytest.raises(AlreadyFinalized): signer.update(b"more data") + def test_sign(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = b"one little message" + algorithm = hashes.SHA1() + signature = private_key.sign(message, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, algorithm) + + def test_prehashed_sign(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = Prehashed(hashes.SHA1()) + signature = private_key.sign(digest, prehashed_alg) + public_key = private_key.public_key() + public_key.verify(signature, message, hashes.SHA1()) + + def test_prehashed_digest_mismatch(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = Prehashed(hashes.SHA224()) + with pytest.raises(ValueError): + private_key.sign(digest, prehashed_alg) + class TestDSANumbers(object): def test_dsa_parameter_numbers(self): @@ -730,6 +597,21 @@ class TestDSANumbers(object): with pytest.raises(TypeError): dsa.DSAPrivateNumbers(x=None, public_numbers=public_numbers) + def test_repr(self): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + assert ( + repr(parameter_numbers) == "<DSAParameterNumbers(p=1, q=2, g=3)>" + ) + + public_numbers = dsa.DSAPublicNumbers( + y=4, + parameter_numbers=parameter_numbers + ) + assert repr(public_numbers) == ( + "<DSAPublicNumbers(y=4, parameter_numbers=<DSAParameterNumbers(p=1" + ", q=2, g=3)>)>" + ) + class TestDSANumberEquality(object): def test_parameter_numbers_eq(self): @@ -819,7 +701,6 @@ class TestDSASerialization(object): lambda pemfile: pemfile.read().encode() ) key = serialization.load_pem_private_key(key_bytes, None, backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( serialization.Encoding.PEM, fmt, @@ -833,6 +714,20 @@ class TestDSASerialization(object): assert loaded_priv_num == priv_num @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), + (serialization.Encoding.DER, serialization.PrivateFormat.Raw), + (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), + ] + ) + def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): + key = DSA_KEY_1024.private_key(backend) + with pytest.raises(ValueError): + key.private_bytes(encoding, fmt, serialization.NoEncryption()) + + @pytest.mark.parametrize( ("fmt", "password"), [ [serialization.PrivateFormat.PKCS8, b"s"], @@ -847,7 +742,6 @@ class TestDSASerialization(object): lambda pemfile: pemfile.read().encode() ) key = serialization.load_pem_private_key(key_bytes, None, backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( serialization.Encoding.DER, fmt, @@ -888,7 +782,6 @@ class TestDSASerialization(object): def test_private_bytes_unencrypted(self, backend, encoding, fmt, loader_func): key = DSA_KEY_1024.private_key(backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( encoding, fmt, serialization.NoEncryption() ) @@ -934,7 +827,6 @@ class TestDSASerialization(object): def test_private_bytes_traditional_der_encrypted_invalid(self, backend): key = DSA_KEY_1024.private_key(backend) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.DER, @@ -949,7 +841,6 @@ class TestDSASerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( "notencoding", @@ -964,7 +855,6 @@ class TestDSASerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, @@ -979,7 +869,6 @@ class TestDSASerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, @@ -994,12 +883,11 @@ class TestDSASerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, - DummyKeyEncryption() + DummyKeySerializationEncryption() ) @@ -1030,15 +918,36 @@ class TestDSAPEMPublicKeySerialization(object): key_path, lambda pemfile: pemfile.read(), mode="rb" ) key = loader_func(key_bytes, backend) - _skip_if_no_serialization(key, backend) serialized = key.public_bytes( encoding, serialization.PublicFormat.SubjectPublicKeyInfo, ) assert serialized == key_bytes + def test_public_bytes_openssh(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pub.pem"), + lambda pemfile: pemfile.read(), mode="rb" + ) + key = serialization.load_pem_public_key(key_bytes, backend) + + ssh_bytes = key.public_bytes( + serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH + ) + assert ssh_bytes == ( + b"ssh-dss AAAAB3NzaC1kc3MAAACBAKoJMMwUWCUiHK/6KKwolBlqJ4M95ewhJweR" + b"aJQgd3Si57I4sNNvGySZosJYUIPrAUMpJEGNhn+qIS3RBx1NzrJ4J5StOTzAik1K" + b"2n9o1ug5pfzTS05ALYLLioy0D+wxkRv5vTYLA0yqy0xelHmSVzyekAmcGw8FlAyr" + b"5dLeSaFnAAAAFQCtwOhps28KwBOmgf301ImdaYIEUQAAAIEAjGtFia+lOk0QSL/D" + b"RtHzhsp1UhzPct2qJRKGiA7hMgH/SIkLv8M9ebrK7HHnp3hQe9XxpmQi45QVvgPn" + b"EUG6Mk9bkxMZKRgsiKn6QGKDYGbOvnS1xmkMfRARBsJAq369VOTjMB/Qhs5q2ski" + b"+ycTorCIfLoTubxozlz/8kHNMkYAAACAKyYOqX3GoSrpMsZA5989j/BKigWgMk+N" + b"Xxsj8V+hcP8/QgYRJO/yWGyxG0moLc3BuQ/GqE+xAQnLZ9tdLalxrq8Xvl43KEVj" + b"5MZNnl/ISAJYsxnw3inVTYNQcNnih5FNd9+BSR9EI7YtqYTrP0XrKin86l2uUlrG" + b"q2vM4Ev99bY=" + ) + def test_public_bytes_invalid_encoding(self, backend): key = DSA_KEY_2048.private_key(backend).public_key() - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.public_bytes( "notencoding", @@ -1047,14 +956,39 @@ class TestDSAPEMPublicKeySerialization(object): def test_public_bytes_invalid_format(self, backend): key = DSA_KEY_2048.private_key(backend).public_key() - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.public_bytes(serialization.Encoding.PEM, "invalidformat") def test_public_bytes_pkcs1_unsupported(self, backend): key = DSA_KEY_2048.private_key(backend).public_key() - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 ) + + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + ( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ), + (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), + ] + list(itertools.product( + [ + serialization.Encoding.Raw, + serialization.Encoding.X962, + serialization.Encoding.PEM, + serialization.Encoding.DER + ], + [ + serialization.PublicFormat.Raw, + serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint + ] + )) + ) + def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): + key = DSA_KEY_2048.private_key(backend).public_key() + with pytest.raises(ValueError): + key.public_bytes(encoding, fmt) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index cc185145..987c0ff0 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -4,24 +4,30 @@ from __future__ import absolute_import, division, print_function +import binascii import itertools import os +from binascii import hexlify import pytest -from cryptography import exceptions, utils +from cryptography import exceptions, utils, x509 from cryptography.hazmat.backends.interfaces import ( EllipticCurveBackend, PEMSerializationBackend ) from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import ( - encode_rfc6979_signature + Prehashed, encode_dss_signature ) +from cryptography.utils import CryptographyDeprecationWarning +from .fixtures_ec import EC_KEY_SECP384R1 +from ...doubles import DummyKeySerializationEncryption from ...utils import ( load_fips_ecdsa_key_pair_vectors, load_fips_ecdsa_signing_vectors, - load_vectors_from_file, raises_unsupported_algorithm + load_kasvs_ecdh_vectors, load_nist_vectors, load_vectors_from_file, + raises_unsupported_algorithm ) _HASH_TYPES = { @@ -33,25 +39,13 @@ _HASH_TYPES = { } -def _skip_if_no_serialization(key, backend): - if not isinstance( - key, ( - ec.EllipticCurvePrivateKeyWithSerialization, - ec.EllipticCurvePublicKeyWithSerialization - ) - ): - pytest.skip( - "{0} does not support EC key serialization".format(backend) - ) - - def _skip_ecdsa_vector(backend, curve_type, hash_type): if not backend.elliptic_curve_signature_algorithm_supported( ec.ECDSA(hash_type()), curve_type() ): pytest.skip( - "ECDSA not supported with this hash {0} and curve {1}".format( + "ECDSA not supported with this hash {} and curve {}".format( hash_type().name, curve_type().name ) ) @@ -60,12 +54,29 @@ def _skip_ecdsa_vector(backend, curve_type, hash_type): def _skip_curve_unsupported(backend, curve): if not backend.elliptic_curve_supported(curve): pytest.skip( - "Curve {0} is not supported by this backend {1}".format( + "Curve {} is not supported by this backend {}".format( curve.name, backend ) ) +def _skip_exchange_algorithm_unsupported(backend, algorithm, curve): + if not backend.elliptic_curve_exchange_algorithm_supported( + algorithm, curve + ): + pytest.skip( + "Exchange with {} curve is not supported by {}".format( + curve.name, backend + ) + ) + + +def test_get_curve_for_oid(): + assert ec.get_curve_for_oid(ec.EllipticCurveOID.SECP256R1) == ec.SECP256R1 + with pytest.raises(LookupError): + ec.get_curve_for_oid(x509.ObjectIdentifier("1.1.1.1")) + + @utils.register_interface(ec.EllipticCurve) class DummyCurve(object): name = "dummy-curve" @@ -77,20 +88,51 @@ class DummySignatureAlgorithm(object): algorithm = None -@utils.register_interface(serialization.KeySerializationEncryption) -class DummyKeyEncryption(object): - pass - - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_skip_curve_unsupported(backend): with pytest.raises(pytest.skip.Exception): _skip_curve_unsupported(backend, DummyCurve()) -def test_skip_no_serialization(): +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_skip_exchange_algorithm_unsupported(backend): + with pytest.raises(pytest.skip.Exception): + _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), DummyCurve()) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_skip_ecdsa_vector(backend): with pytest.raises(pytest.skip.Exception): - _skip_if_no_serialization("fakebackend", "fakekey") + _skip_ecdsa_vector(backend, DummyCurve, hashes.SHA256) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_derive_private_key_success(backend): + curve = ec.SECP256K1() + _skip_curve_unsupported(backend, curve) + + private_numbers = ec.generate_private_key(curve, backend).private_numbers() + + derived_key = ec.derive_private_key( + private_numbers.private_value, curve, backend + ) + + assert private_numbers == derived_key.private_numbers() + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_derive_private_key_errors(backend): + curve = ec.SECP256K1() + _skip_curve_unsupported(backend, curve) + + with pytest.raises(TypeError): + ec.derive_private_key('one', curve, backend) + + with pytest.raises(TypeError): + ec.derive_private_key(10, 'five', backend) + + with pytest.raises(ValueError): + ec.derive_private_key(-7, curve, backend) def test_ec_numbers(): @@ -106,43 +148,135 @@ def test_ec_numbers(): assert numbers.public_numbers.y == 3 assert isinstance(numbers.public_numbers.curve, DummyCurve) + +@pytest.mark.parametrize( + ("private_value", "x", "y", "curve"), + [ + (None, 2, 3, DummyCurve()), + (1, None, 3, DummyCurve()), + (1, 2, None, DummyCurve()), + (1, 2, 3, None), + ] +) +def test_invalid_ec_numbers_args(private_value, x, y, curve): with pytest.raises(TypeError): ec.EllipticCurvePrivateNumbers( - None, - ec.EllipticCurvePublicNumbers( - 2, 3, DummyCurve() - ) + private_value, ec.EllipticCurvePublicNumbers(x, y, curve) ) + +def test_invalid_private_numbers_public_numbers(): with pytest.raises(TypeError): - ec.EllipticCurvePrivateNumbers( - 1, - ec.EllipticCurvePublicNumbers( - None, 3, DummyCurve() - ) + ec.EllipticCurvePrivateNumbers(1, None) + + +def test_encode_point(): + # secp256r1 point + x = int( + '233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec', + 16 + ) + y = int( + '3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e', + 16 + ) + pn = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()) + with pytest.warns(utils.PersistentlyDeprecated2019): + data = pn.encode_point() + assert data == binascii.unhexlify( + "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" + "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e" + ) + + +def test_from_encoded_point(): + # secp256r1 point + data = binascii.unhexlify( + "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" + "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e" + ) + with pytest.warns(CryptographyDeprecationWarning): + pn = ec.EllipticCurvePublicNumbers.from_encoded_point( + ec.SECP256R1(), data ) + assert pn.x == int( + '233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec', + 16 + ) + assert pn.y == int( + '3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e', + 16 + ) - with pytest.raises(TypeError): - ec.EllipticCurvePrivateNumbers( - 1, - ec.EllipticCurvePublicNumbers( - 2, None, DummyCurve() + +def test_from_encoded_point_invalid_length(): + bad_data = binascii.unhexlify( + "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" + "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460" + ) + with pytest.raises(ValueError): + with pytest.warns(CryptographyDeprecationWarning): + ec.EllipticCurvePublicNumbers.from_encoded_point( + ec.SECP384R1(), bad_data ) - ) - with pytest.raises(TypeError): - ec.EllipticCurvePrivateNumbers( - 1, - ec.EllipticCurvePublicNumbers( - 2, 3, None + +def test_from_encoded_point_unsupported_point_no_backend(): + # set to point type 2. + unsupported_type = binascii.unhexlify( + "02233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22a" + ) + with pytest.raises(ValueError): + with pytest.warns(CryptographyDeprecationWarning): + ec.EllipticCurvePublicNumbers.from_encoded_point( + ec.SECP256R1(), unsupported_type ) - ) + +def test_from_encoded_point_not_a_curve(): with pytest.raises(TypeError): - ec.EllipticCurvePrivateNumbers( - 1, - None - ) + with pytest.warns(CryptographyDeprecationWarning): + ec.EllipticCurvePublicNumbers.from_encoded_point( + "notacurve", b"\x04data" + ) + + +def test_ec_public_numbers_repr(): + pn = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) + assert repr(pn) == "<EllipticCurvePublicNumbers(curve=secp256r1, x=2, y=3>" + + +def test_ec_public_numbers_hash(): + pn1 = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) + pn2 = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) + pn3 = ec.EllipticCurvePublicNumbers(1, 3, ec.SECP256R1()) + + assert hash(pn1) == hash(pn2) + assert hash(pn1) != hash(pn3) + + +def test_ec_private_numbers_hash(): + numbers1 = ec.EllipticCurvePrivateNumbers( + 1, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) + ) + numbers2 = ec.EllipticCurvePrivateNumbers( + 1, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) + ) + numbers3 = ec.EllipticCurvePrivateNumbers( + 2, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) + ) + + assert hash(numbers1) == hash(numbers2) + assert hash(numbers1) != hash(numbers3) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_ec_key_key_size(backend): + curve = ec.SECP256R1() + _skip_curve_unsupported(backend, curve) + key = ec.generate_private_key(curve, backend) + assert key.key_size == 256 + assert key.public_key().key_size == 256 @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) @@ -173,12 +307,11 @@ class TestECWithNumbers(object): ).private_key(backend) assert key - if isinstance(key, ec.EllipticCurvePrivateKeyWithSerialization): - priv_num = key.private_numbers() - assert priv_num.private_value == vector['d'] - assert priv_num.public_numbers.x == vector['x'] - assert priv_num.public_numbers.y == vector['y'] - assert curve_type().name == priv_num.public_numbers.curve.name + priv_num = key.private_numbers() + assert priv_num.private_value == vector['d'] + assert priv_num.public_numbers.x == vector['x'] + assert priv_num.public_numbers.y == vector['y'] + assert curve_type().name == priv_num.public_numbers.curve.name @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) @@ -212,11 +345,13 @@ class TestECDSAVectors(object): pkey = key.public_key() assert pkey - signer = key.signer(ec.ECDSA(hash_type())) + with pytest.warns(CryptographyDeprecationWarning): + signer = key.signer(ec.ECDSA(hash_type())) signer.update(b"YELLOW SUBMARINE") signature = signer.finalize() - verifier = pkey.verifier(signature, ec.ECDSA(hash_type())) + with pytest.warns(CryptographyDeprecationWarning): + verifier = pkey.verifier(signature, ec.ECDSA(hash_type())) verifier.update(b"YELLOW SUBMARINE") verifier.verify() @@ -254,14 +389,26 @@ class TestECDSAVectors(object): with raises_unsupported_algorithm( exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): + ), pytest.warns(CryptographyDeprecationWarning): key.signer(DummySignatureAlgorithm()) with raises_unsupported_algorithm( exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM ): + key.sign(b"somedata", DummySignatureAlgorithm()) + + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ), pytest.warns(CryptographyDeprecationWarning): key.public_key().verifier(b"", DummySignatureAlgorithm()) + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + key.public_key().verify( + b"signature", b"data", DummySignatureAlgorithm() + ) + assert backend.elliptic_curve_signature_algorithm_supported( DummySignatureAlgorithm(), ec.SECP192R1() @@ -303,6 +450,35 @@ class TestECDSAVectors(object): with pytest.raises(ValueError): numbers.private_key(backend) + def test_load_invalid_public_ec_key_from_numbers(self, backend): + _skip_curve_unsupported(backend, ec.SECP521R1()) + + # Bad X coordinate + numbers = ec.EllipticCurvePublicNumbers( + int("000003647356b91f8ace114c7247ecf4f4a622553fc025e04a178f179ef27" + "9090c184af678a4c78f635483bdd8aa544851c6ef291c1f0d6a241ebfd145" + "77d1d30d9903ce", 16), + int("000001499bc7e079322ea0fcfbd6b40103fa6a1536c2257b182db0df4b369" + "6ec643adf100eb4f2025d1b873f82e5a475d6e4400ba777090eeb4563a115" + "09e4c87319dc26", 16), + ec.SECP521R1() + ) + with pytest.raises(ValueError): + numbers.public_key(backend) + + # Bad Y coordinate + numbers = ec.EllipticCurvePublicNumbers( + int("0000019aadc221cc0525118ab6d5aa1f64720603de0be128cbfea0b381ad8" + "02a2facc6370bb58cf88b3f0c692bc654ee19d6cad198f10d4b681b396f20" + "d2e40603fa945b", 16), + int("0000025da392803a320717a08d4cb3dea932039badff363b71bdb8064e726" + "6c7f4f4b748d4d425347fc33e3885d34b750fa7fcd5691f4d90c89522ce33" + "feff5db10088a5", 16), + ec.SECP521R1() + ) + with pytest.raises(ValueError): + numbers.public_key(backend) + @pytest.mark.parametrize( "vector", itertools.chain( @@ -330,14 +506,13 @@ class TestECDSAVectors(object): curve_type() ).public_key(backend) - signature = encode_rfc6979_signature(vector['r'], vector['s']) + signature = encode_dss_signature(vector['r'], vector['s']) - verifier = key.verifier( + key.verify( signature, + vector['message'], ec.ECDSA(hash_type()) ) - verifier.update(vector['message']) - assert verifier.verify() @pytest.mark.parametrize( "vector", @@ -359,19 +534,107 @@ class TestECDSAVectors(object): curve_type() ).public_key(backend) - signature = encode_rfc6979_signature(vector['r'], vector['s']) - - verifier = key.verifier( - signature, - ec.ECDSA(hash_type()) - ) - verifier.update(vector['message']) + signature = encode_dss_signature(vector['r'], vector['s']) if vector["fail"] is True: with pytest.raises(exceptions.InvalidSignature): - verifier.verify() + key.verify( + signature, + vector['message'], + ec.ECDSA(hash_type()) + ) else: - verifier.verify() + key.verify( + signature, + vector['message'], + ec.ECDSA(hash_type()) + ) + + def test_sign(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + algorithm = ec.ECDSA(hashes.SHA1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signature = private_key.sign(message, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, algorithm) + + def test_sign_prehashed(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + algorithm = ec.ECDSA(Prehashed(hashes.SHA1())) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signature = private_key.sign(data, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, ec.ECDSA(hashes.SHA1())) + + def test_sign_prehashed_digest_mismatch(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + algorithm = ec.ECDSA(Prehashed(hashes.SHA256())) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + with pytest.raises(ValueError): + private_key.sign(data, algorithm) + + def test_verify(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + algorithm = ec.ECDSA(hashes.SHA1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signature = private_key.sign(message, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, algorithm) + + def test_verify_prehashed(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + algorithm = ec.ECDSA(hashes.SHA1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signature = private_key.sign(message, algorithm) + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + public_key = private_key.public_key() + public_key.verify( + signature, data, ec.ECDSA(Prehashed(hashes.SHA1())) + ) + + def test_verify_prehashed_digest_mismatch(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + public_key = private_key.public_key() + with pytest.raises(ValueError): + public_key.verify( + b"\x00" * 32, data, ec.ECDSA(Prehashed(hashes.SHA256())) + ) + + def test_prehashed_unsupported_in_signer_ctx(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + private_key.signer(ec.ECDSA(Prehashed(hashes.SHA1()))) + + def test_prehashed_unsupported_in_verifier_ctx(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + public_key = private_key.public_key() + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + public_key.verifier( + b"0" * 64, + ec.ECDSA(Prehashed(hashes.SHA1())) + ) class TestECNumbersEquality(object): @@ -437,7 +700,6 @@ class TestECSerialization(object): lambda pemfile: pemfile.read().encode() ) key = serialization.load_pem_private_key(key_bytes, None, backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( serialization.Encoding.PEM, fmt, @@ -451,6 +713,21 @@ class TestECSerialization(object): assert loaded_priv_num == priv_num @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), + (serialization.Encoding.DER, serialization.PrivateFormat.Raw), + (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), + ] + ) + def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = ec.generate_private_key(ec.SECP256R1(), backend) + with pytest.raises(ValueError): + key.private_bytes(encoding, fmt, serialization.NoEncryption()) + + @pytest.mark.parametrize( ("fmt", "password"), [ [serialization.PrivateFormat.PKCS8, b"s"], @@ -467,7 +744,6 @@ class TestECSerialization(object): lambda pemfile: pemfile.read().encode() ) key = serialization.load_pem_private_key(key_bytes, None, backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( serialization.Encoding.DER, fmt, @@ -514,7 +790,6 @@ class TestECSerialization(object): lambda pemfile: pemfile.read().encode() ) key = serialization.load_pem_private_key(key_bytes, None, backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( encoding, fmt, serialization.NoEncryption() ) @@ -566,7 +841,6 @@ class TestECSerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.DER, @@ -583,7 +857,6 @@ class TestECSerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( "notencoding", @@ -600,7 +873,6 @@ class TestECSerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, @@ -617,7 +889,6 @@ class TestECSerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, @@ -634,12 +905,11 @@ class TestECSerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, - DummyKeyEncryption() + DummyKeySerializationEncryption() ) def test_public_bytes_from_derived_public_key(self, backend): @@ -651,7 +921,6 @@ class TestECSerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) public = key.public_key() pem = public.public_bytes( serialization.Encoding.PEM, @@ -689,12 +958,39 @@ class TestEllipticCurvePEMPublicKeySerialization(object): key_path, lambda pemfile: pemfile.read(), mode="rb" ) key = loader_func(key_bytes, backend) - _skip_if_no_serialization(key, backend) serialized = key.public_bytes( encoding, serialization.PublicFormat.SubjectPublicKeyInfo, ) assert serialized == key_bytes + def test_public_bytes_openssh(self, backend): + _skip_curve_unsupported(backend, ec.SECP192R1()) + _skip_curve_unsupported(backend, ec.SECP256R1()) + + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", "ec_public_key.pem" + ), + lambda pemfile: pemfile.read(), mode="rb" + ) + key = serialization.load_pem_public_key(key_bytes, backend) + + ssh_bytes = key.public_bytes( + serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH + ) + assert ssh_bytes == ( + b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" + b"NTYAAABBBCS8827s9rUZyxZTi/um01+oIlWrwLHOjQxRU9CDAndom00zVAw5BRrI" + b"KtHB+SWD4P+sVJTARSq1mHt8kOIWrPc=" + ) + + key = ec.generate_private_key(ec.SECP192R1(), backend).public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.OpenSSH, + serialization.PublicFormat.OpenSSH + ) + def test_public_bytes_invalid_encoding(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = load_vectors_from_file( @@ -705,13 +1001,40 @@ class TestEllipticCurvePEMPublicKeySerialization(object): pemfile.read().encode(), backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.public_bytes( "notencoding", serialization.PublicFormat.SubjectPublicKeyInfo ) + @pytest.mark.parametrize( + ("encoding", "fmt"), + list(itertools.product( + [ + serialization.Encoding.Raw, + serialization.Encoding.X962, + serialization.Encoding.PEM, + serialization.Encoding.DER + ], + [ + serialization.PublicFormat.Raw, + ] + )) + list(itertools.product( + [serialization.Encoding.Raw], + [ + serialization.PublicFormat.SubjectPublicKeyInfo, + serialization.PublicFormat.PKCS1, + serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint, + ] + )) + ) + def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = ec.generate_private_key(ec.SECP256R1(), backend).public_key() + with pytest.raises(ValueError): + key.public_bytes(encoding, fmt) + def test_public_bytes_invalid_format(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = load_vectors_from_file( @@ -722,7 +1045,6 @@ class TestEllipticCurvePEMPublicKeySerialization(object): pemfile.read().encode(), backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.public_bytes(serialization.Encoding.PEM, "invalidformat") @@ -736,8 +1058,250 @@ class TestEllipticCurvePEMPublicKeySerialization(object): pemfile.read().encode(), backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 ) + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "EC", "compressed_points.txt"), + load_nist_vectors + ) + ) + def test_from_encoded_point_compressed(self, vector, backend): + curve = { + b"SECP256R1": ec.SECP256R1(), + b"SECP256K1": ec.SECP256K1(), + }[vector["curve"]] + _skip_curve_unsupported(backend, curve) + point = binascii.unhexlify(vector["point"]) + pn = ec.EllipticCurvePublicKey.from_encoded_point(curve, point) + public_num = pn.public_numbers() + assert public_num.x == int(vector["x"], 16) + assert public_num.y == int(vector["y"], 16) + + def test_from_encoded_point_notoncurve(self): + uncompressed_point = binascii.unhexlify( + "047399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac" + "686699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f" + "6e" + ) + with pytest.raises(ValueError): + ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP256R1(), uncompressed_point + ) + + def test_from_encoded_point_uncompressed(self): + uncompressed_point = binascii.unhexlify( + "047399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac" + "686699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f" + "6d" + ) + pn = ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP256R1(), uncompressed_point + ) + assert pn.public_numbers().x == int( + '7399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac68', + 16 + ) + assert pn.public_numbers().y == int( + '6699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f6d', + 16 + ) + + def test_from_encoded_point_invalid_length(self): + bad_data = binascii.unhexlify( + "047399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac" + "686699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f" + "6d" + ) + with pytest.raises(ValueError): + ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP384R1(), bad_data + ) + + def test_from_encoded_point_empty_byte_string(self): + with pytest.raises(ValueError): + ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP384R1(), b"" + ) + + def test_from_encoded_point_not_a_curve(self): + with pytest.raises(TypeError): + ec.EllipticCurvePublicKey.from_encoded_point( + "notacurve", b"\x04data" + ) + + def test_from_encoded_point_unsupported_encoding(self): + unsupported_type = binascii.unhexlify( + "057399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac6" + "8" + ) + with pytest.raises(ValueError): + ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP256R1(), unsupported_type + ) + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "EC", "compressed_points.txt"), + load_nist_vectors + ) + ) + def test_serialize_point(self, vector, backend): + curve = { + b"SECP256R1": ec.SECP256R1(), + b"SECP256K1": ec.SECP256K1(), + }[vector["curve"]] + _skip_curve_unsupported(backend, curve) + point = binascii.unhexlify(vector["point"]) + key = ec.EllipticCurvePublicKey.from_encoded_point(curve, point) + key2 = ec.EllipticCurvePublicKey.from_encoded_point( + curve, + key.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint + ) + ) + assert key.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.CompressedPoint + ) == point + assert key2.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.CompressedPoint + ) == point + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +class TestECDSAVerification(object): + def test_signature_not_bytes(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = ec.generate_private_key(ec.SECP256R1(), backend) + public_key = key.public_key() + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + public_key.verifier(1234, ec.ECDSA(hashes.SHA256())) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +class TestECDH(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join( + "asymmetric", "ECDH", + "KASValidityTest_ECCStaticUnified_NOKC_ZZOnly_init.fax"), + load_kasvs_ecdh_vectors + ) + ) + def test_key_exchange_with_vectors(self, backend, vector): + _skip_exchange_algorithm_unsupported( + backend, ec.ECDH(), ec._CURVE_TYPES[vector['curve']] + ) + + key_numbers = vector['IUT'] + private_numbers = ec.EllipticCurvePrivateNumbers( + key_numbers['d'], + ec.EllipticCurvePublicNumbers( + key_numbers['x'], + key_numbers['y'], + ec._CURVE_TYPES[vector['curve']]() + ) + ) + # Errno 5-7 indicates a bad public or private key, this doesn't test + # the ECDH code at all + if vector['fail'] and vector['errno'] in [5, 6, 7]: + with pytest.raises(ValueError): + private_numbers.private_key(backend) + return + else: + private_key = private_numbers.private_key(backend) + + peer_numbers = vector['CAVS'] + public_numbers = ec.EllipticCurvePublicNumbers( + peer_numbers['x'], + peer_numbers['y'], + ec._CURVE_TYPES[vector['curve']]() + ) + # Errno 1 and 2 indicates a bad public key, this doesn't test the ECDH + # code at all + if vector['fail'] and vector['errno'] in [1, 2]: + with pytest.raises(ValueError): + public_numbers.public_key(backend) + return + else: + peer_pubkey = public_numbers.public_key(backend) + + z = private_key.exchange(ec.ECDH(), peer_pubkey) + z = int(hexlify(z).decode('ascii'), 16) + # At this point fail indicates that one of the underlying keys was + # changed. This results in a non-matching derived key. + if vector['fail']: + # Errno 8 indicates Z should be changed. + assert vector['errno'] == 8 + assert z != vector['Z'] + else: + assert z == vector['Z'] + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "ECDH", "brainpool.txt"), + load_nist_vectors + ) + ) + def test_brainpool_kex(self, backend, vector): + curve = ec._CURVE_TYPES[vector['curve'].decode('ascii')] + _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) + key = ec.EllipticCurvePrivateNumbers( + int(vector['da'], 16), + ec.EllipticCurvePublicNumbers( + int(vector['x_qa'], 16), int(vector['y_qa'], 16), curve() + ) + ).private_key(backend) + peer = ec.EllipticCurvePrivateNumbers( + int(vector['db'], 16), + ec.EllipticCurvePublicNumbers( + int(vector['x_qb'], 16), int(vector['y_qb'], 16), curve() + ) + ).private_key(backend) + shared_secret = key.exchange(ec.ECDH(), peer.public_key()) + assert shared_secret == binascii.unhexlify(vector["x_z"]) + shared_secret_2 = peer.exchange(ec.ECDH(), key.public_key()) + assert shared_secret_2 == binascii.unhexlify(vector["x_z"]) + + def test_exchange_unsupported_algorithm(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + ): + key.exchange(None, key.public_key()) + + def test_exchange_non_matching_curve(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + _skip_curve_unsupported(backend, ec.SECP384R1()) + + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + public_key = EC_KEY_SECP384R1.public_numbers.public_key(backend) + + with pytest.raises(ValueError): + key.exchange(ec.ECDH(), public_key) diff --git a/tests/hazmat/primitives/test_ed25519.py b/tests/hazmat/primitives/test_ed25519.py new file mode 100644 index 00000000..aecc8572 --- /dev/null +++ b/tests/hazmat/primitives/test_ed25519.py @@ -0,0 +1,226 @@ +# 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 InvalidSignature, _Reasons +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed25519 import ( + Ed25519PrivateKey, Ed25519PublicKey +) + +from ...utils import ( + load_vectors_from_file, raises_unsupported_algorithm +) + + +def load_ed25519_vectors(vector_data): + """ + djb's ed25519 vectors are structured as a colon delimited array: + 0: secret key (32 bytes) + public key (32 bytes) + 1: public key (32 bytes) + 2: message (0+ bytes) + 3: signature + message (64+ bytes) + """ + data = [] + for line in vector_data: + secret_key, public_key, message, signature, _ = line.split(':') + secret_key = secret_key[0:64] + signature = signature[0:128] + data.append({ + "secret_key": secret_key, + "public_key": public_key, + "message": message, + "signature": signature + }) + return data + + +@pytest.mark.supported( + only_if=lambda backend: not backend.ed25519_supported(), + skip_message="Requires OpenSSL without Ed25519 support" +) +def test_ed25519_unsupported(backend): + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed25519PublicKey.from_public_bytes(b"0" * 32) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed25519PrivateKey.from_private_bytes(b"0" * 32) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed25519PrivateKey.generate() + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" +) +class TestEd25519Signing(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "Ed25519", "sign.input"), + load_ed25519_vectors + ) + ) + def test_sign_verify_input(self, vector, backend): + sk = binascii.unhexlify(vector["secret_key"]) + pk = binascii.unhexlify(vector["public_key"]) + message = binascii.unhexlify(vector["message"]) + signature = binascii.unhexlify(vector["signature"]) + private_key = Ed25519PrivateKey.from_private_bytes(sk) + computed_sig = private_key.sign(message) + assert computed_sig == signature + public_key = private_key.public_key() + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == pk + public_key.verify(signature, message) + + def test_invalid_signature(self, backend): + key = Ed25519PrivateKey.generate() + signature = key.sign(b"test data") + with pytest.raises(InvalidSignature): + key.public_key().verify(signature, b"wrong data") + + with pytest.raises(InvalidSignature): + key.public_key().verify(b"0" * 64, b"test data") + + def test_generate(self, backend): + key = Ed25519PrivateKey.generate() + assert key + assert key.public_key() + + def test_load_public_bytes(self, backend): + public_key = Ed25519PrivateKey.generate().public_key() + public_bytes = public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) + public_key2 = Ed25519PublicKey.from_public_bytes(public_bytes) + assert public_bytes == public_key2.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) + + def test_invalid_type_public_bytes(self, backend): + with pytest.raises(TypeError): + Ed25519PublicKey.from_public_bytes(object()) + + def test_invalid_type_private_bytes(self, backend): + with pytest.raises(TypeError): + Ed25519PrivateKey.from_private_bytes(object()) + + def test_invalid_length_from_public_bytes(self, backend): + with pytest.raises(ValueError): + Ed25519PublicKey.from_public_bytes(b"a" * 31) + with pytest.raises(ValueError): + Ed25519PublicKey.from_public_bytes(b"a" * 33) + + def test_invalid_length_from_private_bytes(self, backend): + with pytest.raises(ValueError): + Ed25519PrivateKey.from_private_bytes(b"a" * 31) + with pytest.raises(ValueError): + Ed25519PrivateKey.from_private_bytes(b"a" * 33) + + def test_invalid_private_bytes(self, backend): + key = Ed25519PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.PKCS8, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) + + def test_invalid_public_bytes(self, backend): + key = Ed25519PrivateKey.generate().public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1 + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.Raw + ) + + @pytest.mark.parametrize( + ("encoding", "fmt", "encryption", "passwd", "load_func"), + [ + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_der_private_key + ), + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_der_private_key + ), + ] + ) + def test_round_trip_private_serialization(self, encoding, fmt, encryption, + passwd, load_func, backend): + key = Ed25519PrivateKey.generate() + serialized = key.private_bytes(encoding, fmt, encryption) + loaded_key = load_func(serialized, passwd, backend) + assert isinstance(loaded_key, Ed25519PrivateKey) + + def test_buffer_protocol(self, backend): + private_bytes = os.urandom(32) + key = Ed25519PrivateKey.from_private_bytes(bytearray(private_bytes)) + assert key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes diff --git a/tests/hazmat/primitives/test_ed448.py b/tests/hazmat/primitives/test_ed448.py new file mode 100644 index 00000000..b02f821a --- /dev/null +++ b/tests/hazmat/primitives/test_ed448.py @@ -0,0 +1,239 @@ +# 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 InvalidSignature, _Reasons +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed448 import ( + Ed448PrivateKey, Ed448PublicKey +) + +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) + + +@pytest.mark.supported( + only_if=lambda backend: not backend.ed448_supported(), + skip_message="Requires OpenSSL without Ed448 support" +) +def test_ed448_unsupported(backend): + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed448PublicKey.from_public_bytes(b"0" * 57) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed448PrivateKey.from_private_bytes(b"0" * 57) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed448PrivateKey.generate() + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" +) +class TestEd448Signing(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "rfc8032.txt"), + load_nist_vectors + ) + ) + def test_sign_input(self, vector, backend): + if vector.get("context") is not None: + pytest.skip("ed448 contexts are not currently supported") + + sk = binascii.unhexlify(vector["secret"]) + pk = binascii.unhexlify(vector["public"]) + message = binascii.unhexlify(vector["message"]) + signature = binascii.unhexlify(vector["signature"]) + private_key = Ed448PrivateKey.from_private_bytes(sk) + computed_sig = private_key.sign(message) + assert computed_sig == signature + public_key = private_key.public_key() + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == pk + public_key.verify(signature, message) + + def test_invalid_signature(self, backend): + key = Ed448PrivateKey.generate() + signature = key.sign(b"test data") + with pytest.raises(InvalidSignature): + key.public_key().verify(signature, b"wrong data") + + with pytest.raises(InvalidSignature): + key.public_key().verify(b"0" * 64, b"test data") + + def test_generate(self, backend): + key = Ed448PrivateKey.generate() + assert key + assert key.public_key() + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "rfc8032.txt"), + load_nist_vectors + ) + ) + def test_pub_priv_bytes_raw(self, vector, backend): + sk = binascii.unhexlify(vector["secret"]) + pk = binascii.unhexlify(vector["public"]) + private_key = Ed448PrivateKey.from_private_bytes(sk) + assert private_key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == sk + assert private_key.public_key().public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == pk + public_key = Ed448PublicKey.from_public_bytes(pk) + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == pk + + @pytest.mark.parametrize( + ("encoding", "fmt", "encryption", "passwd", "load_func"), + [ + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_der_private_key + ), + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_der_private_key + ), + ] + ) + def test_round_trip_private_serialization(self, encoding, fmt, encryption, + passwd, load_func, backend): + key = Ed448PrivateKey.generate() + serialized = key.private_bytes(encoding, fmt, encryption) + loaded_key = load_func(serialized, passwd, backend) + assert isinstance(loaded_key, Ed448PrivateKey) + + def test_invalid_type_public_bytes(self, backend): + with pytest.raises(TypeError): + Ed448PublicKey.from_public_bytes(object()) + + def test_invalid_type_private_bytes(self, backend): + with pytest.raises(TypeError): + Ed448PrivateKey.from_private_bytes(object()) + + def test_invalid_length_from_public_bytes(self, backend): + with pytest.raises(ValueError): + Ed448PublicKey.from_public_bytes(b"a" * 56) + with pytest.raises(ValueError): + Ed448PublicKey.from_public_bytes(b"a" * 58) + + def test_invalid_length_from_private_bytes(self, backend): + with pytest.raises(ValueError): + Ed448PrivateKey.from_private_bytes(b"a" * 56) + with pytest.raises(ValueError): + Ed448PrivateKey.from_private_bytes(b"a" * 58) + + def test_invalid_private_bytes(self, backend): + key = Ed448PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.PKCS8, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) + + def test_invalid_public_bytes(self, backend): + key = Ed448PrivateKey.generate().public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1 + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.Raw + ) + + def test_buffer_protocol(self, backend): + private_bytes = os.urandom(57) + key = Ed448PrivateKey.from_private_bytes(bytearray(private_bytes)) + assert key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes + + def test_malleability(self, backend): + # This is a signature where r > the group order. It should be + # rejected to prevent signature malleability issues. This test can + # be removed when wycheproof grows ed448 vectors + public_bytes = binascii.unhexlify( + "fedb02a658d74990244d9d10cf338e977565cbbda6b24c716829ed6ee1e4f28cf" + "2620c052db8d878f6243bffc22242816c1aaa67d2f3603600" + ) + signature = binascii.unhexlify( + "0cc16ba24d69277f927c1554b0f08a2a711bbdd20b058ccc660d00ca13542a3ce" + "f9e5c44c54ab23a2eb14f947e167b990b080863e28b399380f30db6e54d5d1406" + "d23378ffde11b1fb81b2b438a3b8e8aa7f7f4e1befcc905023fab5a5465053844" + "f04cf0c1b51d84760f869588687f57500" + ) + key = Ed448PublicKey.from_public_bytes(public_bytes) + with pytest.raises(InvalidSignature): + key.verify(signature, b"8") diff --git a/tests/hazmat/primitives/test_hash_vectors.py b/tests/hazmat/primitives/test_hash_vectors.py index c6e98283..0698f413 100644 --- a/tests/hazmat/primitives/test_hash_vectors.py +++ b/tests/hazmat/primitives/test_hash_vectors.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function +import binascii import os import pytest @@ -11,8 +12,8 @@ import pytest from cryptography.hazmat.backends.interfaces import HashBackend from cryptography.hazmat.primitives import hashes -from .utils import generate_hash_test, generate_long_string_hash_test -from ...utils import load_hash_vectors +from .utils import _load_all_params, generate_hash_test +from ...utils import load_hash_vectors, load_nist_vectors @pytest.mark.supported( @@ -21,7 +22,7 @@ from ...utils import load_hash_vectors ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA1(object): - test_SHA1 = generate_hash_test( + test_sha1 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA1"), [ @@ -38,7 +39,7 @@ class TestSHA1(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA224(object): - test_SHA224 = generate_hash_test( + test_sha224 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), [ @@ -55,7 +56,7 @@ class TestSHA224(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA256(object): - test_SHA256 = generate_hash_test( + test_sha256 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), [ @@ -72,7 +73,7 @@ class TestSHA256(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA384(object): - test_SHA384 = generate_hash_test( + test_sha384 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), [ @@ -89,7 +90,7 @@ class TestSHA384(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA512(object): - test_SHA512 = generate_hash_test( + test_sha512 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), [ @@ -101,46 +102,36 @@ class TestSHA512(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160()), - skip_message="Does not support RIPEMD160", + only_if=lambda backend: backend.hash_supported(hashes.SHA512_224()), + skip_message="Does not support SHA512/224", ) @pytest.mark.requires_backend_interface(interface=HashBackend) -class TestRIPEMD160(object): - test_RIPEMD160 = generate_hash_test( +class TestSHA512224(object): + test_sha512_224 = generate_hash_test( load_hash_vectors, - os.path.join("hashes", "ripemd160"), + os.path.join("hashes", "SHA2"), [ - "ripevectors.txt", + "SHA512_224LongMsg.rsp", + "SHA512_224ShortMsg.rsp", ], - hashes.RIPEMD160(), - ) - - test_RIPEMD160_long_string = generate_long_string_hash_test( - hashes.RIPEMD160(), - "52783243c1697bdbe16d37f97f68f08325dc1528", + hashes.SHA512_224(), ) @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.Whirlpool()), - skip_message="Does not support Whirlpool", + only_if=lambda backend: backend.hash_supported(hashes.SHA512_256()), + skip_message="Does not support SHA512/256", ) @pytest.mark.requires_backend_interface(interface=HashBackend) -class TestWhirlpool(object): - test_whirlpool = generate_hash_test( +class TestSHA512256(object): + test_sha512_256 = generate_hash_test( load_hash_vectors, - os.path.join("hashes", "whirlpool"), + os.path.join("hashes", "SHA2"), [ - "iso-test-vectors.txt", + "SHA512_256LongMsg.rsp", + "SHA512_256ShortMsg.rsp", ], - hashes.Whirlpool(), - ) - - test_whirlpool_long_string = generate_long_string_hash_test( - hashes.Whirlpool(), - ("0c99005beb57eff50a7cf005560ddf5d29057fd86b2" - "0bfd62deca0f1ccea4af51fc15490eddc47af32bb2b" - "66c34ff9ad8c6008ad677f77126953b226e4ed8b01"), + hashes.SHA512_256(), ) @@ -158,3 +149,177 @@ class TestMD5(object): ], hashes.MD5(), ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2b(digest_size=64)), + skip_message="Does not support BLAKE2b", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestBLAKE2b(object): + test_b2b = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "blake2"), + [ + "blake2b.txt", + ], + hashes.BLAKE2b(digest_size=64), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2s(digest_size=32)), + skip_message="Does not support BLAKE2s", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestBLAKE2s256(object): + test_b2s = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "blake2"), + [ + "blake2s.txt", + ], + hashes.BLAKE2s(digest_size=32), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA3_224()), + skip_message="Does not support SHA3_224", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA3224(object): + test_sha3_224 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA3"), + [ + "SHA3_224LongMsg.rsp", + "SHA3_224ShortMsg.rsp", + ], + hashes.SHA3_224(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA3_256()), + skip_message="Does not support SHA3_256", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA3256(object): + test_sha3_256 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA3"), + [ + "SHA3_256LongMsg.rsp", + "SHA3_256ShortMsg.rsp", + ], + hashes.SHA3_256(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA3_384()), + skip_message="Does not support SHA3_384", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA3384(object): + test_sha3_384 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA3"), + [ + "SHA3_384LongMsg.rsp", + "SHA3_384ShortMsg.rsp", + ], + hashes.SHA3_384(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA3_512()), + skip_message="Does not support SHA3_512", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA3512(object): + test_sha3_512 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA3"), + [ + "SHA3_512LongMsg.rsp", + "SHA3_512ShortMsg.rsp", + ], + hashes.SHA3_512(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.SHAKE128(digest_size=16)), + skip_message="Does not support SHAKE128", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHAKE128(object): + test_shake128 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHAKE"), + [ + "SHAKE128LongMsg.rsp", + "SHAKE128ShortMsg.rsp", + ], + hashes.SHAKE128(digest_size=16), + ) + + @pytest.mark.parametrize( + "vector", + _load_all_params( + os.path.join("hashes", "SHAKE"), + [ + "SHAKE128VariableOut.rsp", + ], + load_nist_vectors, + ) + ) + def test_shake128_variable(self, vector, backend): + output_length = int(vector['outputlen']) // 8 + msg = binascii.unhexlify(vector['msg']) + shake = hashes.SHAKE128(digest_size=output_length) + m = hashes.Hash(shake, backend=backend) + m.update(msg) + assert m.finalize() == binascii.unhexlify(vector['output']) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.SHAKE256(digest_size=32)), + skip_message="Does not support SHAKE256", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHAKE256(object): + test_shake256 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHAKE"), + [ + "SHAKE256LongMsg.rsp", + "SHAKE256ShortMsg.rsp", + ], + hashes.SHAKE256(digest_size=32), + ) + + @pytest.mark.parametrize( + "vector", + _load_all_params( + os.path.join("hashes", "SHAKE"), + [ + "SHAKE256VariableOut.rsp", + ], + load_nist_vectors, + ) + ) + def test_shake256_variable(self, vector, backend): + output_length = int(vector['outputlen']) // 8 + msg = binascii.unhexlify(vector['msg']) + shake = hashes.SHAKE256(digest_size=output_length) + m = hashes.Hash(shake, backend=backend) + m.update(msg) + assert m.finalize() == binascii.unhexlify(vector['output']) diff --git a/tests/hazmat/primitives/test_hashes.py b/tests/hazmat/primitives/test_hashes.py index 8f7fdb18..a743045c 100644 --- a/tests/hazmat/primitives/test_hashes.py +++ b/tests/hazmat/primitives/test_hashes.py @@ -4,27 +4,19 @@ from __future__ import absolute_import, division, print_function -import pretend +import binascii import pytest -from cryptography import utils from cryptography.exceptions import AlreadyFinalized, _Reasons from cryptography.hazmat.backends.interfaces import HashBackend from cryptography.hazmat.primitives import hashes from .utils import generate_base_hash_test -from ..backends.test_multibackend import DummyHashBackend +from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm -@utils.register_interface(hashes.HashAlgorithm) -class UnsupportedDummyHash(object): - name = "unsupported-dummy-hash" - block_size = None - digest_size = None - - @pytest.mark.requires_backend_interface(interface=HashBackend) class TestHashContext(object): def test_hash_reject_unicode(self, backend): @@ -32,14 +24,6 @@ class TestHashContext(object): with pytest.raises(TypeError): m.update(u"\u00FC") - def test_copy_backend_object(self): - backend = DummyHashBackend([hashes.SHA1]) - copied_ctx = pretend.stub() - pretend_ctx = pretend.stub(copy=lambda: copied_ctx) - h = hashes.Hash(hashes.SHA1(), backend=backend, ctx=pretend_ctx) - assert h._backend is backend - assert h.copy()._backend is h._backend - def test_hash_algorithm_instance(self, backend): with pytest.raises(TypeError): hashes.Hash(hashes.SHA1, backend=backend) @@ -59,7 +43,7 @@ class TestHashContext(object): def test_unsupported_hash(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - hashes.Hash(UnsupportedDummyHash(), backend) + hashes.Hash(DummyHashAlgorithm(), backend) @pytest.mark.supported( @@ -68,10 +52,9 @@ class TestHashContext(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA1(object): - test_SHA1 = generate_base_hash_test( + test_sha1 = generate_base_hash_test( hashes.SHA1(), digest_size=20, - block_size=64, ) @@ -81,10 +64,9 @@ class TestSHA1(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA224(object): - test_SHA224 = generate_base_hash_test( + test_sha224 = generate_base_hash_test( hashes.SHA224(), digest_size=28, - block_size=64, ) @@ -94,10 +76,9 @@ class TestSHA224(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA256(object): - test_SHA256 = generate_base_hash_test( + test_sha256 = generate_base_hash_test( hashes.SHA256(), digest_size=32, - block_size=64, ) @@ -107,10 +88,9 @@ class TestSHA256(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA384(object): - test_SHA384 = generate_base_hash_test( + test_sha384 = generate_base_hash_test( hashes.SHA384(), digest_size=48, - block_size=128, ) @@ -120,54 +100,103 @@ class TestSHA384(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA512(object): - test_SHA512 = generate_base_hash_test( + test_sha512 = generate_base_hash_test( hashes.SHA512(), digest_size=64, - block_size=128, ) @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160()), - skip_message="Does not support RIPEMD160", + only_if=lambda backend: backend.hash_supported(hashes.MD5()), + skip_message="Does not support MD5", ) @pytest.mark.requires_backend_interface(interface=HashBackend) -class TestRIPEMD160(object): - test_RIPEMD160 = generate_base_hash_test( - hashes.RIPEMD160(), - digest_size=20, - block_size=64, +class TestMD5(object): + test_md5 = generate_base_hash_test( + hashes.MD5(), + digest_size=16, ) @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.Whirlpool()), - skip_message="Does not support Whirlpool", + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2b(digest_size=64)), + skip_message="Does not support BLAKE2b", ) @pytest.mark.requires_backend_interface(interface=HashBackend) -class TestWhirlpool(object): - test_Whirlpool = generate_base_hash_test( - hashes.Whirlpool(), +class TestBLAKE2b(object): + test_blake2b = generate_base_hash_test( + hashes.BLAKE2b(digest_size=64), digest_size=64, - block_size=64, ) + def test_invalid_digest_size(self, backend): + with pytest.raises(ValueError): + hashes.BLAKE2b(digest_size=65) + + with pytest.raises(ValueError): + hashes.BLAKE2b(digest_size=0) + + with pytest.raises(ValueError): + hashes.BLAKE2b(digest_size=-1) + @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.MD5()), - skip_message="Does not support MD5", + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2s(digest_size=32)), + skip_message="Does not support BLAKE2s", ) @pytest.mark.requires_backend_interface(interface=HashBackend) -class TestMD5(object): - test_MD5 = generate_base_hash_test( - hashes.MD5(), - digest_size=16, - block_size=64, +class TestBLAKE2s(object): + test_blake2s = generate_base_hash_test( + hashes.BLAKE2s(digest_size=32), + digest_size=32, ) + def test_invalid_digest_size(self, backend): + with pytest.raises(ValueError): + hashes.BLAKE2s(digest_size=33) + + with pytest.raises(ValueError): + hashes.BLAKE2s(digest_size=0) + + with pytest.raises(ValueError): + hashes.BLAKE2s(digest_size=-1) + def test_invalid_backend(): pretend_backend = object() with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): hashes.Hash(hashes.SHA1(), pretend_backend) + + +@pytest.mark.requires_backend_interface(interface=HashBackend) +def test_buffer_protocol_hash(backend): + data = binascii.unhexlify(b"b4190e") + h = hashes.Hash(hashes.SHA256(), backend) + h.update(bytearray(data)) + assert h.finalize() == binascii.unhexlify( + b"dff2e73091f6c05e528896c4c831b9448653dc2ff043528f6769437bc7b975c2" + ) + + +class TestSHAKE(object): + @pytest.mark.parametrize( + "xof", + [hashes.SHAKE128, hashes.SHAKE256] + ) + def test_invalid_digest_type(self, xof): + with pytest.raises(TypeError): + xof(digest_size=object()) + + @pytest.mark.parametrize( + "xof", + [hashes.SHAKE128, hashes.SHAKE256] + ) + def test_invalid_digest_size(self, xof): + with pytest.raises(ValueError): + xof(digest_size=-5) + + with pytest.raises(ValueError): + xof(digest_size=0) diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index e33529c9..195bfb3a 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function import binascii +import os import pytest @@ -15,13 +16,15 @@ from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand -from ...utils import raises_unsupported_algorithm +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) @pytest.mark.requires_backend_interface(interface=HMACBackend) class TestHKDF(object): def test_length_limit(self, backend): - big_length = 255 * (hashes.SHA256().digest_size // 8) + 1 + big_length = 255 * hashes.SHA256().digest_size + 1 with pytest.raises(ValueError): HKDF( @@ -142,6 +145,47 @@ class TestHKDF(object): hkdf.verify(b"foo", u"bar") + def test_derive_short_output(self, backend): + hkdf = HKDF( + hashes.SHA256(), + 4, + salt=None, + info=None, + backend=backend + ) + + assert hkdf.derive(b"\x01" * 16) == b"gJ\xfb{" + + def test_derive_long_output(self, backend): + vector = load_vectors_from_file( + os.path.join("KDF", "hkdf-generated.txt"), load_nist_vectors + )[0] + hkdf = HKDF( + hashes.SHA256(), + int(vector["l"]), + salt=vector["salt"], + info=vector["info"], + backend=backend + ) + ikm = binascii.unhexlify(vector["ikm"]) + + assert hkdf.derive(ikm) == binascii.unhexlify(vector["okm"]) + + def test_buffer_protocol(self, backend): + vector = load_vectors_from_file( + os.path.join("KDF", "hkdf-generated.txt"), load_nist_vectors + )[0] + hkdf = HKDF( + hashes.SHA256(), + int(vector["l"]), + salt=vector["salt"], + info=vector["info"], + backend=backend + ) + ikm = bytearray(binascii.unhexlify(vector["ikm"])) + + assert hkdf.derive(ikm) == binascii.unhexlify(vector["okm"]) + @pytest.mark.requires_backend_interface(interface=HMACBackend) class TestHKDFExpand(object): @@ -158,6 +202,19 @@ class TestHKDFExpand(object): assert binascii.hexlify(hkdf.derive(prk)) == okm + def test_buffer_protocol(self, backend): + prk = bytearray(binascii.unhexlify( + b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" + )) + + okm = (b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c" + b"5bf34007208d5b887185865") + + info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") + hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) + + assert binascii.hexlify(hkdf.derive(prk)) == okm + def test_verify(self, backend): prk = binascii.unhexlify( b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" diff --git a/tests/hazmat/primitives/test_hkdf_vectors.py b/tests/hazmat/primitives/test_hkdf_vectors.py index 74bf9291..290cefbf 100644 --- a/tests/hazmat/primitives/test_hkdf_vectors.py +++ b/tests/hazmat/primitives/test_hkdf_vectors.py @@ -21,7 +21,7 @@ from ...utils import load_nist_vectors ) @pytest.mark.requires_backend_interface(interface=HMACBackend) class TestHKDFSHA1(object): - test_HKDFSHA1 = generate_hkdf_test( + test_hkdfsha1 = generate_hkdf_test( load_nist_vectors, os.path.join("KDF"), ["rfc-5869-HKDF-SHA1.txt"], @@ -35,7 +35,7 @@ class TestHKDFSHA1(object): ) @pytest.mark.requires_backend_interface(interface=HMACBackend) class TestHKDFSHA256(object): - test_HKDFSHA1 = generate_hkdf_test( + test_hkdfsha256 = generate_hkdf_test( load_nist_vectors, os.path.join("KDF"), ["rfc-5869-HKDF-SHA256.txt"], diff --git a/tests/hazmat/primitives/test_hmac.py b/tests/hazmat/primitives/test_hmac.py index 83b18cbc..0e2fe688 100644 --- a/tests/hazmat/primitives/test_hmac.py +++ b/tests/hazmat/primitives/test_hmac.py @@ -4,11 +4,10 @@ from __future__ import absolute_import, division, print_function -import pretend +import binascii import pytest -from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidSignature, _Reasons ) @@ -16,17 +15,10 @@ from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import hashes, hmac from .utils import generate_base_hmac_test -from ..backends.test_multibackend import DummyHMACBackend +from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm -@utils.register_interface(hashes.HashAlgorithm) -class UnsupportedDummyHash(object): - name = "unsupported-dummy-hash" - block_size = None - digest_size = None - - @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.MD5()), skip_message="Does not support MD5", @@ -45,14 +37,6 @@ class TestHMAC(object): with pytest.raises(TypeError): h.update(u"\u00FC") - def test_copy_backend_object(self): - backend = DummyHMACBackend([hashes.SHA1]) - copied_ctx = pretend.stub() - pretend_ctx = pretend.stub(copy=lambda: copied_ctx) - h = hmac.HMAC(b"key", hashes.SHA1(), backend=backend, ctx=pretend_ctx) - assert h._backend is backend - assert h.copy()._backend is backend - def test_hmac_algorithm_instance(self, backend): with pytest.raises(TypeError): hmac.HMAC(b"key", hashes.SHA1, backend=backend) @@ -95,7 +79,15 @@ class TestHMAC(object): def test_unsupported_hash(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - hmac.HMAC(b"key", UnsupportedDummyHash(), backend) + hmac.HMAC(b"key", DummyHashAlgorithm(), backend) + + def test_buffer_protocol(self, backend): + key = bytearray(b"2b7e151628aed2a6abf7158809cf4f3c") + h = hmac.HMAC(key, hashes.SHA256(), backend) + h.update(bytearray(b"6bc1bee22e409f96e93d7e117393172a")) + assert h.finalize() == binascii.unhexlify( + b"a1bf7169c56a501c6585190ff4f07cad6e492a3ee187c0372614fb444b9fc3f0" + ) def test_invalid_backend(): diff --git a/tests/hazmat/primitives/test_hmac_vectors.py b/tests/hazmat/primitives/test_hmac_vectors.py index 8704e724..6ff71fe3 100644 --- a/tests/hazmat/primitives/test_hmac_vectors.py +++ b/tests/hazmat/primitives/test_hmac_vectors.py @@ -4,10 +4,12 @@ from __future__ import absolute_import, division, print_function +import binascii + import pytest from cryptography.hazmat.backends.interfaces import HMACBackend -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, hmac from .utils import generate_hmac_test from ...utils import load_hash_vectors @@ -110,16 +112,26 @@ class TestHMACSHA512(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.RIPEMD160()), - skip_message="Does not support RIPEMD160", + only_if=lambda backend: backend.hmac_supported(hashes.BLAKE2b( + digest_size=64 + )), + skip_message="Does not support BLAKE2", ) @pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHMACRIPEMD160(object): - test_hmac_ripemd160 = generate_hmac_test( - load_hash_vectors, - "HMAC", - [ - "rfc-2286-ripemd160.txt", - ], - hashes.RIPEMD160(), - ) +class TestHMACBLAKE2(object): + def test_blake2b(self, backend): + h = hmac.HMAC(b"0" * 64, hashes.BLAKE2b(digest_size=64), backend) + h.update(b"test") + digest = h.finalize() + assert digest == binascii.unhexlify( + b"b5319122f8a24ba134a0c9851922448104e25be5d1b91265c0c68b22722f0f29" + b"87dba4aeaa69e6bed7edc44f48d6b1be493a3ce583f9c737c53d6bacc09e2f32" + ) + + def test_blake2s(self, backend): + h = hmac.HMAC(b"0" * 32, hashes.BLAKE2s(digest_size=32), backend) + h.update(b"test") + digest = h.finalize() + assert digest == binascii.unhexlify( + b"51477cc5bdf1faf952cf97bb934ee936de1f4d5d7448a84eeb6f98d23b392166" + ) diff --git a/tests/hazmat/primitives/test_idea.py b/tests/hazmat/primitives/test_idea.py index 1b15ed67..6b8a2a87 100644 --- a/tests/hazmat/primitives/test_idea.py +++ b/tests/hazmat/primitives/test_idea.py @@ -18,13 +18,13 @@ from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.IDEA("\x00" * 16), modes.ECB() + algorithms.IDEA(b"\x00" * 16), modes.ECB() ), skip_message="Does not support IDEA ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestIDEAModeECB(object): - test_ECB = generate_encrypt_test( + test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-ecb.txt"], @@ -35,13 +35,13 @@ class TestIDEAModeECB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.IDEA("\x00" * 16), modes.CBC("\x00" * 8) + algorithms.IDEA(b"\x00" * 16), modes.CBC(b"\x00" * 8) ), skip_message="Does not support IDEA CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestIDEAModeCBC(object): - test_CBC = generate_encrypt_test( + test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-cbc.txt"], @@ -52,13 +52,13 @@ class TestIDEAModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.IDEA("\x00" * 16), modes.OFB("\x00" * 8) + algorithms.IDEA(b"\x00" * 16), modes.OFB(b"\x00" * 8) ), skip_message="Does not support IDEA OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestIDEAModeOFB(object): - test_OFB = generate_encrypt_test( + test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-ofb.txt"], @@ -69,13 +69,13 @@ class TestIDEAModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.IDEA("\x00" * 16), modes.CFB("\x00" * 8) + algorithms.IDEA(b"\x00" * 16), modes.CFB(b"\x00" * 8) ), skip_message="Does not support IDEA CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestIDEAModeCFB(object): - test_CFB = generate_encrypt_test( + test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-cfb.txt"], diff --git a/tests/hazmat/primitives/test_kbkdf.py b/tests/hazmat/primitives/test_kbkdf.py new file mode 100644 index 00000000..a16f1768 --- /dev/null +++ b/tests/hazmat/primitives/test_kbkdf.py @@ -0,0 +1,158 @@ +# 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 cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, _Reasons +) +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.kbkdf import ( + CounterLocation, KBKDFHMAC, Mode +) + +from ...doubles import DummyHashAlgorithm +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestKBKDFHMAC(object): + def test_invalid_key(self, backend): + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + key = kdf.derive(b"material") + + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + with pytest.raises(InvalidKey): + kdf.verify(b"material2", key) + + def test_already_finalized(self, backend): + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + kdf.derive(b'material') + + with pytest.raises(AlreadyFinalized): + kdf.derive(b'material2') + + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + key = kdf.derive(b'material') + + with pytest.raises(AlreadyFinalized): + kdf.verify(b'material', key) + + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + kdf.verify(b'material', key) + + with pytest.raises(AlreadyFinalized): + kdf.verify(b"material", key) + + def test_key_length(self, backend): + kdf = KBKDFHMAC(hashes.SHA1(), Mode.CounterMode, 85899345920, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + with pytest.raises(ValueError): + kdf.derive(b'material') + + def test_rlen(self, backend): + with pytest.raises(ValueError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 5, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_r_type(self, backend): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA1(), Mode.CounterMode, 32, b'r', 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_l_type(self, backend): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA1(), Mode.CounterMode, 32, 4, b'l', + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_l(self, backend): + with pytest.raises(ValueError): + KBKDFHMAC(hashes.SHA1(), Mode.CounterMode, 32, 4, None, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_unsupported_mode(self, backend): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA256(), None, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_unsupported_location(self, backend): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + None, b'label', b'context', None, + backend=backend) + + def test_unsupported_parameters(self, backend): + with pytest.raises(ValueError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + b'fixed', backend=backend) + + def test_unsupported_hash(self, backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + KBKDFHMAC(object(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_unsupported_algorithm(self, backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + KBKDFHMAC(DummyHashAlgorithm(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_invalid_backend(self, backend): + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=object()) + + def test_unicode_error_label(self, backend): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, u'label', b'context', + backend=backend) + + def test_unicode_error_context(self, backend): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', u'context', + None, backend=backend) + + def test_unicode_error_key_material(self, backend): + with pytest.raises(TypeError): + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', + b'context', None, backend=backend) + kdf.derive(u'material') + + def test_buffer_protocol(self, backend): + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 10, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + key = kdf.derive(bytearray(b"material")) + assert key == b'\xb7\x01\x05\x98\xf5\x1a\x12L\xc7.' diff --git a/tests/hazmat/primitives/test_kbkdf_vectors.py b/tests/hazmat/primitives/test_kbkdf_vectors.py new file mode 100644 index 00000000..7bdbbdc7 --- /dev/null +++ b/tests/hazmat/primitives/test_kbkdf_vectors.py @@ -0,0 +1,23 @@ +# 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 os + +import pytest + +from cryptography.hazmat.backends.interfaces import HMACBackend + +from .utils import generate_kbkdf_counter_mode_test +from ...utils import load_nist_kbkdf_vectors + + +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestCounterKDFCounterMode(object): + test_kbkdfctr = generate_kbkdf_counter_mode_test( + load_nist_kbkdf_vectors, + os.path.join("KDF"), + ["nist-800-108-KBKDF-CTR.txt"] + ) diff --git a/tests/hazmat/primitives/test_keywrap.py b/tests/hazmat/primitives/test_keywrap.py new file mode 100644 index 00000000..c74b144b --- /dev/null +++ b/tests/hazmat/primitives/test_keywrap.py @@ -0,0 +1,207 @@ +# 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.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives import keywrap +from cryptography.hazmat.primitives.ciphers import algorithms, modes + +from .utils import _load_all_params +from ...utils import load_nist_vectors + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESKeyWrap(object): + @pytest.mark.parametrize( + "params", + _load_all_params( + os.path.join("keywrap", "kwtestvectors"), + ["KW_AE_128.txt", "KW_AE_192.txt", "KW_AE_256.txt"], + load_nist_vectors + ) + ) + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_wrap(self, backend, params): + wrapping_key = binascii.unhexlify(params["k"]) + key_to_wrap = binascii.unhexlify(params["p"]) + wrapped_key = keywrap.aes_key_wrap(wrapping_key, key_to_wrap, backend) + assert params["c"] == binascii.hexlify(wrapped_key) + + @pytest.mark.parametrize( + "params", + _load_all_params( + os.path.join("keywrap", "kwtestvectors"), + ["KW_AD_128.txt", "KW_AD_192.txt", "KW_AD_256.txt"], + load_nist_vectors + ) + ) + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_unwrap(self, backend, params): + wrapping_key = binascii.unhexlify(params["k"]) + wrapped_key = binascii.unhexlify(params["c"]) + if params.get("fail") is True: + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap(wrapping_key, wrapped_key, backend) + else: + unwrapped_key = keywrap.aes_key_unwrap( + wrapping_key, wrapped_key, backend + ) + assert params["p"] == binascii.hexlify(unwrapped_key) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_wrap_invalid_key_length(self, backend): + # The wrapping key must be of length [16, 24, 32] + with pytest.raises(ValueError): + keywrap.aes_key_wrap(b"badkey", b"sixteen_byte_key", backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_unwrap_invalid_key_length(self, backend): + with pytest.raises(ValueError): + keywrap.aes_key_unwrap(b"badkey", b"\x00" * 24, backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_wrap_invalid_key_to_wrap_length(self, backend): + # Keys to wrap must be at least 16 bytes long + with pytest.raises(ValueError): + keywrap.aes_key_wrap(b"sixteen_byte_key", b"\x00" * 15, backend) + + # Keys to wrap must be a multiple of 8 bytes + with pytest.raises(ValueError): + keywrap.aes_key_wrap(b"sixteen_byte_key", b"\x00" * 23, backend) + + def test_unwrap_invalid_wrapped_key_length(self, backend): + # Keys to unwrap must be at least 24 bytes + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap(b"sixteen_byte_key", b"\x00" * 16, backend) + + # Keys to unwrap must be a multiple of 8 bytes + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap(b"sixteen_byte_key", b"\x00" * 27, backend) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 5649) because AES-ECB" + " is unsupported", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESKeyWrapWithPadding(object): + @pytest.mark.parametrize( + "params", + _load_all_params( + os.path.join("keywrap", "kwtestvectors"), + ["KWP_AE_128.txt", "KWP_AE_192.txt", "KWP_AE_256.txt"], + load_nist_vectors + ) + ) + def test_wrap(self, backend, params): + wrapping_key = binascii.unhexlify(params["k"]) + key_to_wrap = binascii.unhexlify(params["p"]) + wrapped_key = keywrap.aes_key_wrap_with_padding( + wrapping_key, key_to_wrap, backend + ) + assert params["c"] == binascii.hexlify(wrapped_key) + + @pytest.mark.parametrize( + "params", + _load_all_params("keywrap", ["kwp_botan.txt"], load_nist_vectors) + ) + def test_wrap_additional_vectors(self, backend, params): + wrapping_key = binascii.unhexlify(params["key"]) + key_to_wrap = binascii.unhexlify(params["input"]) + wrapped_key = keywrap.aes_key_wrap_with_padding( + wrapping_key, key_to_wrap, backend + ) + assert wrapped_key == binascii.unhexlify(params["output"]) + + @pytest.mark.parametrize( + "params", + _load_all_params( + os.path.join("keywrap", "kwtestvectors"), + ["KWP_AD_128.txt", "KWP_AD_192.txt", "KWP_AD_256.txt"], + load_nist_vectors + ) + ) + def test_unwrap(self, backend, params): + wrapping_key = binascii.unhexlify(params["k"]) + wrapped_key = binascii.unhexlify(params["c"]) + if params.get("fail") is True: + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap_with_padding( + wrapping_key, wrapped_key, backend + ) + else: + unwrapped_key = keywrap.aes_key_unwrap_with_padding( + wrapping_key, wrapped_key, backend + ) + assert params["p"] == binascii.hexlify(unwrapped_key) + + @pytest.mark.parametrize( + "params", + _load_all_params("keywrap", ["kwp_botan.txt"], load_nist_vectors) + ) + def test_unwrap_additional_vectors(self, backend, params): + wrapping_key = binascii.unhexlify(params["key"]) + wrapped_key = binascii.unhexlify(params["output"]) + unwrapped_key = keywrap.aes_key_unwrap_with_padding( + wrapping_key, wrapped_key, backend + ) + assert unwrapped_key == binascii.unhexlify(params["input"]) + + def test_unwrap_invalid_wrapped_key_length(self, backend): + # Keys to unwrap must be at least 16 bytes + with pytest.raises( + keywrap.InvalidUnwrap, match='Must be at least 16 bytes' + ): + keywrap.aes_key_unwrap_with_padding( + b"sixteen_byte_key", b"\x00" * 15, backend + ) + + def test_wrap_invalid_key_length(self, backend): + with pytest.raises(ValueError, match='must be a valid AES key length'): + keywrap.aes_key_wrap_with_padding(b"badkey", b"\x00", backend) + + def test_unwrap_invalid_key_length(self, backend): + with pytest.raises(ValueError, match='must be a valid AES key length'): + keywrap.aes_key_unwrap_with_padding( + b"badkey", b"\x00" * 16, backend + ) diff --git a/tests/hazmat/primitives/test_padding.py b/tests/hazmat/primitives/test_padding.py index 392ea737..fb72a794 100644 --- a/tests/hazmat/primitives/test_padding.py +++ b/tests/hazmat/primitives/test_padding.py @@ -6,6 +6,8 @@ from __future__ import absolute_import, division, print_function import pytest +import six + from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives import padding @@ -99,3 +101,109 @@ class TestPKCS7(object): unpadder.update(b"") with pytest.raises(AlreadyFinalized): unpadder.finalize() + + def test_large_padding(self): + padder = padding.PKCS7(2040).padder() + padded_data = padder.update(b"") + padded_data += padder.finalize() + + for i in six.iterbytes(padded_data): + assert i == 255 + + unpadder = padding.PKCS7(2040).unpadder() + data = unpadder.update(padded_data) + data += unpadder.finalize() + + assert data == b"" + + +class TestANSIX923(object): + @pytest.mark.parametrize("size", [127, 4096, -2]) + def test_invalid_block_size(self, size): + with pytest.raises(ValueError): + padding.ANSIX923(size) + + @pytest.mark.parametrize(("size", "padded"), [ + (128, b"1111"), + (128, b"1111111111111111"), + (128, b"111111111111111\x06"), + (128, b"1111111111\x06\x06\x06\x06\x06\x06"), + (128, b""), + (128, b"\x06" * 6), + (128, b"\x00" * 16), + ]) + def test_invalid_padding(self, size, padded): + unpadder = padding.ANSIX923(size).unpadder() + with pytest.raises(ValueError): + unpadder.update(padded) + unpadder.finalize() + + def test_non_bytes(self): + padder = padding.ANSIX923(128).padder() + with pytest.raises(TypeError): + padder.update(u"abc") + unpadder = padding.ANSIX923(128).unpadder() + with pytest.raises(TypeError): + unpadder.update(u"abc") + + @pytest.mark.parametrize(("size", "unpadded", "padded"), [ + ( + 128, + b"1111111111", + b"1111111111\x00\x00\x00\x00\x00\x06", + ), + ( + 128, + b"111111111111111122222222222222", + b"111111111111111122222222222222\x00\x02", + ), + ( + 128, + b"1" * 16, + b"1" * 16 + b"\x00" * 15 + b"\x10", + ), + ( + 128, + b"1" * 17, + b"1" * 17 + b"\x00" * 14 + b"\x0F", + ) + ]) + def test_pad(self, size, unpadded, padded): + padder = padding.ANSIX923(size).padder() + result = padder.update(unpadded) + result += padder.finalize() + assert result == padded + + @pytest.mark.parametrize(("size", "unpadded", "padded"), [ + ( + 128, + b"1111111111", + b"1111111111\x00\x00\x00\x00\x00\x06", + ), + ( + 128, + b"111111111111111122222222222222", + b"111111111111111122222222222222\x00\x02", + ), + ]) + def test_unpad(self, size, unpadded, padded): + unpadder = padding.ANSIX923(size).unpadder() + result = unpadder.update(padded) + result += unpadder.finalize() + assert result == unpadded + + def test_use_after_finalize(self): + padder = padding.ANSIX923(128).padder() + b = padder.finalize() + with pytest.raises(AlreadyFinalized): + padder.update(b"") + with pytest.raises(AlreadyFinalized): + padder.finalize() + + unpadder = padding.ANSIX923(128).unpadder() + unpadder.update(b) + unpadder.finalize() + with pytest.raises(AlreadyFinalized): + unpadder.update(b"") + with pytest.raises(AlreadyFinalized): + unpadder.finalize() diff --git a/tests/hazmat/primitives/test_pbkdf2hmac.py b/tests/hazmat/primitives/test_pbkdf2hmac.py index 7fb6bbd6..0254b216 100644 --- a/tests/hazmat/primitives/test_pbkdf2hmac.py +++ b/tests/hazmat/primitives/test_pbkdf2hmac.py @@ -6,7 +6,6 @@ from __future__ import absolute_import, division, print_function import pytest -from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidKey, _Reasons ) @@ -14,16 +13,10 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm -@utils.register_interface(hashes.HashAlgorithm) -class DummyHash(object): - name = "dummy-hash" - block_size = None - digest_size = None - - class TestPBKDF2HMAC(object): def test_already_finalized(self): kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) @@ -43,7 +36,9 @@ class TestPBKDF2HMAC(object): def test_unsupported_algorithm(self): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - PBKDF2HMAC(DummyHash(), 20, b"salt", 10, default_backend()) + PBKDF2HMAC( + DummyHashAlgorithm(), 20, b"salt", 10, default_backend() + ) def test_invalid_key(self): kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) @@ -62,6 +57,11 @@ class TestPBKDF2HMAC(object): with pytest.raises(TypeError): kdf.derive(u"unicode here") + def test_buffer_protocol(self, backend): + kdf = PBKDF2HMAC(hashes.SHA1(), 10, b"salt", 10, default_backend()) + data = bytearray(b"data") + assert kdf.derive(data) == b"\xe9n\xaa\x81\xbbt\xa4\xf6\x08\xce" + def test_invalid_backend(): pretend_backend = object() diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py new file mode 100644 index 00000000..0bb76e25 --- /dev/null +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -0,0 +1,139 @@ +# 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 os + +import pytest + +from cryptography import x509 +from cryptography.hazmat.backends.interfaces import DERSerializationBackend +from cryptography.hazmat.backends.openssl.backend import _RC2 +from cryptography.hazmat.primitives.serialization import load_pem_private_key +from cryptography.hazmat.primitives.serialization.pkcs12 import ( + load_key_and_certificates +) + +from .utils import load_vectors_from_file + + +@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) +class TestPKCS12(object): + def _test_load_pkcs12_ec_keys(self, filename, password, backend): + cert = load_vectors_from_file( + os.path.join("x509", "custom", "ca", "ca.pem"), + lambda pemfile: x509.load_pem_x509_certificate( + pemfile.read(), backend + ), mode="rb" + ) + key = load_vectors_from_file( + os.path.join("x509", "custom", "ca", "ca_key.pem"), + lambda pemfile: load_pem_private_key( + pemfile.read(), None, backend + ), mode="rb" + ) + parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( + os.path.join("pkcs12", filename), + lambda derfile: load_key_and_certificates( + derfile.read(), password, backend + ), mode="rb" + ) + assert parsed_cert == cert + assert parsed_key.private_numbers() == key.private_numbers() + assert parsed_more_certs == [] + + @pytest.mark.parametrize( + ("filename", "password"), + [ + ("cert-key-aes256cbc.p12", b"cryptography"), + ("cert-none-key-none.p12", b"cryptography"), + ] + ) + def test_load_pkcs12_ec_keys(self, filename, password, backend): + self._test_load_pkcs12_ec_keys(filename, password, backend) + + @pytest.mark.parametrize( + ("filename", "password"), + [ + ("cert-rc2-key-3des.p12", b"cryptography"), + ("no-password.p12", None), + ] + ) + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported(_RC2(), None), + skip_message="Does not support RC2" + ) + def test_load_pkcs12_ec_keys_rc2(self, filename, password, backend): + self._test_load_pkcs12_ec_keys(filename, password, backend) + + def test_load_pkcs12_cert_only(self, backend): + cert = load_vectors_from_file( + os.path.join("x509", "custom", "ca", "ca.pem"), + lambda pemfile: x509.load_pem_x509_certificate( + pemfile.read(), backend + ), mode="rb" + ) + parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( + os.path.join("pkcs12", "cert-aes256cbc-no-key.p12"), + lambda data: load_key_and_certificates( + data.read(), b"cryptography", backend + ), + mode="rb" + ) + assert parsed_cert is None + assert parsed_key is None + assert parsed_more_certs == [cert] + + def test_load_pkcs12_key_only(self, backend): + key = load_vectors_from_file( + os.path.join("x509", "custom", "ca", "ca_key.pem"), + lambda pemfile: load_pem_private_key( + pemfile.read(), None, backend + ), mode="rb" + ) + parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( + os.path.join("pkcs12", "no-cert-key-aes256cbc.p12"), + lambda data: load_key_and_certificates( + data.read(), b"cryptography", backend + ), + mode="rb" + ) + assert parsed_key.private_numbers() == key.private_numbers() + assert parsed_cert is None + assert parsed_more_certs == [] + + def test_non_bytes(self, backend): + with pytest.raises(TypeError): + load_key_and_certificates( + b"irrelevant", object(), backend + ) + + def test_not_a_pkcs12(self, backend): + with pytest.raises(ValueError): + load_key_and_certificates( + b"invalid", b"pass", backend + ) + + def test_invalid_password(self, backend): + with pytest.raises(ValueError): + load_vectors_from_file( + os.path.join("pkcs12", "cert-key-aes256cbc.p12"), + lambda derfile: load_key_and_certificates( + derfile.read(), b"invalid", backend + ), mode="rb" + ) + + def test_buffer_protocol(self, backend): + p12 = load_vectors_from_file( + os.path.join("pkcs12", "cert-key-aes256cbc.p12"), + lambda derfile: derfile.read(), mode="rb" + ) + p12buffer = bytearray(p12) + parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( + p12buffer, bytearray(b"cryptography"), backend + ) + assert parsed_key is not None + assert parsed_cert is not None + assert parsed_more_certs == [] diff --git a/tests/hazmat/primitives/test_poly1305.py b/tests/hazmat/primitives/test_poly1305.py new file mode 100644 index 00000000..edca4623 --- /dev/null +++ b/tests/hazmat/primitives/test_poly1305.py @@ -0,0 +1,152 @@ +# 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 ( + AlreadyFinalized, InvalidSignature, _Reasons +) +from cryptography.hazmat.primitives.poly1305 import Poly1305 + +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) + + +@pytest.mark.supported( + only_if=lambda backend: not backend.poly1305_supported(), + skip_message="Requires OpenSSL without poly1305 support" +) +def test_poly1305_unsupported(backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MAC): + Poly1305(b"0" * 32) + + +@pytest.mark.supported( + only_if=lambda backend: backend.poly1305_supported(), + skip_message="Requires OpenSSL with poly1305 support" +) +class TestPoly1305(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("poly1305", "rfc7539.txt"), load_nist_vectors + ) + ) + def test_vectors(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + msg = binascii.unhexlify(vector["msg"]) + tag = binascii.unhexlify(vector["tag"]) + poly = Poly1305(key) + poly.update(msg) + assert poly.finalize() == tag + + assert Poly1305.generate_tag(key, msg) == tag + Poly1305.verify_tag(key, msg, tag) + + def test_key_with_no_additional_references(self, backend): + poly = Poly1305(os.urandom(32)) + assert len(poly.finalize()) == 16 + + def test_raises_after_finalize(self, backend): + poly = Poly1305(b"0" * 32) + poly.finalize() + + with pytest.raises(AlreadyFinalized): + poly.update(b"foo") + + with pytest.raises(AlreadyFinalized): + poly.finalize() + + def test_reject_unicode(self, backend): + poly = Poly1305(b"0" * 32) + with pytest.raises(TypeError): + poly.update(u'') + + with pytest.raises(TypeError): + Poly1305.generate_tag(b"0" * 32, u'') + + def test_verify(self, backend): + poly = Poly1305(b"0" * 32) + poly.update(b"msg") + tag = poly.finalize() + + with pytest.raises(AlreadyFinalized): + poly.verify(b"") + + poly2 = Poly1305(b"0" * 32) + poly2.update(b"msg") + poly2.verify(tag) + + Poly1305.verify_tag(b"0" * 32, b"msg", tag) + + def test_invalid_verify(self, backend): + poly = Poly1305(b"0" * 32) + poly.update(b"msg") + with pytest.raises(InvalidSignature): + poly.verify(b"") + + p2 = Poly1305(b"0" * 32) + p2.update(b"msg") + with pytest.raises(InvalidSignature): + p2.verify(b"\x00" * 16) + + with pytest.raises(InvalidSignature): + Poly1305.verify_tag(b"0" * 32, b"msg", b"\x00" * 16) + + def test_verify_reject_unicode(self, backend): + poly = Poly1305(b"0" * 32) + with pytest.raises(TypeError): + poly.verify(u'') + + with pytest.raises(TypeError): + Poly1305.verify_tag(b"0" * 32, b"msg", u'') + + def test_invalid_key_type(self, backend): + with pytest.raises(TypeError): + Poly1305(object()) + + with pytest.raises(TypeError): + Poly1305.generate_tag(object(), b"msg") + + def test_invalid_key_length(self, backend): + with pytest.raises(ValueError): + Poly1305(b"0" * 31) + + with pytest.raises(ValueError): + Poly1305.generate_tag(b"0" * 31, b"msg") + + with pytest.raises(ValueError): + Poly1305(b"0" * 33) + + with pytest.raises(ValueError): + Poly1305.generate_tag(b"0" * 33, b"msg") + + def test_buffer_protocol(self, backend): + key = binascii.unhexlify( + b"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cb" + b"c207075c0" + ) + msg = binascii.unhexlify( + b"2754776173206272696c6c69672c20616e642074686520736c69746" + b"87920746f7665730a446964206779726520616e642067696d626c65" + b"20696e2074686520776162653a0a416c6c206d696d7379207765726" + b"52074686520626f726f676f7665732c0a416e6420746865206d6f6d" + b"65207261746873206f757467726162652e" + ) + key = bytearray(key) + poly = Poly1305(key) + poly.update(bytearray(msg)) + assert poly.finalize() == binascii.unhexlify( + b"4541669a7eaaee61e708dc7cbcc5eb62" + ) + + assert Poly1305.generate_tag(key, msg) == binascii.unhexlify( + b"4541669a7eaaee61e708dc7cbcc5eb62" + ) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index eb12df8d..e6482651 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -6,12 +6,10 @@ from __future__ import absolute_import, division, print_function import binascii import itertools -import math import os import pytest -from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidSignature, _Reasons ) @@ -19,38 +17,43 @@ from cryptography.hazmat.backends.interfaces import ( PEMSerializationBackend, RSABackend ) from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding, rsa +from cryptography.hazmat.primitives.asymmetric import ( + padding, rsa, utils as asym_utils +) from cryptography.hazmat.primitives.asymmetric.rsa import ( RSAPrivateNumbers, RSAPublicNumbers ) +from cryptography.utils import CryptographyDeprecationWarning from .fixtures_rsa import ( RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, RSA_KEY_1536, RSA_KEY_2048, - RSA_KEY_512, RSA_KEY_512_ALT, RSA_KEY_522, RSA_KEY_599, RSA_KEY_745, - RSA_KEY_768, + RSA_KEY_2048_ALT, RSA_KEY_512, RSA_KEY_512_ALT, RSA_KEY_522, RSA_KEY_599, + RSA_KEY_745, RSA_KEY_768, ) from .utils import ( _check_rsa_private_numbers, generate_rsa_verification_test ) +from ...doubles import ( + DummyAsymmetricPadding, DummyHashAlgorithm, DummyKeySerializationEncryption +) from ...utils import ( - load_pkcs1_vectors, load_rsa_nist_vectors, load_vectors_from_file, - raises_unsupported_algorithm + load_nist_vectors, load_pkcs1_vectors, load_rsa_nist_vectors, + load_vectors_from_file, raises_unsupported_algorithm ) -@utils.register_interface(padding.AsymmetricPadding) -class DummyPadding(object): - name = "UNSUPPORTED-PADDING" - - class DummyMGF(object): _salt_length = 0 -@utils.register_interface(serialization.KeySerializationEncryption) -class DummyKeyEncryption(object): - pass +def _check_rsa_private_numbers_if_serializable(key): + if isinstance(key, rsa.RSAPrivateKeyWithSerialization): + _check_rsa_private_numbers(key.private_numbers()) + + +def test_check_rsa_private_numbers_if_serializable(): + _check_rsa_private_numbers_if_serializable("notserializable") def _flatten_pkcs1_examples(vectors): @@ -64,6 +67,60 @@ def _flatten_pkcs1_examples(vectors): return flattened_vectors +def _build_oaep_sha2_vectors(): + base_path = os.path.join("asymmetric", "RSA", "oaep-custom") + vectors = [] + hashalgs = [ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), + ] + for mgf1alg, oaepalg in itertools.product(hashalgs, hashalgs): + if mgf1alg.name == "sha1" and oaepalg.name == "sha1": + # We need to generate the cartesian product of the permutations + # of all the SHAs above, but SHA1/SHA1 is something we already + # tested previously and thus did not generate custom vectors for. + continue + + examples = _flatten_pkcs1_examples( + load_vectors_from_file( + os.path.join( + base_path, + "oaep-{}-{}.txt".format( + mgf1alg.name, oaepalg.name + ) + ), + load_pkcs1_vectors + ) + ) + # We've loaded the files, but the loaders don't give us any information + # about the mgf1 or oaep hash algorithms. We know this info so we'll + # just add that to the end of the tuple + for private, public, vector in examples: + vectors.append((private, public, vector, mgf1alg, oaepalg)) + return vectors + + +def _skip_pss_hash_algorithm_unsupported(backend, hash_alg): + if not backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hash_alg), + salt_length=padding.PSS.MAX_LENGTH + ) + ): + pytest.skip( + "Does not support {} in MGF1 using PSS.".format(hash_alg.name) + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +def test_skip_pss_hash_algorithm_unsupported(backend): + with pytest.raises(pytest.skip.Exception): + _skip_pss_hash_algorithm_unsupported(backend, DummyHashAlgorithm()) + + def test_modular_inverse(): p = int( "d1f9f6c09fd3d38987f7970247b85a6da84907753d42ec52bc23b745093f4fff5cff3" @@ -85,21 +142,6 @@ def test_modular_inverse(): ) -def _skip_if_no_serialization(key, backend): - if not isinstance( - key, - (rsa.RSAPrivateKeyWithSerialization, rsa.RSAPublicKeyWithSerialization) - ): - pytest.skip( - "{0} does not support RSA key serialization".format(backend) - ) - - -def test_skip_if_no_serialization(): - with pytest.raises(pytest.skip.Exception): - _skip_if_no_serialization("notakeywithserialization", "backend") - - @pytest.mark.requires_backend_interface(interface=RSABackend) class TestRSA(object): @pytest.mark.parametrize( @@ -113,10 +155,9 @@ class TestRSA(object): skey = rsa.generate_private_key(public_exponent, key_size, backend) assert skey.key_size == key_size - if isinstance(skey, rsa.RSAPrivateKeyWithSerialization): - _check_rsa_private_numbers(skey.private_numbers()) - pkey = skey.public_key() - assert isinstance(pkey.public_numbers(), rsa.RSAPublicNumbers) + _check_rsa_private_numbers_if_serializable(skey) + pkey = skey.public_key() + assert isinstance(pkey.public_numbers(), rsa.RSAPublicNumbers) def test_generate_bad_public_exponent(self, backend): with pytest.raises(ValueError): @@ -177,6 +218,133 @@ class TestRSA(object): assert public_num.n == public_num2.n assert public_num.e == public_num2.e + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "RSA", "oaep-label.txt"), + load_nist_vectors) + ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=b"label" + ) + ), + skip_message="Does not support RSA OAEP labels" + ) + def test_oaep_label_decrypt(self, vector, backend): + private_key = serialization.load_der_private_key( + binascii.unhexlify(vector["key"]), None, backend + ) + assert vector["oaepdigest"] == b"SHA512" + decrypted = private_key.decrypt( + binascii.unhexlify(vector["input"]), + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA512()), + algorithm=hashes.SHA512(), + label=binascii.unhexlify(vector["oaeplabel"]) + ) + ) + assert vector["output"][1:-1] == decrypted + + @pytest.mark.parametrize( + ("msg", "label"), + [ + (b"amazing encrypted msg", b"some label"), + (b"amazing encrypted msg", b""), + ] + ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=b"label" + ) + ), + skip_message="Does not support RSA OAEP labels" + ) + def test_oaep_label_roundtrip(self, msg, label, backend): + private_key = RSA_KEY_2048.private_key(backend) + ct = private_key.public_key().encrypt( + msg, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=label + ) + ) + pt = private_key.decrypt( + ct, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=label + ) + ) + assert pt == msg + + @pytest.mark.parametrize( + ("enclabel", "declabel"), + [ + (b"label1", b"label2"), + (b"label3", b""), + (b"", b"label4"), + ] + ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=b"label" + ) + ), + skip_message="Does not support RSA OAEP labels" + ) + def test_oaep_wrong_label(self, enclabel, declabel, backend): + private_key = RSA_KEY_2048.private_key(backend) + msg = b"test" + ct = private_key.public_key().encrypt( + msg, padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=enclabel + ) + ) + with pytest.raises(ValueError): + private_key.decrypt( + ct, padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=declabel + ) + ) + + @pytest.mark.supported( + only_if=lambda backend: not backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=b"label" + ) + ), + skip_message="Requires backend without RSA OAEP label support" + ) + def test_unsupported_oaep_label_decrypt(self, backend): + private_key = RSA_KEY_512.private_key(backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + private_key.decrypt( + b"0" * 64, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=b"label" + ) + ) + def test_rsa_generate_invalid_backend(): pretend_backend = object() @@ -215,9 +383,11 @@ class TestRSASignature(object): n=private["modulus"] ) ).private_key(backend) - signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) - signer.update(binascii.unhexlify(example["message"])) - signature = signer.finalize() + signature = private_key.sign( + binascii.unhexlify(example["message"]), + padding.PKCS1v15(), + hashes.SHA1() + ) assert binascii.hexlify(signature) == example["signature"] @pytest.mark.supported( @@ -255,56 +425,43 @@ class TestRSASignature(object): e=public["public_exponent"], n=public["modulus"] ).public_key(backend) - signer = private_key.signer( + signature = private_key.sign( + binascii.unhexlify(example["message"]), padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA1() ) - signer.update(binascii.unhexlify(example["message"])) - signature = signer.finalize() - assert len(signature) == math.ceil(private_key.key_size / 8.0) + assert len(signature) == (private_key.key_size + 7) // 8 # PSS signatures contain randomness so we can't do an exact # signature check. Instead we'll verify that the signature created # successfully verifies. - verifier = public_key.verifier( + public_key.verify( signature, + binascii.unhexlify(example["message"]), padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA1(), ) - verifier.update(binascii.unhexlify(example["message"])) - verifier.verify() @pytest.mark.parametrize( "hash_alg", [hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512()] ) def test_pss_signing_sha2(self, hash_alg, backend): - if not backend.rsa_padding_supported( - padding.PSS( - mgf=padding.MGF1(hash_alg), - salt_length=padding.PSS.MAX_LENGTH - ) - ): - pytest.skip( - "Does not support {0} in MGF1 using PSS.".format(hash_alg.name) - ) + _skip_pss_hash_algorithm_unsupported(backend, hash_alg) private_key = RSA_KEY_768.private_key(backend) public_key = private_key.public_key() pss = padding.PSS( mgf=padding.MGF1(hash_alg), salt_length=padding.PSS.MAX_LENGTH ) - signer = private_key.signer(pss, hash_alg) - signer.update(b"testing signature") - signature = signer.finalize() - verifier = public_key.verifier(signature, pss, hash_alg) - verifier.update(b"testing signature") - verifier.verify() + msg = b"testing signature" + signature = private_key.sign(msg, pss, hash_alg) + public_key.verify(signature, msg, pss, hash_alg) @pytest.mark.supported( only_if=lambda backend: ( @@ -320,15 +477,14 @@ class TestRSASignature(object): ) def test_pss_minimum_key_size_for_digest(self, backend): private_key = RSA_KEY_522.private_key(backend) - signer = private_key.signer( + private_key.sign( + b"no failure", padding.PSS( mgf=padding.MGF1(hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA512() ) - signer.update(b"no failure") - signer.finalize() @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -346,7 +502,8 @@ class TestRSASignature(object): def test_pss_signing_digest_too_large_for_key_size(self, backend): private_key = RSA_KEY_512.private_key(backend) with pytest.raises(ValueError): - private_key.signer( + private_key.sign( + b"msg", padding.PSS( mgf=padding.MGF1(hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH @@ -365,16 +522,15 @@ class TestRSASignature(object): ) def test_pss_signing_salt_length_too_long(self, backend): private_key = RSA_KEY_512.private_key(backend) - signer = private_key.signer( - padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), - salt_length=1000000 - ), - hashes.SHA1() - ) - signer.update(b"failure coming") with pytest.raises(ValueError): - signer.finalize() + private_key.sign( + b"failure coming", + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=1000000 + ), + hashes.SHA1() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -384,7 +540,8 @@ class TestRSASignature(object): ) def test_use_after_finalize(self, backend): private_key = RSA_KEY_512.private_key(backend) - signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) + with pytest.warns(CryptographyDeprecationWarning): + signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) signer.update(b"sign me") signer.finalize() with pytest.raises(AlreadyFinalized): @@ -395,12 +552,12 @@ class TestRSASignature(object): def test_unsupported_padding(self, backend): private_key = RSA_KEY_512.private_key(backend) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - private_key.signer(DummyPadding(), hashes.SHA1()) + private_key.sign(b"msg", DummyAsymmetricPadding(), hashes.SHA1()) def test_padding_incorrect_type(self, backend): private_key = RSA_KEY_512.private_key(backend) with pytest.raises(TypeError): - private_key.signer("notpadding", hashes.SHA1()) + private_key.sign(b"msg", "notpadding", hashes.SHA1()) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -411,7 +568,8 @@ class TestRSASignature(object): def test_unsupported_pss_mgf(self, backend): private_key = RSA_KEY_512.private_key(backend) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): - private_key.signer( + private_key.sign( + b"msg", padding.PSS( mgf=DummyMGF(), salt_length=padding.PSS.MAX_LENGTH @@ -427,13 +585,12 @@ class TestRSASignature(object): ) def test_pkcs1_digest_too_large_for_key_size(self, backend): private_key = RSA_KEY_599.private_key(backend) - signer = private_key.signer( - padding.PKCS1v15(), - hashes.SHA512() - ) - signer.update(b"failure coming") with pytest.raises(ValueError): - signer.finalize() + private_key.sign( + b"failure coming", + padding.PKCS1v15(), + hashes.SHA512() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -443,12 +600,104 @@ class TestRSASignature(object): ) def test_pkcs1_minimum_key_size(self, backend): private_key = RSA_KEY_745.private_key(backend) - signer = private_key.signer( + private_key.sign( + b"no failure", padding.PKCS1v15(), hashes.SHA512() ) - signer.update(b"no failure") - signer.finalize() + + def test_sign(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + pkcs = padding.PKCS1v15() + algorithm = hashes.SHA1() + signature = private_key.sign(message, pkcs, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, pkcs, algorithm) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) + def test_prehashed_sign(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + signature = private_key.sign(digest, pss, prehashed_alg) + public_key = private_key.public_key() + public_key.verify(signature, message, pss, hashes.SHA1()) + + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2s(digest_size=32)), + skip_message="Does not support BLAKE2s", + ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) + def test_unsupported_hash(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + private_key.sign(message, pss, hashes.BLAKE2s(32)) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) + def test_prehashed_digest_mismatch(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA512(), backend) + h.update(message) + digest = h.finalize() + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + with pytest.raises(ValueError): + private_key.sign(digest, pss, prehashed_alg) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_prehashed_unsupported_in_signer_ctx(self, backend): + private_key = RSA_KEY_512.private_key(backend) + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + private_key.signer( + padding.PKCS1v15(), + asym_utils.Prehashed(hashes.SHA1()) + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_prehashed_unsupported_in_verifier_ctx(self, backend): + public_key = RSA_KEY_512.private_key(backend).public_key() + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + public_key.verifier( + b"0" * 64, + padding.PKCS1v15(), + asym_utils.Prehashed(hashes.SHA1()) + ) @pytest.mark.requires_backend_interface(interface=RSABackend) @@ -473,13 +722,12 @@ class TestRSAVerification(object): e=public["public_exponent"], n=public["modulus"] ).public_key(backend) - verifier = public_key.verifier( + public_key.verify( binascii.unhexlify(example["signature"]), + binascii.unhexlify(example["message"]), padding.PKCS1v15(), hashes.SHA1() ) - verifier.update(binascii.unhexlify(example["message"])) - verifier.verify() @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -490,17 +738,51 @@ class TestRSAVerification(object): def test_invalid_pkcs1v15_signature_wrong_data(self, backend): private_key = RSA_KEY_512.private_key(backend) public_key = private_key.public_key() - signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) - signer.update(b"sign me") - signature = signer.finalize() - verifier = public_key.verifier( - signature, - padding.PKCS1v15(), - hashes.SHA1() + signature = private_key.sign( + b"sign me", padding.PKCS1v15(), hashes.SHA1() ) - verifier.update(b"incorrect data") with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, + b"incorrect data", + padding.PKCS1v15(), + hashes.SHA1() + ) + + def test_invalid_signature_sequence_removed(self, backend): + """ + This test comes from wycheproof + """ + key_der = binascii.unhexlify( + b"30820122300d06092a864886f70d01010105000382010f003082010a02820101" + b"00a2b451a07d0aa5f96e455671513550514a8a5b462ebef717094fa1fee82224" + b"e637f9746d3f7cafd31878d80325b6ef5a1700f65903b469429e89d6eac88450" + b"97b5ab393189db92512ed8a7711a1253facd20f79c15e8247f3d3e42e46e48c9" + b"8e254a2fe9765313a03eff8f17e1a029397a1fa26a8dce26f490ed81299615d9" + b"814c22da610428e09c7d9658594266f5c021d0fceca08d945a12be82de4d1ece" + b"6b4c03145b5d3495d4ed5411eb878daf05fd7afc3e09ada0f1126422f590975a" + b"1969816f48698bcbba1b4d9cae79d460d8f9f85e7975005d9bc22c4e5ac0f7c1" + b"a45d12569a62807d3b9a02e5a530e773066f453d1f5b4c2e9cf7820283f742b9" + b"d50203010001" + ) + sig = binascii.unhexlify( + b"498209f59a0679a1f926eccf3056da2cba553d7ab3064e7c41ad1d739f038249" + b"f02f5ad12ee246073d101bc3cdb563e8b6be61562056422b7e6c16ad53deb12a" + b"f5de744197753a35859833f41bb59c6597f3980132b7478fd0b95fd27dfad64a" + b"20fd5c25312bbd41a85286cd2a83c8df5efa0779158d01b0747ff165b055eb28" + b"80ea27095700a295593196d8c5922cf6aa9d7e29b5056db5ded5eb20aeb31b89" + b"42e26b15a5188a4934cd7e39cfe379a197f49a204343a493452deebca436ee61" + b"4f4daf989e355544489f7e69ffa8ccc6a1e81cf0ab33c3e6d7591091485a6a31" + b"bda3b33946490057b9a3003d3fd9daf7c4778b43fd46144d945d815f12628ff4" + ) + public_key = serialization.load_der_public_key(key_der, backend) + with pytest.raises(InvalidSignature): + public_key.verify( + sig, + binascii.unhexlify(b"313233343030"), + padding.PKCS1v15(), + hashes.SHA256() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -512,17 +794,12 @@ class TestRSAVerification(object): private_key = RSA_KEY_512.private_key(backend) private_key2 = RSA_KEY_512_ALT.private_key(backend) public_key = private_key2.public_key() - signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) - signer.update(b"sign me") - signature = signer.finalize() - verifier = public_key.verifier( - signature, - padding.PKCS1v15(), - hashes.SHA1() - ) - verifier.update(b"sign me") + msg = b"sign me" + signature = private_key.sign(msg, padding.PKCS1v15(), hashes.SHA1()) with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, msg, padding.PKCS1v15(), hashes.SHA1() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -547,16 +824,15 @@ class TestRSAVerification(object): e=public["public_exponent"], n=public["modulus"] ).public_key(backend) - verifier = public_key.verifier( + public_key.verify( binascii.unhexlify(example["signature"]), + binascii.unhexlify(example["message"]), padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA1()), salt_length=20 ), hashes.SHA1() ) - verifier.update(binascii.unhexlify(example["message"])) - verifier.verify() @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -580,17 +856,16 @@ class TestRSAVerification(object): b"0e68c3649df91c5bc3665f96e157efa75b71934aaa514d91e94ca8418d100f45" b"6f05288e58525f99666bab052adcffdf7186eb40f583bd38d98c97d3d524808b" ) - verifier = public_key.verifier( - signature, - padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA1() - ) - verifier.update(b"incorrect data") with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, + b"incorrect data", + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -616,17 +891,16 @@ class TestRSAVerification(object): ), e=65537 ).public_key(backend) - verifier = public_key.verifier( - signature, - padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA1() - ) - verifier.update(b"sign me") with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, + b"sign me", + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -652,17 +926,16 @@ class TestRSAVerification(object): ), e=65537 ).public_key(backend) - verifier = public_key.verifier( - signature, - padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA1() - ) - verifier.update(b"sign me") with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, + b"sign me", + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -673,15 +946,16 @@ class TestRSAVerification(object): def test_use_after_finalize(self, backend): private_key = RSA_KEY_512.private_key(backend) public_key = private_key.public_key() - signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) - signer.update(b"sign me") - signature = signer.finalize() - - verifier = public_key.verifier( - signature, - padding.PKCS1v15(), - hashes.SHA1() + signature = private_key.sign( + b"sign me", padding.PKCS1v15(), hashes.SHA1() ) + + with pytest.warns(CryptographyDeprecationWarning): + verifier = public_key.verifier( + signature, + padding.PKCS1v15(), + hashes.SHA1() + ) verifier.update(b"sign me") verifier.verify() with pytest.raises(AlreadyFinalized): @@ -693,13 +967,33 @@ class TestRSAVerification(object): private_key = RSA_KEY_512.private_key(backend) public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - public_key.verifier(b"sig", DummyPadding(), hashes.SHA1()) + public_key.verify( + b"sig", b"msg", DummyAsymmetricPadding(), hashes.SHA1() + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_signature_not_bytes(self, backend): + public_key = RSA_KEY_512.public_numbers.public_key(backend) + signature = 1234 + + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + public_key.verifier( + signature, + padding.PKCS1v15(), + hashes.SHA1() + ) def test_padding_incorrect_type(self, backend): private_key = RSA_KEY_512.private_key(backend) public_key = private_key.public_key() with pytest.raises(TypeError): - public_key.verifier(b"sig", "notpadding", hashes.SHA1()) + public_key.verify(b"sig", b"msg", "notpadding", hashes.SHA1()) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -711,8 +1005,9 @@ class TestRSAVerification(object): private_key = RSA_KEY_512.private_key(backend) public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): - public_key.verifier( + public_key.verify( b"sig", + b"msg", padding.PSS( mgf=DummyMGF(), salt_length=padding.PSS.MAX_LENGTH @@ -741,8 +1036,9 @@ class TestRSAVerification(object): ) public_key = private_key.public_key() with pytest.raises(ValueError): - public_key.verifier( + public_key.verify( signature, + b"msg doesn't matter", padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH @@ -772,19 +1068,50 @@ class TestRSAVerification(object): ), e=65537 ).public_key(backend) - verifier = public_key.verifier( - signature, - padding.PSS( - mgf=padding.MGF1( - algorithm=hashes.SHA1(), - ), - salt_length=1000000 - ), - hashes.SHA1() - ) - verifier.update(b"sign me") with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, + b"sign me", + padding.PSS( + mgf=padding.MGF1( + algorithm=hashes.SHA1(), + ), + salt_length=1000000 + ), + hashes.SHA1() + ) + + def test_verify(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + pkcs = padding.PKCS1v15() + algorithm = hashes.SHA1() + signature = private_key.sign(message, pkcs, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, pkcs, algorithm) + + def test_prehashed_verify(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + pkcs = padding.PKCS1v15() + signature = private_key.sign(message, pkcs, hashes.SHA1()) + public_key = private_key.public_key() + public_key.verify(signature, digest, pkcs, prehashed_alg) + + def test_prehashed_digest_mismatch(self, backend): + public_key = RSA_KEY_512.private_key(backend).public_key() + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + prehashed_alg = asym_utils.Prehashed(hashes.SHA512()) + pkcs = padding.PKCS1v15() + with pytest.raises(ValueError): + public_key.verify(b"\x00" * 64, data, pkcs, prehashed_alg) @pytest.mark.requires_backend_interface(interface=RSABackend) @@ -1009,6 +1336,10 @@ class TestRSAPKCS1Verification(object): class TestPSS(object): + def test_calculate_max_pss_salt_length(self): + with pytest.raises(TypeError): + padding.calculate_max_pss_salt_length(object(), hashes.SHA256()) + def test_invalid_salt_length_not_integer(self): with pytest.raises(TypeError): padding.PSS( @@ -1096,14 +1427,14 @@ class TestRSADecryption(object): ) ).private_key(backend) ciphertext = binascii.unhexlify(example["encryption"]) - assert len(ciphertext) == math.ceil(skey.key_size / 8.0) + assert len(ciphertext) == (skey.key_size + 7) // 8 message = skey.decrypt(ciphertext, padding.PKCS1v15()) assert message == binascii.unhexlify(example["message"]) def test_unsupported_padding(self, backend): private_key = RSA_KEY_512.private_key(backend) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - private_key.decrypt(b"0" * 64, DummyPadding()) + private_key.decrypt(b"0" * 64, DummyAsymmetricPadding()) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -1193,6 +1524,119 @@ class TestRSADecryption(object): ) assert message == binascii.unhexlify(example["message"]) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA224()), + algorithm=hashes.SHA224(), + label=None + ) + ), + skip_message="Does not support OAEP using SHA224 MGF1 and SHA224 hash." + ) + @pytest.mark.parametrize( + "vector", + _build_oaep_sha2_vectors() + ) + def test_decrypt_oaep_sha2_vectors(self, vector, backend): + private, public, example, mgf1_alg, hash_alg = vector + skey = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], + n=private["modulus"] + ) + ).private_key(backend) + message = skey.decrypt( + binascii.unhexlify(example["encryption"]), + padding.OAEP( + mgf=padding.MGF1(algorithm=mgf1_alg), + algorithm=hash_alg, + label=None + ) + ) + assert message == binascii.unhexlify(example["message"]) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ), + skip_message="Does not support OAEP." + ) + def test_invalid_oaep_decryption(self, backend): + # More recent versions of OpenSSL may raise RSA_R_OAEP_DECODING_ERROR + # This test triggers it and confirms that we properly handle it. Other + # backends should also return the proper ValueError. + private_key = RSA_KEY_512.private_key(backend) + + ciphertext = private_key.public_key().encrypt( + b'secure data', + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ) + + private_key_alt = RSA_KEY_512_ALT.private_key(backend) + + with pytest.raises(ValueError): + private_key_alt.decrypt( + ciphertext, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ), + skip_message="Does not support OAEP." + ) + def test_invalid_oaep_decryption_data_to_large_for_modulus(self, backend): + key = RSA_KEY_2048_ALT.private_key(backend) + + ciphertext = ( + b'\xb1ph\xc0\x0b\x1a|\xe6\xda\xea\xb5\xd7%\x94\x07\xf96\xfb\x96' + b'\x11\x9b\xdc4\xea.-\x91\x80\x13S\x94\x04m\xe9\xc5/F\x1b\x9b:\\' + b'\x1d\x04\x16ML\xae\xb32J\x01yuA\xbb\x83\x1c\x8f\xf6\xa5\xdbp\xcd' + b'\nx\xc7\xf6\x15\xb2/\xdcH\xae\xe7\x13\x13by\r4t\x99\x0fc\x1f\xc1' + b'\x1c\xb1\xdd\xc5\x08\xd1\xee\xa1XQ\xb8H@L5v\xc3\xaf\xf2\r\x97' + b'\xed\xaa\xe7\xf1\xd4xai\xd3\x83\xd9\xaa9\xbfx\xe1\x87F \x01\xff' + b'L\xccv}ae\xb3\xfa\xf2B\xb8\xf9\x04H\x94\x85\xcb\x86\xbb\\ghx!W31' + b'\xc7;t\na_E\xc2\x16\xb0;\xa1\x18\t\x1b\xe1\xdb\x80>)\x15\xc6\x12' + b'\xcb\xeeg`\x8b\x9b\x1b\x05y4\xb0\x84M6\xcd\xa1\x827o\xfd\x96\xba' + b'Z#\x8d\xae\x01\xc9\xf2\xb6\xde\x89{8&eQ\x1e8\x03\x01#?\xb66\\' + b'\xad.\xe9\xfa!\x95 c{\xcaz\xe0*\tP\r\x91\x9a)B\xb5\xadN\xf4$\x83' + b'\t\xb5u\xab\x19\x99' + ) + + with pytest.raises(ValueError): + key.decrypt( + ciphertext, + padding.OAEP( + algorithm=hashes.SHA1(), + mgf=padding.MGF1(hashes.SHA1()), + label=None + ) + ) + def test_unsupported_oaep_mgf(self, backend): private_key = RSA_KEY_512.private_key(backend) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): @@ -1239,7 +1683,48 @@ class TestRSAEncryption(object): public_key = private_key.public_key() ct = public_key.encrypt(pt, pad) assert ct != pt - assert len(ct) == math.ceil(public_key.key_size / 8.0) + assert len(ct) == (public_key.key_size + 7) // 8 + recovered_pt = private_key.decrypt(ct, pad) + assert recovered_pt == pt + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA512(), + label=None + ) + ), + skip_message="Does not support OAEP using SHA256 MGF1 and SHA512 hash." + ) + @pytest.mark.parametrize( + ("mgf1hash", "oaephash"), + itertools.product([ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), + ], [ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), + ]) + ) + def test_rsa_encrypt_oaep_sha2(self, mgf1hash, oaephash, backend): + pad = padding.OAEP( + mgf=padding.MGF1(algorithm=mgf1hash), + algorithm=oaephash, + label=None + ) + private_key = RSA_KEY_2048.private_key(backend) + pt = b"encrypt me using sha2 hashes!" + public_key = private_key.public_key() + ct = public_key.encrypt(pt, pad) + assert ct != pt + assert len(ct) == (public_key.key_size + 7) // 8 recovered_pt = private_key.decrypt(ct, pad) assert recovered_pt == pt @@ -1264,7 +1749,7 @@ class TestRSAEncryption(object): public_key = private_key.public_key() ct = public_key.encrypt(pt, pad) assert ct != pt - assert len(ct) == math.ceil(public_key.key_size / 8.0) + assert len(ct) == (public_key.key_size + 7) // 8 recovered_pt = private_key.decrypt(ct, pad) assert recovered_pt == pt @@ -1306,7 +1791,7 @@ class TestRSAEncryption(object): public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - public_key.encrypt(b"somedata", DummyPadding()) + public_key.encrypt(b"somedata", DummyAsymmetricPadding()) with pytest.raises(TypeError): public_key.encrypt(b"somedata", padding=object()) @@ -1367,304 +1852,81 @@ class TestRSANumbers(object): with pytest.raises(TypeError): rsa.RSAPublicNumbers(e=1, n=None) - def test_private_numbers_invalid_types(self): - public_numbers = rsa.RSAPublicNumbers(e=1, n=15) - - with pytest.raises(TypeError): - rsa.RSAPrivateNumbers( - p=None, - q=5, - d=1, - dmp1=1, - dmq1=1, - iqmp=2, - public_numbers=public_numbers - ) - - with pytest.raises(TypeError): - rsa.RSAPrivateNumbers( - p=3, - q=None, - d=1, - dmp1=1, - dmq1=1, - iqmp=2, - public_numbers=public_numbers - ) - - with pytest.raises(TypeError): - rsa.RSAPrivateNumbers( - p=3, - q=5, - d=None, - dmp1=1, - dmq1=1, - iqmp=2, - public_numbers=public_numbers - ) - - with pytest.raises(TypeError): - rsa.RSAPrivateNumbers( - p=3, - q=5, - d=1, - dmp1=None, - dmq1=1, - iqmp=2, - public_numbers=public_numbers - ) - - with pytest.raises(TypeError): - rsa.RSAPrivateNumbers( - p=3, - q=5, - d=1, - dmp1=1, - dmq1=None, - iqmp=2, - public_numbers=public_numbers - ) - + @pytest.mark.parametrize( + ("p", "q", "d", "dmp1", "dmq1", "iqmp", "public_numbers"), + [ + (None, 5, 1, 1, 1, 2, rsa.RSAPublicNumbers(e=1, n=15)), + (3, None, 1, 1, 1, 2, rsa.RSAPublicNumbers(e=1, n=15)), + (3, 5, None, 1, 1, 2, rsa.RSAPublicNumbers(e=1, n=15)), + (3, 5, 1, None, 1, 2, rsa.RSAPublicNumbers(e=1, n=15)), + (3, 5, 1, 1, None, 2, rsa.RSAPublicNumbers(e=1, n=15)), + (3, 5, 1, 1, 1, None, rsa.RSAPublicNumbers(e=1, n=15)), + (3, 5, 1, 1, 1, 2, None), + ] + ) + def test_private_numbers_invalid_types(self, p, q, d, dmp1, dmq1, iqmp, + public_numbers): with pytest.raises(TypeError): rsa.RSAPrivateNumbers( - p=3, - q=5, - d=1, - dmp1=1, - dmq1=1, - iqmp=None, + p=p, q=q, + d=d, + dmp1=dmp1, + dmq1=dmq1, + iqmp=iqmp, public_numbers=public_numbers ) - with pytest.raises(TypeError): - rsa.RSAPrivateNumbers( - p=3, - q=5, - d=1, - dmp1=1, - dmq1=1, - iqmp=2, - public_numbers=None - ) - - def test_invalid_public_numbers_argument_values(self, backend): + @pytest.mark.parametrize( + ("e", "n"), + [ + (7, 2), # modulus < 3 + (1, 15), # public_exponent < 3 + (17, 15), # public_exponent > modulus + (14, 15), # public_exponent not odd + ] + ) + def test_invalid_public_numbers_argument_values(self, e, n, backend): # Start with public_exponent=7, modulus=15. Then change one value at a # time to test the bounds. - # Test a modulus < 3. - - with pytest.raises(ValueError): - rsa.RSAPublicNumbers(e=7, n=2).public_key(backend) - - # Test a public_exponent < 3 with pytest.raises(ValueError): - rsa.RSAPublicNumbers(e=1, n=15).public_key(backend) + rsa.RSAPublicNumbers(e=e, n=n).public_key(backend) - # Test a public_exponent > modulus - with pytest.raises(ValueError): - rsa.RSAPublicNumbers(e=17, n=15).public_key(backend) - - # Test a public_exponent that is not odd. - with pytest.raises(ValueError): - rsa.RSAPublicNumbers(e=14, n=15).public_key(backend) - - def test_invalid_private_numbers_argument_values(self, backend): + @pytest.mark.parametrize( + ("p", "q", "d", "dmp1", "dmq1", "iqmp", "e", "n"), + [ + (3, 11, 3, 1, 3, 2, 7, 2), # modulus < 3 + (3, 11, 3, 1, 3, 2, 7, 35), # modulus != p * q + (37, 11, 3, 1, 3, 2, 7, 33), # p > modulus + (3, 37, 3, 1, 3, 2, 7, 33), # q > modulus + (3, 11, 3, 35, 3, 2, 7, 33), # dmp1 > modulus + (3, 11, 3, 1, 35, 2, 7, 33), # dmq1 > modulus + (3, 11, 3, 1, 3, 35, 7, 33), # iqmp > modulus + (3, 11, 37, 1, 3, 2, 7, 33), # d > modulus + (3, 11, 3, 1, 3, 2, 1, 33), # public_exponent < 3 + (3, 11, 3, 1, 3, 35, 65537, 33), # public_exponent > modulus + (3, 11, 3, 1, 3, 2, 6, 33), # public_exponent is not odd + (3, 11, 3, 2, 3, 2, 7, 33), # dmp1 is not odd + (3, 11, 3, 1, 4, 2, 7, 33), # dmq1 is not odd + ] + ) + def test_invalid_private_numbers_argument_values(self, p, q, d, dmp1, dmq1, + iqmp, e, n, backend): # Start with p=3, q=11, private_exponent=3, public_exponent=7, # modulus=33, dmp1=1, dmq1=3, iqmp=2. Then change one value at # a time to test the bounds. - # Test a modulus < 3. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=2 - ) - ).private_key(backend) - - # Test a modulus != p * q. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=35 - ) - ).private_key(backend) - - # Test a p > modulus. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=37, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test a q > modulus. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=37, - d=3, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test a dmp1 > modulus. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=35, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test a dmq1 > modulus. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=35, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test an iqmp > modulus. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=35, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test a private_exponent > modulus - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=37, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test a public_exponent < 3 - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=1, - n=33 - ) - ).private_key(backend) - - # Test a public_exponent > modulus with pytest.raises(ValueError): rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=35, + p=p, + q=q, + d=d, + dmp1=dmp1, + dmq1=dmq1, + iqmp=iqmp, public_numbers=rsa.RSAPublicNumbers( - e=65537, - n=33 - ) - ).private_key(backend) - - # Test a public_exponent that is not odd. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=6, - n=33 - ) - ).private_key(backend) - - # Test a dmp1 that is not odd. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=2, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test a dmq1 that is not odd. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=4, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 + e=e, + n=n ) ).private_key(backend) @@ -1721,6 +1983,22 @@ class TestRSANumbersEquality(object): ) assert num != object() + def test_public_numbers_hash(self): + pub1 = RSAPublicNumbers(3, 17) + pub2 = RSAPublicNumbers(3, 17) + pub3 = RSAPublicNumbers(7, 21) + + assert hash(pub1) == hash(pub2) + assert hash(pub1) != hash(pub3) + + def test_private_numbers_hash(self): + priv1 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 2)) + priv2 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 2)) + priv3 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 3)) + + assert hash(priv1) == hash(priv2) + assert hash(priv1) != hash(priv3) + class TestRSAPrimeFactorRecovery(object): @pytest.mark.parametrize( @@ -1739,10 +2017,11 @@ class TestRSAPrimeFactorRecovery(object): private["private_exponent"] ) # Unfortunately there is no convention on which prime should be p - # and which one q. The function we use always makes p < q, but the - # NIST vectors are not so consistent. Accordingly we verify we've + # and which one q. The function we use always makes p > q, but the + # NIST vectors are not so consistent. Accordingly, we verify we've # recovered the proper (p, q) by sorting them and asserting on that. assert sorted([p, q]) == sorted([private["p"], private["q"]]) + assert p > q def test_invalid_recover_prime_factors(self): with pytest.raises(ValueError): @@ -1769,7 +2048,6 @@ class TestRSAPrivateKeySerialization(object): ) def test_private_bytes_encrypted_pem(self, backend, fmt, password): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( serialization.Encoding.PEM, fmt, @@ -1783,6 +2061,20 @@ class TestRSAPrivateKeySerialization(object): assert loaded_priv_num == priv_num @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), + (serialization.Encoding.DER, serialization.PrivateFormat.Raw), + (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), + ] + ) + def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): + key = RSA_KEY_2048.private_key(backend) + with pytest.raises(ValueError): + key.private_bytes(encoding, fmt, serialization.NoEncryption()) + + @pytest.mark.parametrize( ("fmt", "password"), [ [serialization.PrivateFormat.PKCS8, b"s"], @@ -1793,7 +2085,6 @@ class TestRSAPrivateKeySerialization(object): ) def test_private_bytes_encrypted_der(self, backend, fmt, password): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( serialization.Encoding.DER, fmt, @@ -1834,7 +2125,6 @@ class TestRSAPrivateKeySerialization(object): def test_private_bytes_unencrypted(self, backend, encoding, fmt, loader_func): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( encoding, fmt, serialization.NoEncryption() ) @@ -1878,7 +2168,6 @@ class TestRSAPrivateKeySerialization(object): def test_private_bytes_traditional_der_encrypted_invalid(self, backend): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.DER, @@ -1888,7 +2177,6 @@ class TestRSAPrivateKeySerialization(object): def test_private_bytes_invalid_encoding(self, backend): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( "notencoding", @@ -1898,7 +2186,6 @@ class TestRSAPrivateKeySerialization(object): def test_private_bytes_invalid_format(self, backend): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, @@ -1908,7 +2195,6 @@ class TestRSAPrivateKeySerialization(object): def test_private_bytes_invalid_encryption_algorithm(self, backend): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, @@ -1918,12 +2204,11 @@ class TestRSAPrivateKeySerialization(object): def test_private_bytes_unsupported_encryption_type(self, backend): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, - DummyKeyEncryption() + DummyKeySerializationEncryption() ) @@ -1966,18 +2251,78 @@ class TestRSAPEMPublicKeySerialization(object): key_path, lambda pemfile: pemfile.read(), mode="rb" ) key = loader_func(key_bytes, backend) - _skip_if_no_serialization(key, backend) serialized = key.public_bytes(encoding, format) assert serialized == key_bytes + def test_public_bytes_openssh(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.pem"), + lambda pemfile: pemfile.read(), mode="rb" + ) + key = serialization.load_pem_public_key(key_bytes, backend) + + ssh_bytes = key.public_bytes( + serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH + ) + assert ssh_bytes == ( + b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7JHoJfg6yNzLMOWet8Z49a4KD" + b"0dCspMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkkFPZk/7x0" + b"xmdsTPECSWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAvSKAFKEvy" + b"D43si00DQnXWrYHAEQ==" + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, serialization.PublicFormat.OpenSSH + ) + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.DER, serialization.PublicFormat.OpenSSH + ) + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.OpenSSH, + serialization.PublicFormat.PKCS1, + ) + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.OpenSSH, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + def test_public_bytes_invalid_encoding(self, backend): key = RSA_KEY_2048.private_key(backend).public_key() - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.public_bytes("notencoding", serialization.PublicFormat.PKCS1) def test_public_bytes_invalid_format(self, backend): key = RSA_KEY_2048.private_key(backend).public_key() - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.public_bytes(serialization.Encoding.PEM, "invalidformat") + + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + ( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ), + (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), + ] + list(itertools.product( + [ + serialization.Encoding.Raw, + serialization.Encoding.X962, + serialization.Encoding.PEM, + serialization.Encoding.DER + ], + [ + serialization.PublicFormat.Raw, + serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint + ] + )) + ) + def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): + key = RSA_KEY_2048.private_key(backend).public_key() + with pytest.raises(ValueError): + key.public_bytes(encoding, fmt) diff --git a/tests/hazmat/primitives/test_scrypt.py b/tests/hazmat/primitives/test_scrypt.py new file mode 100644 index 00000000..8f3a14ed --- /dev/null +++ b/tests/hazmat/primitives/test_scrypt.py @@ -0,0 +1,183 @@ +# 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 ( + AlreadyFinalized, InvalidKey, UnsupportedAlgorithm +) +from cryptography.hazmat.backends.interfaces import ScryptBackend +from cryptography.hazmat.primitives.kdf.scrypt import Scrypt, _MEM_LIMIT + +from tests.utils import load_nist_vectors, load_vectors_from_file + +vectors = load_vectors_from_file( + os.path.join("KDF", "scrypt.txt"), load_nist_vectors) + + +def _skip_if_memory_limited(memory_limit, params): + # Memory calc adapted from OpenSSL (URL split over 2 lines, thanks PEP8) + # https://github.com/openssl/openssl/blob/6286757141a8c6e14d647ec733634a + # e0c83d9887/crypto/evp/scrypt.c#L189-L221 + blen = int(params["p"]) * 128 * int(params["r"]) + vlen = 32 * int(params["r"]) * (int(params["n"]) + 2) * 4 + memory_required = blen + vlen + if memory_limit < memory_required: + pytest.skip("Test exceeds Scrypt memory limit. " + "This is likely a 32-bit platform.") + + +def test_memory_limit_skip(): + with pytest.raises(pytest.skip.Exception): + _skip_if_memory_limited(1000, {"p": 16, "r": 64, "n": 1024}) + + _skip_if_memory_limited(2 ** 31, {"p": 16, "r": 64, "n": 1024}) + + +@pytest.mark.requires_backend_interface(interface=ScryptBackend) +class TestScrypt(object): + @pytest.mark.parametrize("params", vectors) + def test_derive(self, backend, params): + _skip_if_memory_limited(_MEM_LIMIT, params) + password = params["password"] + work_factor = int(params["n"]) + block_size = int(params["r"]) + parallelization_factor = int(params["p"]) + length = int(params["length"]) + salt = params["salt"] + derived_key = params["derived_key"] + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + assert binascii.hexlify(scrypt.derive(password)) == derived_key + + def test_unsupported_backend(self): + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + backend = object() + + with pytest.raises(UnsupportedAlgorithm): + Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + def test_salt_not_bytes(self, backend): + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = 1 + + with pytest.raises(TypeError): + Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + def test_scrypt_malloc_failure(self, backend): + password = b"NaCl" + work_factor = 1024 ** 3 + block_size = 589824 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + with pytest.raises(MemoryError): + scrypt.derive(password) + + def test_password_not_bytes(self, backend): + password = 1 + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + with pytest.raises(TypeError): + scrypt.derive(password) + + def test_buffer_protocol(self, backend): + password = bytearray(b"password") + work_factor = 256 + block_size = 8 + parallelization_factor = 16 + length = 10 + salt = b"NaCl" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + assert scrypt.derive(password) == b'\xf4\x92\x86\xb2\x06\x0c\x848W\x87' + + @pytest.mark.parametrize("params", vectors) + def test_verify(self, backend, params): + _skip_if_memory_limited(_MEM_LIMIT, params) + password = params["password"] + work_factor = int(params["n"]) + block_size = int(params["r"]) + parallelization_factor = int(params["p"]) + length = int(params["length"]) + salt = params["salt"] + derived_key = params["derived_key"] + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + assert scrypt.verify(password, binascii.unhexlify(derived_key)) is None + + def test_invalid_verify(self, backend): + password = b"password" + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + derived_key = b"fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e773" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + with pytest.raises(InvalidKey): + scrypt.verify(password, binascii.unhexlify(derived_key)) + + def test_already_finalized(self, backend): + password = b"password" + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + scrypt.derive(password) + with pytest.raises(AlreadyFinalized): + scrypt.derive(password) + + def test_invalid_n(self, backend): + # n is less than 2 + with pytest.raises(ValueError): + Scrypt(b"NaCl", 64, 1, 8, 16, backend) + + # n is not a power of 2 + with pytest.raises(ValueError): + Scrypt(b"NaCl", 64, 3, 8, 16, backend) + + def test_invalid_r(self, backend): + with pytest.raises(ValueError): + Scrypt(b"NaCl", 64, 2, 0, 16, backend) + + def test_invalid_p(self, backend): + with pytest.raises(ValueError): + Scrypt(b"NaCl", 64, 2, 8, 0, backend) diff --git a/tests/hazmat/primitives/test_seed.py b/tests/hazmat/primitives/test_seed.py index 7697bd8b..2d03d774 100644 --- a/tests/hazmat/primitives/test_seed.py +++ b/tests/hazmat/primitives/test_seed.py @@ -18,13 +18,13 @@ from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.SEED("\x00" * 16), modes.ECB() + algorithms.SEED(b"\x00" * 16), modes.ECB() ), skip_message="Does not support SEED ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestSEEDModeECB(object): - test_ECB = generate_encrypt_test( + test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["rfc-4269.txt"], @@ -35,13 +35,13 @@ class TestSEEDModeECB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.SEED("\x00" * 16), modes.CBC("\x00" * 16) + algorithms.SEED(b"\x00" * 16), modes.CBC(b"\x00" * 16) ), skip_message="Does not support SEED CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestSEEDModeCBC(object): - test_CBC = generate_encrypt_test( + test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["rfc-4196.txt"], @@ -52,13 +52,13 @@ class TestSEEDModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.SEED("\x00" * 16), modes.OFB("\x00" * 16) + algorithms.SEED(b"\x00" * 16), modes.OFB(b"\x00" * 16) ), skip_message="Does not support SEED OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestSEEDModeOFB(object): - test_OFB = generate_encrypt_test( + test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["seed-ofb.txt"], @@ -69,13 +69,13 @@ class TestSEEDModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.SEED("\x00" * 16), modes.CFB("\x00" * 16) + algorithms.SEED(b"\x00" * 16), modes.CFB(b"\x00" * 16) ), skip_message="Does not support SEED CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestSEEDModeCFB(object): - test_CFB = generate_encrypt_test( + test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["seed-cfb.txt"], diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index 22c2145c..8e8e15af 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -16,10 +16,15 @@ from cryptography.hazmat.backends.interfaces import ( DERSerializationBackend, DSABackend, EllipticCurveBackend, PEMSerializationBackend, RSABackend ) -from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa +from cryptography.hazmat.primitives.asymmetric import ( + dsa, ec, ed25519, ed448, rsa, x25519, x448 +) from cryptography.hazmat.primitives.serialization import ( - BestAvailableEncryption, load_der_private_key, load_der_public_key, - load_pem_private_key, load_pem_public_key, load_ssh_public_key + BestAvailableEncryption, Encoding, NoEncryption, + PrivateFormat, PublicFormat, + load_der_parameters, load_der_private_key, + load_der_public_key, load_pem_parameters, load_pem_private_key, + load_pem_public_key, load_ssh_public_key ) @@ -31,6 +36,55 @@ from .utils import ( from ...utils import raises_unsupported_algorithm +class TestBufferProtocolSerialization(object): + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.parametrize( + ("key_path", "password"), + [ + (["DER_Serialization", "enc-rsa-pkcs8.der"], bytearray(b"foobar")), + (["DER_Serialization", "enc2-rsa-pkcs8.der"], bytearray(b"baz")), + (["DER_Serialization", "unenc-rsa-pkcs8.der"], None), + (["DER_Serialization", "testrsa.der"], None), + ] + ) + def test_load_der_rsa_private_key(self, key_path, password, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda derfile: derfile.read(), mode="rb" + ) + key = load_der_private_key(bytearray(data), password, backend) + assert key + assert isinstance(key, rsa.RSAPrivateKey) + _check_rsa_private_numbers(key.private_numbers()) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.parametrize( + ("key_path", "password"), + [ + ( + ["PEM_Serialization", "rsa_private_key.pem"], + bytearray(b"123456") + ), + (["PKCS8", "unenc-rsa-pkcs8.pem"], None), + (["PKCS8", "enc-rsa-pkcs8.pem"], bytearray(b"foobar")), + (["PKCS8", "enc2-rsa-pkcs8.pem"], bytearray(b"baz")), + ( + ["Traditional_OpenSSL_Serialization", "key1.pem"], + bytearray(b"123456") + ), + ] + ) + def test_load_pem_rsa_private_key(self, key_path, password, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: pemfile.read(), mode="rb" + ) + key = load_pem_private_key(bytearray(data), password, backend) + assert key + assert isinstance(key, rsa.RSAPrivateKey) + _check_rsa_private_numbers(key.private_numbers()) + + @pytest.mark.requires_backend_interface(interface=DERSerializationBackend) class TestDERSerialization(object): @pytest.mark.requires_backend_interface(interface=RSABackend) @@ -53,8 +107,7 @@ class TestDERSerialization(object): ) assert key assert isinstance(key, rsa.RSAPrivateKey) - if isinstance(key, rsa.RSAPrivateKeyWithSerialization): - _check_rsa_private_numbers(key.private_numbers()) + _check_rsa_private_numbers(key.private_numbers()) @pytest.mark.requires_backend_interface(interface=DSABackend) @pytest.mark.parametrize( @@ -76,8 +129,27 @@ class TestDERSerialization(object): ) assert key assert isinstance(key, dsa.DSAPrivateKey) - if isinstance(key, dsa.DSAPrivateKeyWithSerialization): - _check_dsa_private_numbers(key.private_numbers()) + _check_dsa_private_numbers(key.private_numbers()) + + @pytest.mark.parametrize( + "key_path", + [ + ["DER_Serialization", "enc-rsa-pkcs8.der"], + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_password_not_bytes(self, key_path, backend): + key_file = os.path.join("asymmetric", *key_path) + password = u"this password is not bytes" + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda derfile: load_der_private_key( + derfile.read(), password, backend + ), + mode="rb" + ) @pytest.mark.parametrize( ("key_path", "password"), @@ -247,9 +319,8 @@ class TestDERSerialization(object): ) assert key assert isinstance(key, rsa.RSAPublicKey) - if isinstance(key, rsa.RSAPublicKeyWithSerialization): - numbers = key.public_numbers() - assert numbers.e == 65537 + numbers = key.public_numbers() + assert numbers.e == 65537 def test_load_der_invalid_public_key(self, backend): with pytest.raises(ValueError): @@ -293,6 +364,14 @@ class TestDERSerialization(object): assert key.curve.name == "secp256r1" assert key.curve.key_size == 256 + def test_wrong_parameters_format(self, backend): + param_data = b"---- NOT A KEY ----\n" + + with pytest.raises(ValueError): + load_der_parameters( + param_data, backend + ) + @pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) class TestPEMSerialization(object): @@ -330,8 +409,7 @@ class TestPEMSerialization(object): assert key assert isinstance(key, rsa.RSAPrivateKey) - if isinstance(key, rsa.RSAPrivateKeyWithSerialization): - _check_rsa_private_numbers(key.private_numbers()) + _check_rsa_private_numbers(key.private_numbers()) @pytest.mark.parametrize( ("key_path", "password"), @@ -352,8 +430,7 @@ class TestPEMSerialization(object): ) assert key assert isinstance(key, dsa.DSAPrivateKey) - if isinstance(key, dsa.DSAPrivateKeyWithSerialization): - _check_dsa_private_numbers(key.private_numbers()) + _check_dsa_private_numbers(key.private_numbers()) @pytest.mark.parametrize( ("key_path", "password"), @@ -397,9 +474,8 @@ class TestPEMSerialization(object): ) assert key assert isinstance(key, rsa.RSAPublicKey) - if isinstance(key, rsa.RSAPublicKeyWithSerialization): - numbers = key.public_numbers() - assert numbers.e == 65537 + numbers = key.public_numbers() + assert numbers.e == 65537 @pytest.mark.parametrize( ("key_file"), @@ -498,6 +574,43 @@ class TestPEMSerialization(object): ) ) + def test_invalid_encoding_with_traditional(self, backend): + key_file = os.path.join( + "asymmetric", "Traditional_OpenSSL_Serialization", "testrsa.pem" + ) + key = load_vectors_from_file( + key_file, + lambda pemfile: load_pem_private_key( + pemfile.read(), None, backend + ), + mode="rb" + ) + + for enc in (Encoding.OpenSSH, Encoding.Raw, Encoding.X962): + with pytest.raises(ValueError): + key.private_bytes( + enc, PrivateFormat.TraditionalOpenSSL, NoEncryption() + ) + + @pytest.mark.parametrize( + "key_path", + [ + ["Traditional_OpenSSL_Serialization", "testrsa-encrypted.pem"], + ["PKCS8", "enc-rsa-pkcs8.pem"] + ] + ) + def test_password_not_bytes(self, key_path, backend): + key_file = os.path.join("asymmetric", *key_path) + password = u"this password is not bytes" + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), password, backend + ) + ) + @pytest.mark.parametrize( "key_path", [ @@ -558,6 +671,12 @@ class TestPEMSerialization(object): with pytest.raises(ValueError): load_pem_public_key(key_data, backend) + def test_wrong_parameters_format(self, backend): + param_data = b"---- NOT A KEY ----\n" + + with pytest.raises(ValueError): + load_pem_parameters(param_data, backend) + def test_corrupt_traditional_format(self, backend): # privkey.pem with a bunch of data missing. key_data = textwrap.dedent("""\ @@ -770,41 +889,40 @@ class TestPEMSerialization(object): params = key.parameters() assert isinstance(params, dsa.DSAParameters) - if isinstance(params, dsa.DSAParametersWithNumbers): - num = key.private_numbers() - pub = num.public_numbers - parameter_numbers = pub.parameter_numbers - assert num.x == int("00a535a8e1d0d91beafc8bee1d9b2a3a8de3311203", - 16) - assert pub.y == int( - "2b260ea97dc6a12ae932c640e7df3d8ff04a8a05a0324f8d5f1b23f15fa1" - "70ff3f42061124eff2586cb11b49a82dcdc1b90fc6a84fb10109cb67db5d" - "2da971aeaf17be5e37284563e4c64d9e5fc8480258b319f0de29d54d8350" - "70d9e287914d77df81491f4423b62da984eb3f45eb2a29fcea5dae525ac6" - "ab6bcce04bfdf5b6", - 16 - ) + num = key.private_numbers() + pub = num.public_numbers + parameter_numbers = pub.parameter_numbers + assert num.x == int("00a535a8e1d0d91beafc8bee1d9b2a3a8de3311203", + 16) + assert pub.y == int( + "2b260ea97dc6a12ae932c640e7df3d8ff04a8a05a0324f8d5f1b23f15fa1" + "70ff3f42061124eff2586cb11b49a82dcdc1b90fc6a84fb10109cb67db5d" + "2da971aeaf17be5e37284563e4c64d9e5fc8480258b319f0de29d54d8350" + "70d9e287914d77df81491f4423b62da984eb3f45eb2a29fcea5dae525ac6" + "ab6bcce04bfdf5b6", + 16 + ) - assert parameter_numbers.p == int( - "00aa0930cc145825221caffa28ac2894196a27833de5ec21270791689420" - "7774a2e7b238b0d36f1b2499a2c2585083eb01432924418d867faa212dd1" - "071d4dceb2782794ad393cc08a4d4ada7f68d6e839a5fcd34b4e402d82cb" - "8a8cb40fec31911bf9bd360b034caacb4c5e947992573c9e90099c1b0f05" - "940cabe5d2de49a167", - 16 - ) + assert parameter_numbers.p == int( + "00aa0930cc145825221caffa28ac2894196a27833de5ec21270791689420" + "7774a2e7b238b0d36f1b2499a2c2585083eb01432924418d867faa212dd1" + "071d4dceb2782794ad393cc08a4d4ada7f68d6e839a5fcd34b4e402d82cb" + "8a8cb40fec31911bf9bd360b034caacb4c5e947992573c9e90099c1b0f05" + "940cabe5d2de49a167", + 16 + ) - assert parameter_numbers.q == int( - "00adc0e869b36f0ac013a681fdf4d4899d69820451", 16) + assert parameter_numbers.q == int( + "00adc0e869b36f0ac013a681fdf4d4899d69820451", 16) - assert parameter_numbers.g == int( - "008c6b4589afa53a4d1048bfc346d1f386ca75521ccf72ddaa251286880e" - "e13201ff48890bbfc33d79bacaec71e7a778507bd5f1a66422e39415be03" - "e71141ba324f5b93131929182c88a9fa4062836066cebe74b5c6690c7d10" - "1106c240ab7ebd54e4e3301fd086ce6adac922fb2713a2b0887cba13b9bc" - "68ce5cfff241cd3246", - 16 - ) + assert parameter_numbers.g == int( + "008c6b4589afa53a4d1048bfc346d1f386ca75521ccf72ddaa251286880e" + "e13201ff48890bbfc33d79bacaec71e7a778507bd5f1a66422e39415be03" + "e71141ba324f5b93131929182c88a9fa4062836066cebe74b5c6690c7d10" + "1106c240ab7ebd54e4e3301fd086ce6adac922fb2713a2b0887cba13b9bc" + "68ce5cfff241cd3246", + 16 + ) @pytest.mark.parametrize( ("key_file", "password"), @@ -813,9 +931,7 @@ class TestPEMSerialization(object): ] ) def test_load_bad_oid_key(self, key_file, password, backend): - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): + with pytest.raises(ValueError): load_vectors_from_file( os.path.join( "asymmetric", "PKCS8", key_file), @@ -861,7 +977,18 @@ class TestRSASSHSerialization(object): with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) - def test_load_ssh_public_key_rsa_extra_string_after_comment(self, backend): + def test_load_ssh_public_key_truncated_int(self, backend): + ssh_key = b'ssh-rsa AAAAB3NzaC1yc2EAAAA=' + + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + ssh_key = b'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAACKr+IHXo' + + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_rsa_comment_with_spaces(self, backend): ssh_key = ( b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" @@ -873,8 +1000,7 @@ class TestRSASSHSerialization(object): b"2MzHvnbv testkey@localhost extra" ) - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) + load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_rsa_extra_data_after_modulo(self, backend): ssh_key = ( @@ -950,7 +1076,7 @@ class TestDSSSSHSerialization(object): with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) - def test_load_ssh_public_key_dss_extra_string_after_comment(self, backend): + def test_load_ssh_public_key_dss_comment_with_spaces(self, backend): ssh_key = ( b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" @@ -964,8 +1090,7 @@ class TestDSSSSHSerialization(object): b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost extra" ) - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) + load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_dss_extra_data_after_modulo(self, backend): ssh_key = ( @@ -1169,6 +1294,53 @@ class TestECDSASSHSerialization(object): load_ssh_public_key(ssh_key, backend) +@pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" +) +class TestEd25519SSHSerialization(object): + def test_load_ssh_public_key(self, backend): + ssh_key = ( + b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2fgpmpYO61qeAxGd0wgRaN/E4" + b"GR+xWvBmvxjxrB1vG user@chiron.local" + ) + key = load_ssh_public_key(ssh_key, backend) + assert isinstance(key, ed25519.Ed25519PublicKey) + assert key.public_bytes( + Encoding.Raw, PublicFormat.Raw + ) == ( + b"m\x9f\x82\x99\xa9`\xee\xb5\xa9\xe01\x19\xdd0\x81\x16\x8d\xfc" + b"N\x06G\xecV\xbc\x19\xaf\xc6<k\x07[\xc6" + ) + + def test_public_bytes_openssh(self, backend): + ssh_key = ( + b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2fgpmpYO61qeAxGd0wgRaN/E4" + b"GR+xWvBmvxjxrB1vG" + ) + key = load_ssh_public_key(ssh_key, backend) + assert isinstance(key, ed25519.Ed25519PublicKey) + assert key.public_bytes( + Encoding.OpenSSH, PublicFormat.OpenSSH + ) == ssh_key + + def test_load_ssh_public_key_not_32_bytes(self, backend): + ssh_key = ( + b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI22fgpmpYO61qeAxGd0wgRaN/E4" + b"GR+xWvBmvxjxrB1vGaGVs user@chiron.local" + ) + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_trailing_data(self, backend): + ssh_key = ( + b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2fgpmpYO61qeAxGd0wgRa" + b"N/E4GR+xWvBmvxjxrB1vGdHJhaWxpbmdkYXRh user@chiron.local" + ) + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + class TestKeySerializationEncryptionTypes(object): def test_non_bytes_password(self): with pytest.raises(ValueError): @@ -1177,3 +1349,363 @@ class TestKeySerializationEncryptionTypes(object): def test_encryption_with_zero_length_password(self): with pytest.raises(ValueError): BestAvailableEncryption(b"") + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" +) +class TestEd25519Serialization(object): + def test_load_der_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8-enc.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + key = load_der_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.DER, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + def test_load_pem_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8-enc.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + key = load_pem_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader"), + [ + ( + ["Ed25519", "ed25519-pub.pem"], + Encoding.PEM, + load_pem_public_key + ), + ( + ["Ed25519", "ed25519-pub.der"], + Encoding.DER, + load_der_public_key + ), + ] + ) + def test_load_public_key(self, key_path, encoding, loader, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: pemfile.read(), + mode="rb" + ) + public_key = loader(data, backend) + assert public_key.public_bytes( + encoding, PublicFormat.SubjectPublicKeyInfo + ) == data + + def test_openssl_serialization_unsupported(self, backend): + key = ed25519.Ed25519PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + with pytest.raises(ValueError): + key.private_bytes( + Encoding.DER, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.x448_supported(), + skip_message="Requires OpenSSL with X448 support" +) +class TestX448Serialization(object): + def test_load_der_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8-enc.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + key = load_der_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.DER, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + def test_load_pem_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8-enc.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + key = load_pem_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader"), + [ + ( + ["X448", "x448-pub.pem"], + Encoding.PEM, + load_pem_public_key + ), + ( + ["X448", "x448-pub.der"], + Encoding.DER, + load_der_public_key + ), + ] + ) + def test_load_public_key(self, key_path, encoding, loader, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: pemfile.read(), + mode="rb" + ) + public_key = loader(data, backend) + assert public_key.public_bytes( + encoding, PublicFormat.SubjectPublicKeyInfo + ) == data + + def test_openssl_serialization_unsupported(self, backend): + key = x448.X448PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + with pytest.raises(ValueError): + key.private_bytes( + Encoding.DER, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.x25519_supported(), + skip_message="Requires OpenSSL with X25519 support" +) +class TestX25519Serialization(object): + def test_load_der_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "X25519", "x25519-pkcs8-enc.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "X25519", "x25519-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + key = load_der_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.DER, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + def test_load_pem_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "X25519", "x25519-pkcs8-enc.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "X25519", "x25519-pkcs8.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + key = load_pem_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader"), + [ + ( + ["X25519", "x25519-pub.pem"], + Encoding.PEM, + load_pem_public_key + ), + ( + ["X25519", "x25519-pub.der"], + Encoding.DER, + load_der_public_key + ), + ] + ) + def test_load_public_key(self, key_path, encoding, loader, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: pemfile.read(), + mode="rb" + ) + public_key = loader(data, backend) + assert public_key.public_bytes( + encoding, PublicFormat.SubjectPublicKeyInfo + ) == data + + def test_openssl_serialization_unsupported(self, backend): + key = x25519.X25519PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + with pytest.raises(ValueError): + key.private_bytes( + Encoding.DER, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" +) +class TestEd448Serialization(object): + def test_load_der_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8-enc.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + key = load_der_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.DER, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + def test_load_pem_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8-enc.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + key = load_pem_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader"), + [ + ( + ["Ed448", "ed448-pub.pem"], + Encoding.PEM, + load_pem_public_key + ), + ( + ["Ed448", "ed448-pub.der"], + Encoding.DER, + load_der_public_key + ), + ] + ) + def test_load_public_key(self, key_path, encoding, loader, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: pemfile.read(), + mode="rb" + ) + public_key = loader(data, backend) + assert public_key.public_bytes( + encoding, PublicFormat.SubjectPublicKeyInfo + ) == data + + def test_openssl_serialization_unsupported(self, backend): + key = ed448.Ed448PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + with pytest.raises(ValueError): + key.private_bytes( + Encoding.DER, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + + def test_openssh_serialization_unsupported(self, backend): + key = ed448.Ed448PrivateKey.generate() + with pytest.raises(ValueError): + key.public_key().public_bytes( + Encoding.OpenSSH, PublicFormat.OpenSSH, + ) + + +class TestDHSerialization(object): + """Test all options with least-supported key type. + """ + def test_dh_public_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "DH", "dhkey.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + public_key = load_pem_private_key(data, None, backend).public_key() + for enc in ( + Encoding.PEM, Encoding.DER, Encoding.OpenSSH, + Encoding.Raw, Encoding.X962 + ): + for fmt in ( + PublicFormat.SubjectPublicKeyInfo, PublicFormat.PKCS1, + PublicFormat.OpenSSH, PublicFormat.Raw, + PublicFormat.CompressedPoint, PublicFormat.UncompressedPoint, + ): + if ( + enc in (Encoding.PEM, Encoding.DER) and + fmt == PublicFormat.SubjectPublicKeyInfo + ): + # tested elsewhere + continue + with pytest.raises(ValueError): + public_key.public_bytes(enc, fmt) + + def test_dh_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "DH", "dhkey.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + private_key = load_pem_private_key(data, None, backend) + for enc in ( + Encoding.PEM, Encoding.DER, Encoding.OpenSSH, + Encoding.Raw, Encoding.X962 + ): + for fmt in ( + PrivateFormat.PKCS8, PrivateFormat.TraditionalOpenSSL, + PrivateFormat.Raw + ): + if ( + enc in (Encoding.PEM, Encoding.DER) and + fmt is PrivateFormat.PKCS8 + ): + # tested elsewhere + continue + with pytest.raises(ValueError): + private_key.private_bytes(enc, fmt, NoEncryption()) diff --git a/tests/hazmat/primitives/test_x25519.py b/tests/hazmat/primitives/test_x25519.py new file mode 100644 index 00000000..30dc2818 --- /dev/null +++ b/tests/hazmat/primitives/test_x25519.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 os + +import pytest + +from cryptography.exceptions import _Reasons +from cryptography.hazmat.primitives import serialization +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" +) +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.from_private_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" +) +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) + + def test_public_bytes_bad_args(self, backend): + key = X25519PrivateKey.generate().public_key() + with pytest.raises(ValueError): + key.public_bytes(None, serialization.PublicFormat.Raw) + with pytest.raises(TypeError): + key.public_bytes(serialization.Encoding.Raw) + + # 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_pub_priv_bytes_raw(self, private_bytes, public_bytes, backend): + private_key = X25519PrivateKey.from_private_bytes(private_bytes) + assert private_key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes + assert private_key.public_key().public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == public_bytes + public_key = X25519PublicKey.from_public_bytes(public_bytes) + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == 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) + + def test_invalid_length_from_private_bytes(self, backend): + with pytest.raises(ValueError): + X25519PrivateKey.from_private_bytes(b"a" * 31) + + with pytest.raises(ValueError): + X25519PrivateKey.from_private_bytes(b"a" * 33) + + def test_invalid_private_bytes(self, backend): + key = X25519PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.PKCS8, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) + + def test_invalid_public_bytes(self, backend): + key = X25519PrivateKey.generate().public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1 + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.Raw + ) + + @pytest.mark.parametrize( + ("encoding", "fmt", "encryption", "passwd", "load_func"), + [ + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_der_private_key + ), + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_der_private_key + ), + ] + ) + def test_round_trip_private_serialization(self, encoding, fmt, encryption, + passwd, load_func, backend): + key = X25519PrivateKey.generate() + serialized = key.private_bytes(encoding, fmt, encryption) + loaded_key = load_func(serialized, passwd, backend) + assert isinstance(loaded_key, X25519PrivateKey) + + def test_buffer_protocol(self, backend): + private_bytes = bytearray(os.urandom(32)) + key = X25519PrivateKey.from_private_bytes(private_bytes) + assert key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes diff --git a/tests/hazmat/primitives/test_x448.py b/tests/hazmat/primitives/test_x448.py new file mode 100644 index 00000000..472d49cd --- /dev/null +++ b/tests/hazmat/primitives/test_x448.py @@ -0,0 +1,239 @@ +# 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.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.x448 import ( + X448PrivateKey, X448PublicKey +) + +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) + + +@pytest.mark.supported( + only_if=lambda backend: not backend.x448_supported(), + skip_message="Requires OpenSSL without X448 support" +) +def test_x448_unsupported(backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM): + X448PublicKey.from_public_bytes(b"0" * 56) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM): + X448PrivateKey.from_private_bytes(b"0" * 56) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM): + X448PrivateKey.generate() + + +@pytest.mark.supported( + only_if=lambda backend: backend.x448_supported(), + skip_message="Requires OpenSSL with X448 support" +) +class TestX448Exchange(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "X448", "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 = X448PrivateKey.from_private_bytes(private) + public_key = X448PublicKey.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"05000000000000000000000000000000000000000000000000000000" + b"00000000000000000000000000000000000000000000000000000000" + ) + shared_key = binascii.unhexlify( + b"aa3b4749d55b9daf1e5b00288826c467274ce3ebbdd5c17b975e09d4" + b"af6c67cf10d087202db88286e2b79fceea3ec353ef54faa26e219f38" + ) + private_key = X448PrivateKey.from_private_bytes(private) + public_key = X448PublicKey.from_public_bytes(public) + for _ in range(1000): + computed_shared_key = private_key.exchange(public_key) + private_key = X448PrivateKey.from_private_bytes( + computed_shared_key + ) + public_key = X448PublicKey.from_public_bytes(old_private) + old_private = computed_shared_key + + assert computed_shared_key == shared_key + + # These vectors are also from RFC 7748 + # https://tools.ietf.org/html/rfc7748#section-6.2 + @pytest.mark.parametrize( + ("private_bytes", "public_bytes"), + [ + ( + binascii.unhexlify( + b"9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d" + b"d9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b" + ), + binascii.unhexlify( + b"9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c" + b"22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0" + ) + ), + ( + binascii.unhexlify( + b"1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d" + b"6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d" + ), + binascii.unhexlify( + b"3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b430" + b"27d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609" + ) + ) + ] + ) + def test_pub_priv_bytes_raw(self, private_bytes, public_bytes, backend): + private_key = X448PrivateKey.from_private_bytes(private_bytes) + assert private_key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes + assert private_key.public_key().public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == public_bytes + public_key = X448PublicKey.from_public_bytes(public_bytes) + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == public_bytes + + @pytest.mark.parametrize( + ("encoding", "fmt", "encryption", "passwd", "load_func"), + [ + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_der_private_key + ), + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_der_private_key + ), + ] + ) + def test_round_trip_private_serialization(self, encoding, fmt, encryption, + passwd, load_func, backend): + key = X448PrivateKey.generate() + serialized = key.private_bytes(encoding, fmt, encryption) + loaded_key = load_func(serialized, passwd, backend) + assert isinstance(loaded_key, X448PrivateKey) + + def test_generate(self, backend): + key = X448PrivateKey.generate() + assert key + assert key.public_key() + + def test_invalid_type_exchange(self, backend): + key = X448PrivateKey.generate() + with pytest.raises(TypeError): + key.exchange(object()) + + def test_invalid_length_from_public_bytes(self, backend): + with pytest.raises(ValueError): + X448PublicKey.from_public_bytes(b"a" * 55) + + with pytest.raises(ValueError): + X448PublicKey.from_public_bytes(b"a" * 57) + + def test_invalid_length_from_private_bytes(self, backend): + with pytest.raises(ValueError): + X448PrivateKey.from_private_bytes(b"a" * 55) + + with pytest.raises(ValueError): + X448PrivateKey.from_private_bytes(b"a" * 57) + + def test_invalid_private_bytes(self, backend): + key = X448PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.PKCS8, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) + + def test_invalid_public_bytes(self, backend): + key = X448PrivateKey.generate().public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1 + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.Raw + ) + + def test_buffer_protocol(self, backend): + private_bytes = binascii.unhexlify( + b"9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d" + b"d9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b" + ) + key = X448PrivateKey.from_private_bytes(bytearray(private_bytes)) + assert key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes diff --git a/tests/hazmat/primitives/test_x963_vectors.py b/tests/hazmat/primitives/test_x963_vectors.py new file mode 100644 index 00000000..c75afa41 --- /dev/null +++ b/tests/hazmat/primitives/test_x963_vectors.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 os + +import pytest + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF + +from ...doubles import DummyHashAlgorithm +from ...utils import load_vectors_from_file, load_x963_vectors + + +def _skip_hashfn_unsupported(backend, hashfn): + if not backend.hash_supported(hashfn): + pytest.skip( + "Hash {} is not supported by this backend {}".format( + hashfn.name, backend + ) + ) + + +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestX963(object): + _algorithms_dict = { + 'SHA-1': hashes.SHA1, + 'SHA-224': hashes.SHA224, + 'SHA-256': hashes.SHA256, + 'SHA-384': hashes.SHA384, + 'SHA-512': hashes.SHA512 + } + + @pytest.mark.parametrize( + ("vector"), + load_vectors_from_file( + os.path.join("KDF", "ansx963_2001.txt"), + load_x963_vectors + ) + ) + def test_x963(self, backend, vector): + hashfn = self._algorithms_dict[vector["hash"]] + _skip_hashfn_unsupported(backend, hashfn()) + + key = binascii.unhexlify(vector["Z"]) + sharedinfo = None + if vector["sharedinfo_length"] != 0: + sharedinfo = binascii.unhexlify(vector["sharedinfo"]) + key_data_len = vector["key_data_length"] // 8 + key_data = binascii.unhexlify(vector["key_data"]) + + xkdf = X963KDF(algorithm=hashfn(), + length=key_data_len, + sharedinfo=sharedinfo, + backend=default_backend()) + xkdf.verify(key, key_data) + + def test_unsupported_hash(self, backend): + with pytest.raises(pytest.skip.Exception): + _skip_hashfn_unsupported(backend, DummyHashAlgorithm()) diff --git a/tests/hazmat/primitives/test_x963kdf.py b/tests/hazmat/primitives/test_x963kdf.py new file mode 100644 index 00000000..c4dd8925 --- /dev/null +++ b/tests/hazmat/primitives/test_x963kdf.py @@ -0,0 +1,131 @@ +# 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 ( + AlreadyFinalized, InvalidKey, _Reasons +) +from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF + +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestX963KDF(object): + def test_length_limit(self, backend): + big_length = hashes.SHA256().digest_size * (2 ** 32 - 1) + 1 + + with pytest.raises(ValueError): + X963KDF(hashes.SHA256(), big_length, None, backend) + + def test_already_finalized(self, backend): + xkdf = X963KDF(hashes.SHA256(), 16, None, backend) + + xkdf.derive(b"\x01" * 16) + + with pytest.raises(AlreadyFinalized): + xkdf.derive(b"\x02" * 16) + + def test_derive(self, backend): + key = binascii.unhexlify( + b"96c05619d56c328ab95fe84b18264b08725b85e33fd34f08" + ) + + derivedkey = binascii.unhexlify(b"443024c3dae66b95e6f5670601558f71") + + xkdf = X963KDF(hashes.SHA256(), 16, None, backend) + + assert xkdf.derive(key) == derivedkey + + def test_buffer_protocol(self, backend): + key = bytearray(binascii.unhexlify( + b"96c05619d56c328ab95fe84b18264b08725b85e33fd34f08" + )) + + derivedkey = binascii.unhexlify(b"443024c3dae66b95e6f5670601558f71") + + xkdf = X963KDF(hashes.SHA256(), 16, None, backend) + + assert xkdf.derive(key) == derivedkey + + def test_verify(self, backend): + key = binascii.unhexlify( + b"22518b10e70f2a3f243810ae3254139efbee04aa57c7af7d" + ) + + sharedinfo = binascii.unhexlify(b"75eef81aa3041e33b80971203d2c0c52") + + derivedkey = binascii.unhexlify( + b"c498af77161cc59f2962b9a713e2b215152d139766ce34a776df11866a69bf2e" + b"52a13d9c7c6fc878c50c5ea0bc7b00e0da2447cfd874f6cf92f30d0097111485" + b"500c90c3af8b487872d04685d14c8d1dc8d7fa08beb0ce0ababc11f0bd496269" + b"142d43525a78e5bc79a17f59676a5706dc54d54d4d1f0bd7e386128ec26afc21" + ) + + xkdf = X963KDF(hashes.SHA256(), 128, sharedinfo, backend) + + assert xkdf.verify(key, derivedkey) is None + + def test_invalid_verify(self, backend): + key = binascii.unhexlify( + b"96c05619d56c328ab95fe84b18264b08725b85e33fd34f08" + ) + + xkdf = X963KDF(hashes.SHA256(), 16, None, backend) + + with pytest.raises(InvalidKey): + xkdf.verify(key, b"wrong derived key") + + def test_unicode_typeerror(self, backend): + with pytest.raises(TypeError): + X963KDF( + hashes.SHA256(), + 16, + sharedinfo=u"foo", + backend=backend + ) + + with pytest.raises(TypeError): + xkdf = X963KDF( + hashes.SHA256(), + 16, + sharedinfo=None, + backend=backend + ) + + xkdf.derive(u"foo") + + with pytest.raises(TypeError): + xkdf = X963KDF( + hashes.SHA256(), + 16, + sharedinfo=None, + backend=backend + ) + + xkdf.verify(u"foo", b"bar") + + with pytest.raises(TypeError): + xkdf = X963KDF( + hashes.SHA256(), + 16, + sharedinfo=None, + backend=backend + ) + + xkdf.verify(b"foo", u"bar") + + +def test_invalid_backend(): + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + X963KDF(hashes.SHA256(), 16, None, pretend_backend) diff --git a/tests/hazmat/primitives/twofactor/test_hotp.py b/tests/hazmat/primitives/twofactor/test_hotp.py index ab5f93c5..14cb08a8 100644 --- a/tests/hazmat/primitives/twofactor/test_hotp.py +++ b/tests/hazmat/primitives/twofactor/test_hotp.py @@ -35,6 +35,10 @@ class TestHOTP(object): with pytest.raises(ValueError): HOTP(secret, 6, SHA1(), backend) + def test_unenforced_invalid_kwy_length(self, backend): + secret = os.urandom(10) + HOTP(secret, 6, SHA1(), backend, enforce_key_length=False) + def test_invalid_hotp_length(self, backend): secret = os.urandom(16) @@ -105,6 +109,11 @@ class TestHOTP(object): "GNBVGY3TQOJQGEZDGNBVGY3TQOJQ&algorithm=SHA1&issuer=Foo" "&counter=1") + def test_buffer_protocol(self, backend): + key = bytearray(b"a long key with lots of entropy goes here") + hotp = HOTP(key, 6, SHA1(), backend) + assert hotp.generate(10) == b"559978" + def test_invalid_backend(): secret = b"12345678901234567890" diff --git a/tests/hazmat/primitives/twofactor/test_totp.py b/tests/hazmat/primitives/twofactor/test_totp.py index 95829713..59d875af 100644 --- a/tests/hazmat/primitives/twofactor/test_totp.py +++ b/tests/hazmat/primitives/twofactor/test_totp.py @@ -139,6 +139,12 @@ class TestTOTP(object): "DGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&algorithm=SHA1&issuer=World" "&period=30") + def test_buffer_protocol(self, backend): + key = bytearray(b"a long key with lots of entropy goes here") + totp = TOTP(key, 8, hashes.SHA512(), 30, backend) + time = 60 + assert totp.generate(time) == b"53049576" + def test_invalid_backend(): secret = b"12345678901234567890" diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index e148bc63..4aa5ce71 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -18,6 +18,9 @@ from cryptography.hazmat.primitives import hashes, hmac from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand +from cryptography.hazmat.primitives.kdf.kbkdf import ( + CounterLocation, KBKDFHMAC, Mode +) from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from ...utils import load_vectors_from_file @@ -44,6 +47,10 @@ def generate_encrypt_test(param_loader, path, file_names, cipher_factory, def encrypt_test(backend, cipher_factory, mode_factory, params): + assert backend.cipher_supported( + cipher_factory(**params), mode_factory(**params) + ) + plaintext = params["plaintext"] ciphertext = params["ciphertext"] cipher = Cipher( @@ -161,16 +168,15 @@ def hash_test(backend, algorithm, params): assert m.finalize() == binascii.unhexlify(expected_md) -def generate_base_hash_test(algorithm, digest_size, block_size): +def generate_base_hash_test(algorithm, digest_size): def test_base_hash(self, backend): - base_hash_test(backend, algorithm, digest_size, block_size) + base_hash_test(backend, algorithm, digest_size) return test_base_hash -def base_hash_test(backend, algorithm, digest_size, block_size): +def base_hash_test(backend, algorithm, digest_size): m = hashes.Hash(algorithm, backend=backend) assert m.algorithm.digest_size == digest_size - assert m.algorithm.block_size == block_size m_copy = m.copy() assert m != m_copy assert m._ctx != m_copy._ctx @@ -182,18 +188,6 @@ def base_hash_test(backend, algorithm, digest_size, block_size): assert copy.finalize() == m.finalize() -def generate_long_string_hash_test(hash_factory, md): - def test_long_string_hash(self, backend): - long_string_hash_test(backend, hash_factory, md) - return test_long_string_hash - - -def long_string_hash_test(backend, algorithm, md): - m = hashes.Hash(algorithm, backend=backend) - m.update(b"a" * 1000000) - assert m.finalize() == binascii.unhexlify(md.lower().encode("ascii")) - - def generate_base_hmac_test(hash_cls): def test_base_hmac(self, backend): base_hmac_test(backend, hash_cls) @@ -296,8 +290,6 @@ def aead_tag_exception_test(backend, cipher_factory, mode_factory): mode_factory(binascii.unhexlify(b"0" * 24)), backend ) - with pytest.raises(ValueError): - cipher.decryptor() with pytest.raises(ValueError): mode_factory(binascii.unhexlify(b"0" * 24), b"000") @@ -370,6 +362,57 @@ def generate_hkdf_test(param_loader, path, file_names, algorithm): return test_hkdf +def generate_kbkdf_counter_mode_test(param_loader, path, file_names): + all_params = _load_all_params(path, file_names, param_loader) + + @pytest.mark.parametrize("params", all_params) + def test_kbkdf(self, backend, params): + kbkdf_counter_mode_test(backend, params) + return test_kbkdf + + +def kbkdf_counter_mode_test(backend, params): + supported_algorithms = { + 'hmac_sha1': hashes.SHA1, + 'hmac_sha224': hashes.SHA224, + 'hmac_sha256': hashes.SHA256, + 'hmac_sha384': hashes.SHA384, + 'hmac_sha512': hashes.SHA512, + } + + supported_counter_locations = { + "before_fixed": CounterLocation.BeforeFixed, + "after_fixed": CounterLocation.AfterFixed, + } + + algorithm = supported_algorithms.get(params.get('prf')) + if algorithm is None or not backend.hmac_supported(algorithm()): + pytest.skip("KBKDF does not support algorithm: {}".format( + params.get('prf') + )) + + ctr_loc = supported_counter_locations.get(params.get("ctrlocation")) + if ctr_loc is None or not isinstance(ctr_loc, CounterLocation): + pytest.skip("Does not support counter location: {}".format( + params.get('ctrlocation') + )) + + ctrkdf = KBKDFHMAC( + algorithm(), + Mode.CounterMode, + params['l'] // 8, + params['rlen'] // 8, + None, + ctr_loc, + None, + None, + binascii.unhexlify(params['fixedinputdata']), + backend=backend) + + ko = ctrkdf.derive(binascii.unhexlify(params['ki'])) + assert binascii.hexlify(ko) == params["ko"] + + def generate_rsa_verification_test(param_loader, path, file_names, hash_alg, pad_factory): all_params = _load_all_params(path, file_names, param_loader) @@ -390,17 +433,23 @@ def rsa_verification_test(backend, params, hash_alg, pad_factory): ) public_key = public_numbers.public_key(backend) pad = pad_factory(params, hash_alg) - verifier = public_key.verifier( - binascii.unhexlify(params["s"]), - pad, - hash_alg - ) - verifier.update(binascii.unhexlify(params["msg"])) + signature = binascii.unhexlify(params["s"]) + msg = binascii.unhexlify(params["msg"]) if params["fail"]: with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, + msg, + pad, + hash_alg + ) else: - verifier.verify() + public_key.verify( + signature, + msg, + pad, + hash_alg + ) def _check_rsa_private_numbers(skey): diff --git a/tests/hazmat/test_der.py b/tests/hazmat/test_der.py new file mode 100644 index 00000000..d052802c --- /dev/null +++ b/tests/hazmat/test_der.py @@ -0,0 +1,225 @@ +# 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 cryptography.hazmat._der import ( + DERReader, INTEGER, NULL, OCTET_STRING, SEQUENCE, encode_der, + encode_der_integer +) + + +def test_der_reader_basic(): + reader = DERReader(b"123456789") + assert reader.read_byte() == ord(b"1") + assert reader.read_bytes(1).tobytes() == b"2" + assert reader.read_bytes(4).tobytes() == b"3456" + + with pytest.raises(ValueError): + reader.read_bytes(4) + + assert reader.read_bytes(3).tobytes() == b"789" + + # The input is now empty. + with pytest.raises(ValueError): + reader.read_bytes(1) + with pytest.raises(ValueError): + reader.read_byte() + + +def test_der(): + # This input is the following structure, using + # https://github.com/google/der-ascii + # + # SEQUENCE { + # SEQUENCE { + # NULL {} + # INTEGER { 42 } + # OCTET_STRING { "hello" } + # } + # } + der = b"\x30\x0e\x30\x0c\x05\x00\x02\x01\x2a\x04\x05\x68\x65\x6c\x6c\x6f" + reader = DERReader(der) + with pytest.raises(ValueError): + reader.check_empty() + + with pytest.raises(ValueError): + with reader: + pass + + with pytest.raises(ZeroDivisionError): + with DERReader(der): + raise ZeroDivisionError + + # Parse the outer element. + outer = reader.read_element(SEQUENCE) + reader.check_empty() + assert outer.data.tobytes() == der[2:] + + # Parse the outer element with read_any_element. + reader = DERReader(der) + tag, outer2 = reader.read_any_element() + reader.check_empty() + assert tag == SEQUENCE + assert outer2.data.tobytes() == der[2:] + + # Parse the outer element with read_single_element. + outer3 = DERReader(der).read_single_element(SEQUENCE) + assert outer3.data.tobytes() == der[2:] + + # read_single_element rejects trailing data. + with pytest.raises(ValueError): + DERReader(der + der).read_single_element(SEQUENCE) + + # Continue parsing the structure. + inner = outer.read_element(SEQUENCE) + outer.check_empty() + + # Parsing a missing optional element should work. + assert inner.read_optional_element(INTEGER) is None + + null = inner.read_element(NULL) + null.check_empty() + + # Parsing a present optional element should work. + integer = inner.read_optional_element(INTEGER) + assert integer.as_integer() == 42 + + octet_string = inner.read_element(OCTET_STRING) + assert octet_string.data.tobytes() == b"hello" + + # Parsing a missing optional element should work when the input is empty. + inner.check_empty() + assert inner.read_optional_element(INTEGER) is None + + # Re-encode the same structure. + der2 = encode_der( + SEQUENCE, + encode_der( + SEQUENCE, + encode_der(NULL), + encode_der(INTEGER, encode_der_integer(42)), + encode_der(OCTET_STRING, b"hello"), + ) + ) + assert der2 == der + + +@pytest.mark.parametrize( + "length,header", + [ + # Single-byte lengths. + (0, b"\x04\x00"), + (1, b"\x04\x01"), + (2, b"\x04\x02"), + (127, b"\x04\x7f"), + # Long-form lengths. + (128, b"\x04\x81\x80"), + (129, b"\x04\x81\x81"), + (255, b"\x04\x81\xff"), + (0x100, b"\x04\x82\x01\x00"), + (0x101, b"\x04\x82\x01\x01"), + (0xffff, b"\x04\x82\xff\xff"), + (0x10000, b"\x04\x83\x01\x00\x00"), + ] +) +def test_der_lengths(length, header): + body = length * b"a" + der = header + body + + reader = DERReader(der) + element = reader.read_element(OCTET_STRING) + reader.check_empty() + assert element.data.tobytes() == body + + assert encode_der(OCTET_STRING, body) == der + + +@pytest.mark.parametrize( + "bad_input", + [ + # The input ended before the tag. + b"", + # The input ended before the length. + b"\x30", + # The input ended before the second byte of the length. + b"\x30\x81", + # The input ended before the body. + b"\x30\x01", + # The length used long form when it should be short form. + b"\x30\x81\x01\x00", + # The length was not minimally-encoded. + b"\x30\x82\x00\x80" + (0x80 * b"a"), + # Indefinite-length encoding is not valid DER. + b"\x30\x80\x00\x00" + # Tag number (the bottom 5 bits) 31 indicates long form tags, which we + # do not support. + b"\x1f\x00", + b"\x9f\x00", + b"\xbf\x00", + b"\xff\x00", + ] +) +def test_der_reader_bad_input(bad_input): + reader = DERReader(bad_input) + with pytest.raises(ValueError): + reader.read_any_element() + + +def test_der_reader_wrong_tag(): + reader = DERReader(b"\x04\x00") + with pytest.raises(ValueError): + reader.read_element(SEQUENCE) + + +@pytest.mark.parametrize( + "value,der", + [ + (0, b'\x00'), + (1, b'\x01'), + (2, b'\x02'), + (3, b'\x03'), + (127, b'\x7f'), + (128, b'\x00\x80'), + (0x112233445566778899aabbccddeeff, + b'\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff'), + ] +) +def test_integer(value, der): + assert encode_der_integer(value) == der + assert DERReader(der).as_integer() == value + + +@pytest.mark.parametrize( + "bad_input", + [ + # Zero is encoded as b"\x00", not the empty string. + b"", + # Too many leading zeros. + b"\x00\x00", + b"\x00\x7f", + # Too many leading ones. + b"\xff\xff", + b"\xff\x80", + # Negative integers are not supported. + b"\x80", + b"\x81", + b"\x80\x00\x00", + b"\xff", + ] +) +def test_invalid_integer(bad_input): + reader = DERReader(bad_input) + with pytest.raises(ValueError): + reader.as_integer() + + +def test_invalid_integer_encode(): + with pytest.raises(ValueError): + encode_der_integer(-1) + + with pytest.raises(ValueError): + encode_der_integer("not an integer") diff --git a/tests/hazmat/test_oid.py b/tests/hazmat/test_oid.py new file mode 100644 index 00000000..d1a34f8e --- /dev/null +++ b/tests/hazmat/test_oid.py @@ -0,0 +1,39 @@ +# 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 cryptography.hazmat._oid import ObjectIdentifier + + +def test_basic_oid(): + assert ObjectIdentifier('1.2.3.4').dotted_string == '1.2.3.4' + + +def test_oid_constraint(): + # Too short + with pytest.raises(ValueError): + ObjectIdentifier('1') + + # First node too big + with pytest.raises(ValueError): + ObjectIdentifier('3.2.1') + + # Outside range + with pytest.raises(ValueError): + ObjectIdentifier('1.40') + with pytest.raises(ValueError): + ObjectIdentifier('0.42') + + # non-decimal oid + with pytest.raises(ValueError): + ObjectIdentifier('1.2.foo.bar') + with pytest.raises(ValueError): + ObjectIdentifier('1.2.0xf00.0xba4') + + # negative oid + with pytest.raises(ValueError): + ObjectIdentifier('1.2.-3.-4') diff --git a/tests/hypothesis/__init__.py b/tests/hypothesis/__init__.py new file mode 100644 index 00000000..4b540884 --- /dev/null +++ b/tests/hypothesis/__init__.py @@ -0,0 +1,5 @@ +# 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 diff --git a/tests/hypothesis/test_fernet.py b/tests/hypothesis/test_fernet.py new file mode 100644 index 00000000..75195f53 --- /dev/null +++ b/tests/hypothesis/test_fernet.py @@ -0,0 +1,16 @@ +# 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 hypothesis import HealthCheck, given, settings +from hypothesis.strategies import binary + +from cryptography.fernet import Fernet + + +@settings(suppress_health_check=[HealthCheck.too_slow], deadline=None) +@given(binary()) +def test_fernet(data): + f = Fernet(Fernet.generate_key()) + ct = f.encrypt(data) + assert f.decrypt(ct) == data diff --git a/tests/hypothesis/test_padding.py b/tests/hypothesis/test_padding.py new file mode 100644 index 00000000..74a58eb8 --- /dev/null +++ b/tests/hypothesis/test_padding.py @@ -0,0 +1,34 @@ +# 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 hypothesis import HealthCheck, given, settings +from hypothesis.strategies import binary, integers + +from cryptography.hazmat.primitives.padding import ANSIX923, PKCS7 + + +@settings(suppress_health_check=[HealthCheck.too_slow], deadline=None) +@given(integers(min_value=1, max_value=255), binary()) +def test_pkcs7(block_size, data): + # Generate in [1, 31] so we can easily get block_size in bits by + # multiplying by 8. + p = PKCS7(block_size=block_size * 8) + padder = p.padder() + unpadder = p.unpadder() + + padded = padder.update(data) + padder.finalize() + + assert unpadder.update(padded) + unpadder.finalize() == data + + +@settings(suppress_health_check=[HealthCheck.too_slow]) +@given(integers(min_value=1, max_value=255), binary()) +def test_ansix923(block_size, data): + a = ANSIX923(block_size=block_size * 8) + padder = a.padder() + unpadder = a.unpadder() + + padded = padder.update(data) + padder.finalize() + + assert unpadder.update(padded) + unpadder.finalize() == data diff --git a/tests/test_cryptography_utils.py b/tests/test_cryptography_utils.py new file mode 100644 index 00000000..ddea7602 --- /dev/null +++ b/tests/test_cryptography_utils.py @@ -0,0 +1,65 @@ +# 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 cryptography import utils + + +def test_int_from_bytes_bytearray(): + assert utils.int_from_bytes(bytearray(b"\x02\x10"), "big") == 528 + with pytest.raises(TypeError): + utils.int_from_bytes(["list", "is", "not", "bytes"], "big") + + +class TestCachedProperty(object): + def test_simple(self): + accesses = [] + + class T(object): + @utils.cached_property + def t(self): + accesses.append(None) + return 14 + + assert T.t + t = T() + assert t.t == 14 + assert len(accesses) == 1 + assert t.t == 14 + assert len(accesses) == 1 + + t = T() + assert t.t == 14 + assert len(accesses) == 2 + assert t.t == 14 + assert len(accesses) == 2 + + def test_set(self): + accesses = [] + + class T(object): + @utils.cached_property + def t(self): + accesses.append(None) + return 14 + + t = T() + with pytest.raises(AttributeError): + t.t = None + assert len(accesses) == 0 + assert t.t == 14 + assert len(accesses) == 1 + with pytest.raises(AttributeError): + t.t = None + assert len(accesses) == 1 + assert t.t == 14 + assert len(accesses) == 1 + + +def test_bit_length(): + assert utils.bit_length(1) == 1 + assert utils.bit_length(11) == 4 diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 0b93f017..da2096fb 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -45,7 +45,7 @@ def test_default_backend(): @pytest.mark.requires_backend_interface(interface=HMACBackend) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16) + algorithms.AES(b"\x00" * 32), modes.CBC(b"\x00" * 16) ), skip_message="Does not support AES CBC", ) @@ -69,6 +69,10 @@ class TestFernet(object): monkeypatch): f = Fernet(secret.encode("ascii"), backend=backend) current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) + payload = f.decrypt_at_time( + token.encode("ascii"), ttl=ttl_sec, current_time=current_time, + ) + assert payload == src.encode("ascii") monkeypatch.setattr(time, "time", lambda: current_time) payload = f.decrypt(token.encode("ascii"), ttl=ttl_sec) assert payload == src.encode("ascii") @@ -77,6 +81,10 @@ class TestFernet(object): def test_invalid(self, secret, token, now, ttl_sec, backend, monkeypatch): f = Fernet(secret.encode("ascii"), backend=backend) current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) + with pytest.raises(InvalidToken): + f.decrypt_at_time( + token.encode("ascii"), ttl=ttl_sec, current_time=current_time, + ) monkeypatch.setattr(time, "time", lambda: current_time) with pytest.raises(InvalidToken): f.decrypt(token.encode("ascii"), ttl=ttl_sec) @@ -103,6 +111,17 @@ class TestFernet(object): with pytest.raises(TypeError): f.decrypt(u"") + def test_timestamp_ignored_no_ttl(self, monkeypatch, backend): + f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + pt = b"encrypt me" + token = f.encrypt(pt) + ts = "1985-10-26T01:20:01-07:00" + current_time = calendar.timegm(iso8601.parse_date(ts).utctimetuple()) + assert f.decrypt_at_time( + token, ttl=None, current_time=current_time) == pt + monkeypatch.setattr(time, "time", lambda: current_time) + assert f.decrypt(token, ttl=None) == pt + @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xFF\x00\x80"]) def test_roundtrips(self, message, backend): f = Fernet(Fernet.generate_key(), backend=backend) @@ -112,12 +131,20 @@ class TestFernet(object): with pytest.raises(ValueError): Fernet(base64.urlsafe_b64encode(b"abc"), backend=backend) + def test_extract_timestamp(self, monkeypatch, backend): + f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + current_time = 1526138327 + token = f.encrypt_at_time(b'encrypt me', current_time) + assert f.extract_timestamp(token) == current_time + with pytest.raises(InvalidToken): + f.extract_timestamp(b"nonsensetoken") + @pytest.mark.requires_backend_interface(interface=CipherBackend) @pytest.mark.requires_backend_interface(interface=HMACBackend) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16) + algorithms.AES(b"\x00" * 32), modes.CBC(b"\x00" * 16) ), skip_message="Does not support AES CBC", ) @@ -147,3 +174,51 @@ class TestMultiFernet(object): def test_non_iterable_argument(self, backend): with pytest.raises(TypeError): MultiFernet(None) + + def test_rotate(self, backend): + f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) + + mf1 = MultiFernet([f1]) + mf2 = MultiFernet([f2, f1]) + + plaintext = b"abc" + mf1_ciphertext = mf1.encrypt(plaintext) + + assert mf2.decrypt(mf1_ciphertext) == plaintext + + rotated = mf2.rotate(mf1_ciphertext) + + assert rotated != mf1_ciphertext + assert mf2.decrypt(rotated) == plaintext + + with pytest.raises(InvalidToken): + mf1.decrypt(rotated) + + def test_rotate_preserves_timestamp(self, backend, monkeypatch): + f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) + + mf1 = MultiFernet([f1]) + mf2 = MultiFernet([f2, f1]) + + plaintext = b"abc" + original_time = int(time.time()) - 5 * 60 + mf1_ciphertext = mf1.encrypt_at_time(plaintext, original_time) + + rotated_time, _ = Fernet._get_unverified_token_data( + mf2.rotate(mf1_ciphertext) + ) + + assert int(time.time()) != rotated_time + assert original_time == rotated_time + + def test_rotate_decrypt_no_shared_keys(self, backend): + f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) + + mf1 = MultiFernet([f1]) + mf2 = MultiFernet([f2]) + + with pytest.raises(InvalidToken): + mf2.rotate(mf1.encrypt(b"abc")) diff --git a/tests/test_interfaces.py b/tests/test_interfaces.py index 4d571ea6..97df45a3 100644 --- a/tests/test_interfaces.py +++ b/tests/test_interfaces.py @@ -8,7 +8,33 @@ import pytest import six -from cryptography.utils import InterfaceNotImplemented, verify_interface +from cryptography.utils import ( + InterfaceNotImplemented, register_interface_if, verify_interface +) + + +def test_register_interface_if_true(): + @six.add_metaclass(abc.ABCMeta) + class SimpleInterface(object): + pass + + @register_interface_if(1 == 1, SimpleInterface) + class SimpleClass(object): + pass + + assert issubclass(SimpleClass, SimpleInterface) is True + + +def test_register_interface_if_false(): + @six.add_metaclass(abc.ABCMeta) + class SimpleInterface(object): + pass + + @register_interface_if(1 == 2, SimpleInterface) + class SimpleClass(object): + pass + + assert issubclass(SimpleClass, SimpleInterface) is False class TestVerifyInterface(object): @@ -36,6 +62,8 @@ class TestVerifyInterface(object): def method(self): """Method with no arguments""" + # Invoke this to ensure the line is covered + NonImplementer().method() with pytest.raises(InterfaceNotImplemented): verify_interface(SimpleInterface, NonImplementer) @@ -51,4 +79,6 @@ class TestVerifyInterface(object): def property(self): """A concrete property""" + # Invoke this to ensure the line is covered + NonImplementer().property verify_interface(SimpleInterface, NonImplementer) diff --git a/tests/test_utils.py b/tests/test_utils.py index 8601d11d..ecd257bd 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -18,77 +18,24 @@ from cryptography.exceptions import UnsupportedAlgorithm, _Reasons import cryptography_vectors from .utils import ( - check_backend_support, load_cryptrec_vectors, + check_backend_support, load_cryptrec_vectors, load_ed25519_vectors, load_fips_dsa_key_pair_vectors, load_fips_dsa_sig_vectors, load_fips_ecdsa_key_pair_vectors, load_fips_ecdsa_signing_vectors, load_hash_vectors, load_kasvs_dh_vectors, - load_kasvs_ecdh_vectors, load_nist_vectors, - load_pkcs1_vectors, load_rsa_nist_vectors, load_vectors_from_file, - raises_unsupported_algorithm, select_backends, skip_if_empty + load_kasvs_ecdh_vectors, load_nist_ccm_vectors, load_nist_kbkdf_vectors, + load_nist_vectors, load_pkcs1_vectors, load_rsa_nist_vectors, + load_vectors_from_file, load_x963_vectors, raises_unsupported_algorithm ) -class FakeInterface(object): - pass - - -def test_select_one_backend(): - b1 = pretend.stub(name="b1") - b2 = pretend.stub(name="b2") - b3 = pretend.stub(name="b3") - backends = [b1, b2, b3] - name = "b2" - selected_backends = select_backends(name, backends) - assert len(selected_backends) == 1 - assert selected_backends[0] == b2 - - -def test_select_no_backend(): - b1 = pretend.stub(name="b1") - b2 = pretend.stub(name="b2") - b3 = pretend.stub(name="b3") - backends = [b1, b2, b3] - name = "back!" - with pytest.raises(ValueError): - select_backends(name, backends) - - -def test_select_backends_none(): - b1 = pretend.stub(name="b1") - b2 = pretend.stub(name="b2") - b3 = pretend.stub(name="b3") - backends = [b1, b2, b3] - name = None - selected_backends = select_backends(name, backends) - assert len(selected_backends) == 3 - - -def test_select_two_backends(): - b1 = pretend.stub(name="b1") - b2 = pretend.stub(name="b2") - b3 = pretend.stub(name="b3") - backends = [b1, b2, b3] - name = "b2 ,b1 " - selected_backends = select_backends(name, backends) - assert len(selected_backends) == 2 - assert selected_backends == [b1, b2] - - -def test_skip_if_empty(): - with pytest.raises(pytest.skip.Exception): - skip_if_empty([], [FakeInterface]) - - skip_if_empty(["notempty"], [FakeInterface]) - - def test_check_backend_support_skip(): supported = pretend.stub( kwargs={"only_if": lambda backend: False, "skip_message": "Nope"} ) - item = pretend.stub(keywords={"supported": supported}, - funcargs={"backend": True}) + node = pretend.stub(iter_markers=lambda x: [supported]) + item = pretend.stub(node=node) with pytest.raises(pytest.skip.Exception) as exc_info: - check_backend_support(item) + check_backend_support(True, item) assert exc_info.value.args[0] == "Nope (True)" @@ -96,19 +43,9 @@ def test_check_backend_support_no_skip(): supported = pretend.stub( kwargs={"only_if": lambda backend: True, "skip_message": "Nope"} ) - item = pretend.stub(keywords={"supported": supported}, - funcargs={"backend": True}) - assert check_backend_support(item) is None - - -def test_check_backend_support_no_backend(): - supported = pretend.stub( - kwargs={"only_if": "notalambda", "skip_message": "Nope"} - ) - item = pretend.stub(keywords={"supported": supported}, - funcargs={}) - with pytest.raises(ValueError): - check_backend_support(item) + node = pretend.stub(iter_markers=lambda x: [supported]) + item = pretend.stub(node=node) + assert check_backend_support(None, item) is None def test_load_nist_vectors(): @@ -196,9 +133,101 @@ def test_load_nist_vectors_with_null_chars(): ] +def test_load_ed25519_vectors(): + vector_data = ( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60d75a9" + "80182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a:d75a98018" + "2b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a::e5564300c360" + "ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc" + "61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b:\n" + "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb3d401" + "7c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c:3d4017c3e" + "843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c:72:92a009a9f0" + "d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996" + "e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c0072:\n" + "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7fc51c" + "d8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:fc51cd8e6" + "218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:af82:6291d657" + "deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f" + "290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40aaf82:\n" + "0d4a05b07352a5436e180356da0ae6efa0345ff7fb1572575772e8005ed978e9e61a1" + "85bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057:e61a185bc" + "ef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057:cbc77b:d9868d" + "52c2bebce5f3fa5a79891970f309cb6591e3e1702a70276fa97c24b3a8e58606c38c9" + "758529da50ee31b8219cba45271c689afa60b0ea26c99db19b00ccbc77b:\n" + ).splitlines() + + assert load_ed25519_vectors(vector_data) == [ + { + "secret_key": ( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7" + "f60" + ), + "public_key": ( + "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f7075" + "11a" + ), + "message": "", + "signature": ( + "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490" + "1555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e" + "7a100b" + ) + }, + { + "secret_key": ( + "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a" + "6fb" + ), + "public_key": ( + "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af46" + "60c" + ), + "message": "72", + "signature": ( + "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb6" + "9da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612" + "bb0c00" + ) + }, + { + "secret_key": ( + "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b445" + "8f7" + ), + "public_key": ( + "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908" + "025" + ), + "message": "af82", + "signature": ( + "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac" + "3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea" + "1ec40a" + ) + }, + { + "secret_key": ( + "0d4a05b07352a5436e180356da0ae6efa0345ff7fb1572575772e8005ed97" + "8e9" + ), + "public_key": ( + "e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57" + "057" + ), + "message": "cbc77b", + "signature": ( + "d9868d52c2bebce5f3fa5a79891970f309cb6591e3e1702a70276fa97c24b" + "3a8e58606c38c9758529da50ee31b8219cba45271c689afa60b0ea26c99db" + "19b00c" + ) + }, + ] + + def test_load_cryptrec_vectors(): vector_data = textwrap.dedent(""" - # Vectors taken from http://info.isl.ntt.co.jp/crypt/eng/camellia/ + # Vectors taken from https://info.isl.ntt.co.jp/crypt/eng/camellia/ # Download is t_camelia.txt # Camellia with 128-bit key @@ -238,7 +267,7 @@ def test_load_cryptrec_vectors(): def test_load_cryptrec_vectors_invalid(): vector_data = textwrap.dedent(""" - # Vectors taken from http://info.isl.ntt.co.jp/crypt/eng/camellia/ + # Vectors taken from https://info.isl.ntt.co.jp/crypt/eng/camellia/ # Download is t_camelia.txt # Camellia with 128-bit key @@ -253,7 +282,7 @@ def test_load_cryptrec_vectors_invalid(): def test_load_hash_vectors(): vector_data = textwrap.dedent(""" - # http://tools.ietf.org/html/rfc1321 + # https://tools.ietf.org/html/rfc1321 [irrelevant] Len = 0 @@ -298,7 +327,7 @@ MD = 750c783e6ab0b503eaa86e310a5db738 def test_load_hash_vectors_bad_data(): vector_data = textwrap.dedent(""" - # http://tools.ietf.org/html/rfc1321 + # https://tools.ietf.org/html/rfc1321 Len = 0 Msg = 00 @@ -1487,42 +1516,6 @@ e80204646bf99b5771d249a6fea627 7383a06365af82225dd3713ca5a45006316f53bd12b0e260d5f79795e5a4c9f353f12867a1d320\ 2394673ada8563b71555e53f415254 - [mod = L=2048, N=224] - - P = 904ef8e31e14721910fa0969e77c99b79f190071a86026e37a887a6053960dbfb74390\ -a6641319fe0af32c4e982934b0f1f4c5bc57534e8e56d77c36f0a99080c0d5bc9022fa34f58922\ -81d7b1009571cb5b35699303f912b276d86b1b0722fc0b1500f0ffb2e4d90867a3bdca181a9734\ -617a8a9f991aa7c14dec1cf45ceba00600f8425440ed0c3b52c82e3aa831932a98b477da220867\ -eb2d5e0ca34580b33b1b65e558411ed09c369f4717bf03b551787e13d9e47c267c91c697225265\ -da157945cd8b32e84fc45b80533265239aa00a2dd3d05f5cb231b7daf724b7ecdce170360a8397\ -2e5be94626273d449f441be300a7345db387bebadad67d8060a7 - Q = d7d0a83e84d13032b830ed74a6a88592ec9a4cf42bf37080c6600aad - G = 2050b18d3c9f39fac396c009310d6616f9309b67b59aef9aee813d6b4f12ee29ba8a6b\ -350b11d4336d44b4641230002d870f1e6b1d8728bdd40262df0d2440999185ae077f7034c61679\ -f4360fbb5d181569e7cb8acb04371c11ba55f1bbd777b74304b99b66d4405303e7120dc8bc4785\ -f56e9533e65b63a0c77cce7bba0d5d6069df5edffa927c5a255a09405a008258ed93506a843366\ -2154f6f67e922d7c9788f04d4ec09581063950d9cde8e373ea59a58b2a6df6ba8663345574fabb\ -a9ca981696d83aeac1f34f14f1a813ba900b3f0341dea23f7d3297f919a97e1ae00ac0728c93fe\ -0a88b66591baf4eb0bc6900f39ba5feb41cbbeea7eb7919aa4d3 - - X = 3f19424da3b4f0cafca3fc5019fcd225dd7e496ffdf6b77e364f45be - Y = 7681ed0ac257ab7ff17c52de4638c0614749792707a0c0d23883697e34963df15c806f\ -a6206f7fafb3269018e7703bd1e6f518d13544331a017713dbbe0cee8da6c095271fbf24edb74a\ -44e18b1d3b835622f68d31921c67c83e8479d1972ed0cb106c68188fe22c044254251ebf880b90\ -49dc3b7958ef61e1e67d2f677d2a7d2ab6b7c42b70cc5dedc3e5de7459a2dbc70c69008553d7ff\ -b6bf81c012c8bd67bdddeaab9a4a4373027912a7c7d9cd9cfc6c81dffe0cc7a6d40c3b2065aee7\ -be80e3c35497d64c8045bc511edaf7314c84c56bd9f0fecf62262ea5b45b49a0cffb223713bdbd\ -3ad03a25a0bb2211eba41ffcd08ab0e1ad485c29a3fc25ee8359 - - X = 241396352dd26efe0e2e184da52fe2b61d9d51b91b5009674c447854 - Y = 2f07a3aa9884c65288e5fef56c7b7f4445632273290bae6fcaab87c90058b2bef81ad3\ -34958657cf649ffb976d618b34ce69ef6d68c0d8bfe275cf097a301e8dd5595958e0c668c15f67\ -b5c0b0d01983057ce61593635aab5e0564ed720b0336f055a86755c76be22df3b8487f16e2ba0b\ -5136fd30d7e3b1d30c3bd298d3acc0a1988a11756c94e9a53184d0d3edfbb649caf03eace3083d\ -e9933921e627f4b2e011d1c79e45d8ea1eb7e4e59a1cbd8382b3238474eb949749c985200fbb25\ -41e2dce080aa881945d4d935076e48a0846dc5513bb4da8563b946af54f546455931e79c065ce7\ -ca223a98f8fde40091d38eb2c3eb8e3b81d88374f3146b0afc42 - [mod = L=2048, N=256] P = ea1fb1af22881558ef93be8a5f8653c5a559434c49c8c2c12ace5e9c41434c9cf0a8e9\ @@ -1856,17 +1849,6 @@ d291b6da18eb0cbe51676ceb0977504eb97c27c0b191883f72fb2710a9fbd8bcf13be\ S = 18ea15bd9f00475b25204cbc23f8c23e01588015 Result = F (3 - R changed ) - [mod = L=2048, N=224, SHA-1] - - # unsupported so we ignore this - - Msg = f9d01693df99a125b4f17e184331c6b6e8ca00f54f3a - X = e0c4b7d58836046c436fbb2322 - Y = fb6d9bf23a07215093f319725ad0877accff - R = 5764e8dae0327c5bf1972ff7681b9 - S = 475b25204cbc23f8c23e01588015 - Result = F (3 - R changed ) - [mod = L=2048, N=256, SHA-384] P = e7c1c86125db9ef417da1ced7ea0861bdad629216a3f3c745df42a46b989e59f4d984\ @@ -3045,8 +3027,13 @@ d518475576730ed528779366568e46b7dd4ed787cb72d0733c93 assert expected == load_kasvs_dh_vectors(vector_data) +def test_load_kasvs_ecdh_vectors_empty_vector_data(): + assert [] == load_kasvs_ecdh_vectors([]) + + def test_load_kasvs_ecdh_vectors(): vector_data = textwrap.dedent(""" + # CAVS 11.0 # Parameter set(s) supported: EA EB EC ED EE # CAVSid: CAVSid (in hex: 434156536964) # IUTid: In hex: a1b2c3d4e5 @@ -3357,7 +3344,7 @@ def test_load_kasvs_ecdh_kdf_vectors(): [EB - SHA224] - COUNT = 0 + COUNT = 50 dsCAVS = 540904b67b3716823dd621ed72ad3dbc615887b4f56f910b78a57199 QsCAVSx = 28e5f3a72d8f6b8499dd1bcdfceafcecec68a0d715789bcf4b55fe15 QsCAVSy = 8c8006a7da7c1a19f5328d7e865522b0c0dfb9a29b2c46dc96590d2a @@ -3379,7 +3366,7 @@ ffdfa60dd7 expected = [ {'errno': 12, 'fail': True, - 'COUNT': 0, + 'COUNT': 50, 'CAVS': { 'd': int("540904b67b3716823dd621ed72ad3dbc615887b4f56f910b" "78a57199", 16), @@ -3405,6 +3392,363 @@ ffdfa60dd7 assert expected == load_kasvs_ecdh_vectors(vector_data) +def test_load_x963_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 12.0 + # 'ANS X9.63-2001' information for sample + + [SHA-1] + [shared secret length = 192] + [SharedInfo length = 0] + [key data length = 128] + + COUNT = 0 + Z = 1c7d7b5f0597b03d06a018466ed1a93e30ed4b04dc64ccdd + SharedInfo = + Counter = 00000001 + Hash input 1 = 1c7d7b5f0597b03d06a018466ed1a93e30ed4b04dc64ccdd00000001 + K1 = bf71dffd8f4d99223936beb46fee8ccc60439b7e + key_data = bf71dffd8f4d99223936beb46fee8ccc + + COUNT = 1 + Z = 5ed096510e3fcf782ceea98e9737993e2b21370f6cda2ab1 + SharedInfo = + Counter = 00000001 + Hash input 1 = 5ed096510e3fcf782ceea98e9737993e2b21370f6cda2ab100000001 + K1 = ec3e224446bfd7b3be1df404104af953c1b2d0f5 + key_data = ec3e224446bfd7b3be1df404104af953 + + [SHA-512] + [shared secret length = 521] + [SharedInfo length = 128] + [key data length = 1024] + + COUNT = 0 + Z = 00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9adb369348b8150739\ +2f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e753f55ef05a2d + SharedInfo = e3b5b4c1b0d5cf1d2b3a2f9937895d31 + Counter = 00000001 + Hash input 1 = 00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9ad\ +b369348b81507392f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e753f55ef05a2d0\ +0000001e3b5b4c1b0d5cf1d2b3a2f9937895d31 + K1 = 4463f869f3cc18769b52264b0112b5858f7ad32a5a2d96d8cffabf7fa733633d6\ +e4dd2a599acceb3ea54a6217ce0b50eef4f6b40a5c30250a5a8eeee20800226 + Counter = 00000002 + Hash input 2 = 00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9ad\ +b369348b81507392f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e753f55ef05a2d0\ +0000002e3b5b4c1b0d5cf1d2b3a2f9937895d31 + K2 = 7089dbf351f3f5022aa9638bf1ee419dea9c4ff745a25ac27bda33ca08bd56dd1\ +a59b4106cf2dbbc0ab2aa8e2efa7b17902d34276951ceccab87f9661c3e8816 + key_data = 4463f869f3cc18769b52264b0112b5858f7ad32a5a2d96d8cffabf7fa733633\ +d6e4dd2a599acceb3ea54a6217ce0b50eef4f6b40a5c30250a5a8eeee208002267089dbf351f3f\ +5022aa9638bf1ee419dea9c4ff745a25ac27bda33ca08bd56dd1a59b4106cf2dbbc0ab2aa8e2ef\ +a7b17902d34276951ceccab87f9661c3e8816 + """).splitlines() + + assert load_x963_vectors(vector_data) == [ + {"hash": "SHA-1", "count": 0, + "shared_secret_length": 192, + "Z": "1c7d7b5f0597b03d06a018466ed1a93e30ed4b04dc64ccdd", + "sharedinfo_length": 0, + "key_data_length": 128, + "key_data": "bf71dffd8f4d99223936beb46fee8ccc"}, + {"hash": "SHA-1", "count": 1, + "shared_secret_length": 192, + "Z": "5ed096510e3fcf782ceea98e9737993e2b21370f6cda2ab1", + "sharedinfo_length": 0, + "key_data_length": 128, + "key_data": "ec3e224446bfd7b3be1df404104af953"}, + {"hash": "SHA-512", "count": 0, + "shared_secret_length": 521, + "Z": "00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9adb369348b\ +81507392f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e753f55ef05a2d", + "sharedinfo_length": 128, + "sharedinfo": "e3b5b4c1b0d5cf1d2b3a2f9937895d31", + "key_data_length": 1024, + "key_data": "4463f869f3cc18769b52264b0112b5858f7ad32a5a2d96d8cffabf7f\ +a733633d6e4dd2a599acceb3ea54a6217ce0b50eef4f6b40a5c30250a5a8eeee208002267089db\ +f351f3f5022aa9638bf1ee419dea9c4ff745a25ac27bda33ca08bd56dd1a59b4106cf2dbbc0ab2\ +aa8e2efa7b17902d34276951ceccab87f9661c3e8816"}, + ] + + +def test_load_kbkdf_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 14.4 + # "SP800-108 - KDF" information for "test1" + # KDF Mode Supported: Counter Mode + # Location of counter tested: (Before Fixed Input Data)\ +( After Fixed Input Data)(In Middle of Fixed Input Data before Context) + # PRFs tested: CMAC with key sizes: AES128 AES192 AES256 TDES2 TDES3\ +HMAC with key sizes: SHA1 SHA224 SHA256 SHA384 SHA512 + # Generated on Tue Apr 23 12:20:16 2013 + + [PRF=HMAC_SHA1] + [CTRLOCATION=BEFORE_FIXED] + [RLEN=8_BITS] + + COUNT=0 + L = 128 + KI = 00a39bd547fb88b2d98727cf64c195c61e1cad6c + FixedInputDataByteLen = 60 + FixedInputData = 98132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc30056f68\ +76f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a68242 + Binary rep of i = 01 + instring = 0198132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc30056f68\ +76f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a68242 + KO = 0611e1903609b47ad7a5fc2c82e47702 + + COUNT=1 + L = 128 + KI = a39bdf744ed7e33fdec060c8736e9725179885a8 + FixedInputDataByteLen = 60 + FixedInputData = af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591e182\ +35019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e832 + Binary rep of i = 01 + instring = 01af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591e182\ +35019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e832 + KO = 51dc4668947e3685099bc3b5f8527468 + + [PRF=HMAC_SHA224] + [CTRLOCATION=AFTER_FIXED] + [RLEN=8_BITS] + + COUNT=0 + L = 128 + KI = ab56556b107a3a79fe084df0f1bb3ad049a6cc1490f20da4b3df282c + FixedInputDataByteLen = 60 + FixedInputData = 7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91ee\ +b5730d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4a7 + Binary rep of i = 01 + instring = 7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91eeb5\ +730d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4a701 + KO = b8894c6133a46701909b5c8a84322dec + """).splitlines() + + assert load_nist_kbkdf_vectors(vector_data) == [ + {'prf': 'hmac_sha1', + 'ctrlocation': 'before_fixed', + 'rlen': 8, + 'l': 128, + 'ki': b'00a39bd547fb88b2d98727cf64c195c61e1cad6c', + 'fixedinputdatabytelen': b'60', + 'fixedinputdata': b'98132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc\ +30056f6876f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a68242', + 'binary rep of i': b'01', + 'instring': b'0198132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc3005\ +6f6876f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a68242', + 'ko': b'0611e1903609b47ad7a5fc2c82e47702'}, + {'prf': 'hmac_sha1', + 'ctrlocation': 'before_fixed', + 'rlen': 8, + 'l': 128, + 'ki': b'a39bdf744ed7e33fdec060c8736e9725179885a8', + 'fixedinputdatabytelen': b'60', + 'fixedinputdata': b'af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9\ +c591e18235019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e832', + 'binary rep of i': b'01', + 'instring': b'01af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591\ +e18235019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e832', + 'ko': b'51dc4668947e3685099bc3b5f8527468'}, + {'prf': 'hmac_sha224', + 'ctrlocation': 'after_fixed', + 'rlen': 8, + 'l': 128, + 'ki': b'ab56556b107a3a79fe084df0f1bb3ad049a6cc1490f20da4b3df282c', + 'fixedinputdatabytelen': b'60', + 'fixedinputdata': b'7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43\ +aa1b91eeb5730d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4a7', + 'binary rep of i': b'01', + 'instring': b'7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91\ +eeb5730d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4a701', + 'ko': b'b8894c6133a46701909b5c8a84322dec'} + ] + + +def test_load_nist_ccm_vectors_dvpt(): + vector_data = textwrap.dedent(""" + # CAVS 11.0 + # "CCM-DVPT" information + # AES Keylen: 128 + # Generated on Tue Mar 15 08:09:25 2011 + + + [Alen = 0, Plen = 0, Nlen = 7, Tlen = 4] + + Key = 4ae701103c63deca5b5a3939d7d05992 + + Count = 0 + Nonce = 5a8aa485c316e9 + Adata = 00 + CT = 02209f55 + Result = Pass + Payload = 00 + + Count = 1 + Nonce = 3796cf51b87266 + Adata = 00 + CT = 9a04c241 + Result = Fail + + [Alen = 0, Plen = 0, Nlen = 7, Tlen = 16] + + Key = 4bb3c4a4f893ad8c9bdc833c325d62b3 + + Count = 15 + Nonce = 5a8aa485c316e9 + Adata = 00 + CT = 75d582db43ce9b13ab4b6f7f14341330 + Result = Pass + Payload = 00 + + Count = 16 + Nonce = 3796cf51b87266 + Adata = 00 + CT = 3a65e03af37b81d05acc7ec1bc39deb0 + Result = Fail + """).splitlines() + assert load_nist_ccm_vectors(vector_data) == [ + { + 'key': b'4ae701103c63deca5b5a3939d7d05992', + 'alen': 0, + 'plen': 0, + 'nlen': 7, + 'tlen': 4, + 'nonce': b'5a8aa485c316e9', + 'adata': b'00', + 'ct': b'02209f55', + 'fail': False, + 'payload': b'00' + }, + { + 'key': b'4ae701103c63deca5b5a3939d7d05992', + 'alen': 0, + 'plen': 0, + 'nlen': 7, + 'tlen': 4, + 'nonce': b'3796cf51b87266', + 'adata': b'00', + 'ct': b'9a04c241', + 'fail': True, + 'payload': b'00' + }, + { + 'key': b'4bb3c4a4f893ad8c9bdc833c325d62b3', + 'alen': 0, + 'plen': 0, + 'nlen': 7, + 'tlen': 16, + 'nonce': b'5a8aa485c316e9', + 'adata': b'00', + 'ct': b'75d582db43ce9b13ab4b6f7f14341330', + 'fail': False, + 'payload': b'00' + }, + { + 'key': b'4bb3c4a4f893ad8c9bdc833c325d62b3', + 'alen': 0, + 'plen': 0, + 'nlen': 7, + 'tlen': 16, + 'nonce': b'3796cf51b87266', + 'adata': b'00', + 'ct': b'3a65e03af37b81d05acc7ec1bc39deb0', + 'fail': True, + 'payload': b'00' + } + ] + + +def test_load_nist_ccm_vectors_vadt(): + vector_data = textwrap.dedent(""" + # CAVS 11.0 + # "CCM-VADT" information + # AES Keylen: 128 + # Generated on Tue Mar 15 08:09:24 2011 + + Plen = 24 + Nlen = 13 + Tlen = 16 + + [Alen = 0] + + Key = d24a3d3dde8c84830280cb87abad0bb3 + Nonce = f1100035bb24a8d26004e0e24b + + Count = 0 + Adata = 00 + Payload = 7c86135ed9c2a515aaae0e9a208133897269220f30870006 + CT = 1faeb0ee2ca2cd52f0aa3966578344f24e69b742c4ab37ab11233 + + Count = 1 + Adata = 00 + Payload = 48df73208cdc63d716752df7794807b1b2a80794a2433455 + CT = 2bf7d09079bc0b904c711a0b0e4a70ca8ea892d9566f03f8b77a1 + CT = 642145210f947bc4a0b1e678fd8c990c2c1d89d4110a95c954d61 + + [Alen = 1] + + Key = 08b0da255d2083808a1b4d367090bacc + Nonce = 777828b13679a9e2ca89568233 + + Count = 10 + Adata = dd + Payload = 1b156d7e2bf7c9a25ad91cff7b0b02161cb78ff9162286b0 + CT = e8b80af4960d5417c15726406e345c5c46831192b03432eed16b6 + + Count = 11 + Adata = c5 + Payload = 032fee9dbffccc751e6a1ee6d07bb218b3a7ec6bf5740ead + CT = f0828917020651c085e42459c544ec52e99372005362baf308ebe + """).splitlines() + assert load_nist_ccm_vectors(vector_data) == [ + { + 'plen': 24, + 'nlen': 13, + 'tlen': 16, + 'alen': 0, + 'key': b'd24a3d3dde8c84830280cb87abad0bb3', + 'nonce': b'f1100035bb24a8d26004e0e24b', + 'adata': b'00', + 'payload': b'7c86135ed9c2a515aaae0e9a208133897269220f30870006', + 'ct': b'1faeb0ee2ca2cd52f0aa3966578344f24e69b742c4ab37ab11233' + }, + { + 'plen': 24, + 'nlen': 13, + 'tlen': 16, + 'alen': 0, + 'key': b'd24a3d3dde8c84830280cb87abad0bb3', + 'nonce': b'f1100035bb24a8d26004e0e24b', + 'adata': b'00', + 'payload': b'48df73208cdc63d716752df7794807b1b2a80794a2433455', + 'ct': b'642145210f947bc4a0b1e678fd8c990c2c1d89d4110a95c954d61' + }, + { + 'plen': 24, + 'nlen': 13, + 'tlen': 16, + 'alen': 1, + 'key': b'08b0da255d2083808a1b4d367090bacc', + 'nonce': b'777828b13679a9e2ca89568233', + 'adata': b'dd', + 'payload': b'1b156d7e2bf7c9a25ad91cff7b0b02161cb78ff9162286b0', + 'ct': b'e8b80af4960d5417c15726406e345c5c46831192b03432eed16b6' + }, + { + 'plen': 24, + 'nlen': 13, + 'tlen': 16, + 'alen': 1, + 'key': b'08b0da255d2083808a1b4d367090bacc', + 'nonce': b'777828b13679a9e2ca89568233', + 'adata': b'c5', + 'payload': b'032fee9dbffccc751e6a1ee6d07bb218b3a7ec6bf5740ead', + 'ct': b'f0828917020651c085e42459c544ec52e99372005362baf308ebe' + } + ] + + def test_vector_version(): assert cryptography.__version__ == cryptography_vectors.__version__ diff --git a/tests/test_warnings.py b/tests/test_warnings.py index 9946baa7..d27e757f 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -8,6 +8,8 @@ import sys import types import warnings +import pytest + from cryptography.utils import deprecated @@ -45,3 +47,42 @@ class TestDeprecated(object): assert msg2.message.args == ("more deprecated text",) assert "Y" in dir(mod) + + def test_deleting_deprecated_members(self, monkeypatch): + mod = types.ModuleType("TestDeprecated/test_deprecated") + monkeypatch.setitem(sys.modules, mod.__name__, mod) + mod.X = deprecated( + value=1, + module_name=mod.__name__, + message="deprecated message text", + warning_class=DeprecationWarning + ) + mod.Y = deprecated( + value=2, + module_name=mod.__name__, + message="more deprecated text", + warning_class=PendingDeprecationWarning, + ) + mod = sys.modules[mod.__name__] + mod.Z = 3 + + with warnings.catch_warnings(record=True) as log: + warnings.simplefilter("always", PendingDeprecationWarning) + warnings.simplefilter("always", DeprecationWarning) + del mod.X + del mod.Y + del mod.Z + + [msg1, msg2] = log + assert msg1.category is DeprecationWarning + assert msg1.message.args == ("deprecated message text",) + + assert msg2.category is PendingDeprecationWarning + assert msg2.message.args == ("more deprecated text",) + + assert "X" not in dir(mod) + assert "Y" not in dir(mod) + assert "Z" not in dir(mod) + + with pytest.raises(AttributeError): + del mod.X diff --git a/tests/test_x509.py b/tests/test_x509.py deleted file mode 100644 index 08dae0ce..00000000 --- a/tests/test_x509.py +++ /dev/null @@ -1,1132 +0,0 @@ -# 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 datetime -import os - -import pytest - -import six - -from cryptography import x509 -from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.hazmat.backends.interfaces import ( - DSABackend, EllipticCurveBackend, RSABackend, X509Backend -) -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa - -from .hazmat.primitives.fixtures_dsa import DSA_KEY_2048 -from .hazmat.primitives.fixtures_rsa import RSA_KEY_2048 -from .hazmat.primitives.test_ec import _skip_curve_unsupported -from .utils import load_vectors_from_file - - -def _load_cert(filename, loader, backend): - cert = load_vectors_from_file( - filename=filename, - loader=lambda pemfile: loader(pemfile.read(), backend), - mode="rb" - ) - return cert - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestRSACertificate(object): - def test_load_pem_cert(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "post2000utctime.pem"), - x509.load_pem_x509_certificate, - backend - ) - assert isinstance(cert, x509.Certificate) - assert cert.serial == 11559813051657483483 - fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) - assert fingerprint == b"2b619ed04bfc9c3b08eb677d272192286a0947a8" - assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) - - def test_load_der_cert(self, backend): - cert = _load_cert( - os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), - x509.load_der_x509_certificate, - backend - ) - assert isinstance(cert, x509.Certificate) - assert cert.serial == 2 - fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) - assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" - assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) - - def test_issuer(self, backend): - cert = _load_cert( - os.path.join( - "x509", "PKITS_data", "certs", - "Validpre2000UTCnotBeforeDateTest3EE.crt" - ), - x509.load_der_x509_certificate, - backend - ) - issuer = cert.issuer - assert isinstance(issuer, x509.Name) - assert list(issuer) == [ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute( - x509.OID_ORGANIZATION_NAME, u'Test Certificates 2011' - ), - x509.NameAttribute(x509.OID_COMMON_NAME, u'Good CA') - ] - assert issuer.get_attributes_for_oid(x509.OID_COMMON_NAME) == [ - x509.NameAttribute(x509.OID_COMMON_NAME, u'Good CA') - ] - - def test_all_issuer_name_types(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", - "all_supported_names.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - issuer = cert.issuer - - assert isinstance(issuer, x509.Name) - assert list(issuer) == [ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'CA'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Illinois'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Chicago'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'Zero, LLC'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'One, LLC'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'common name 0'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'common name 1'), - x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, u'OU 0'), - x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, u'OU 1'), - x509.NameAttribute(x509.OID_DN_QUALIFIER, u'dnQualifier0'), - x509.NameAttribute(x509.OID_DN_QUALIFIER, u'dnQualifier1'), - x509.NameAttribute(x509.OID_SERIAL_NUMBER, u'123'), - x509.NameAttribute(x509.OID_SERIAL_NUMBER, u'456'), - x509.NameAttribute(x509.OID_TITLE, u'Title 0'), - x509.NameAttribute(x509.OID_TITLE, u'Title 1'), - x509.NameAttribute(x509.OID_SURNAME, u'Surname 0'), - x509.NameAttribute(x509.OID_SURNAME, u'Surname 1'), - x509.NameAttribute(x509.OID_GIVEN_NAME, u'Given Name 0'), - x509.NameAttribute(x509.OID_GIVEN_NAME, u'Given Name 1'), - x509.NameAttribute(x509.OID_PSEUDONYM, u'Incognito 0'), - x509.NameAttribute(x509.OID_PSEUDONYM, u'Incognito 1'), - x509.NameAttribute(x509.OID_GENERATION_QUALIFIER, u'Last Gen'), - x509.NameAttribute(x509.OID_GENERATION_QUALIFIER, u'Next Gen'), - x509.NameAttribute(x509.OID_DOMAIN_COMPONENT, u'dc0'), - x509.NameAttribute(x509.OID_DOMAIN_COMPONENT, u'dc1'), - x509.NameAttribute(x509.OID_EMAIL_ADDRESS, u'test0@test.local'), - x509.NameAttribute(x509.OID_EMAIL_ADDRESS, u'test1@test.local'), - ] - - def test_subject(self, backend): - cert = _load_cert( - os.path.join( - "x509", "PKITS_data", "certs", - "Validpre2000UTCnotBeforeDateTest3EE.crt" - ), - x509.load_der_x509_certificate, - backend - ) - subject = cert.subject - assert isinstance(subject, x509.Name) - assert list(subject) == [ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute( - x509.OID_ORGANIZATION_NAME, u'Test Certificates 2011' - ), - x509.NameAttribute( - x509.OID_COMMON_NAME, - u'Valid pre2000 UTC notBefore Date EE Certificate Test3' - ) - ] - assert subject.get_attributes_for_oid(x509.OID_COMMON_NAME) == [ - x509.NameAttribute( - x509.OID_COMMON_NAME, - u'Valid pre2000 UTC notBefore Date EE Certificate Test3' - ) - ] - - def test_unicode_name(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", - "utf8_common_name.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - assert cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME) == [ - x509.NameAttribute( - x509.OID_COMMON_NAME, - u'We heart UTF8!\u2122' - ) - ] - assert cert.issuer.get_attributes_for_oid(x509.OID_COMMON_NAME) == [ - x509.NameAttribute( - x509.OID_COMMON_NAME, - u'We heart UTF8!\u2122' - ) - ] - - def test_all_subject_name_types(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", - "all_supported_names.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - subject = cert.subject - assert isinstance(subject, x509.Name) - assert list(subject) == [ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'AU'), - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'DE'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'California'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'New York'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'San Francisco'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Ithaca'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'Org Zero, LLC'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'Org One, LLC'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'CN 0'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'CN 1'), - x509.NameAttribute( - x509.OID_ORGANIZATIONAL_UNIT_NAME, u'Engineering 0' - ), - x509.NameAttribute( - x509.OID_ORGANIZATIONAL_UNIT_NAME, u'Engineering 1' - ), - x509.NameAttribute(x509.OID_DN_QUALIFIER, u'qualified0'), - x509.NameAttribute(x509.OID_DN_QUALIFIER, u'qualified1'), - x509.NameAttribute(x509.OID_SERIAL_NUMBER, u'789'), - x509.NameAttribute(x509.OID_SERIAL_NUMBER, u'012'), - x509.NameAttribute(x509.OID_TITLE, u'Title IX'), - x509.NameAttribute(x509.OID_TITLE, u'Title X'), - x509.NameAttribute(x509.OID_SURNAME, u'Last 0'), - x509.NameAttribute(x509.OID_SURNAME, u'Last 1'), - x509.NameAttribute(x509.OID_GIVEN_NAME, u'First 0'), - x509.NameAttribute(x509.OID_GIVEN_NAME, u'First 1'), - x509.NameAttribute(x509.OID_PSEUDONYM, u'Guy Incognito 0'), - x509.NameAttribute(x509.OID_PSEUDONYM, u'Guy Incognito 1'), - x509.NameAttribute(x509.OID_GENERATION_QUALIFIER, u'32X'), - x509.NameAttribute(x509.OID_GENERATION_QUALIFIER, u'Dreamcast'), - x509.NameAttribute(x509.OID_DOMAIN_COMPONENT, u'dc2'), - x509.NameAttribute(x509.OID_DOMAIN_COMPONENT, u'dc3'), - x509.NameAttribute(x509.OID_EMAIL_ADDRESS, u'test2@test.local'), - x509.NameAttribute(x509.OID_EMAIL_ADDRESS, u'test3@test.local'), - ] - - def test_load_good_ca_cert(self, backend): - cert = _load_cert( - os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), - x509.load_der_x509_certificate, - backend - ) - - assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) - assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) - assert cert.serial == 2 - public_key = cert.public_key() - assert isinstance(public_key, rsa.RSAPublicKey) - assert cert.version is x509.Version.v3 - fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) - assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" - - def test_utc_pre_2000_not_before_cert(self, backend): - cert = _load_cert( - os.path.join( - "x509", "PKITS_data", "certs", - "Validpre2000UTCnotBeforeDateTest3EE.crt" - ), - x509.load_der_x509_certificate, - backend - ) - - assert cert.not_valid_before == datetime.datetime(1950, 1, 1, 12, 1) - - def test_pre_2000_utc_not_after_cert(self, backend): - cert = _load_cert( - os.path.join( - "x509", "PKITS_data", "certs", - "Invalidpre2000UTCEEnotAfterDateTest7EE.crt" - ), - x509.load_der_x509_certificate, - backend - ) - - assert cert.not_valid_after == datetime.datetime(1999, 1, 1, 12, 1) - - def test_post_2000_utc_cert(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "post2000utctime.pem"), - x509.load_pem_x509_certificate, - backend - ) - assert cert.not_valid_before == datetime.datetime( - 2014, 11, 26, 21, 41, 20 - ) - assert cert.not_valid_after == datetime.datetime( - 2014, 12, 26, 21, 41, 20 - ) - - def test_generalized_time_not_before_cert(self, backend): - cert = _load_cert( - os.path.join( - "x509", "PKITS_data", "certs", - "ValidGeneralizedTimenotBeforeDateTest4EE.crt" - ), - x509.load_der_x509_certificate, - backend - ) - assert cert.not_valid_before == datetime.datetime(2002, 1, 1, 12, 1) - assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) - assert cert.version is x509.Version.v3 - - def test_generalized_time_not_after_cert(self, backend): - cert = _load_cert( - os.path.join( - "x509", "PKITS_data", "certs", - "ValidGeneralizedTimenotAfterDateTest8EE.crt" - ), - x509.load_der_x509_certificate, - backend - ) - assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) - assert cert.not_valid_after == datetime.datetime(2050, 1, 1, 12, 1) - assert cert.version is x509.Version.v3 - - def test_invalid_version_cert(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "invalid_version.pem"), - x509.load_pem_x509_certificate, - backend - ) - with pytest.raises(x509.InvalidVersion) as exc: - cert.version - - assert exc.value.parsed_version == 7 - - def test_eq(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "post2000utctime.pem"), - x509.load_pem_x509_certificate, - backend - ) - cert2 = _load_cert( - os.path.join("x509", "custom", "post2000utctime.pem"), - x509.load_pem_x509_certificate, - backend - ) - assert cert == cert2 - - def test_ne(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "post2000utctime.pem"), - x509.load_pem_x509_certificate, - backend - ) - cert2 = _load_cert( - os.path.join( - "x509", "PKITS_data", "certs", - "ValidGeneralizedTimenotAfterDateTest8EE.crt" - ), - x509.load_der_x509_certificate, - backend - ) - assert cert != cert2 - assert cert != object() - - def test_version_1_cert(self, backend): - cert = _load_cert( - os.path.join("x509", "v1_cert.pem"), - x509.load_pem_x509_certificate, - backend - ) - assert cert.version is x509.Version.v1 - - def test_invalid_pem(self, backend): - with pytest.raises(ValueError): - x509.load_pem_x509_certificate(b"notacert", backend) - - def test_invalid_der(self, backend): - with pytest.raises(ValueError): - x509.load_der_x509_certificate(b"notacert", backend) - - def test_unsupported_signature_hash_algorithm_cert(self, backend): - cert = _load_cert( - os.path.join("x509", "verisign_md2_root.pem"), - x509.load_pem_x509_certificate, - backend - ) - with pytest.raises(UnsupportedAlgorithm): - cert.signature_hash_algorithm - - def test_public_bytes_pem(self, backend): - # Load an existing certificate. - cert = _load_cert( - os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), - x509.load_der_x509_certificate, - backend - ) - - # Encode it to PEM and load it back. - cert = x509.load_pem_x509_certificate(cert.public_bytes( - encoding=serialization.Encoding.PEM, - ), backend) - - # We should recover what we had to start with. - assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) - assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) - assert cert.serial == 2 - public_key = cert.public_key() - assert isinstance(public_key, rsa.RSAPublicKey) - assert cert.version is x509.Version.v3 - fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) - assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" - - def test_public_bytes_der(self, backend): - # Load an existing certificate. - cert = _load_cert( - os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), - x509.load_der_x509_certificate, - backend - ) - - # Encode it to DER and load it back. - cert = x509.load_der_x509_certificate(cert.public_bytes( - encoding=serialization.Encoding.DER, - ), backend) - - # We should recover what we had to start with. - assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) - assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) - assert cert.serial == 2 - public_key = cert.public_key() - assert isinstance(public_key, rsa.RSAPublicKey) - assert cert.version is x509.Version.v3 - fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) - assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" - - def test_public_bytes_invalid_encoding(self, backend): - cert = _load_cert( - os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), - x509.load_der_x509_certificate, - backend - ) - - with pytest.raises(TypeError): - cert.public_bytes('NotAnEncoding') - - @pytest.mark.parametrize( - ("cert_path", "loader_func", "encoding"), - [ - ( - os.path.join("x509", "v1_cert.pem"), - x509.load_pem_x509_certificate, - serialization.Encoding.PEM, - ), - ( - os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), - x509.load_der_x509_certificate, - serialization.Encoding.DER, - ), - ] - ) - def test_public_bytes_match(self, cert_path, loader_func, encoding, - backend): - cert_bytes = load_vectors_from_file( - cert_path, lambda pemfile: pemfile.read(), mode="rb" - ) - cert = loader_func(cert_bytes, backend) - serialized = cert.public_bytes(encoding) - assert serialized == cert_bytes - - def test_certificate_repr(self, backend): - cert = _load_cert( - os.path.join( - "x509", "cryptography.io.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - if six.PY3: - assert repr(cert) == ( - "<Certificate(subject=<Name([<NameAttribute(oid=<ObjectIdentif" - "ier(oid=2.5.4.11, name=organizationalUnitName)>, value='GT487" - "42965')>, <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.11, " - "name=organizationalUnitName)>, value='See www.rapidssl.com/re" - "sources/cps (c)14')>, <NameAttribute(oid=<ObjectIdentifier(oi" - "d=2.5.4.11, name=organizationalUnitName)>, value='Domain Cont" - "rol Validated - RapidSSL(R)')>, <NameAttribute(oid=<ObjectIde" - "ntifier(oid=2.5.4.3, name=commonName)>, value='www.cryptograp" - "hy.io')>])>, ...)>" - ) - else: - assert repr(cert) == ( - "<Certificate(subject=<Name([<NameAttribute(oid=<ObjectIdentif" - "ier(oid=2.5.4.11, name=organizationalUnitName)>, value=u'GT48" - "742965')>, <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.11," - " name=organizationalUnitName)>, value=u'See www.rapidssl.com/" - "resources/cps (c)14')>, <NameAttribute(oid=<ObjectIdentifier(" - "oid=2.5.4.11, name=organizationalUnitName)>, value=u'Domain C" - "ontrol Validated - RapidSSL(R)')>, <NameAttribute(oid=<Object" - "Identifier(oid=2.5.4.3, name=commonName)>, value=u'www.crypto" - "graphy.io')>])>, ...)>" - ) - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestRSACertificateRequest(object): - @pytest.mark.parametrize( - ("path", "loader_func"), - [ - [ - os.path.join("x509", "requests", "rsa_sha1.pem"), - x509.load_pem_x509_csr - ], - [ - os.path.join("x509", "requests", "rsa_sha1.der"), - x509.load_der_x509_csr - ], - ] - ) - def test_load_rsa_certificate_request(self, path, loader_func, backend): - request = _load_cert(path, loader_func, backend) - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) - public_key = request.public_key() - assert isinstance(public_key, rsa.RSAPublicKey) - subject = request.subject - assert isinstance(subject, x509.Name) - assert list(subject) == [ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), - ] - extensions = request.extensions - assert isinstance(extensions, x509.Extensions) - assert list(extensions) == [] - - @pytest.mark.parametrize( - "loader_func", - [x509.load_pem_x509_csr, x509.load_der_x509_csr] - ) - def test_invalid_certificate_request(self, loader_func, backend): - with pytest.raises(ValueError): - loader_func(b"notacsr", backend) - - def test_unsupported_signature_hash_algorithm_request(self, backend): - request = _load_cert( - os.path.join("x509", "requests", "rsa_md4.pem"), - x509.load_pem_x509_csr, - backend - ) - with pytest.raises(UnsupportedAlgorithm): - request.signature_hash_algorithm - - def test_duplicate_extension(self, backend): - request = _load_cert( - os.path.join( - "x509", "requests", "two_basic_constraints.pem" - ), - x509.load_pem_x509_csr, - backend - ) - with pytest.raises(x509.DuplicateExtension) as exc: - request.extensions - - assert exc.value.oid == x509.OID_BASIC_CONSTRAINTS - - def test_unsupported_critical_extension(self, backend): - request = _load_cert( - os.path.join( - "x509", "requests", "unsupported_extension_critical.pem" - ), - x509.load_pem_x509_csr, - backend - ) - with pytest.raises(x509.UnsupportedExtension) as exc: - request.extensions - - assert exc.value.oid == x509.ObjectIdentifier('1.2.3.4') - - def test_unsupported_extension(self, backend): - request = _load_cert( - os.path.join( - "x509", "requests", "unsupported_extension.pem" - ), - x509.load_pem_x509_csr, - backend - ) - extensions = request.extensions - assert len(extensions) == 0 - - def test_request_basic_constraints(self, backend): - request = _load_cert( - os.path.join( - "x509", "requests", "basic_constraints.pem" - ), - x509.load_pem_x509_csr, - backend - ) - extensions = request.extensions - assert isinstance(extensions, x509.Extensions) - assert list(extensions) == [ - x509.Extension( - x509.OID_BASIC_CONSTRAINTS, - True, - x509.BasicConstraints(ca=True, path_length=1), - ), - ] - - def test_public_bytes_pem(self, backend): - # Load an existing CSR. - request = _load_cert( - os.path.join("x509", "requests", "rsa_sha1.pem"), - x509.load_pem_x509_csr, - backend - ) - - # Encode it to PEM and load it back. - request = x509.load_pem_x509_csr(request.public_bytes( - encoding=serialization.Encoding.PEM, - ), backend) - - # We should recover what we had to start with. - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) - public_key = request.public_key() - assert isinstance(public_key, rsa.RSAPublicKey) - subject = request.subject - assert isinstance(subject, x509.Name) - assert list(subject) == [ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), - ] - - def test_public_bytes_der(self, backend): - # Load an existing CSR. - request = _load_cert( - os.path.join("x509", "requests", "rsa_sha1.pem"), - x509.load_pem_x509_csr, - backend - ) - - # Encode it to DER and load it back. - request = x509.load_der_x509_csr(request.public_bytes( - encoding=serialization.Encoding.DER, - ), backend) - - # We should recover what we had to start with. - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) - public_key = request.public_key() - assert isinstance(public_key, rsa.RSAPublicKey) - subject = request.subject - assert isinstance(subject, x509.Name) - assert list(subject) == [ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), - ] - - def test_public_bytes_invalid_encoding(self, backend): - request = _load_cert( - os.path.join("x509", "requests", "rsa_sha1.pem"), - x509.load_pem_x509_csr, - backend - ) - - with pytest.raises(TypeError): - request.public_bytes('NotAnEncoding') - - @pytest.mark.parametrize( - ("request_path", "loader_func", "encoding"), - [ - ( - os.path.join("x509", "requests", "rsa_sha1.pem"), - x509.load_pem_x509_csr, - serialization.Encoding.PEM, - ), - ( - os.path.join("x509", "requests", "rsa_sha1.der"), - x509.load_der_x509_csr, - serialization.Encoding.DER, - ), - ] - ) - def test_public_bytes_match(self, request_path, loader_func, encoding, - backend): - request_bytes = load_vectors_from_file( - request_path, lambda pemfile: pemfile.read(), mode="rb" - ) - request = loader_func(request_bytes, backend) - serialized = request.public_bytes(encoding) - assert serialized == request_bytes - - -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestCertificateSigningRequestBuilder(object): - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_sign_invalid_hash_algorithm(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - - builder = x509.CertificateSigningRequestBuilder() - with pytest.raises(TypeError): - builder.sign(private_key, 'NotAHash', backend) - - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_build_ca_request_with_rsa(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - - request = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([ - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - ]) - ).add_extension( - x509.BasicConstraints(ca=True, path_length=2), critical=True - ).sign(private_key, hashes.SHA1(), backend) - - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) - public_key = request.public_key() - assert isinstance(public_key, rsa.RSAPublicKey) - subject = request.subject - assert isinstance(subject, x509.Name) - assert list(subject) == [ - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - ] - basic_constraints = request.extensions.get_extension_for_oid( - x509.OID_BASIC_CONSTRAINTS - ) - assert basic_constraints.value.ca is True - assert basic_constraints.value.path_length == 2 - - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_build_ca_request_with_unicode(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - - request = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([ - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, - u'PyCA\U0001f37a'), - ]) - ).add_extension( - x509.BasicConstraints(ca=True, path_length=2), critical=True - ).sign(private_key, hashes.SHA1(), backend) - - loaded_request = x509.load_pem_x509_csr( - request.public_bytes(encoding=serialization.Encoding.PEM), backend - ) - subject = loaded_request.subject - assert isinstance(subject, x509.Name) - assert list(subject) == [ - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA\U0001f37a'), - ] - - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_build_nonca_request_with_rsa(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - - request = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - ]) - ).add_extension( - x509.BasicConstraints(ca=False, path_length=None), critical=True, - ).sign(private_key, hashes.SHA1(), backend) - - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) - public_key = request.public_key() - assert isinstance(public_key, rsa.RSAPublicKey) - subject = request.subject - assert isinstance(subject, x509.Name) - assert list(subject) == [ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - ] - basic_constraints = request.extensions.get_extension_for_oid( - x509.OID_BASIC_CONSTRAINTS - ) - assert basic_constraints.value.ca is False - assert basic_constraints.value.path_length is None - - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) - def test_build_ca_request_with_ec(self, backend): - if backend._lib.OPENSSL_VERSION_NUMBER < 0x10001000: - pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") - - _skip_curve_unsupported(backend, ec.SECP256R1()) - private_key = ec.generate_private_key(ec.SECP256R1(), backend) - - request = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([ - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - ]) - ).add_extension( - x509.BasicConstraints(ca=True, path_length=2), critical=True - ).sign(private_key, hashes.SHA1(), backend) - - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) - public_key = request.public_key() - assert isinstance(public_key, ec.EllipticCurvePublicKey) - subject = request.subject - assert isinstance(subject, x509.Name) - assert list(subject) == [ - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - ] - basic_constraints = request.extensions.get_extension_for_oid( - x509.OID_BASIC_CONSTRAINTS - ) - assert basic_constraints.value.ca is True - assert basic_constraints.value.path_length == 2 - - @pytest.mark.requires_backend_interface(interface=DSABackend) - def test_build_ca_request_with_dsa(self, backend): - if backend._lib.OPENSSL_VERSION_NUMBER < 0x10001000: - pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") - - private_key = DSA_KEY_2048.private_key(backend) - - request = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - ]) - ).add_extension( - x509.BasicConstraints(ca=True, path_length=2), critical=True - ).sign(private_key, hashes.SHA1(), backend) - - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) - public_key = request.public_key() - assert isinstance(public_key, dsa.DSAPublicKey) - subject = request.subject - assert isinstance(subject, x509.Name) - assert list(subject) == [ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - ] - basic_constraints = request.extensions.get_extension_for_oid( - x509.OID_BASIC_CONSTRAINTS - ) - assert basic_constraints.value.ca is True - assert basic_constraints.value.path_length == 2 - - def test_add_duplicate_extension(self): - builder = x509.CertificateSigningRequestBuilder().add_extension( - x509.BasicConstraints(True, 2), critical=True, - ) - with pytest.raises(ValueError): - builder.add_extension( - x509.BasicConstraints(True, 2), critical=True, - ) - - def test_set_invalid_subject(self): - builder = x509.CertificateSigningRequestBuilder() - with pytest.raises(TypeError): - builder.subject_name('NotAName') - - def test_add_unsupported_extension(self): - builder = x509.CertificateSigningRequestBuilder() - with pytest.raises(NotImplementedError): - builder.add_extension( - x509.AuthorityKeyIdentifier('keyid', None, None), - critical=False, - ) - - def test_add_unsupported_extension_in_backend(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - builder = x509.CertificateSigningRequestBuilder() - builder = builder.subject_name( - x509.Name([ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - ]) - ).add_extension( - x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), - critical=False, - ) - with pytest.raises(NotImplementedError): - builder.sign(private_key, hashes.SHA256(), backend) - - def test_set_subject_twice(self): - builder = x509.CertificateSigningRequestBuilder() - builder = builder.subject_name( - x509.Name([ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - ]) - ) - with pytest.raises(ValueError): - builder.subject_name( - x509.Name([ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - ]) - ) - - -@pytest.mark.requires_backend_interface(interface=DSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestDSACertificate(object): - def test_load_dsa_cert(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), - x509.load_pem_x509_certificate, - backend - ) - assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) - public_key = cert.public_key() - assert isinstance(public_key, dsa.DSAPublicKey) - if isinstance(public_key, dsa.DSAPublicKeyWithSerialization): - num = public_key.public_numbers() - assert num.y == int( - "4c08bfe5f2d76649c80acf7d431f6ae2124b217abc8c9f6aca776ddfa94" - "53b6656f13e543684cd5f6431a314377d2abfa068b7080cb8ddc065afc2" - "dea559f0b584c97a2b235b9b69b46bc6de1aed422a6f341832618bcaae2" - "198aba388099dafb05ff0b5efecb3b0ae169a62e1c72022af50ae68af3b" - "033c18e6eec1f7df4692c456ccafb79cc7e08da0a5786e9816ceda651d6" - "1b4bb7b81c2783da97cea62df67af5e85991fdc13aff10fc60e06586386" - "b96bb78d65750f542f86951e05a6d81baadbcd35a2e5cad4119923ae6a2" - "002091a3d17017f93c52970113cdc119970b9074ca506eac91c3dd37632" - "5df4af6b3911ef267d26623a5a1c5df4a6d13f1c", 16 - ) - assert num.parameter_numbers.g == int( - "4b7ced71dc353965ecc10d441a9a06fc24943a32d66429dd5ef44d43e67" - "d789d99770aec32c0415dc92970880872da45fef8dd1e115a3e4801387b" - "a6d755861f062fd3b6e9ea8e2641152339b828315b1528ee6c7b79458d2" - "1f3db973f6fc303f9397174c2799dd2351282aa2d8842c357a73495bbaa" - "c4932786414c55e60d73169f5761036fba29e9eebfb049f8a3b1b7cee6f" - "3fbfa136205f130bee2cf5b9c38dc1095d4006f2e73335c07352c64130a" - "1ab2b89f13b48f628d3cc3868beece9bb7beade9f830eacc6fa241425c0" - "b3fcc0df416a0c89f7bf35668d765ec95cdcfbe9caff49cfc156c668c76" - "fa6247676a6d3ac945844a083509c6a1b436baca", 16 - ) - assert num.parameter_numbers.p == int( - "bfade6048e373cd4e48b677e878c8e5b08c02102ae04eb2cb5c46a523a3" - "af1c73d16b24f34a4964781ae7e50500e21777754a670bd19a7420d6330" - "84e5556e33ca2c0e7d547ea5f46a07a01bf8669ae3bdec042d9b2ae5e6e" - "cf49f00ba9dac99ab6eff140d2cedf722ee62c2f9736857971444c25d0a" - "33d2017dc36d682a1054fe2a9428dda355a851ce6e6d61e03e419fd4ca4" - "e703313743d86caa885930f62ed5bf342d8165627681e9cc3244ba72aa2" - "2148400a6bbe80154e855d042c9dc2a3405f1e517be9dea50562f56da93" - "f6085f844a7e705c1f043e65751c583b80d29103e590ccb26efdaa0893d" - "833e36468f3907cfca788a3cb790f0341c8a31bf", 16 - ) - assert num.parameter_numbers.q == int( - "822ff5d234e073b901cf5941f58e1f538e71d40d", 16 - ) - - @pytest.mark.parametrize( - ("path", "loader_func"), - [ - [ - os.path.join("x509", "requests", "dsa_sha1.pem"), - x509.load_pem_x509_csr - ], - [ - os.path.join("x509", "requests", "dsa_sha1.der"), - x509.load_der_x509_csr - ], - ] - ) - def test_load_dsa_request(self, path, loader_func, backend): - request = _load_cert(path, loader_func, backend) - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) - public_key = request.public_key() - assert isinstance(public_key, dsa.DSAPublicKey) - subject = request.subject - assert isinstance(subject, x509.Name) - assert list(subject) == [ - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - ] - - -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestECDSACertificate(object): - def test_load_ecdsa_cert(self, backend): - _skip_curve_unsupported(backend, ec.SECP384R1()) - cert = _load_cert( - os.path.join("x509", "ecdsa_root.pem"), - x509.load_pem_x509_certificate, - backend - ) - assert isinstance(cert.signature_hash_algorithm, hashes.SHA384) - public_key = cert.public_key() - assert isinstance(public_key, ec.EllipticCurvePublicKey) - if isinstance(public_key, ec.EllipticCurvePublicKeyWithSerialization): - num = public_key.public_numbers() - assert num.x == int( - "dda7d9bb8ab80bfb0b7f21d2f0bebe73f3335d1abc34eadec69bbcd095f" - "6f0ccd00bba615b51467e9e2d9fee8e630c17", 16 - ) - assert num.y == int( - "ec0770f5cf842e40839ce83f416d3badd3a4145936789d0343ee10136c7" - "2deae88a7a16bb543ce67dc23ff031ca3e23e", 16 - ) - assert isinstance(num.curve, ec.SECP384R1) - - def test_load_ecdsa_no_named_curve(self, backend): - _skip_curve_unsupported(backend, ec.SECP256R1()) - cert = _load_cert( - os.path.join("x509", "custom", "ec_no_named_curve.pem"), - x509.load_pem_x509_certificate, - backend - ) - with pytest.raises(NotImplementedError): - cert.public_key() - - @pytest.mark.parametrize( - ("path", "loader_func"), - [ - [ - os.path.join("x509", "requests", "ec_sha256.pem"), - x509.load_pem_x509_csr - ], - [ - os.path.join("x509", "requests", "ec_sha256.der"), - x509.load_der_x509_csr - ], - ] - ) - def test_load_ecdsa_certificate_request(self, path, loader_func, backend): - _skip_curve_unsupported(backend, ec.SECP384R1()) - request = _load_cert(path, loader_func, backend) - assert isinstance(request.signature_hash_algorithm, hashes.SHA256) - public_key = request.public_key() - assert isinstance(public_key, ec.EllipticCurvePublicKey) - subject = request.subject - assert isinstance(subject, x509.Name) - assert list(subject) == [ - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - ] - - -class TestNameAttribute(object): - def test_init_bad_oid(self): - with pytest.raises(TypeError): - x509.NameAttribute(None, u'value') - - def test_init_bad_value(self): - with pytest.raises(TypeError): - x509.NameAttribute( - x509.ObjectIdentifier('oid'), - b'bytes' - ) - - def test_eq(self): - assert x509.NameAttribute( - x509.ObjectIdentifier('oid'), u'value' - ) == x509.NameAttribute( - x509.ObjectIdentifier('oid'), u'value' - ) - - def test_ne(self): - assert x509.NameAttribute( - x509.ObjectIdentifier('2.5.4.3'), u'value' - ) != x509.NameAttribute( - x509.ObjectIdentifier('2.5.4.5'), u'value' - ) - assert x509.NameAttribute( - x509.ObjectIdentifier('oid'), u'value' - ) != x509.NameAttribute( - x509.ObjectIdentifier('oid'), u'value2' - ) - assert x509.NameAttribute( - x509.ObjectIdentifier('oid'), u'value' - ) != object() - - def test_repr(self): - na = x509.NameAttribute(x509.ObjectIdentifier('2.5.4.3'), u'value') - if six.PY3: - assert repr(na) == ( - "<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commo" - "nName)>, value='value')>" - ) - else: - assert repr(na) == ( - "<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commo" - "nName)>, value=u'value')>" - ) - - -class TestObjectIdentifier(object): - def test_eq(self): - oid1 = x509.ObjectIdentifier('oid') - oid2 = x509.ObjectIdentifier('oid') - assert oid1 == oid2 - - def test_ne(self): - oid1 = x509.ObjectIdentifier('oid') - assert oid1 != x509.ObjectIdentifier('oid1') - assert oid1 != object() - - def test_repr(self): - oid = x509.ObjectIdentifier("2.5.4.3") - assert repr(oid) == "<ObjectIdentifier(oid=2.5.4.3, name=commonName)>" - oid = x509.ObjectIdentifier("oid1") - assert repr(oid) == "<ObjectIdentifier(oid=oid1, name=Unknown OID)>" - - -class TestName(object): - def test_eq(self): - name1 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('oid'), u'value1'), - x509.NameAttribute(x509.ObjectIdentifier('oid2'), u'value2'), - ]) - name2 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('oid'), u'value1'), - x509.NameAttribute(x509.ObjectIdentifier('oid2'), u'value2'), - ]) - assert name1 == name2 - - def test_ne(self): - name1 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('oid'), u'value1'), - x509.NameAttribute(x509.ObjectIdentifier('oid2'), u'value2'), - ]) - name2 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('oid2'), u'value2'), - x509.NameAttribute(x509.ObjectIdentifier('oid'), u'value1'), - ]) - assert name1 != name2 - assert name1 != object() - - def test_repr(self): - name = x509.Name([ - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - ]) - - if six.PY3: - assert repr(name) == ( - "<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name" - "=commonName)>, value='cryptography.io')>, <NameAttribute(oid=" - "<ObjectIdentifier(oid=2.5.4.10, name=organizationName)>, valu" - "e='PyCA')>])>" - ) - else: - assert repr(name) == ( - "<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name" - "=commonName)>, value=u'cryptography.io')>, <NameAttribute(oid" - "=<ObjectIdentifier(oid=2.5.4.10, name=organizationName)>, val" - "ue=u'PyCA')>])>" - ) diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py deleted file mode 100644 index cacc0573..00000000 --- a/tests/test_x509_ext.py +++ /dev/null @@ -1,2568 +0,0 @@ -# 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 ipaddress -import os - -import pytest - -import six - -from cryptography import x509 -from cryptography.hazmat.backends.interfaces import RSABackend, X509Backend - -from .test_x509 import _load_cert - - -class TestExtension(object): - def test_not_an_oid(self): - bc = x509.BasicConstraints(ca=False, path_length=None) - with pytest.raises(TypeError): - x509.Extension("notanoid", True, bc) - - def test_critical_not_a_bool(self): - bc = x509.BasicConstraints(ca=False, path_length=None) - with pytest.raises(TypeError): - x509.Extension(x509.OID_BASIC_CONSTRAINTS, "notabool", bc) - - def test_repr(self): - bc = x509.BasicConstraints(ca=False, path_length=None) - ext = x509.Extension(x509.OID_BASIC_CONSTRAINTS, True, bc) - assert repr(ext) == ( - "<Extension(oid=<ObjectIdentifier(oid=2.5.29.19, name=basicConst" - "raints)>, critical=True, value=<BasicConstraints(ca=False, path" - "_length=None)>)>" - ) - - def test_eq(self): - ext1 = x509.Extension( - x509.ObjectIdentifier('1.2.3.4'), False, 'value' - ) - ext2 = x509.Extension( - x509.ObjectIdentifier('1.2.3.4'), False, 'value' - ) - assert ext1 == ext2 - - def test_ne(self): - ext1 = x509.Extension( - x509.ObjectIdentifier('1.2.3.4'), False, 'value' - ) - ext2 = x509.Extension( - x509.ObjectIdentifier('1.2.3.5'), False, 'value' - ) - ext3 = x509.Extension( - x509.ObjectIdentifier('1.2.3.4'), True, 'value' - ) - ext4 = x509.Extension( - x509.ObjectIdentifier('1.2.3.4'), False, 'value4' - ) - assert ext1 != ext2 - assert ext1 != ext3 - assert ext1 != ext4 - assert ext1 != object() - - -class TestNoticeReference(object): - def test_notice_numbers_not_all_int(self): - with pytest.raises(TypeError): - x509.NoticeReference("org", [1, 2, "three"]) - - def test_notice_numbers_none(self): - with pytest.raises(TypeError): - x509.NoticeReference("org", None) - - def test_repr(self): - nr = x509.NoticeReference(u"org", [1, 3, 4]) - - if six.PY3: - assert repr(nr) == ( - "<NoticeReference(organization='org', notice_numbers=[1, 3, 4" - "])>" - ) - else: - assert repr(nr) == ( - "<NoticeReference(organization=u'org', notice_numbers=[1, 3, " - "4])>" - ) - - def test_eq(self): - nr = x509.NoticeReference("org", [1, 2]) - nr2 = x509.NoticeReference("org", [1, 2]) - assert nr == nr2 - - def test_ne(self): - nr = x509.NoticeReference("org", [1, 2]) - nr2 = x509.NoticeReference("org", [1]) - nr3 = x509.NoticeReference(None, [1, 2]) - assert nr != nr2 - assert nr != nr3 - assert nr != object() - - -class TestUserNotice(object): - def test_notice_reference_invalid(self): - with pytest.raises(TypeError): - x509.UserNotice("invalid", None) - - def test_notice_reference_none(self): - un = x509.UserNotice(None, "text") - assert un.notice_reference is None - assert un.explicit_text == "text" - - def test_repr(self): - un = x509.UserNotice(x509.NoticeReference(u"org", [1]), u"text") - if six.PY3: - assert repr(un) == ( - "<UserNotice(notice_reference=<NoticeReference(organization='" - "org', notice_numbers=[1])>, explicit_text='text')>" - ) - else: - assert repr(un) == ( - "<UserNotice(notice_reference=<NoticeReference(organization=u" - "'org', notice_numbers=[1])>, explicit_text=u'text')>" - ) - - def test_eq(self): - nr = x509.NoticeReference("org", [1, 2]) - nr2 = x509.NoticeReference("org", [1, 2]) - un = x509.UserNotice(nr, "text") - un2 = x509.UserNotice(nr2, "text") - assert un == un2 - - def test_ne(self): - nr = x509.NoticeReference("org", [1, 2]) - nr2 = x509.NoticeReference("org", [1]) - un = x509.UserNotice(nr, "text") - un2 = x509.UserNotice(nr2, "text") - un3 = x509.UserNotice(nr, "text3") - assert un != un2 - assert un != un3 - assert un != object() - - -class TestPolicyInformation(object): - def test_invalid_policy_identifier(self): - with pytest.raises(TypeError): - x509.PolicyInformation("notanoid", None) - - def test_none_policy_qualifiers(self): - pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), None) - assert pi.policy_identifier == x509.ObjectIdentifier("1.2.3") - assert pi.policy_qualifiers is None - - def test_policy_qualifiers(self): - pq = [u"string"] - pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) - assert pi.policy_identifier == x509.ObjectIdentifier("1.2.3") - assert pi.policy_qualifiers == pq - - def test_invalid_policy_identifiers(self): - with pytest.raises(TypeError): - x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), [1, 2]) - - def test_repr(self): - pq = [u"string", x509.UserNotice(None, u"hi")] - pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) - if six.PY3: - assert repr(pi) == ( - "<PolicyInformation(policy_identifier=<ObjectIdentifier(oid=1." - "2.3, name=Unknown OID)>, policy_qualifiers=['string', <UserNo" - "tice(notice_reference=None, explicit_text='hi')>])>" - ) - else: - assert repr(pi) == ( - "<PolicyInformation(policy_identifier=<ObjectIdentifier(oid=1." - "2.3, name=Unknown OID)>, policy_qualifiers=[u'string', <UserN" - "otice(notice_reference=None, explicit_text=u'hi')>])>" - ) - - def test_eq(self): - pi = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), - [u"string", x509.UserNotice(None, u"hi")] - ) - pi2 = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), - [u"string", x509.UserNotice(None, u"hi")] - ) - assert pi == pi2 - - def test_ne(self): - pi = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string"] - ) - pi2 = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string2"] - ) - pi3 = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3.4"), [u"string"] - ) - assert pi != pi2 - assert pi != pi3 - assert pi != object() - - -class TestCertificatePolicies(object): - def test_invalid_policies(self): - pq = [u"string"] - pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) - with pytest.raises(TypeError): - x509.CertificatePolicies([1, pi]) - - def test_iter_len(self): - pq = [u"string"] - pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) - cp = x509.CertificatePolicies([pi]) - assert len(cp) == 1 - for policyinfo in cp: - assert policyinfo == pi - - def test_repr(self): - pq = [u"string"] - pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) - cp = x509.CertificatePolicies([pi]) - if six.PY3: - assert repr(cp) == ( - "<CertificatePolicies([<PolicyInformation(policy_identifier=<O" - "bjectIdentifier(oid=1.2.3, name=Unknown OID)>, policy_qualifi" - "ers=['string'])>])>" - ) - else: - assert repr(cp) == ( - "<CertificatePolicies([<PolicyInformation(policy_identifier=<O" - "bjectIdentifier(oid=1.2.3, name=Unknown OID)>, policy_qualifi" - "ers=[u'string'])>])>" - ) - - def test_eq(self): - pi = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string"] - ) - cp = x509.CertificatePolicies([pi]) - pi2 = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string"] - ) - cp2 = x509.CertificatePolicies([pi2]) - assert cp == cp2 - - def test_ne(self): - pi = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string"] - ) - cp = x509.CertificatePolicies([pi]) - pi2 = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string2"] - ) - cp2 = x509.CertificatePolicies([pi2]) - assert cp != cp2 - assert cp != object() - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestCertificatePoliciesExtension(object): - def test_cps_uri_policy_qualifier(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "cp_cps_uri.pem"), - x509.load_pem_x509_certificate, - backend - ) - - cp = cert.extensions.get_extension_for_oid( - x509.OID_CERTIFICATE_POLICIES - ).value - - assert cp == x509.CertificatePolicies([ - x509.PolicyInformation( - x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), - [u"http://other.com/cps"] - ) - ]) - - def test_user_notice_with_notice_reference(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "cp_user_notice_with_notice_reference.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - - cp = cert.extensions.get_extension_for_oid( - x509.OID_CERTIFICATE_POLICIES - ).value - - assert cp == x509.CertificatePolicies([ - x509.PolicyInformation( - x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), - [ - u"http://example.com/cps", - u"http://other.com/cps", - x509.UserNotice( - x509.NoticeReference(u"my org", [1, 2, 3, 4]), - u"thing" - ) - ] - ) - ]) - - def test_user_notice_with_explicit_text(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "cp_user_notice_with_explicit_text.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - - cp = cert.extensions.get_extension_for_oid( - x509.OID_CERTIFICATE_POLICIES - ).value - - assert cp == x509.CertificatePolicies([ - x509.PolicyInformation( - x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), - [x509.UserNotice(None, u"thing")] - ) - ]) - - def test_user_notice_no_explicit_text(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "cp_user_notice_no_explicit_text.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - - cp = cert.extensions.get_extension_for_oid( - x509.OID_CERTIFICATE_POLICIES - ).value - - assert cp == x509.CertificatePolicies([ - x509.PolicyInformation( - x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), - [ - x509.UserNotice( - x509.NoticeReference(u"my org", [1, 2, 3, 4]), - None - ) - ] - ) - ]) - - -class TestKeyUsage(object): - def test_key_agreement_false_encipher_decipher_true(self): - with pytest.raises(ValueError): - x509.KeyUsage( - digital_signature=False, - content_commitment=False, - key_encipherment=False, - data_encipherment=False, - key_agreement=False, - key_cert_sign=False, - crl_sign=False, - encipher_only=True, - decipher_only=False - ) - - with pytest.raises(ValueError): - x509.KeyUsage( - digital_signature=False, - content_commitment=False, - key_encipherment=False, - data_encipherment=False, - key_agreement=False, - key_cert_sign=False, - crl_sign=False, - encipher_only=True, - decipher_only=True - ) - - with pytest.raises(ValueError): - x509.KeyUsage( - digital_signature=False, - content_commitment=False, - key_encipherment=False, - data_encipherment=False, - key_agreement=False, - key_cert_sign=False, - crl_sign=False, - encipher_only=False, - decipher_only=True - ) - - def test_properties_key_agreement_true(self): - ku = x509.KeyUsage( - digital_signature=True, - content_commitment=True, - key_encipherment=False, - data_encipherment=False, - key_agreement=False, - key_cert_sign=True, - crl_sign=False, - encipher_only=False, - decipher_only=False - ) - assert ku.digital_signature is True - assert ku.content_commitment is True - assert ku.key_encipherment is False - assert ku.data_encipherment is False - assert ku.key_agreement is False - assert ku.key_cert_sign is True - assert ku.crl_sign is False - - def test_key_agreement_true_properties(self): - ku = x509.KeyUsage( - digital_signature=False, - content_commitment=False, - key_encipherment=False, - data_encipherment=False, - key_agreement=True, - key_cert_sign=False, - crl_sign=False, - encipher_only=False, - decipher_only=True - ) - assert ku.key_agreement is True - assert ku.encipher_only is False - assert ku.decipher_only is True - - def test_key_agreement_false_properties(self): - ku = x509.KeyUsage( - digital_signature=False, - content_commitment=False, - key_encipherment=False, - data_encipherment=False, - key_agreement=False, - key_cert_sign=False, - crl_sign=False, - encipher_only=False, - decipher_only=False - ) - assert ku.key_agreement is False - with pytest.raises(ValueError): - ku.encipher_only - - with pytest.raises(ValueError): - ku.decipher_only - - def test_repr_key_agreement_false(self): - ku = x509.KeyUsage( - digital_signature=True, - content_commitment=True, - key_encipherment=False, - data_encipherment=False, - key_agreement=False, - key_cert_sign=True, - crl_sign=False, - encipher_only=False, - decipher_only=False - ) - assert repr(ku) == ( - "<KeyUsage(digital_signature=True, content_commitment=True, key_en" - "cipherment=False, data_encipherment=False, key_agreement=False, k" - "ey_cert_sign=True, crl_sign=False, encipher_only=None, decipher_o" - "nly=None)>" - ) - - def test_repr_key_agreement_true(self): - ku = x509.KeyUsage( - digital_signature=True, - content_commitment=True, - key_encipherment=False, - data_encipherment=False, - key_agreement=True, - key_cert_sign=True, - crl_sign=False, - encipher_only=False, - decipher_only=False - ) - assert repr(ku) == ( - "<KeyUsage(digital_signature=True, content_commitment=True, key_en" - "cipherment=False, data_encipherment=False, key_agreement=True, k" - "ey_cert_sign=True, crl_sign=False, encipher_only=False, decipher_" - "only=False)>" - ) - - def test_eq(self): - ku = x509.KeyUsage( - digital_signature=False, - content_commitment=False, - key_encipherment=False, - data_encipherment=False, - key_agreement=True, - key_cert_sign=False, - crl_sign=False, - encipher_only=False, - decipher_only=True - ) - ku2 = x509.KeyUsage( - digital_signature=False, - content_commitment=False, - key_encipherment=False, - data_encipherment=False, - key_agreement=True, - key_cert_sign=False, - crl_sign=False, - encipher_only=False, - decipher_only=True - ) - assert ku == ku2 - - def test_ne(self): - ku = x509.KeyUsage( - digital_signature=False, - content_commitment=False, - key_encipherment=False, - data_encipherment=False, - key_agreement=True, - key_cert_sign=False, - crl_sign=False, - encipher_only=False, - decipher_only=True - ) - ku2 = x509.KeyUsage( - digital_signature=False, - content_commitment=False, - key_encipherment=False, - data_encipherment=False, - key_agreement=False, - key_cert_sign=False, - crl_sign=False, - encipher_only=False, - decipher_only=False - ) - assert ku != ku2 - assert ku != object() - - -class TestSubjectKeyIdentifier(object): - def test_properties(self): - value = binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") - ski = x509.SubjectKeyIdentifier(value) - assert ski.digest == value - - def test_repr(self): - ski = x509.SubjectKeyIdentifier( - binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") - ) - ext = x509.Extension(x509.OID_SUBJECT_KEY_IDENTIFIER, False, ski) - if six.PY3: - assert repr(ext) == ( - "<Extension(oid=<ObjectIdentifier(oid=2.5.29.14, name=subjectK" - "eyIdentifier)>, critical=False, value=<SubjectKeyIdentifier(d" - "igest=b\'\\t#\\x84\\x93\"0I\\x8b\\xc9\\x80\\xaa\\x80\\x98Eoo" - "\\xf7\\xff:\\xc9\')>)>" - ) - else: - assert repr(ext) == ( - "<Extension(oid=<ObjectIdentifier(oid=2.5.29.14, name=subjectK" - "eyIdentifier)>, critical=False, value=<SubjectKeyIdentifier(d" - "igest=\'\\t#\\x84\\x93\"0I\\x8b\\xc9\\x80\\xaa\\x80\\x98Eoo" - "\\xf7\\xff:\\xc9\')>)>" - ) - - def test_eq(self): - ski = x509.SubjectKeyIdentifier( - binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") - ) - ski2 = x509.SubjectKeyIdentifier( - binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") - ) - assert ski == ski2 - - def test_ne(self): - ski = x509.SubjectKeyIdentifier( - binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") - ) - ski2 = x509.SubjectKeyIdentifier( - binascii.unhexlify(b"aa8098456f6ff7ff3ac9092384932230498bc980") - ) - assert ski != ski2 - assert ski != object() - - -class TestAuthorityKeyIdentifier(object): - def test_authority_cert_issuer_not_generalname(self): - with pytest.raises(TypeError): - x509.AuthorityKeyIdentifier(b"identifier", ["notname"], 3) - - def test_authority_cert_serial_number_not_integer(self): - dirname = x509.DirectoryName( - x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('oid'), u'value1'), - x509.NameAttribute(x509.ObjectIdentifier('oid2'), u'value2'), - ]) - ) - with pytest.raises(TypeError): - x509.AuthorityKeyIdentifier(b"identifier", [dirname], "notanint") - - def test_authority_issuer_none_serial_not_none(self): - with pytest.raises(ValueError): - x509.AuthorityKeyIdentifier(b"identifier", None, 3) - - def test_authority_issuer_not_none_serial_none(self): - dirname = x509.DirectoryName( - x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('oid'), u'value1'), - x509.NameAttribute(x509.ObjectIdentifier('oid2'), u'value2'), - ]) - ) - with pytest.raises(ValueError): - x509.AuthorityKeyIdentifier(b"identifier", [dirname], None) - - def test_authority_cert_serial_and_issuer_none(self): - aki = x509.AuthorityKeyIdentifier(b"id", None, None) - assert aki.key_identifier == b"id" - assert aki.authority_cert_issuer is None - assert aki.authority_cert_serial_number is None - - def test_repr(self): - dirname = x509.DirectoryName( - x509.Name([x509.NameAttribute(x509.OID_COMMON_NAME, u'myCN')]) - ) - aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) - - if six.PY3: - assert repr(aki) == ( - "<AuthorityKeyIdentifier(key_identifier=b'digest', authority_" - "cert_issuer=[<DirectoryName(value=<Name([<NameAttribute(oid=" - "<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value='myC" - "N')>])>)>], authority_cert_serial_number=1234)>" - ) - else: - assert repr(aki) == ( - "<AuthorityKeyIdentifier(key_identifier='digest', authority_ce" - "rt_issuer=[<DirectoryName(value=<Name([<NameAttribute(oid=<Ob" - "jectIdentifier(oid=2.5.4.3, name=commonName)>, value=u'myCN')" - ">])>)>], authority_cert_serial_number=1234)>" - ) - - def test_eq(self): - dirname = x509.DirectoryName( - x509.Name([x509.NameAttribute(x509.OID_COMMON_NAME, u'myCN')]) - ) - aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) - dirname2 = x509.DirectoryName( - x509.Name([x509.NameAttribute(x509.OID_COMMON_NAME, u'myCN')]) - ) - aki2 = x509.AuthorityKeyIdentifier(b"digest", [dirname2], 1234) - assert aki == aki2 - - def test_ne(self): - dirname = x509.DirectoryName( - x509.Name([x509.NameAttribute(x509.OID_COMMON_NAME, u'myCN')]) - ) - dirname5 = x509.DirectoryName( - x509.Name([x509.NameAttribute(x509.OID_COMMON_NAME, u'aCN')]) - ) - aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) - aki2 = x509.AuthorityKeyIdentifier(b"diges", [dirname], 1234) - aki3 = x509.AuthorityKeyIdentifier(b"digest", None, None) - aki4 = x509.AuthorityKeyIdentifier(b"digest", [dirname], 12345) - aki5 = x509.AuthorityKeyIdentifier(b"digest", [dirname5], 12345) - assert aki != aki2 - assert aki != aki3 - assert aki != aki4 - assert aki != aki5 - assert aki != object() - - -class TestBasicConstraints(object): - def test_ca_not_boolean(self): - with pytest.raises(TypeError): - x509.BasicConstraints(ca="notbool", path_length=None) - - def test_path_length_not_ca(self): - with pytest.raises(ValueError): - x509.BasicConstraints(ca=False, path_length=0) - - def test_path_length_not_int(self): - with pytest.raises(TypeError): - x509.BasicConstraints(ca=True, path_length=1.1) - - with pytest.raises(TypeError): - x509.BasicConstraints(ca=True, path_length="notint") - - def test_path_length_negative(self): - with pytest.raises(TypeError): - x509.BasicConstraints(ca=True, path_length=-1) - - def test_repr(self): - na = x509.BasicConstraints(ca=True, path_length=None) - assert repr(na) == ( - "<BasicConstraints(ca=True, path_length=None)>" - ) - - def test_eq(self): - na = x509.BasicConstraints(ca=True, path_length=None) - na2 = x509.BasicConstraints(ca=True, path_length=None) - assert na == na2 - - def test_ne(self): - na = x509.BasicConstraints(ca=True, path_length=None) - na2 = x509.BasicConstraints(ca=True, path_length=1) - na3 = x509.BasicConstraints(ca=False, path_length=None) - assert na != na2 - assert na != na3 - assert na != object() - - -class TestExtendedKeyUsage(object): - def test_not_all_oids(self): - with pytest.raises(TypeError): - x509.ExtendedKeyUsage(["notoid"]) - - def test_iter_len(self): - eku = x509.ExtendedKeyUsage([ - x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), - x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), - ]) - assert len(eku) == 2 - assert list(eku) == [ - x509.OID_SERVER_AUTH, - x509.OID_CLIENT_AUTH - ] - - def test_repr(self): - eku = x509.ExtendedKeyUsage([ - x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), - x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), - ]) - assert repr(eku) == ( - "<ExtendedKeyUsage([<ObjectIdentifier(oid=1.3.6.1.5.5.7.3.1, name=" - "serverAuth)>, <ObjectIdentifier(oid=1.3.6.1.5.5.7.3.2, name=clien" - "tAuth)>])>" - ) - - def test_eq(self): - eku = x509.ExtendedKeyUsage([ - x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7") - ]) - eku2 = x509.ExtendedKeyUsage([ - x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7") - ]) - assert eku == eku2 - - def test_ne(self): - eku = x509.ExtendedKeyUsage([x509.ObjectIdentifier("1.3.6")]) - eku2 = x509.ExtendedKeyUsage([x509.ObjectIdentifier("1.3.6.1")]) - assert eku != eku2 - assert eku != object() - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestExtensions(object): - def test_no_extensions(self, backend): - cert = _load_cert( - os.path.join("x509", "verisign_md2_root.pem"), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions - assert len(ext) == 0 - assert list(ext) == [] - with pytest.raises(x509.ExtensionNotFound) as exc: - ext.get_extension_for_oid(x509.OID_BASIC_CONSTRAINTS) - - assert exc.value.oid == x509.OID_BASIC_CONSTRAINTS - - def test_one_extension(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "basic_constraints_not_critical.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - extensions = cert.extensions - ext = extensions.get_extension_for_oid(x509.OID_BASIC_CONSTRAINTS) - assert ext is not None - assert ext.value.ca is False - - def test_duplicate_extension(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "two_basic_constraints.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - with pytest.raises(x509.DuplicateExtension) as exc: - cert.extensions - - assert exc.value.oid == x509.OID_BASIC_CONSTRAINTS - - def test_unsupported_critical_extension(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "unsupported_extension_critical.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - with pytest.raises(x509.UnsupportedExtension) as exc: - cert.extensions - - assert exc.value.oid == x509.ObjectIdentifier("1.2.3.4") - - def test_unsupported_extension(self, backend): - # TODO: this will raise an exception when all extensions are complete - cert = _load_cert( - os.path.join( - "x509", "custom", "unsupported_extension.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - extensions = cert.extensions - assert len(extensions) == 0 - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestBasicConstraintsExtension(object): - def test_ca_true_pathlen_6(self, backend): - cert = _load_cert( - os.path.join( - "x509", "PKITS_data", "certs", "pathLenConstraint6CACert.crt" - ), - x509.load_der_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_BASIC_CONSTRAINTS - ) - assert ext is not None - assert ext.critical is True - assert ext.value.ca is True - assert ext.value.path_length == 6 - - def test_path_length_zero(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "bc_path_length_zero.pem"), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_BASIC_CONSTRAINTS - ) - assert ext is not None - assert ext.critical is True - assert ext.value.ca is True - assert ext.value.path_length == 0 - - def test_ca_true_no_pathlen(self, backend): - cert = _load_cert( - os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), - x509.load_der_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_BASIC_CONSTRAINTS - ) - assert ext is not None - assert ext.critical is True - assert ext.value.ca is True - assert ext.value.path_length is None - - def test_ca_false(self, backend): - cert = _load_cert( - os.path.join("x509", "cryptography.io.pem"), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_BASIC_CONSTRAINTS - ) - assert ext is not None - assert ext.critical is True - assert ext.value.ca is False - assert ext.value.path_length is None - - def test_no_basic_constraints(self, backend): - cert = _load_cert( - os.path.join( - "x509", - "PKITS_data", - "certs", - "ValidCertificatePathTest1EE.crt" - ), - x509.load_der_x509_certificate, - backend - ) - with pytest.raises(x509.ExtensionNotFound): - cert.extensions.get_extension_for_oid(x509.OID_BASIC_CONSTRAINTS) - - def test_basic_constraint_not_critical(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "basic_constraints_not_critical.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_BASIC_CONSTRAINTS - ) - assert ext is not None - assert ext.critical is False - assert ext.value.ca is False - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestSubjectKeyIdentifierExtension(object): - def test_subject_key_identifier(self, backend): - cert = _load_cert( - os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), - x509.load_der_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_SUBJECT_KEY_IDENTIFIER - ) - ski = ext.value - assert ext is not None - assert ext.critical is False - assert ski.digest == binascii.unhexlify( - b"580184241bbc2b52944a3da510721451f5af3ac9" - ) - - def test_no_subject_key_identifier(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "bc_path_length_zero.pem"), - x509.load_pem_x509_certificate, - backend - ) - with pytest.raises(x509.ExtensionNotFound): - cert.extensions.get_extension_for_oid( - x509.OID_SUBJECT_KEY_IDENTIFIER - ) - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestKeyUsageExtension(object): - def test_no_key_usage(self, backend): - cert = _load_cert( - os.path.join("x509", "verisign_md2_root.pem"), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions - with pytest.raises(x509.ExtensionNotFound) as exc: - ext.get_extension_for_oid(x509.OID_KEY_USAGE) - - assert exc.value.oid == x509.OID_KEY_USAGE - - def test_all_purposes(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "all_key_usages.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - extensions = cert.extensions - ext = extensions.get_extension_for_oid(x509.OID_KEY_USAGE) - assert ext is not None - - ku = ext.value - assert ku.digital_signature is True - assert ku.content_commitment is True - assert ku.key_encipherment is True - assert ku.data_encipherment is True - assert ku.key_agreement is True - assert ku.key_cert_sign is True - assert ku.crl_sign is True - assert ku.encipher_only is True - assert ku.decipher_only is True - - def test_key_cert_sign_crl_sign(self, backend): - cert = _load_cert( - os.path.join( - "x509", "PKITS_data", "certs", "pathLenConstraint6CACert.crt" - ), - x509.load_der_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid(x509.OID_KEY_USAGE) - assert ext is not None - assert ext.critical is True - - ku = ext.value - assert ku.digital_signature is False - assert ku.content_commitment is False - assert ku.key_encipherment is False - assert ku.data_encipherment is False - assert ku.key_agreement is False - assert ku.key_cert_sign is True - assert ku.crl_sign is True - - -@pytest.mark.parametrize( - "name", [ - x509.RFC822Name, - x509.DNSName, - x509.UniformResourceIdentifier - ] -) -class TestTextGeneralNames(object): - def test_not_text(self, name): - with pytest.raises(TypeError): - name(b"notaunicodestring") - - with pytest.raises(TypeError): - name(1.3) - - def test_repr(self, name): - gn = name(u"string") - assert repr(gn) == "<{0}(value=string)>".format(name.__name__) - - def test_eq(self, name): - gn = name(u"string") - gn2 = name(u"string") - assert gn == gn2 - - def test_ne(self, name): - gn = name(u"string") - gn2 = name(u"string2") - assert gn != gn2 - assert gn != object() - - -class TestDirectoryName(object): - def test_not_name(self): - with pytest.raises(TypeError): - x509.DirectoryName(b"notaname") - - with pytest.raises(TypeError): - x509.DirectoryName(1.3) - - def test_repr(self): - name = x509.Name([x509.NameAttribute(x509.OID_COMMON_NAME, u'value1')]) - gn = x509.DirectoryName(x509.Name([name])) - if six.PY3: - assert repr(gn) == ( - "<DirectoryName(value=<Name([<Name([<NameAttribute(oid=<Object" - "Identifier(oid=2.5.4.3, name=commonName)>, value='value1')>])" - ">])>)>" - ) - else: - assert repr(gn) == ( - "<DirectoryName(value=<Name([<Name([<NameAttribute(oid=<Object" - "Identifier(oid=2.5.4.3, name=commonName)>, value=u'value1')>]" - ")>])>)>" - ) - - def test_eq(self): - name = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('oid'), u'value1') - ]) - name2 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('oid'), u'value1') - ]) - gn = x509.DirectoryName(x509.Name([name])) - gn2 = x509.DirectoryName(x509.Name([name2])) - assert gn == gn2 - - def test_ne(self): - name = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('oid'), u'value1') - ]) - name2 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('oid'), u'value2') - ]) - gn = x509.DirectoryName(x509.Name([name])) - gn2 = x509.DirectoryName(x509.Name([name2])) - assert gn != gn2 - assert gn != object() - - -class TestRegisteredID(object): - def test_not_oid(self): - with pytest.raises(TypeError): - x509.RegisteredID(b"notanoid") - - with pytest.raises(TypeError): - x509.RegisteredID(1.3) - - def test_repr(self): - gn = x509.RegisteredID(x509.OID_COMMON_NAME) - assert repr(gn) == ( - "<RegisteredID(value=<ObjectIdentifier(oid=2.5.4.3, name=commonNam" - "e)>)>" - ) - - def test_eq(self): - gn = x509.RegisteredID(x509.OID_COMMON_NAME) - gn2 = x509.RegisteredID(x509.OID_COMMON_NAME) - assert gn == gn2 - - def test_ne(self): - gn = x509.RegisteredID(x509.OID_COMMON_NAME) - gn2 = x509.RegisteredID(x509.OID_BASIC_CONSTRAINTS) - assert gn != gn2 - assert gn != object() - - -class TestIPAddress(object): - def test_not_ipaddress(self): - with pytest.raises(TypeError): - x509.IPAddress(b"notanipaddress") - - with pytest.raises(TypeError): - x509.IPAddress(1.3) - - def test_repr(self): - gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) - assert repr(gn) == "<IPAddress(value=127.0.0.1)>" - - gn2 = x509.IPAddress(ipaddress.IPv6Address(u"ff::")) - assert repr(gn2) == "<IPAddress(value=ff::)>" - - gn3 = x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24")) - assert repr(gn3) == "<IPAddress(value=192.168.0.0/24)>" - - gn4 = x509.IPAddress(ipaddress.IPv6Network(u"ff::/96")) - assert repr(gn4) == "<IPAddress(value=ff::/96)>" - - def test_eq(self): - gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) - gn2 = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) - assert gn == gn2 - - def test_ne(self): - gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) - gn2 = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.2")) - assert gn != gn2 - assert gn != object() - - -class TestGeneralNames(object): - def test_get_values_for_type(self): - gns = x509.GeneralNames( - [x509.DNSName(u"cryptography.io")] - ) - names = gns.get_values_for_type(x509.DNSName) - assert names == [u"cryptography.io"] - - def test_iter_names(self): - gns = x509.GeneralNames([ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), - ]) - assert len(gns) == 2 - assert list(gns) == [ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), - ] - - def test_invalid_general_names(self): - with pytest.raises(TypeError): - x509.GeneralNames( - [x509.DNSName(u"cryptography.io"), "invalid"] - ) - - def test_repr(self): - gns = x509.GeneralNames( - [ - x509.DNSName(u"cryptography.io") - ] - ) - assert repr(gns) == ( - "<GeneralNames([<DNSName(value=cryptography.io)>])>" - ) - - def test_eq(self): - gns = x509.GeneralNames( - [x509.DNSName(u"cryptography.io")] - ) - gns2 = x509.GeneralNames( - [x509.DNSName(u"cryptography.io")] - ) - assert gns == gns2 - - def test_ne(self): - gns = x509.GeneralNames( - [x509.DNSName(u"cryptography.io")] - ) - gns2 = x509.GeneralNames( - [x509.RFC822Name(u"admin@cryptography.io")] - ) - assert gns != gns2 - assert gns != object() - - -class TestIssuerAlternativeName(object): - def test_get_values_for_type(self): - san = x509.IssuerAlternativeName( - [x509.DNSName(u"cryptography.io")] - ) - names = san.get_values_for_type(x509.DNSName) - assert names == [u"cryptography.io"] - - def test_iter_names(self): - san = x509.IssuerAlternativeName([ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), - ]) - assert len(san) == 2 - assert list(san) == [ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), - ] - - def test_invalid_general_names(self): - with pytest.raises(TypeError): - x509.IssuerAlternativeName( - [x509.DNSName(u"cryptography.io"), "invalid"] - ) - - def test_repr(self): - san = x509.IssuerAlternativeName( - [ - x509.DNSName(u"cryptography.io") - ] - ) - assert repr(san) == ( - "<IssuerAlternativeName(" - "<GeneralNames([<DNSName(value=cryptography.io)>])>)>" - ) - - def test_eq(self): - san = x509.IssuerAlternativeName( - [x509.DNSName(u"cryptography.io")] - ) - san2 = x509.IssuerAlternativeName( - [x509.DNSName(u"cryptography.io")] - ) - assert san == san2 - - def test_ne(self): - san = x509.IssuerAlternativeName( - [x509.DNSName(u"cryptography.io")] - ) - san2 = x509.IssuerAlternativeName( - [x509.RFC822Name(u"admin@cryptography.io")] - ) - assert san != san2 - assert san != object() - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestRSAIssuerAlternativeNameExtension(object): - def test_uri(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "ian_uri.pem"), - x509.load_pem_x509_certificate, - backend, - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_ISSUER_ALTERNATIVE_NAME - ) - assert list(ext.value) == [ - x509.UniformResourceIdentifier(u"http://path.to.root/root.crt"), - ] - - -class TestSubjectAlternativeName(object): - def test_get_values_for_type(self): - san = x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io")] - ) - names = san.get_values_for_type(x509.DNSName) - assert names == [u"cryptography.io"] - - def test_iter_names(self): - san = x509.SubjectAlternativeName([ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), - ]) - assert len(san) == 2 - assert list(san) == [ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), - ] - - def test_invalid_general_names(self): - with pytest.raises(TypeError): - x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io"), "invalid"] - ) - - def test_repr(self): - san = x509.SubjectAlternativeName( - [ - x509.DNSName(u"cryptography.io") - ] - ) - assert repr(san) == ( - "<SubjectAlternativeName(" - "<GeneralNames([<DNSName(value=cryptography.io)>])>)>" - ) - - def test_eq(self): - san = x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io")] - ) - san2 = x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io")] - ) - assert san == san2 - - def test_ne(self): - san = x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io")] - ) - san2 = x509.SubjectAlternativeName( - [x509.RFC822Name(u"admin@cryptography.io")] - ) - assert san != san2 - assert san != object() - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestRSASubjectAlternativeNameExtension(object): - def test_dns_name(self, backend): - cert = _load_cert( - os.path.join("x509", "cryptography.io.pem"), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_SUBJECT_ALTERNATIVE_NAME - ) - assert ext is not None - assert ext.critical is False - - san = ext.value - - dns = san.get_values_for_type(x509.DNSName) - assert dns == [u"www.cryptography.io", u"cryptography.io"] - - def test_unsupported_other_name(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "san_other_name.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - with pytest.raises(x509.UnsupportedGeneralNameType) as exc: - cert.extensions - - assert exc.value.type == 0 - - def test_registered_id(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "san_registered_id.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_SUBJECT_ALTERNATIVE_NAME - ) - assert ext is not None - assert ext.critical is False - - san = ext.value - rid = san.get_values_for_type(x509.RegisteredID) - assert rid == [x509.ObjectIdentifier("1.2.3.4")] - - def test_uri(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "san_uri_with_port.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_SUBJECT_ALTERNATIVE_NAME - ) - assert ext is not None - uri = ext.value.get_values_for_type( - x509.UniformResourceIdentifier - ) - assert uri == [ - u"gopher://\u043f\u044b\u043a\u0430.cryptography:70/path?q=s#hel" - u"lo", - u"http://someregulardomain.com", - ] - - def test_ipaddress(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "san_ipaddr.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_SUBJECT_ALTERNATIVE_NAME - ) - assert ext is not None - assert ext.critical is False - - san = ext.value - - ip = san.get_values_for_type(x509.IPAddress) - assert [ - ipaddress.ip_address(u"127.0.0.1"), - ipaddress.ip_address(u"ff::") - ] == ip - - def test_dirname(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "san_dirname.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_SUBJECT_ALTERNATIVE_NAME - ) - assert ext is not None - assert ext.critical is False - - san = ext.value - - dirname = san.get_values_for_type(x509.DirectoryName) - assert [ - x509.Name([ - x509.NameAttribute(x509.OID_COMMON_NAME, u'test'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'Org'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - ]) - ] == dirname - - def test_rfc822name(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "san_rfc822_idna.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_SUBJECT_ALTERNATIVE_NAME - ) - assert ext is not None - assert ext.critical is False - - san = ext.value - - rfc822name = san.get_values_for_type(x509.RFC822Name) - assert [u"email@em\xe5\xefl.com"] == rfc822name - - def test_idna2003_invalid(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "san_idna2003_dnsname.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - with pytest.raises(UnicodeError): - cert.extensions - - def test_unicode_rfc822_name_dns_name_uri(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "san_idna_names.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_SUBJECT_ALTERNATIVE_NAME - ) - assert ext is not None - rfc822_name = ext.value.get_values_for_type(x509.RFC822Name) - dns_name = ext.value.get_values_for_type(x509.DNSName) - uri = ext.value.get_values_for_type(x509.UniformResourceIdentifier) - assert rfc822_name == [u"email@\u043f\u044b\u043a\u0430.cryptography"] - assert dns_name == [u"\u043f\u044b\u043a\u0430.cryptography"] - assert uri == [u"https://www.\u043f\u044b\u043a\u0430.cryptography"] - - def test_rfc822name_dnsname_ipaddress_directoryname_uri(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "san_email_dns_ip_dirname_uri.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_SUBJECT_ALTERNATIVE_NAME - ) - assert ext is not None - assert ext.critical is False - - san = ext.value - - rfc822_name = san.get_values_for_type(x509.RFC822Name) - uri = san.get_values_for_type(x509.UniformResourceIdentifier) - dns = san.get_values_for_type(x509.DNSName) - ip = san.get_values_for_type(x509.IPAddress) - dirname = san.get_values_for_type(x509.DirectoryName) - assert [u"user@cryptography.io"] == rfc822_name - assert [u"https://cryptography.io"] == uri - assert [u"cryptography.io"] == dns - assert [ - x509.Name([ - x509.NameAttribute(x509.OID_COMMON_NAME, u'dirCN'), - x509.NameAttribute( - x509.OID_ORGANIZATION_NAME, u'Cryptographic Authority' - ), - ]) - ] == dirname - assert [ - ipaddress.ip_address(u"127.0.0.1"), - ipaddress.ip_address(u"ff::") - ] == ip - - def test_invalid_rfc822name(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "san_rfc822_names.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - with pytest.raises(ValueError) as exc: - cert.extensions - - assert 'Invalid rfc822name value' in str(exc.value) - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestExtendedKeyUsageExtension(object): - def test_eku(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "extended_key_usage.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_EXTENDED_KEY_USAGE - ) - assert ext is not None - assert ext.critical is False - - assert [ - x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), - x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), - x509.ObjectIdentifier("1.3.6.1.5.5.7.3.3"), - x509.ObjectIdentifier("1.3.6.1.5.5.7.3.4"), - x509.ObjectIdentifier("1.3.6.1.5.5.7.3.9"), - x509.ObjectIdentifier("1.3.6.1.5.5.7.3.8"), - x509.ObjectIdentifier("2.5.29.37.0"), - x509.ObjectIdentifier("2.16.840.1.113730.4.1"), - ] == list(ext.value) - - -class TestAccessDescription(object): - def test_invalid_access_method(self): - with pytest.raises(ValueError): - x509.AccessDescription("notanoid", x509.DNSName(u"test")) - - def test_invalid_access_location(self): - with pytest.raises(TypeError): - x509.AccessDescription(x509.OID_CA_ISSUERS, "invalid") - - def test_repr(self): - ad = x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ) - assert repr(ad) == ( - "<AccessDescription(access_method=<ObjectIdentifier(oid=1.3.6.1.5." - "5.7.48.1, name=OCSP)>, access_location=<UniformResourceIdentifier" - "(value=http://ocsp.domain.com)>)>" - ) - - def test_eq(self): - ad = x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ) - ad2 = x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ) - assert ad == ad2 - - def test_ne(self): - ad = x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ) - ad2 = x509.AccessDescription( - x509.OID_CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ) - ad3 = x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://notthesame") - ) - assert ad != ad2 - assert ad != ad3 - assert ad != object() - - -class TestAuthorityInformationAccess(object): - def test_invalid_descriptions(self): - with pytest.raises(TypeError): - x509.AuthorityInformationAccess(["notanAccessDescription"]) - - def test_iter_len(self): - aia = x509.AuthorityInformationAccess([ - x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ), - x509.AccessDescription( - x509.OID_CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") - ) - ]) - assert len(aia) == 2 - assert list(aia) == [ - x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ), - x509.AccessDescription( - x509.OID_CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") - ) - ] - - def test_repr(self): - aia = x509.AuthorityInformationAccess([ - x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ), - x509.AccessDescription( - x509.OID_CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") - ) - ]) - assert repr(aia) == ( - "<AuthorityInformationAccess([<AccessDescription(access_method=<Ob" - "jectIdentifier(oid=1.3.6.1.5.5.7.48.1, name=OCSP)>, access_locati" - "on=<UniformResourceIdentifier(value=http://ocsp.domain.com)>)>, <" - "AccessDescription(access_method=<ObjectIdentifier(oid=1.3.6.1.5.5" - ".7.48.2, name=caIssuers)>, access_location=<UniformResourceIdenti" - "fier(value=http://domain.com/ca.crt)>)>])>" - ) - - def test_eq(self): - aia = x509.AuthorityInformationAccess([ - x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ), - x509.AccessDescription( - x509.OID_CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") - ) - ]) - aia2 = x509.AuthorityInformationAccess([ - x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ), - x509.AccessDescription( - x509.OID_CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") - ) - ]) - assert aia == aia2 - - def test_ne(self): - aia = x509.AuthorityInformationAccess([ - x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ), - x509.AccessDescription( - x509.OID_CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") - ) - ]) - aia2 = x509.AuthorityInformationAccess([ - x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ), - ]) - - assert aia != aia2 - assert aia != object() - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestAuthorityInformationAccessExtension(object): - def test_aia_ocsp_ca_issuers(self, backend): - cert = _load_cert( - os.path.join("x509", "cryptography.io.pem"), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_AUTHORITY_INFORMATION_ACCESS - ) - assert ext is not None - assert ext.critical is False - - assert ext.value == x509.AuthorityInformationAccess([ - x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://gv.symcd.com") - ), - x509.AccessDescription( - x509.OID_CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://gv.symcb.com/gv.crt") - ), - ]) - - def test_aia_multiple_ocsp_ca_issuers(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "aia_ocsp_ca_issuers.pem"), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_AUTHORITY_INFORMATION_ACCESS - ) - assert ext is not None - assert ext.critical is False - - assert ext.value == x509.AuthorityInformationAccess([ - x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ), - x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp2.domain.com") - ), - x509.AccessDescription( - x509.OID_CA_ISSUERS, - x509.DirectoryName(x509.Name([ - x509.NameAttribute(x509.OID_COMMON_NAME, u"myCN"), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, - u"some Org"), - ])) - ), - ]) - - def test_aia_ocsp_only(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "aia_ocsp.pem"), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_AUTHORITY_INFORMATION_ACCESS - ) - assert ext is not None - assert ext.critical is False - - assert ext.value == x509.AuthorityInformationAccess([ - x509.AccessDescription( - x509.OID_OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ), - ]) - - def test_aia_ca_issuers_only(self, backend): - cert = _load_cert( - os.path.join("x509", "custom", "aia_ca_issuers.pem"), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_AUTHORITY_INFORMATION_ACCESS - ) - assert ext is not None - assert ext.critical is False - - assert ext.value == x509.AuthorityInformationAccess([ - x509.AccessDescription( - x509.OID_CA_ISSUERS, - x509.DirectoryName(x509.Name([ - x509.NameAttribute(x509.OID_COMMON_NAME, u"myCN"), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, - u"some Org"), - ])) - ), - ]) - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestAuthorityKeyIdentifierExtension(object): - def test_aki_keyid(self, backend): - cert = _load_cert( - os.path.join( - "x509", "cryptography.io.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_AUTHORITY_KEY_IDENTIFIER - ) - assert ext is not None - assert ext.critical is False - - assert ext.value.key_identifier == ( - b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08\xcbY" - ) - assert ext.value.authority_cert_issuer is None - assert ext.value.authority_cert_serial_number is None - - def test_aki_all_fields(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "authority_key_identifier.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_AUTHORITY_KEY_IDENTIFIER - ) - assert ext is not None - assert ext.critical is False - - assert ext.value.key_identifier == ( - b"9E>\xca=b\x1d\xea\x86I\xf6Z\xab@\xb7\xa4p\x98\xf1\xec" - ) - assert ext.value.authority_cert_issuer == [ - x509.DirectoryName( - x509.Name([ - x509.NameAttribute( - x509.OID_ORGANIZATION_NAME, u"PyCA" - ), - x509.NameAttribute( - x509.OID_COMMON_NAME, u"cryptography.io" - ) - ]) - ) - ] - assert ext.value.authority_cert_serial_number == 3 - - def test_aki_no_keyid(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "authority_key_identifier_no_keyid.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_AUTHORITY_KEY_IDENTIFIER - ) - assert ext is not None - assert ext.critical is False - - assert ext.value.key_identifier is None - assert ext.value.authority_cert_issuer == [ - x509.DirectoryName( - x509.Name([ - x509.NameAttribute( - x509.OID_ORGANIZATION_NAME, u"PyCA" - ), - x509.NameAttribute( - x509.OID_COMMON_NAME, u"cryptography.io" - ) - ]) - ) - ] - assert ext.value.authority_cert_serial_number == 3 - - -class TestNameConstraints(object): - def test_ipaddress_wrong_type(self): - with pytest.raises(TypeError): - x509.NameConstraints( - permitted_subtrees=[ - x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) - ], - excluded_subtrees=None - ) - - with pytest.raises(TypeError): - x509.NameConstraints( - permitted_subtrees=None, - excluded_subtrees=[ - x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) - ] - ) - - def test_ipaddress_allowed_type(self): - permitted = [x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/29"))] - excluded = [x509.IPAddress(ipaddress.IPv4Network(u"10.10.0.0/24"))] - nc = x509.NameConstraints( - permitted_subtrees=permitted, - excluded_subtrees=excluded - ) - assert nc.permitted_subtrees == permitted - assert nc.excluded_subtrees == excluded - - def test_invalid_permitted_subtrees(self): - with pytest.raises(TypeError): - x509.NameConstraints("badpermitted", None) - - def test_invalid_excluded_subtrees(self): - with pytest.raises(TypeError): - x509.NameConstraints(None, "badexcluded") - - def test_no_subtrees(self): - with pytest.raises(ValueError): - x509.NameConstraints(None, None) - - def test_permitted_none(self): - excluded = [x509.DNSName(u"name.local")] - nc = x509.NameConstraints( - permitted_subtrees=None, excluded_subtrees=excluded - ) - assert nc.permitted_subtrees is None - assert nc.excluded_subtrees is not None - - def test_excluded_none(self): - permitted = [x509.DNSName(u"name.local")] - nc = x509.NameConstraints( - permitted_subtrees=permitted, excluded_subtrees=None - ) - assert nc.permitted_subtrees is not None - assert nc.excluded_subtrees is None - - def test_repr(self): - permitted = [x509.DNSName(u"name.local"), x509.DNSName(u"name2.local")] - nc = x509.NameConstraints( - permitted_subtrees=permitted, - excluded_subtrees=None - ) - assert repr(nc) == ( - "<NameConstraints(permitted_subtrees=[<DNSName(value=name.local)>" - ", <DNSName(value=name2.local)>], excluded_subtrees=None)>" - ) - - def test_eq(self): - nc = x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"name.local")], - excluded_subtrees=[x509.DNSName(u"name2.local")] - ) - nc2 = x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"name.local")], - excluded_subtrees=[x509.DNSName(u"name2.local")] - ) - assert nc == nc2 - - def test_ne(self): - nc = x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"name.local")], - excluded_subtrees=[x509.DNSName(u"name2.local")] - ) - nc2 = x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"name.local")], - excluded_subtrees=None - ) - nc3 = x509.NameConstraints( - permitted_subtrees=None, - excluded_subtrees=[x509.DNSName(u"name2.local")] - ) - - assert nc != nc2 - assert nc != nc3 - assert nc != object() - - -class TestDistributionPoint(object): - def test_distribution_point_full_name_not_general_names(self): - with pytest.raises(TypeError): - x509.DistributionPoint(["notgn"], None, None, None) - - def test_distribution_point_relative_name_not_name(self): - with pytest.raises(TypeError): - x509.DistributionPoint(None, "notname", None, None) - - def test_distribution_point_full_and_relative_not_none(self): - with pytest.raises(ValueError): - x509.DistributionPoint("data", "notname", None, None) - - def test_crl_issuer_not_general_names(self): - with pytest.raises(TypeError): - x509.DistributionPoint(None, None, None, ["notgn"]) - - def test_reason_not_reasonflags(self): - with pytest.raises(TypeError): - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], - None, - frozenset(["notreasonflags"]), - None - ) - - def test_reason_not_frozenset(self): - with pytest.raises(TypeError): - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], - None, - [x509.ReasonFlags.ca_compromise], - None - ) - - def test_disallowed_reasons(self): - with pytest.raises(ValueError): - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], - None, - frozenset([x509.ReasonFlags.unspecified]), - None - ) - - with pytest.raises(ValueError): - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], - None, - frozenset([x509.ReasonFlags.remove_from_crl]), - None - ) - - def test_reason_only(self): - with pytest.raises(ValueError): - x509.DistributionPoint( - None, - None, - frozenset([x509.ReasonFlags.aa_compromise]), - None - ) - - def test_eq(self): - dp = x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], - None, - frozenset([x509.ReasonFlags.superseded]), - [ - x509.DirectoryName( - x509.Name([ - x509.NameAttribute( - x509.OID_COMMON_NAME, u"Important CA" - ) - ]) - ) - ], - ) - dp2 = x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], - None, - frozenset([x509.ReasonFlags.superseded]), - [ - x509.DirectoryName( - x509.Name([ - x509.NameAttribute( - x509.OID_COMMON_NAME, u"Important CA" - ) - ]) - ) - ], - ) - assert dp == dp2 - - def test_ne(self): - dp = x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], - None, - frozenset([x509.ReasonFlags.superseded]), - [ - x509.DirectoryName( - x509.Name([ - x509.NameAttribute( - x509.OID_COMMON_NAME, u"Important CA" - ) - ]) - ) - ], - ) - dp2 = x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], - None, - None, - None - ) - assert dp != dp2 - assert dp != object() - - def test_repr(self): - dp = x509.DistributionPoint( - None, - x509.Name([ - x509.NameAttribute(x509.OID_COMMON_NAME, u"myCN") - ]), - frozenset([x509.ReasonFlags.ca_compromise]), - [ - x509.DirectoryName( - x509.Name([ - x509.NameAttribute( - x509.OID_COMMON_NAME, u"Important CA" - ) - ]) - ) - ], - ) - if six.PY3: - assert repr(dp) == ( - "<DistributionPoint(full_name=None, relative_name=<Name([<Name" - "Attribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)" - ">, value='myCN')>])>, reasons=frozenset({<ReasonFlags.ca_comp" - "romise: 'cACompromise'>}), crl_issuer=[<DirectoryName(value=<" - "Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=" - "commonName)>, value='Important CA')>])>)>])>" - ) - else: - assert repr(dp) == ( - "<DistributionPoint(full_name=None, relative_name=<Name([<Name" - "Attribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)" - ">, value=u'myCN')>])>, reasons=frozenset([<ReasonFlags.ca_com" - "promise: 'cACompromise'>]), crl_issuer=[<DirectoryName(value=" - "<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name" - "=commonName)>, value=u'Important CA')>])>)>])>" - ) - - -class TestCRLDistributionPoints(object): - def test_invalid_distribution_points(self): - with pytest.raises(TypeError): - x509.CRLDistributionPoints(["notadistributionpoint"]) - - def test_iter_len(self): - cdp = x509.CRLDistributionPoints([ - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://domain")], - None, - None, - None - ), - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], - None, - frozenset([ - x509.ReasonFlags.key_compromise, - x509.ReasonFlags.ca_compromise, - ]), - None - ), - ]) - assert len(cdp) == 2 - assert list(cdp) == [ - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://domain")], - None, - None, - None - ), - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], - None, - frozenset([ - x509.ReasonFlags.key_compromise, - x509.ReasonFlags.ca_compromise, - ]), - None - ), - ] - - def test_repr(self): - cdp = x509.CRLDistributionPoints([ - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], - None, - frozenset([x509.ReasonFlags.key_compromise]), - None - ), - ]) - if six.PY3: - assert repr(cdp) == ( - "<CRLDistributionPoints([<DistributionPoint(full_name=[<Unifo" - "rmResourceIdentifier(value=ftp://domain)>], relative_name=No" - "ne, reasons=frozenset({<ReasonFlags.key_compromise: 'keyComp" - "romise'>}), crl_issuer=None)>])>" - ) - else: - assert repr(cdp) == ( - "<CRLDistributionPoints([<DistributionPoint(full_name=[<Unifo" - "rmResourceIdentifier(value=ftp://domain)>], relative_name=No" - "ne, reasons=frozenset([<ReasonFlags.key_compromise: 'keyComp" - "romise'>]), crl_issuer=None)>])>" - ) - - def test_eq(self): - cdp = x509.CRLDistributionPoints([ - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], - None, - frozenset([ - x509.ReasonFlags.key_compromise, - x509.ReasonFlags.ca_compromise, - ]), - [x509.UniformResourceIdentifier(u"uri://thing")], - ), - ]) - cdp2 = x509.CRLDistributionPoints([ - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], - None, - frozenset([ - x509.ReasonFlags.key_compromise, - x509.ReasonFlags.ca_compromise, - ]), - [x509.UniformResourceIdentifier(u"uri://thing")], - ), - ]) - assert cdp == cdp2 - - def test_ne(self): - cdp = x509.CRLDistributionPoints([ - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], - None, - frozenset([ - x509.ReasonFlags.key_compromise, - x509.ReasonFlags.ca_compromise, - ]), - [x509.UniformResourceIdentifier(u"uri://thing")], - ), - ]) - cdp2 = x509.CRLDistributionPoints([ - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain2")], - None, - frozenset([ - x509.ReasonFlags.key_compromise, - x509.ReasonFlags.ca_compromise, - ]), - [x509.UniformResourceIdentifier(u"uri://thing")], - ), - ]) - cdp3 = x509.CRLDistributionPoints([ - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], - None, - frozenset([x509.ReasonFlags.key_compromise]), - [x509.UniformResourceIdentifier(u"uri://thing")], - ), - ]) - cdp4 = x509.CRLDistributionPoints([ - x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], - None, - frozenset([ - x509.ReasonFlags.key_compromise, - x509.ReasonFlags.ca_compromise, - ]), - [x509.UniformResourceIdentifier(u"uri://thing2")], - ), - ]) - assert cdp != cdp2 - assert cdp != cdp3 - assert cdp != cdp4 - assert cdp != object() - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestCRLDistributionPointsExtension(object): - def test_fullname_and_crl_issuer(self, backend): - cert = _load_cert( - os.path.join( - "x509", "PKITS_data", "certs", "ValidcRLIssuerTest28EE.crt" - ), - x509.load_der_x509_certificate, - backend - ) - - cdps = cert.extensions.get_extension_for_oid( - x509.OID_CRL_DISTRIBUTION_POINTS - ).value - - assert cdps == x509.CRLDistributionPoints([ - x509.DistributionPoint( - full_name=[x509.DirectoryName( - x509.Name([ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), - x509.NameAttribute( - x509.OID_ORGANIZATION_NAME, - u"Test Certificates 2011" - ), - x509.NameAttribute( - x509.OID_ORGANIZATIONAL_UNIT_NAME, - u"indirectCRL CA3 cRLIssuer" - ), - x509.NameAttribute( - x509.OID_COMMON_NAME, - u"indirect CRL for indirectCRL CA3" - ), - ]) - )], - relative_name=None, - reasons=None, - crl_issuer=[x509.DirectoryName( - x509.Name([ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), - x509.NameAttribute( - x509.OID_ORGANIZATION_NAME, - u"Test Certificates 2011" - ), - x509.NameAttribute( - x509.OID_ORGANIZATIONAL_UNIT_NAME, - u"indirectCRL CA3 cRLIssuer" - ), - ]) - )], - ) - ]) - - def test_relativename_and_crl_issuer(self, backend): - cert = _load_cert( - os.path.join( - "x509", "PKITS_data", "certs", "ValidcRLIssuerTest29EE.crt" - ), - x509.load_der_x509_certificate, - backend - ) - - cdps = cert.extensions.get_extension_for_oid( - x509.OID_CRL_DISTRIBUTION_POINTS - ).value - - assert cdps == x509.CRLDistributionPoints([ - x509.DistributionPoint( - full_name=None, - relative_name=x509.Name([ - x509.NameAttribute( - x509.OID_COMMON_NAME, - u"indirect CRL for indirectCRL CA3" - ), - ]), - reasons=None, - crl_issuer=[x509.DirectoryName( - x509.Name([ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), - x509.NameAttribute( - x509.OID_ORGANIZATION_NAME, - u"Test Certificates 2011" - ), - x509.NameAttribute( - x509.OID_ORGANIZATIONAL_UNIT_NAME, - u"indirectCRL CA3 cRLIssuer" - ), - ]) - )], - ) - ]) - - def test_fullname_crl_issuer_reasons(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "cdp_fullname_reasons_crl_issuer.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - - cdps = cert.extensions.get_extension_for_oid( - x509.OID_CRL_DISTRIBUTION_POINTS - ).value - - assert cdps == x509.CRLDistributionPoints([ - x509.DistributionPoint( - full_name=[x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" - )], - relative_name=None, - reasons=frozenset([ - x509.ReasonFlags.key_compromise, - x509.ReasonFlags.ca_compromise - ]), - crl_issuer=[x509.DirectoryName( - x509.Name([ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), - x509.NameAttribute( - x509.OID_ORGANIZATION_NAME, u"PyCA" - ), - x509.NameAttribute( - x509.OID_COMMON_NAME, u"cryptography CA" - ), - ]) - )], - ) - ]) - - def test_all_reasons(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "cdp_all_reasons.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - - cdps = cert.extensions.get_extension_for_oid( - x509.OID_CRL_DISTRIBUTION_POINTS - ).value - - assert cdps == x509.CRLDistributionPoints([ - x509.DistributionPoint( - full_name=[x509.UniformResourceIdentifier( - u"http://domain.com/some.crl" - )], - relative_name=None, - reasons=frozenset([ - x509.ReasonFlags.key_compromise, - x509.ReasonFlags.ca_compromise, - x509.ReasonFlags.affiliation_changed, - x509.ReasonFlags.superseded, - x509.ReasonFlags.privilege_withdrawn, - x509.ReasonFlags.cessation_of_operation, - x509.ReasonFlags.aa_compromise, - x509.ReasonFlags.certificate_hold, - ]), - crl_issuer=None - ) - ]) - - def test_single_reason(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "cdp_reason_aa_compromise.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - - cdps = cert.extensions.get_extension_for_oid( - x509.OID_CRL_DISTRIBUTION_POINTS - ).value - - assert cdps == x509.CRLDistributionPoints([ - x509.DistributionPoint( - full_name=[x509.UniformResourceIdentifier( - u"http://domain.com/some.crl" - )], - relative_name=None, - reasons=frozenset([x509.ReasonFlags.aa_compromise]), - crl_issuer=None - ) - ]) - - def test_crl_issuer_only(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "cdp_crl_issuer.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - - cdps = cert.extensions.get_extension_for_oid( - x509.OID_CRL_DISTRIBUTION_POINTS - ).value - - assert cdps == x509.CRLDistributionPoints([ - x509.DistributionPoint( - full_name=None, - relative_name=None, - reasons=None, - crl_issuer=[x509.DirectoryName( - x509.Name([ - x509.NameAttribute( - x509.OID_COMMON_NAME, u"cryptography CA" - ), - ]) - )], - ) - ]) - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestOCSPNoCheckExtension(object): - def test_nocheck(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "ocsp_nocheck.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - ext = cert.extensions.get_extension_for_oid( - x509.OID_OCSP_NO_CHECK - ) - assert isinstance(ext.value, x509.OCSPNoCheck) - - -class TestInhibitAnyPolicy(object): - def test_not_int(self): - with pytest.raises(TypeError): - x509.InhibitAnyPolicy("notint") - - def test_negative_int(self): - with pytest.raises(ValueError): - x509.InhibitAnyPolicy(-1) - - def test_repr(self): - iap = x509.InhibitAnyPolicy(0) - assert repr(iap) == "<InhibitAnyPolicy(skip_certs=0)>" - - def test_eq(self): - iap = x509.InhibitAnyPolicy(1) - iap2 = x509.InhibitAnyPolicy(1) - assert iap == iap2 - - def test_ne(self): - iap = x509.InhibitAnyPolicy(1) - iap2 = x509.InhibitAnyPolicy(4) - assert iap != iap2 - assert iap != object() - - -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestInhibitAnyPolicyExtension(object): - def test_nocheck(self, backend): - cert = _load_cert( - os.path.join( - "x509", "custom", "inhibit_any_policy_5.pem" - ), - x509.load_pem_x509_certificate, - backend - ) - iap = cert.extensions.get_extension_for_oid( - x509.OID_INHIBIT_ANY_POLICY - ).value - assert iap.skip_certs == 5 diff --git a/tests/utils.py b/tests/utils.py index 46d93646..401b4e33 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -6,6 +6,8 @@ from __future__ import absolute_import, division, print_function import binascii import collections +import json +import os import re from contextlib import contextmanager @@ -24,42 +26,12 @@ KeyedHashVector = collections.namedtuple( ) -def select_backends(names, backend_list): - if names is None: - return backend_list - split_names = [x.strip() for x in names.split(',')] - selected_backends = [] - for backend in backend_list: - if backend.name in split_names: - selected_backends.append(backend) - - if len(selected_backends) > 0: - return selected_backends - else: - raise ValueError( - "No backend selected. Tried to select: {0}".format(split_names) - ) - - -def skip_if_empty(backend_list, required_interfaces): - if not backend_list: - pytest.skip( - "No backends provided supply the interface: {0}".format( - ", ".join(iface.__name__ for iface in required_interfaces) - ) - ) - - -def check_backend_support(item): - supported = item.keywords.get("supported") - if supported and "backend" in item.funcargs: - if not supported.kwargs["only_if"](item.funcargs["backend"]): - pytest.skip("{0} ({1})".format( - supported.kwargs["skip_message"], item.funcargs["backend"] +def check_backend_support(backend, item): + for mark in item.node.iter_markers("supported"): + if not mark.kwargs["only_if"](backend): + pytest.skip("{} ({})".format( + mark.kwargs["skip_message"], backend )) - elif supported: - raise ValueError("This mark is only available on methods that take a " - "backend") @contextmanager @@ -161,7 +133,7 @@ def load_hash_vectors(vector_data): # string as hex 00, which is of course not actually an empty # string. So we parse the provided length and catch this edge case. msg = line.split(" = ")[1].encode("ascii") if length > 0 else b"" - elif line.startswith("MD"): + elif line.startswith("MD") or line.startswith("Output"): md = line.split(" = ")[1] # after MD is found the Msg+MD (+ potential key) tuple is complete if key is not None: @@ -275,6 +247,9 @@ def load_pkcs1_vectors(vector_data): attr = None if private_key_vector is None or public_key_vector is None: + # Random garbage to defeat CPython's peephole optimizer so that + # coverage records correctly: https://bugs.python.org/issue2506 + 1 + 1 continue if line.startswith("# Private key"): @@ -377,46 +352,28 @@ def load_fips_dsa_key_pair_vectors(vector_data): Loads data out of the FIPS DSA KeyPair vector files. """ vectors = [] - # When reading_key_data is set to True it tells the loader to continue - # constructing dictionaries. We set reading_key_data to False during the - # blocks of the vectors of N=224 because we don't support it. - reading_key_data = True for line in vector_data: line = line.strip() - if not line or line.startswith("#"): - continue - elif line.startswith("[mod = L=1024"): - continue - elif line.startswith("[mod = L=2048, N=224"): - reading_key_data = False - continue - elif line.startswith("[mod = L=2048, N=256"): - reading_key_data = True - continue - elif line.startswith("[mod = L=3072"): - continue - - if not reading_key_data: + if not line or line.startswith("#") or line.startswith("[mod"): continue - elif reading_key_data: - if line.startswith("P"): - vectors.append({'p': int(line.split("=")[1], 16)}) - elif line.startswith("Q"): - vectors[-1]['q'] = int(line.split("=")[1], 16) - elif line.startswith("G"): - vectors[-1]['g'] = int(line.split("=")[1], 16) - elif line.startswith("X") and 'x' not in vectors[-1]: - vectors[-1]['x'] = int(line.split("=")[1], 16) - elif line.startswith("X") and 'x' in vectors[-1]: - vectors.append({'p': vectors[-1]['p'], - 'q': vectors[-1]['q'], - 'g': vectors[-1]['g'], - 'x': int(line.split("=")[1], 16) - }) - elif line.startswith("Y"): - vectors[-1]['y'] = int(line.split("=")[1], 16) + if line.startswith("P"): + vectors.append({'p': int(line.split("=")[1], 16)}) + elif line.startswith("Q"): + vectors[-1]['q'] = int(line.split("=")[1], 16) + elif line.startswith("G"): + vectors[-1]['g'] = int(line.split("=")[1], 16) + elif line.startswith("X") and 'x' not in vectors[-1]: + vectors[-1]['x'] = int(line.split("=")[1], 16) + elif line.startswith("X") and 'x' in vectors[-1]: + vectors.append({'p': vectors[-1]['p'], + 'q': vectors[-1]['q'], + 'g': vectors[-1]['g'], + 'x': int(line.split("=")[1], 16) + }) + elif line.startswith("Y"): + vectors[-1]['y'] = int(line.split("=")[1], 16) return vectors @@ -429,10 +386,6 @@ def load_fips_dsa_sig_vectors(vector_data): sha_regex = re.compile( r"\[mod = L=...., N=..., SHA-(?P<sha>1|224|256|384|512)\]" ) - # When reading_key_data is set to True it tells the loader to continue - # constructing dictionaries. We set reading_key_data to False during the - # blocks of the vectors of N=224 because we don't support it. - reading_key_data = True for line in vector_data: line = line.strip() @@ -442,16 +395,9 @@ def load_fips_dsa_sig_vectors(vector_data): sha_match = sha_regex.match(line) if sha_match: - digest_algorithm = "SHA-{0}".format(sha_match.group("sha")) + digest_algorithm = "SHA-{}".format(sha_match.group("sha")) - if line.startswith("[mod = L=2048, N=224"): - reading_key_data = False - continue - elif line.startswith("[mod = L=2048, N=256"): - reading_key_data = True - continue - - if not reading_key_data or line.startswith("[mod"): + if line.startswith("[mod"): continue name, value = [c.strip() for c in line.split("=")] @@ -488,7 +434,7 @@ def load_fips_dsa_sig_vectors(vector_data): return vectors -# http://tools.ietf.org/html/rfc4492#appendix-A +# https://tools.ietf.org/html/rfc4492#appendix-A _ECDSA_CURVE_NAMES = { "P-192": "secp192r1", "P-224": "secp224r1", @@ -541,8 +487,8 @@ def load_fips_ecdsa_key_pair_vectors(vector_data): elif line.startswith("Qy = "): key_data["y"] = int(line.split("=")[1], 16) - if key_data is not None: - vectors.append(key_data) + assert key_data is not None + vectors.append(key_data) return vectors @@ -561,13 +507,10 @@ def load_fips_ecdsa_signing_vectors(vector_data): for line in vector_data: line = line.strip() - if not line or line.startswith("#"): - continue - curve_match = curve_rx.match(line) if curve_match: curve_name = _ECDSA_CURVE_NAMES[curve_match.group("curve")] - digest_name = "SHA-{0}".format(curve_match.group("sha")) + digest_name = "SHA-{}".format(curve_match.group("sha")) elif line.startswith("Msg = "): if data is not None: @@ -595,8 +538,8 @@ def load_fips_ecdsa_signing_vectors(vector_data): elif line.startswith("Result = "): data["fail"] = line.split("=")[1].strip()[0] == "F" - if data is not None: - vectors.append(data) + assert data is not None + vectors.append(data) return vectors @@ -675,7 +618,7 @@ def load_kasvs_ecdh_vectors(vector_data): result_rx = re.compile(r"([FP]) \(([0-9]+) -") tags = [] - sets = dict() + sets = {} vectors = [] # find info in header @@ -713,8 +656,8 @@ def load_kasvs_ecdh_vectors(vector_data): # Data data = { - "CAVS": dict(), - "IUT": dict(), + "CAVS": {}, + "IUT": {}, } tag = None for line in vector_data: @@ -726,7 +669,7 @@ def load_kasvs_ecdh_vectors(vector_data): if line.startswith("["): tag = line.split()[0][1:] elif line.startswith("COUNT = "): - data["COUNT"] = int(line.split("=")[1], 16) + data["COUNT"] = int(line.split("=")[1]) elif line.startswith("dsCAVS = "): data["CAVS"]["d"] = int(line.split("=")[1], 16) elif line.startswith("QsCAVSx = "): @@ -760,8 +703,230 @@ def load_kasvs_ecdh_vectors(vector_data): vectors.append(data) data = { - "CAVS": dict(), - "IUT": dict(), + "CAVS": {}, + "IUT": {}, } return vectors + + +def load_x963_vectors(vector_data): + """ + Loads data out of the X9.63 vector data + """ + + vectors = [] + + # Sets Metadata + hashname = None + vector = {} + for line in vector_data: + line = line.strip() + + if line.startswith("[SHA"): + hashname = line[1:-1] + shared_secret_len = 0 + shared_info_len = 0 + key_data_len = 0 + elif line.startswith("[shared secret length"): + shared_secret_len = int(line[1:-1].split("=")[1].strip()) + elif line.startswith("[SharedInfo length"): + shared_info_len = int(line[1:-1].split("=")[1].strip()) + elif line.startswith("[key data length"): + key_data_len = int(line[1:-1].split("=")[1].strip()) + elif line.startswith("COUNT"): + count = int(line.split("=")[1].strip()) + vector["hash"] = hashname + vector["count"] = count + vector["shared_secret_length"] = shared_secret_len + vector["sharedinfo_length"] = shared_info_len + vector["key_data_length"] = key_data_len + elif line.startswith("Z"): + vector["Z"] = line.split("=")[1].strip() + assert ((shared_secret_len + 7) // 8) * 2 == len(vector["Z"]) + elif line.startswith("SharedInfo"): + if shared_info_len != 0: + vector["sharedinfo"] = line.split("=")[1].strip() + silen = len(vector["sharedinfo"]) + assert ((shared_info_len + 7) // 8) * 2 == silen + elif line.startswith("key_data"): + vector["key_data"] = line.split("=")[1].strip() + assert ((key_data_len + 7) // 8) * 2 == len(vector["key_data"]) + vectors.append(vector) + vector = {} + + return vectors + + +def load_nist_kbkdf_vectors(vector_data): + """ + Load NIST SP 800-108 KDF Vectors + """ + vectors = [] + test_data = None + tag = {} + + for line in vector_data: + line = line.strip() + + if not line or line.startswith("#"): + continue + + if line.startswith("[") and line.endswith("]"): + tag_data = line[1:-1] + name, value = [c.strip() for c in tag_data.split("=")] + if value.endswith('_BITS'): + value = int(value.split('_')[0]) + tag.update({name.lower(): value}) + continue + + tag.update({name.lower(): value.lower()}) + elif line.startswith("COUNT="): + test_data = {} + test_data.update(tag) + vectors.append(test_data) + elif line.startswith("L"): + name, value = [c.strip() for c in line.split("=")] + test_data[name.lower()] = int(value) + else: + name, value = [c.strip() for c in line.split("=")] + test_data[name.lower()] = value.encode("ascii") + + return vectors + + +def load_ed25519_vectors(vector_data): + data = [] + for line in vector_data: + secret_key, public_key, message, signature, _ = line.split(':') + # In the vectors the first element is secret key + public key + secret_key = secret_key[0:64] + # In the vectors the signature section is signature + message + signature = signature[0:128] + data.append({ + "secret_key": secret_key, + "public_key": public_key, + "message": message, + "signature": signature + }) + return data + + +def load_nist_ccm_vectors(vector_data): + test_data = None + section_data = None + global_data = {} + new_section = False + data = [] + + for line in vector_data: + line = line.strip() + + # Blank lines and comments should be ignored + if not line or line.startswith("#"): + continue + + # Some of the CCM vectors have global values for this. They are always + # at the top before the first section header (see: VADT, VNT, VPT) + if line.startswith(("Alen", "Plen", "Nlen", "Tlen")): + name, value = [c.strip() for c in line.split("=")] + global_data[name.lower()] = int(value) + continue + + # section headers contain length data we might care about + if line.startswith("["): + new_section = True + section_data = {} + section = line[1:-1] + items = [c.strip() for c in section.split(",")] + for item in items: + name, value = [c.strip() for c in item.split("=")] + section_data[name.lower()] = int(value) + continue + + name, value = [c.strip() for c in line.split("=")] + + if name.lower() in ("key", "nonce") and new_section: + section_data[name.lower()] = value.encode("ascii") + continue + + new_section = False + + # Payload is sometimes special because these vectors are absurd. Each + # example may or may not have a payload. If it does not then the + # previous example's payload should be used. We accomplish this by + # writing it into the section_data. Because we update each example + # with the section data it will be overwritten if a new payload value + # is present. NIST should be ashamed of their vector creation. + if name.lower() == "payload": + section_data[name.lower()] = value.encode("ascii") + + # Result is a special token telling us if the test should pass/fail. + # This is only present in the DVPT CCM tests + if name.lower() == "result": + if value.lower() == "pass": + test_data["fail"] = False + else: + test_data["fail"] = True + continue + + # COUNT is a special token that indicates a new block of data + if name.lower() == "count": + test_data = {} + test_data.update(global_data) + test_data.update(section_data) + data.append(test_data) + continue + # For all other tokens we simply want the name, value stored in + # the dictionary + else: + test_data[name.lower()] = value.encode("ascii") + + return data + + +class WycheproofTest(object): + def __init__(self, testfiledata, testgroup, testcase): + self.testfiledata = testfiledata + self.testgroup = testgroup + self.testcase = testcase + + def __repr__(self): + return "<WycheproofTest({!r}, {!r}, {!r}, tcId={})>".format( + self.testfiledata, + self.testgroup, + self.testcase, + self.testcase["tcId"], + ) + + @property + def valid(self): + return self.testcase["result"] == "valid" + + @property + def acceptable(self): + return self.testcase["result"] == "acceptable" + + @property + def invalid(self): + return self.testcase["result"] == "invalid" + + def has_flag(self, flag): + return flag in self.testcase["flags"] + + +def skip_if_wycheproof_none(wycheproof): + # This is factored into its own function so we can easily test both + # branches + if wycheproof is None: + pytest.skip("--wycheproof-root not provided") + + +def load_wycheproof_tests(wycheproof, test_file): + path = os.path.join(wycheproof, "testvectors", test_file) + with open(path) as f: + data = json.load(f) + for group in data.pop("testGroups"): + cases = group.pop("tests") + for c in cases: + yield WycheproofTest(data, group, c) 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 diff --git a/tests/x509/__init__.py b/tests/x509/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/x509/__init__.py diff --git a/tests/x509/test_ocsp.py b/tests/x509/test_ocsp.py new file mode 100644 index 00000000..2b6ec569 --- /dev/null +++ b/tests/x509/test_ocsp.py @@ -0,0 +1,858 @@ +# 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 base64 +import datetime +import os + +import pytest + +from cryptography import x509 +from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec, ed25519, ed448 +from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 +from cryptography.x509 import ocsp + +from .test_x509 import _load_cert +from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 +from ..utils import load_vectors_from_file + + +def _load_data(filename, loader): + return load_vectors_from_file( + filename=filename, + loader=lambda data: loader(data.read()), + mode="rb" + ) + + +def _cert_and_issuer(): + from cryptography.hazmat.backends.openssl.backend import backend + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + issuer = _load_cert( + os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), + x509.load_pem_x509_certificate, + backend + ) + return cert, issuer + + +def _generate_root(private_key=None, algorithm=hashes.SHA256()): + from cryptography.hazmat.backends.openssl.backend import backend + + if private_key is None: + private_key = EC_KEY_SECP256R1.private_key(backend) + + subject = x509.Name([ + x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.NameOID.COMMON_NAME, u'Cryptography CA'), + ]) + + builder = x509.CertificateBuilder().serial_number( + 123456789 + ).issuer_name( + subject + ).subject_name( + subject + ).public_key( + private_key.public_key() + ).not_valid_before( + datetime.datetime.now() + ).not_valid_after( + datetime.datetime.now() + datetime.timedelta(days=3650) + ) + + cert = builder.sign(private_key, algorithm, backend) + return cert, private_key + + +class TestOCSPRequest(object): + def test_bad_request(self): + with pytest.raises(ValueError): + ocsp.load_der_ocsp_request(b"invalid") + + def test_load_request(self): + req = _load_data( + os.path.join("x509", "ocsp", "req-sha1.der"), + ocsp.load_der_ocsp_request, + ) + assert req.issuer_name_hash == (b"8\xcaF\x8c\x07D\x8d\xf4\x81\x96" + b"\xc7mmLpQ\x9e`\xa7\xbd") + assert req.issuer_key_hash == (b"yu\xbb\x84:\xcb,\xdez\t\xbe1" + b"\x1bC\xbc\x1c*MSX") + assert isinstance(req.hash_algorithm, hashes.SHA1) + assert req.serial_number == int( + "98D9E5C0B4C373552DF77C5D0F1EB5128E4945F9", 16 + ) + assert len(req.extensions) == 0 + + def test_load_request_with_extensions(self): + req = _load_data( + os.path.join("x509", "ocsp", "req-ext-nonce.der"), + ocsp.load_der_ocsp_request, + ) + assert len(req.extensions) == 1 + ext = req.extensions[0] + assert ext.critical is False + assert ext.value == x509.OCSPNonce( + b"\x04\x10{\x80Z\x1d7&\xb8\xb8OH\xd2\xf8\xbf\xd7-\xfd" + ) + + def test_load_request_two_requests(self): + with pytest.raises(NotImplementedError): + _load_data( + os.path.join("x509", "ocsp", "req-multi-sha1.der"), + ocsp.load_der_ocsp_request, + ) + + def test_invalid_hash_algorithm(self): + req = _load_data( + os.path.join("x509", "ocsp", "req-invalid-hash-alg.der"), + ocsp.load_der_ocsp_request, + ) + with pytest.raises(UnsupportedAlgorithm): + req.hash_algorithm + + def test_serialize_request(self): + req_bytes = load_vectors_from_file( + filename=os.path.join("x509", "ocsp", "req-sha1.der"), + loader=lambda data: data.read(), + mode="rb" + ) + req = ocsp.load_der_ocsp_request(req_bytes) + assert req.public_bytes(serialization.Encoding.DER) == req_bytes + + def test_invalid_serialize_encoding(self): + req = _load_data( + os.path.join("x509", "ocsp", "req-sha1.der"), + ocsp.load_der_ocsp_request, + ) + with pytest.raises(ValueError): + req.public_bytes("invalid") + with pytest.raises(ValueError): + req.public_bytes(serialization.Encoding.PEM) + + +class TestOCSPRequestBuilder(object): + def test_add_two_certs(self): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_certificate(cert, issuer, hashes.SHA1()) + with pytest.raises(ValueError): + builder.add_certificate(cert, issuer, hashes.SHA1()) + + def test_create_ocsp_request_no_req(self): + builder = ocsp.OCSPRequestBuilder() + with pytest.raises(ValueError): + builder.build() + + def test_create_ocsp_request_invalid_alg(self): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPRequestBuilder() + with pytest.raises(ValueError): + builder.add_certificate(cert, issuer, hashes.MD5()) + + def test_add_extension_twice(self): + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_extension(x509.OCSPNonce(b"123"), False) + with pytest.raises(ValueError): + builder.add_extension(x509.OCSPNonce(b"123"), False) + + def test_add_invalid_extension(self): + builder = ocsp.OCSPRequestBuilder() + with pytest.raises(TypeError): + builder.add_extension("notanext", False) + + def test_create_ocsp_request_invalid_cert(self): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPRequestBuilder() + with pytest.raises(TypeError): + builder.add_certificate(b"notacert", issuer, hashes.SHA1()) + + with pytest.raises(TypeError): + builder.add_certificate(cert, b"notacert", hashes.SHA1()) + + def test_create_ocsp_request(self): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_certificate(cert, issuer, hashes.SHA1()) + req = builder.build() + serialized = req.public_bytes(serialization.Encoding.DER) + assert serialized == base64.b64decode( + b"MEMwQTA/MD0wOzAJBgUrDgMCGgUABBRAC0Z68eay0wmDug1gfn5ZN0gkxAQUw5zz" + b"/NNGCDS7zkZ/oHxb8+IIy1kCAj8g" + ) + + @pytest.mark.parametrize( + ("ext", "critical"), + [ + [x509.OCSPNonce(b"0000"), False], + [x509.OCSPNonce(b"\x00\x01\x02"), True], + ] + ) + def test_create_ocsp_request_with_extension(self, ext, critical): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_certificate( + cert, issuer, hashes.SHA1() + ).add_extension( + ext, critical + ) + req = builder.build() + assert len(req.extensions) == 1 + assert req.extensions[0].value == ext + assert req.extensions[0].oid == ext.oid + assert req.extensions[0].critical is critical + + +class TestOCSPResponseBuilder(object): + def test_add_response_twice(self): + cert, issuer = _cert_and_issuer() + time = datetime.datetime.now() + builder = ocsp.OCSPResponseBuilder() + builder = builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, + time, None, None + ) + with pytest.raises(ValueError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, + time, None, None + ) + + def test_invalid_add_response(self): + cert, issuer = _cert_and_issuer() + time = datetime.datetime.utcnow() + reason = x509.ReasonFlags.cessation_of_operation + builder = ocsp.OCSPResponseBuilder() + with pytest.raises(TypeError): + builder.add_response( + 'bad', issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, + time, time, None, None + ) + with pytest.raises(TypeError): + builder.add_response( + cert, 'bad', hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, + time, time, None, None + ) + with pytest.raises(ValueError): + builder.add_response( + cert, issuer, 'notahash', ocsp.OCSPCertStatus.GOOD, + time, time, None, None + ) + with pytest.raises(TypeError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, + 'bad', time, None, None + ) + with pytest.raises(TypeError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, + time, 'bad', None, None + ) + + with pytest.raises(TypeError): + builder.add_response( + cert, issuer, hashes.SHA256(), 0, time, time, None, None + ) + with pytest.raises(ValueError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, + time, time, time, None + ) + with pytest.raises(ValueError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, + time, time, None, reason + ) + with pytest.raises(TypeError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.REVOKED, + time, time, None, reason + ) + with pytest.raises(TypeError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.REVOKED, + time, time, time, 0 + ) + with pytest.raises(ValueError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.REVOKED, + time, time, time - datetime.timedelta(days=36500), None + ) + + def test_invalid_certificates(self): + builder = ocsp.OCSPResponseBuilder() + with pytest.raises(ValueError): + builder.certificates([]) + with pytest.raises(TypeError): + builder.certificates(['notacert']) + with pytest.raises(TypeError): + builder.certificates('invalid') + + _, issuer = _cert_and_issuer() + builder = builder.certificates([issuer]) + with pytest.raises(ValueError): + builder.certificates([issuer]) + + def test_invalid_responder_id(self): + builder = ocsp.OCSPResponseBuilder() + cert, _ = _cert_and_issuer() + with pytest.raises(TypeError): + builder.responder_id(ocsp.OCSPResponderEncoding.HASH, 'invalid') + with pytest.raises(TypeError): + builder.responder_id('notanenum', cert) + + builder = builder.responder_id(ocsp.OCSPResponderEncoding.NAME, cert) + with pytest.raises(ValueError): + builder.responder_id(ocsp.OCSPResponderEncoding.NAME, cert) + + def test_invalid_extension(self): + builder = ocsp.OCSPResponseBuilder() + with pytest.raises(TypeError): + builder.add_extension("notanextension", True) + + def test_sign_no_response(self): + builder = ocsp.OCSPResponseBuilder() + root_cert, private_key = _generate_root() + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256()) + + def test_sign_no_responder_id(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + _, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256()) + + def test_sign_invalid_hash_algorithm(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ) + with pytest.raises(TypeError): + builder.sign(private_key, 'notahash') + + def test_sign_good_cert(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert resp.responder_name == root_cert.subject + assert resp.responder_key_hash is None + assert (current_time - resp.produced_at).total_seconds() < 10 + assert (resp.signature_algorithm_oid == + x509.SignatureAlgorithmOID.ECDSA_WITH_SHA256) + assert resp.certificate_status == ocsp.OCSPCertStatus.GOOD + assert resp.revocation_time is None + assert resp.revocation_reason is None + assert resp.this_update == this_update + assert resp.next_update == next_update + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) + ) + + def test_sign_revoked_cert(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + revoked_date = this_update - datetime.timedelta(days=300) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, + this_update, next_update, revoked_date, None + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED + assert resp.revocation_time == revoked_date + assert resp.revocation_reason is None + assert resp.this_update == this_update + assert resp.next_update == next_update + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) + ) + + def test_sign_with_appended_certs(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ).certificates( + [root_cert] + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert resp.certificates == [root_cert] + + def test_sign_revoked_no_next_update(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + revoked_date = this_update - datetime.timedelta(days=300) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, + this_update, None, revoked_date, None + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED + assert resp.revocation_time == revoked_date + assert resp.revocation_reason is None + assert resp.this_update == this_update + assert resp.next_update is None + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) + ) + + def test_sign_revoked_with_reason(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + revoked_date = this_update - datetime.timedelta(days=300) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, + this_update, next_update, revoked_date, + x509.ReasonFlags.key_compromise + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED + assert resp.revocation_time == revoked_date + assert resp.revocation_reason is x509.ReasonFlags.key_compromise + assert resp.this_update == this_update + assert resp.next_update == next_update + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) + ) + + def test_sign_responder_id_key_hash(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.HASH, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert resp.responder_name is None + assert resp.responder_key_hash == ( + b'\x8ca\x94\xe0\x948\xed\x89\xd8\xd4N\x89p\t\xd6\xf9^_\xec}' + ) + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) + ) + + def test_invalid_sign_responder_cert_does_not_match_private_key(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.HASH, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ) + from cryptography.hazmat.backends.openssl.backend import backend + diff_key = ec.generate_private_key(ec.SECP256R1(), backend) + with pytest.raises(ValueError): + builder.sign(diff_key, hashes.SHA256()) + + def test_sign_with_extension(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.HASH, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ).add_extension( + x509.OCSPNonce(b"012345"), False + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert len(resp.extensions) == 1 + assert resp.extensions[0].value == x509.OCSPNonce(b"012345") + assert resp.extensions[0].critical is False + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) + ) + + @pytest.mark.parametrize( + ("status", "der"), + [ + (ocsp.OCSPResponseStatus.MALFORMED_REQUEST, b"0\x03\n\x01\x01"), + (ocsp.OCSPResponseStatus.INTERNAL_ERROR, b"0\x03\n\x01\x02"), + (ocsp.OCSPResponseStatus.TRY_LATER, b"0\x03\n\x01\x03"), + (ocsp.OCSPResponseStatus.SIG_REQUIRED, b"0\x03\n\x01\x05"), + (ocsp.OCSPResponseStatus.UNAUTHORIZED, b"0\x03\n\x01\x06"), + ] + ) + def test_build_non_successful_statuses(self, status, der): + resp = ocsp.OCSPResponseBuilder.build_unsuccessful(status) + assert resp.response_status is status + assert resp.public_bytes(serialization.Encoding.DER) == der + + def test_invalid_build_not_a_status(self): + with pytest.raises(TypeError): + ocsp.OCSPResponseBuilder.build_unsuccessful("notastatus") + + def test_invalid_build_successful_status(self): + with pytest.raises(ValueError): + ocsp.OCSPResponseBuilder.build_unsuccessful( + ocsp.OCSPResponseStatus.SUCCESSFUL + ) + + +class TestOCSPResponse(object): + def test_bad_response(self): + with pytest.raises(ValueError): + ocsp.load_der_ocsp_response(b"invalid") + + def test_load_response(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-sha256.der"), + ocsp.load_der_ocsp_response, + ) + from cryptography.hazmat.backends.openssl.backend import backend + issuer = _load_cert( + os.path.join("x509", "letsencryptx3.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert resp.response_status == ocsp.OCSPResponseStatus.SUCCESSFUL + assert (resp.signature_algorithm_oid == + x509.SignatureAlgorithmOID.RSA_WITH_SHA256) + assert isinstance(resp.signature_hash_algorithm, hashes.SHA256) + assert resp.signature == base64.b64decode( + b"I9KUlyLV/2LbNCVu1BQphxdNlU/jBzXsPYVscPjW5E93pCrSO84GkIWoOJtqsnt" + b"78DLcQPnF3W24NXGzSGKlSWfXIsyoXCxnBm0mIbD5ZMnKyXEnqSR33Z9He/A+ML" + b"A8gbrDUipGNPosesenkKUnOtFIzEGv29hV5E6AMP2ORPVsVlTAZegPJFbbVIWc0" + b"rZGFCXKxijDxtUtgWzBhpBAI50JbPHi+IVuaOe4aDJLYgZ0BIBNa6bDI+rScyoy" + b"5U0DToV7SZn6CoJ3U19X7BHdYn6TLX0xi43eXuzBGzdHnSzmsc7r/DvkAKJm3vb" + b"dVECXqe/gFlXJUBcZ25jhs70MUA==" + ) + assert resp.tbs_response_bytes == base64.b64decode( + b"MIHWoUwwSjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzA" + b"hBgNVBAMTGkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzGA8yMDE4MDgzMDExMT" + b"UwMFowdTBzMEswCQYFKw4DAhoFAAQUfuZq53Kas/z4oiBkbBahLWBxCF0EFKhKa" + b"mMEfd265tE5t6ZFZe/zqOyhAhIDHHh6fckClQB7xfIiCztSevCAABgPMjAxODA4" + b"MzAxMTAwMDBaoBEYDzIwMTgwOTA2MTEwMDAwWg==" + ) + issuer.public_key().verify( + resp.signature, + resp.tbs_response_bytes, + PKCS1v15(), + resp.signature_hash_algorithm + ) + assert resp.certificates == [] + assert resp.responder_key_hash is None + assert resp.responder_name == issuer.subject + assert resp.produced_at == datetime.datetime(2018, 8, 30, 11, 15) + assert resp.certificate_status == ocsp.OCSPCertStatus.GOOD + assert resp.revocation_time is None + assert resp.revocation_reason is None + assert resp.this_update == datetime.datetime(2018, 8, 30, 11, 0) + assert resp.next_update == datetime.datetime(2018, 9, 6, 11, 0) + assert resp.issuer_key_hash == ( + b'\xa8Jjc\x04}\xdd\xba\xe6\xd19\xb7\xa6Ee\xef\xf3\xa8\xec\xa1' + ) + assert resp.issuer_name_hash == ( + b'~\xe6j\xe7r\x9a\xb3\xfc\xf8\xa2 dl\x16\xa1-`q\x08]' + ) + assert isinstance(resp.hash_algorithm, hashes.SHA1) + assert resp.serial_number == 271024907440004808294641238224534273948400 + assert len(resp.extensions) == 0 + + def test_load_unauthorized(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-unauthorized.der"), + ocsp.load_der_ocsp_response, + ) + assert resp.response_status == ocsp.OCSPResponseStatus.UNAUTHORIZED + with pytest.raises(ValueError): + resp.signature_algorithm_oid + with pytest.raises(ValueError): + resp.signature_hash_algorithm + with pytest.raises(ValueError): + resp.signature + with pytest.raises(ValueError): + resp.tbs_response_bytes + with pytest.raises(ValueError): + resp.certificates + with pytest.raises(ValueError): + resp.responder_key_hash + with pytest.raises(ValueError): + resp.responder_name + with pytest.raises(ValueError): + resp.produced_at + with pytest.raises(ValueError): + resp.certificate_status + with pytest.raises(ValueError): + resp.revocation_time + with pytest.raises(ValueError): + resp.revocation_reason + with pytest.raises(ValueError): + resp.this_update + with pytest.raises(ValueError): + resp.next_update + with pytest.raises(ValueError): + resp.issuer_key_hash + with pytest.raises(ValueError): + resp.issuer_name_hash + with pytest.raises(ValueError): + resp.hash_algorithm + with pytest.raises(ValueError): + resp.serial_number + with pytest.raises(ValueError): + resp.extensions + + def test_load_revoked(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-revoked.der"), + ocsp.load_der_ocsp_response, + ) + assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED + assert resp.revocation_time == datetime.datetime( + 2016, 9, 2, 21, 28, 48 + ) + assert resp.revocation_reason is None + + def test_load_delegate_unknown_cert(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-delegate-unknown-cert.der"), + ocsp.load_der_ocsp_response, + ) + assert len(resp.certificates) == 1 + assert isinstance(resp.certificates[0], x509.Certificate) + assert resp.certificate_status == ocsp.OCSPCertStatus.UNKNOWN + + def test_load_invalid_signature_oid(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-invalid-signature-oid.der"), + ocsp.load_der_ocsp_response, + ) + assert resp.signature_algorithm_oid == x509.ObjectIdentifier( + "1.2.840.113549.1.1.2" + ) + with pytest.raises(UnsupportedAlgorithm): + resp.signature_hash_algorithm + + def test_load_responder_key_hash(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-responder-key-hash.der"), + ocsp.load_der_ocsp_response, + ) + assert resp.responder_name is None + assert resp.responder_key_hash == ( + b'\x0f\x80a\x1c\x821a\xd5/(\xe7\x8dF8\xb4,\xe1\xc6\xd9\xe2' + ) + + def test_load_revoked_reason(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-revoked-reason.der"), + ocsp.load_der_ocsp_response, + ) + assert resp.revocation_reason is x509.ReasonFlags.superseded + + def test_load_revoked_no_next_update(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-revoked-no-next-update.der"), + ocsp.load_der_ocsp_response, + ) + assert resp.serial_number == 16160 + assert resp.next_update is None + + def test_response_extensions(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-revoked-reason.der"), + ocsp.load_der_ocsp_response, + ) + assert len(resp.extensions) == 1 + ext = resp.extensions[0] + assert ext.critical is False + assert ext.value == x509.OCSPNonce( + b'\x04\x105\x957\x9fa\x03\x83\x87\x89rW\x8f\xae\x99\xf7"' + ) + + def test_serialize_reponse(self): + resp_bytes = load_vectors_from_file( + filename=os.path.join("x509", "ocsp", "resp-revoked.der"), + loader=lambda data: data.read(), + mode="rb" + ) + resp = ocsp.load_der_ocsp_response(resp_bytes) + assert resp.public_bytes(serialization.Encoding.DER) == resp_bytes + + def test_invalid_serialize_encoding(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-revoked.der"), + ocsp.load_der_ocsp_response, + ) + with pytest.raises(ValueError): + resp.public_bytes("invalid") + with pytest.raises(ValueError): + resp.public_bytes(serialization.Encoding.PEM) + + def test_single_extensions(self, backend): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-single-extension-reason.der"), + ocsp.load_der_ocsp_response, + ) + assert len(resp.single_extensions) == 1 + ext = resp.single_extensions[0] + assert ext.oid == x509.CRLReason.oid + assert ext.value == x509.CRLReason(x509.ReasonFlags.unspecified) + + +class TestOCSPEdDSA(object): + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support / OCSP" + ) + def test_invalid_algorithm(self, backend): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + private_key = ed25519.Ed25519PrivateKey.generate() + root_cert, _ = _generate_root(private_key, None) + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + revoked_date = this_update - datetime.timedelta(days=300) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, + this_update, next_update, revoked_date, + x509.ReasonFlags.key_compromise + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256()) + + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support / OCSP" + ) + def test_sign_ed25519(self, backend): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + private_key = ed25519.Ed25519PrivateKey.generate() + root_cert, _ = _generate_root(private_key, None) + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + revoked_date = this_update - datetime.timedelta(days=300) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, + this_update, next_update, revoked_date, + x509.ReasonFlags.key_compromise + ) + resp = builder.sign(private_key, None) + assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED + assert resp.revocation_time == revoked_date + assert resp.revocation_reason is x509.ReasonFlags.key_compromise + assert resp.this_update == this_update + assert resp.next_update == next_update + assert resp.signature_hash_algorithm is None + assert (resp.signature_algorithm_oid == + x509.SignatureAlgorithmOID.ED25519) + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support / OCSP" + ) + def test_sign_ed448(self, backend): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + private_key = ed448.Ed448PrivateKey.generate() + root_cert, _ = _generate_root(private_key, None) + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + revoked_date = this_update - datetime.timedelta(days=300) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, + this_update, next_update, revoked_date, + x509.ReasonFlags.key_compromise + ) + resp = builder.sign(private_key, None) + assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED + assert resp.revocation_time == revoked_date + assert resp.revocation_reason is x509.ReasonFlags.key_compromise + assert resp.this_update == this_update + assert resp.next_update == next_update + assert resp.signature_hash_algorithm is None + assert (resp.signature_algorithm_oid == + x509.SignatureAlgorithmOID.ED448) + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes + ) diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py new file mode 100644 index 00000000..38fe6bf8 --- /dev/null +++ b/tests/x509/test_x509.py @@ -0,0 +1,4746 @@ +# -*- coding: utf-8 -*- +# 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 collections +import datetime +import ipaddress +import os + +import pytest + +import pytz + +import six + +from cryptography import utils, x509 +from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat._der import ( + BIT_STRING, CONSTRUCTED, CONTEXT_SPECIFIC, DERReader, GENERALIZED_TIME, + INTEGER, OBJECT_IDENTIFIER, PRINTABLE_STRING, SEQUENCE, SET, UTC_TIME +) +from cryptography.hazmat.backends.interfaces import ( + DSABackend, EllipticCurveBackend, RSABackend, X509Backend +) +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ( + dsa, ec, ed25519, ed448, padding, rsa +) +from cryptography.hazmat.primitives.asymmetric.utils import ( + decode_dss_signature +) +from cryptography.x509.name import _ASN1Type +from cryptography.x509.oid import ( + AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID, + NameOID, SignatureAlgorithmOID +) + +from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048 +from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 +from ..hazmat.primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 +from ..hazmat.primitives.test_ec import _skip_curve_unsupported +from ..utils import load_vectors_from_file + + +@utils.register_interface(x509.ExtensionType) +class DummyExtension(object): + oid = x509.ObjectIdentifier("1.2.3.4") + + +@utils.register_interface(x509.GeneralName) +class FakeGeneralName(object): + def __init__(self, value): + self._value = value + + value = utils.read_only_property("_value") + + +def _load_cert(filename, loader, backend): + cert = load_vectors_from_file( + filename=filename, + loader=lambda pemfile: loader(pemfile.read(), backend), + mode="rb" + ) + return cert + + +ParsedCertificate = collections.namedtuple( + "ParsedCertificate", + ["not_before_tag", "not_after_tag", "issuer", "subject"] +) + + +def _parse_cert(der): + # See the Certificate structured, defined in RFC 5280. + with DERReader(der).read_single_element(SEQUENCE) as cert: + tbs_cert = cert.read_element(SEQUENCE) + # Skip outer signature algorithm + _ = cert.read_element(SEQUENCE) + # Skip signature + _ = cert.read_element(BIT_STRING) + + with tbs_cert: + # Skip version + _ = tbs_cert.read_optional_element(CONTEXT_SPECIFIC | CONSTRUCTED | 0) + # Skip serialNumber + _ = tbs_cert.read_element(INTEGER) + # Skip inner signature algorithm + _ = tbs_cert.read_element(SEQUENCE) + issuer = tbs_cert.read_element(SEQUENCE) + validity = tbs_cert.read_element(SEQUENCE) + subject = tbs_cert.read_element(SEQUENCE) + # Skip subjectPublicKeyInfo + _ = tbs_cert.read_element(SEQUENCE) + # Skip issuerUniqueID + _ = tbs_cert.read_optional_element(CONTEXT_SPECIFIC | CONSTRUCTED | 1) + # Skip subjectUniqueID + _ = tbs_cert.read_optional_element(CONTEXT_SPECIFIC | CONSTRUCTED | 2) + # Skip extensions + _ = tbs_cert.read_optional_element(CONTEXT_SPECIFIC | CONSTRUCTED | 3) + + with validity: + not_before_tag, _ = validity.read_any_element() + not_after_tag, _ = validity.read_any_element() + + return ParsedCertificate( + not_before_tag=not_before_tag, + not_after_tag=not_after_tag, + issuer=issuer, + subject=subject, + ) + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestCertificateRevocationList(object): + def test_load_pem_crl(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert isinstance(crl, x509.CertificateRevocationList) + fingerprint = binascii.hexlify(crl.fingerprint(hashes.SHA1())) + assert fingerprint == b"3234b0cb4c0cedf6423724b736729dcfc9e441ef" + assert isinstance(crl.signature_hash_algorithm, hashes.SHA256) + assert ( + crl.signature_algorithm_oid == + SignatureAlgorithmOID.RSA_WITH_SHA256 + ) + + def test_load_der_crl(self, backend): + crl = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + assert isinstance(crl, x509.CertificateRevocationList) + fingerprint = binascii.hexlify(crl.fingerprint(hashes.SHA1())) + assert fingerprint == b"dd3db63c50f4c4a13e090f14053227cb1011a5ad" + assert isinstance(crl.signature_hash_algorithm, hashes.SHA256) + + def test_invalid_pem(self, backend): + with pytest.raises(ValueError): + x509.load_pem_x509_crl(b"notacrl", backend) + + def test_invalid_der(self, backend): + with pytest.raises(ValueError): + x509.load_der_x509_crl(b"notacrl", backend) + + def test_unknown_signature_algorithm(self, backend): + crl = _load_cert( + os.path.join( + "x509", "custom", "crl_md2_unknown_crit_entry_ext.pem" + ), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(UnsupportedAlgorithm): + crl.signature_hash_algorithm() + + def test_issuer(self, backend): + crl = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + assert isinstance(crl.issuer, x509.Name) + assert list(crl.issuer) == [ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute( + x509.OID_ORGANIZATION_NAME, u'Test Certificates 2011' + ), + x509.NameAttribute(x509.OID_COMMON_NAME, u'Good CA') + ] + assert crl.issuer.get_attributes_for_oid(x509.OID_COMMON_NAME) == [ + x509.NameAttribute(x509.OID_COMMON_NAME, u'Good CA') + ] + + def test_equality(self, backend): + crl1 = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + crl2 = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + crl3 = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert crl1 == crl2 + assert crl1 != crl3 + assert crl1 != object() + + def test_update_dates(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert isinstance(crl.next_update, datetime.datetime) + assert isinstance(crl.last_update, datetime.datetime) + + assert crl.next_update.isoformat() == "2016-01-01T00:00:00" + assert crl.last_update.isoformat() == "2015-01-01T00:00:00" + + def test_revoked_cert_retrieval(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + for r in crl: + assert isinstance(r, x509.RevokedCertificate) + + # Check that len() works for CRLs. + assert len(crl) == 12 + + def test_get_revoked_certificate_by_serial_number(self, backend): + crl = _load_cert( + os.path.join( + "x509", "PKITS_data", "crls", "LongSerialNumberCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + serial_number = 725064303890588110203033396814564464046290047507 + revoked = crl.get_revoked_certificate_by_serial_number(serial_number) + assert revoked.serial_number == serial_number + assert crl.get_revoked_certificate_by_serial_number(500) is None + + def test_revoked_cert_retrieval_retain_only_revoked(self, backend): + """ + This test attempts to trigger the crash condition described in + https://github.com/pyca/cryptography/issues/2557 + PyPy does gc at its own pace, so it will only be reliable on CPython. + """ + revoked = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + )[11] + assert revoked.revocation_date == datetime.datetime(2015, 1, 1, 0, 0) + assert revoked.serial_number == 11 + + def test_extensions(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_ian_aia_aki.pem"), + x509.load_pem_x509_crl, + backend + ) + + crl_number = crl.extensions.get_extension_for_oid( + ExtensionOID.CRL_NUMBER + ) + aki = crl.extensions.get_extension_for_class( + x509.AuthorityKeyIdentifier + ) + aia = crl.extensions.get_extension_for_class( + x509.AuthorityInformationAccess + ) + ian = crl.extensions.get_extension_for_class( + x509.IssuerAlternativeName + ) + assert crl_number.value == x509.CRLNumber(1) + assert crl_number.critical is False + assert aki.value == x509.AuthorityKeyIdentifier( + key_identifier=( + b'yu\xbb\x84:\xcb,\xdez\t\xbe1\x1bC\xbc\x1c*MSX' + ), + authority_cert_issuer=None, + authority_cert_serial_number=None + ) + assert aia.value == x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.DNSName(u"cryptography.io") + ) + ]) + assert ian.value == x509.IssuerAlternativeName([ + x509.UniformResourceIdentifier(u"https://cryptography.io"), + ]) + + def test_delta_crl_indicator(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_delta_crl_indicator.pem"), + x509.load_pem_x509_crl, + backend + ) + + dci = crl.extensions.get_extension_for_oid( + ExtensionOID.DELTA_CRL_INDICATOR + ) + assert dci.value == x509.DeltaCRLIndicator(12345678901234567890) + assert dci.critical is False + + def test_signature(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert crl.signature == binascii.unhexlify( + b"536a5a0794f68267361e7bc2f19167a3e667a2ab141535616855d8deb2ba1af" + b"9fd4546b1fe76b454eb436af7b28229fedff4634dfc9dd92254266219ae0ea8" + b"75d9ff972e9a2da23d5945f073da18c50a4265bfed9ca16586347800ef49dd1" + b"6856d7265f4f3c498a57f04dc04404e2bd2e2ada1f5697057aacef779a18371" + b"c621edc9a5c2b8ec1716e8fa22feeb7fcec0ce9156c8d344aa6ae8d1a5d99d0" + b"9386df36307df3b63c83908f4a61a0ff604c1e292ad63b349d1082ddd7ae1b7" + b"c178bba995523ec6999310c54da5706549797bfb1230f5593ba7b4353dade4f" + b"d2be13a57580a6eb20b5c4083f000abac3bf32cd8b75f23e4c8f4b3a79e1e2d" + b"58a472b0" + ) + + def test_tbs_certlist_bytes(self, backend): + crl = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + ca_cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + ca_cert.public_key().verify( + crl.signature, crl.tbs_certlist_bytes, + padding.PKCS1v15(), crl.signature_hash_algorithm + ) + + def test_public_bytes_pem(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_empty.pem"), + x509.load_pem_x509_crl, + backend + ) + + # Encode it to PEM and load it back. + crl = x509.load_pem_x509_crl(crl.public_bytes( + encoding=serialization.Encoding.PEM, + ), backend) + + assert len(crl) == 0 + assert crl.last_update == datetime.datetime(2015, 12, 20, 23, 44, 47) + assert crl.next_update == datetime.datetime(2015, 12, 28, 0, 44, 47) + + def test_public_bytes_der(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + # Encode it to DER and load it back. + crl = x509.load_der_x509_crl(crl.public_bytes( + encoding=serialization.Encoding.DER, + ), backend) + + assert len(crl) == 12 + assert crl.last_update == datetime.datetime(2015, 1, 1, 0, 0, 0) + assert crl.next_update == datetime.datetime(2016, 1, 1, 0, 0, 0) + + @pytest.mark.parametrize( + ("cert_path", "loader_func", "encoding"), + [ + ( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + serialization.Encoding.PEM, + ), + ( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + serialization.Encoding.DER, + ), + ] + ) + def test_public_bytes_match(self, cert_path, loader_func, encoding, + backend): + crl_bytes = load_vectors_from_file( + cert_path, lambda pemfile: pemfile.read(), mode="rb" + ) + crl = loader_func(crl_bytes, backend) + serialized = crl.public_bytes(encoding) + assert serialized == crl_bytes + + def test_public_bytes_invalid_encoding(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_empty.pem"), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(TypeError): + crl.public_bytes('NotAnEncoding') + + def test_verify_bad(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "invalid_signature.pem"), + x509.load_pem_x509_crl, + backend + ) + crt = _load_cert( + os.path.join("x509", "custom", "invalid_signature.pem"), + x509.load_pem_x509_certificate, + backend + ) + + assert not crl.is_signature_valid(crt.public_key()) + + def test_verify_good(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "valid_signature.pem"), + x509.load_pem_x509_crl, + backend + ) + crt = _load_cert( + os.path.join("x509", "custom", "valid_signature.pem"), + x509.load_pem_x509_certificate, + backend + ) + + assert crl.is_signature_valid(crt.public_key()) + + def test_verify_argument_must_be_a_public_key(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "valid_signature.pem"), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(TypeError): + crl.is_signature_valid("not a public key") + + with pytest.raises(TypeError): + crl.is_signature_valid(object) + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRevokedCertificate(object): + def test_revoked_basics(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + for i, rev in enumerate(crl): + assert isinstance(rev, x509.RevokedCertificate) + assert isinstance(rev.serial_number, int) + assert isinstance(rev.revocation_date, datetime.datetime) + assert isinstance(rev.extensions, x509.Extensions) + + assert rev.serial_number == i + assert rev.revocation_date.isoformat() == "2015-01-01T00:00:00" + + def test_revoked_extensions(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + exp_issuer = [ + x509.DirectoryName(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), + x509.NameAttribute(x509.OID_COMMON_NAME, u"cryptography.io"), + ])) + ] + + # First revoked cert doesn't have extensions, test if it is handled + # correctly. + rev0 = crl[0] + # It should return an empty Extensions object. + assert isinstance(rev0.extensions, x509.Extensions) + assert len(rev0.extensions) == 0 + with pytest.raises(x509.ExtensionNotFound): + rev0.extensions.get_extension_for_oid(x509.OID_CRL_REASON) + with pytest.raises(x509.ExtensionNotFound): + rev0.extensions.get_extension_for_oid(x509.OID_CERTIFICATE_ISSUER) + with pytest.raises(x509.ExtensionNotFound): + rev0.extensions.get_extension_for_oid(x509.OID_INVALIDITY_DATE) + + # Test manual retrieval of extension values. + rev1 = crl[1] + assert isinstance(rev1.extensions, x509.Extensions) + + reason = rev1.extensions.get_extension_for_class( + x509.CRLReason).value + assert reason == x509.CRLReason(x509.ReasonFlags.unspecified) + + issuer = rev1.extensions.get_extension_for_class( + x509.CertificateIssuer).value + assert issuer == x509.CertificateIssuer(exp_issuer) + + date = rev1.extensions.get_extension_for_class( + x509.InvalidityDate).value + assert date == x509.InvalidityDate(datetime.datetime(2015, 1, 1, 0, 0)) + + # Check if all reason flags can be found in the CRL. + flags = set(x509.ReasonFlags) + for rev in crl: + try: + r = rev.extensions.get_extension_for_class(x509.CRLReason) + except x509.ExtensionNotFound: + # Not all revoked certs have a reason extension. + pass + else: + flags.discard(r.value.reason) + + assert len(flags) == 0 + + def test_no_revoked_certs(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_empty.pem"), + x509.load_pem_x509_crl, + backend + ) + assert len(crl) == 0 + + def test_duplicate_entry_ext(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_dup_entry_ext.pem"), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(x509.DuplicateExtension): + crl[0].extensions + + def test_unsupported_crit_entry_ext(self, backend): + crl = _load_cert( + os.path.join( + "x509", "custom", "crl_md2_unknown_crit_entry_ext.pem" + ), + x509.load_pem_x509_crl, + backend + ) + + ext = crl[0].extensions.get_extension_for_oid( + x509.ObjectIdentifier("1.2.3.4") + ) + assert ext.value.value == b"\n\x01\x00" + + def test_unsupported_reason(self, backend): + crl = _load_cert( + os.path.join( + "x509", "custom", "crl_unsupported_reason.pem" + ), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(ValueError): + crl[0].extensions + + def test_invalid_cert_issuer_ext(self, backend): + crl = _load_cert( + os.path.join( + "x509", "custom", "crl_inval_cert_issuer_entry_ext.pem" + ), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(ValueError): + crl[0].extensions + + def test_indexing(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(IndexError): + crl[-13] + with pytest.raises(IndexError): + crl[12] + + assert crl[-1].serial_number == crl[11].serial_number + assert len(crl[2:4]) == 2 + assert crl[2:4][0].serial_number == crl[2].serial_number + assert crl[2:4][1].serial_number == crl[3].serial_number + + def test_get_revoked_certificate_doesnt_reorder(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ) + for i in [2, 500, 3, 49, 7, 1]: + revoked_cert = x509.RevokedCertificateBuilder().serial_number( + i + ).revocation_date( + datetime.datetime(2012, 1, 1, 1, 1) + ).build(backend) + builder = builder.add_revoked_certificate(revoked_cert) + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert crl[0].serial_number == 2 + assert crl[2].serial_number == 3 + # make sure get_revoked_certificate_by_serial_number doesn't affect + # ordering after being invoked + crl.get_revoked_certificate_by_serial_number(500) + assert crl[0].serial_number == 2 + assert crl[2].serial_number == 3 + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRSACertificate(object): + def test_load_pem_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert isinstance(cert, x509.Certificate) + assert cert.serial_number == 11559813051657483483 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"2b619ed04bfc9c3b08eb677d272192286a0947a8" + assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) + assert ( + cert.signature_algorithm_oid == SignatureAlgorithmOID.RSA_WITH_SHA1 + ) + + def test_negative_serial_number(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "negative_serial.pem"), + x509.load_pem_x509_certificate, + backend, + ) + assert cert.serial_number == -18008675309 + + def test_alternate_rsa_with_sha1_oid(self, backend): + cert = _load_cert( + os.path.join("x509", "alternate-rsa-sha1-oid.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) + assert ( + cert.signature_algorithm_oid == + SignatureAlgorithmOID._RSA_WITH_SHA1 + ) + + def test_load_der_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + assert isinstance(cert, x509.Certificate) + assert cert.serial_number == 2 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) + + def test_signature(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.signature == binascii.unhexlify( + b"8e0f72fcbebe4755abcaf76c8ce0bae17cde4db16291638e1b1ce04a93cdb4c" + b"44a3486070986c5a880c14fdf8497e7d289b2630ccb21d24a3d1aa1b2d87482" + b"07f3a1e16ccdf8daa8a7ea1a33d49774f513edf09270bd8e665b6300a10f003" + b"66a59076905eb63cf10a81a0ca78a6ef3127f6cb2f6fb7f947fce22a30d8004" + b"8c243ba2c1a54c425fe12310e8a737638f4920354d4cce25cbd9dea25e6a2fe" + b"0d8579a5c8d929b9275be221975479f3f75075bcacf09526523b5fd67f7683f" + b"3cda420fabb1e9e6fc26bc0649cf61bb051d6932fac37066bb16f55903dfe78" + b"53dc5e505e2a10fbba4f9e93a0d3b53b7fa34b05d7ba6eef869bfc34b8e514f" + b"d5419f75" + ) + assert len(cert.signature) == cert.public_key().key_size // 8 + + def test_tbs_certificate_bytes(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.tbs_certificate_bytes == binascii.unhexlify( + b"308202d8a003020102020900a06cb4b955f7f4db300d06092a864886f70d010" + b"10505003058310b3009060355040613024155311330110603550408130a536f" + b"6d652d53746174653121301f060355040a1318496e7465726e6574205769646" + b"769747320507479204c74643111300f0603550403130848656c6c6f20434130" + b"1e170d3134313132363231343132305a170d3134313232363231343132305a3" + b"058310b3009060355040613024155311330110603550408130a536f6d652d53" + b"746174653121301f060355040a1318496e7465726e657420576964676974732" + b"0507479204c74643111300f0603550403130848656c6c6f2043413082012230" + b"0d06092a864886f70d01010105000382010f003082010a0282010100b03af70" + b"2059e27f1e2284b56bbb26c039153bf81f295b73a49132990645ede4d2da0a9" + b"13c42e7d38d3589a00d3940d194f6e6d877c2ef812da22a275e83d8be786467" + b"48b4e7f23d10e873fd72f57a13dec732fc56ab138b1bb308399bb412cd73921" + b"4ef714e1976e09603405e2556299a05522510ac4574db5e9cb2cf5f99e8f48c" + b"1696ab3ea2d6d2ddab7d4e1b317188b76a572977f6ece0a4ad396f0150e7d8b" + b"1a9986c0cb90527ec26ca56e2914c270d2a198b632fa8a2fda55079d3d39864" + b"b6fb96ddbe331cacb3cb8783a8494ccccd886a3525078847ca01ca5f803e892" + b"14403e8a4b5499539c0b86f7a0daa45b204a8e079d8a5b03db7ba1ba3d7011a" + b"70203010001a381bc3081b9301d0603551d0e04160414d8e89dc777e4472656" + b"f1864695a9f66b7b0400ae3081890603551d23048181307f8014d8e89dc777e" + b"4472656f1864695a9f66b7b0400aea15ca45a3058310b300906035504061302" + b"4155311330110603550408130a536f6d652d53746174653121301f060355040" + b"a1318496e7465726e6574205769646769747320507479204c74643111300f06" + b"03550403130848656c6c6f204341820900a06cb4b955f7f4db300c0603551d1" + b"3040530030101ff" + ) + cert.public_key().verify( + cert.signature, cert.tbs_certificate_bytes, + padding.PKCS1v15(), cert.signature_hash_algorithm + ) + + def test_issuer(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "Validpre2000UTCnotBeforeDateTest3EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + issuer = cert.issuer + assert isinstance(issuer, x509.Name) + assert list(issuer) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u'Test Certificates 2011' + ), + x509.NameAttribute(NameOID.COMMON_NAME, u'Good CA') + ] + assert issuer.get_attributes_for_oid(NameOID.COMMON_NAME) == [ + x509.NameAttribute(NameOID.COMMON_NAME, u'Good CA') + ] + + def test_all_issuer_name_types(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", + "all_supported_names.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + issuer = cert.issuer + + assert isinstance(issuer, x509.Name) + assert list(issuer) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'CA'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Illinois'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Chicago'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Zero, LLC'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'One, LLC'), + x509.NameAttribute(NameOID.COMMON_NAME, u'common name 0'), + x509.NameAttribute(NameOID.COMMON_NAME, u'common name 1'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'OU 0'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'OU 1'), + x509.NameAttribute(NameOID.DN_QUALIFIER, u'dnQualifier0'), + x509.NameAttribute(NameOID.DN_QUALIFIER, u'dnQualifier1'), + x509.NameAttribute(NameOID.SERIAL_NUMBER, u'123'), + x509.NameAttribute(NameOID.SERIAL_NUMBER, u'456'), + x509.NameAttribute(NameOID.TITLE, u'Title 0'), + x509.NameAttribute(NameOID.TITLE, u'Title 1'), + x509.NameAttribute(NameOID.SURNAME, u'Surname 0'), + x509.NameAttribute(NameOID.SURNAME, u'Surname 1'), + x509.NameAttribute(NameOID.GIVEN_NAME, u'Given Name 0'), + x509.NameAttribute(NameOID.GIVEN_NAME, u'Given Name 1'), + x509.NameAttribute(NameOID.PSEUDONYM, u'Incognito 0'), + x509.NameAttribute(NameOID.PSEUDONYM, u'Incognito 1'), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u'Last Gen'), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u'Next Gen'), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'dc0'), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'dc1'), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, u'test0@test.local'), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, u'test1@test.local'), + ] + + def test_subject(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "Validpre2000UTCnotBeforeDateTest3EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + subject = cert.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u'Test Certificates 2011' + ), + x509.NameAttribute( + NameOID.COMMON_NAME, + u'Valid pre2000 UTC notBefore Date EE Certificate Test3' + ) + ] + assert subject.get_attributes_for_oid(NameOID.COMMON_NAME) == [ + x509.NameAttribute( + NameOID.COMMON_NAME, + u'Valid pre2000 UTC notBefore Date EE Certificate Test3' + ) + ] + + def test_unicode_name(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", + "utf8_common_name.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + assert cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME) == [ + x509.NameAttribute( + NameOID.COMMON_NAME, + u'We heart UTF8!\u2122' + ) + ] + assert cert.issuer.get_attributes_for_oid(NameOID.COMMON_NAME) == [ + x509.NameAttribute( + NameOID.COMMON_NAME, + u'We heart UTF8!\u2122' + ) + ] + + def test_non_ascii_dns_name(self, backend): + cert = _load_cert( + os.path.join("x509", "utf8-dnsname.pem"), + x509.load_pem_x509_certificate, + backend + ) + san = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName + ).value + + names = san.get_values_for_type(x509.DNSName) + + assert names == [ + u'partner.biztositas.hu', u'biztositas.hu', u'*.biztositas.hu', + u'biztos\xedt\xe1s.hu', u'*.biztos\xedt\xe1s.hu', + u'xn--biztosts-fza2j.hu', u'*.xn--biztosts-fza2j.hu' + ] + + def test_all_subject_name_types(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", + "all_supported_names.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + subject = cert.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'AU'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'DE'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'California'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'New York'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'San Francisco'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Ithaca'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Org Zero, LLC'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Org One, LLC'), + x509.NameAttribute(NameOID.COMMON_NAME, u'CN 0'), + x509.NameAttribute(NameOID.COMMON_NAME, u'CN 1'), + x509.NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, u'Engineering 0' + ), + x509.NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, u'Engineering 1' + ), + x509.NameAttribute(NameOID.DN_QUALIFIER, u'qualified0'), + x509.NameAttribute(NameOID.DN_QUALIFIER, u'qualified1'), + x509.NameAttribute(NameOID.SERIAL_NUMBER, u'789'), + x509.NameAttribute(NameOID.SERIAL_NUMBER, u'012'), + x509.NameAttribute(NameOID.TITLE, u'Title IX'), + x509.NameAttribute(NameOID.TITLE, u'Title X'), + x509.NameAttribute(NameOID.SURNAME, u'Last 0'), + x509.NameAttribute(NameOID.SURNAME, u'Last 1'), + x509.NameAttribute(NameOID.GIVEN_NAME, u'First 0'), + x509.NameAttribute(NameOID.GIVEN_NAME, u'First 1'), + x509.NameAttribute(NameOID.PSEUDONYM, u'Guy Incognito 0'), + x509.NameAttribute(NameOID.PSEUDONYM, u'Guy Incognito 1'), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u'32X'), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u'Dreamcast'), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'dc2'), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'dc3'), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, u'test2@test.local'), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, u'test3@test.local'), + ] + + def test_load_good_ca_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) + assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + assert cert.serial_number == 2 + public_key = cert.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + assert cert.version is x509.Version.v3 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" + + def test_utc_pre_2000_not_before_cert(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "Validpre2000UTCnotBeforeDateTest3EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + + assert cert.not_valid_before == datetime.datetime(1950, 1, 1, 12, 1) + + def test_pre_2000_utc_not_after_cert(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "Invalidpre2000UTCEEnotAfterDateTest7EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + + assert cert.not_valid_after == datetime.datetime(1999, 1, 1, 12, 1) + + def test_post_2000_utc_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.not_valid_before == datetime.datetime( + 2014, 11, 26, 21, 41, 20 + ) + assert cert.not_valid_after == datetime.datetime( + 2014, 12, 26, 21, 41, 20 + ) + + def test_generalized_time_not_before_cert(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "ValidGeneralizedTimenotBeforeDateTest4EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + assert cert.not_valid_before == datetime.datetime(2002, 1, 1, 12, 1) + assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + assert cert.version is x509.Version.v3 + + def test_generalized_time_not_after_cert(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "ValidGeneralizedTimenotAfterDateTest8EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) + assert cert.not_valid_after == datetime.datetime(2050, 1, 1, 12, 1) + assert cert.version is x509.Version.v3 + + def test_invalid_version_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "invalid_version.pem"), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(x509.InvalidVersion) as exc: + cert.version + + assert exc.value.parsed_version == 7 + + def test_eq(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert2 = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert == cert2 + + def test_ne(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert2 = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "ValidGeneralizedTimenotAfterDateTest8EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + assert cert != cert2 + assert cert != object() + + def test_hash(self, backend): + cert1 = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert2 = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert3 = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "ValidGeneralizedTimenotAfterDateTest8EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + + assert hash(cert1) == hash(cert2) + assert hash(cert1) != hash(cert3) + + def test_version_1_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "v1_cert.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.version is x509.Version.v1 + + def test_invalid_pem(self, backend): + with pytest.raises(ValueError): + x509.load_pem_x509_certificate(b"notacert", backend) + + def test_invalid_der(self, backend): + with pytest.raises(ValueError): + x509.load_der_x509_certificate(b"notacert", backend) + + def test_unsupported_signature_hash_algorithm_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "verisign_md2_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(UnsupportedAlgorithm): + cert.signature_hash_algorithm + + def test_public_bytes_pem(self, backend): + # Load an existing certificate. + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + # Encode it to PEM and load it back. + cert = x509.load_pem_x509_certificate(cert.public_bytes( + encoding=serialization.Encoding.PEM, + ), backend) + + # We should recover what we had to start with. + assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) + assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + assert cert.serial_number == 2 + public_key = cert.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + assert cert.version is x509.Version.v3 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" + + def test_public_bytes_der(self, backend): + # Load an existing certificate. + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + # Encode it to DER and load it back. + cert = x509.load_der_x509_certificate(cert.public_bytes( + encoding=serialization.Encoding.DER, + ), backend) + + # We should recover what we had to start with. + assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) + assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + assert cert.serial_number == 2 + public_key = cert.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + assert cert.version is x509.Version.v3 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" + + def test_public_bytes_invalid_encoding(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + with pytest.raises(TypeError): + cert.public_bytes('NotAnEncoding') + + @pytest.mark.parametrize( + ("cert_path", "loader_func", "encoding"), + [ + ( + os.path.join("x509", "v1_cert.pem"), + x509.load_pem_x509_certificate, + serialization.Encoding.PEM, + ), + ( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + serialization.Encoding.DER, + ), + ] + ) + def test_public_bytes_match(self, cert_path, loader_func, encoding, + backend): + cert_bytes = load_vectors_from_file( + cert_path, lambda pemfile: pemfile.read(), mode="rb" + ) + cert = loader_func(cert_bytes, backend) + serialized = cert.public_bytes(encoding) + assert serialized == cert_bytes + + def test_certificate_repr(self, backend): + cert = _load_cert( + os.path.join( + "x509", "cryptography.io.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + assert repr(cert) == ( + "<Certificate(subject=<Name(OU=GT48742965,OU=See www.rapidssl.com" + "/resources/cps (c)14,OU=Domain Control Validated - RapidSSL(R)," + "CN=www.cryptography.io)>, ...)>" + ) + + def test_parse_tls_feature_extension(self, backend): + cert = _load_cert( + os.path.join("x509", "tls-feature-ocsp-staple.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_class(x509.TLSFeature) + assert ext.critical is False + assert ext.value == x509.TLSFeature( + [x509.TLSFeatureType.status_request] + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRSACertificateRequest(object): + @pytest.mark.parametrize( + ("path", "loader_func"), + [ + [ + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr + ], + [ + os.path.join("x509", "requests", "rsa_sha1.der"), + x509.load_der_x509_csr + ], + ] + ) + def test_load_rsa_certificate_request(self, path, loader_func, backend): + request = _load_cert(path, loader_func, backend) + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + assert ( + request.signature_algorithm_oid == + SignatureAlgorithmOID.RSA_WITH_SHA1 + ) + public_key = request.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ] + extensions = request.extensions + assert isinstance(extensions, x509.Extensions) + assert list(extensions) == [] + + @pytest.mark.parametrize( + "loader_func", + [x509.load_pem_x509_csr, x509.load_der_x509_csr] + ) + def test_invalid_certificate_request(self, loader_func, backend): + with pytest.raises(ValueError): + loader_func(b"notacsr", backend) + + def test_unsupported_signature_hash_algorithm_request(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "rsa_md4.pem"), + x509.load_pem_x509_csr, + backend + ) + with pytest.raises(UnsupportedAlgorithm): + request.signature_hash_algorithm + + def test_duplicate_extension(self, backend): + request = _load_cert( + os.path.join( + "x509", "requests", "two_basic_constraints.pem" + ), + x509.load_pem_x509_csr, + backend + ) + with pytest.raises(x509.DuplicateExtension) as exc: + request.extensions + + assert exc.value.oid == ExtensionOID.BASIC_CONSTRAINTS + + def test_unsupported_critical_extension(self, backend): + request = _load_cert( + os.path.join( + "x509", "requests", "unsupported_extension_critical.pem" + ), + x509.load_pem_x509_csr, + backend + ) + ext = request.extensions.get_extension_for_oid( + x509.ObjectIdentifier('1.2.3.4') + ) + assert ext.value.value == b"value" + + def test_unsupported_extension(self, backend): + request = _load_cert( + os.path.join( + "x509", "requests", "unsupported_extension.pem" + ), + x509.load_pem_x509_csr, + backend + ) + extensions = request.extensions + assert len(extensions) == 1 + assert extensions[0].oid == x509.ObjectIdentifier("1.2.3.4") + assert extensions[0].value == x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"value" + ) + + def test_request_basic_constraints(self, backend): + request = _load_cert( + os.path.join( + "x509", "requests", "basic_constraints.pem" + ), + x509.load_pem_x509_csr, + backend + ) + extensions = request.extensions + assert isinstance(extensions, x509.Extensions) + assert list(extensions) == [ + x509.Extension( + ExtensionOID.BASIC_CONSTRAINTS, + True, + x509.BasicConstraints(ca=True, path_length=1), + ), + ] + + def test_subject_alt_name(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "san_rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend, + ) + ext = request.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert list(ext.value) == [ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"sub.cryptography.io"), + ] + + def test_public_bytes_pem(self, backend): + # Load an existing CSR. + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + + # Encode it to PEM and load it back. + request = x509.load_pem_x509_csr(request.public_bytes( + encoding=serialization.Encoding.PEM, + ), backend) + + # We should recover what we had to start with. + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ] + + def test_public_bytes_der(self, backend): + # Load an existing CSR. + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + + # Encode it to DER and load it back. + request = x509.load_der_x509_csr(request.public_bytes( + encoding=serialization.Encoding.DER, + ), backend) + + # We should recover what we had to start with. + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ] + + def test_signature(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.signature == binascii.unhexlify( + b"8364c86ffbbfe0bfc9a21f831256658ca8989741b80576d36f08a934603a43b1" + b"837246d00167a518abb1de7b51a1e5b7ebea14944800818b1a923c804f120a0d" + b"624f6310ef79e8612755c2b01dcc7f59dfdbce0db3f2630f185f504b8c17af80" + b"cbd364fa5fda68337153930948226cd4638287a0aed6524d3006885c19028a1e" + b"e2f5a91d6e77dbaa0b49996ee0a0c60b55b61bd080a08bb34aa7f3e07e91f37f" + b"6a11645be2d8654c1570dcda145ed7cc92017f7d53225d7f283f3459ec5bda41" + b"cf6dd75d43676c543483385226b7e4fa29c8739f1b0eaf199613593991979862" + b"e36181e8c4c270c354b7f52c128db1b70639823324c7ea24791b7bc3d7005f3b" + ) + + def test_tbs_certrequest_bytes(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.tbs_certrequest_bytes == binascii.unhexlify( + b"308201840201003057310b3009060355040613025553310e300c060355040813" + b"055465786173310f300d0603550407130641757374696e310d300b060355040a" + b"130450794341311830160603550403130f63727970746f6772617068792e696f" + b"30820122300d06092a864886f70d01010105000382010f003082010a02820101" + b"00a840a78460cb861066dfa3045a94ba6cf1b7ab9d24c761cffddcc2cb5e3f1d" + b"c3e4be253e7039ef14fe9d6d2304f50d9f2e1584c51530ab75086f357138bff7" + b"b854d067d1d5f384f1f2f2c39cc3b15415e2638554ef8402648ae3ef08336f22" + b"b7ecc6d4331c2b21c3091a7f7a9518180754a646640b60419e4cc6f5c798110a" + b"7f030a639fe87e33b4776dfcd993940ec776ab57a181ad8598857976dc303f9a" + b"573ca619ab3fe596328e92806b828683edc17cc256b41948a2bfa8d047d2158d" + b"3d8e069aa05fa85b3272abb1c4b4422b6366f3b70e642377b145cd6259e5d3e7" + b"db048d51921e50766a37b1b130ee6b11f507d20a834001e8de16a92c14f2e964" + b"a30203010001a000" + ) + request.public_key().verify( + request.signature, + request.tbs_certrequest_bytes, + padding.PKCS1v15(), + request.signature_hash_algorithm + ) + + def test_public_bytes_invalid_encoding(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + + with pytest.raises(TypeError): + request.public_bytes('NotAnEncoding') + + def test_signature_invalid(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "invalid_signature.pem"), + x509.load_pem_x509_csr, + backend + ) + assert not request.is_signature_valid + + def test_signature_valid(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha256.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.is_signature_valid + + @pytest.mark.parametrize( + ("request_path", "loader_func", "encoding"), + [ + ( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + serialization.Encoding.PEM, + ), + ( + os.path.join("x509", "requests", "rsa_sha1.der"), + x509.load_der_x509_csr, + serialization.Encoding.DER, + ), + ] + ) + def test_public_bytes_match(self, request_path, loader_func, encoding, + backend): + request_bytes = load_vectors_from_file( + request_path, lambda pemfile: pemfile.read(), mode="rb" + ) + request = loader_func(request_bytes, backend) + serialized = request.public_bytes(encoding) + assert serialized == request_bytes + + def test_eq(self, backend): + request1 = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + request2 = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + + assert request1 == request2 + + def test_ne(self, backend): + request1 = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + request2 = _load_cert( + os.path.join("x509", "requests", "san_rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + + assert request1 != request2 + assert request1 != object() + + def test_hash(self, backend): + request1 = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + request2 = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + request3 = _load_cert( + os.path.join("x509", "requests", "san_rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + + assert hash(request1) == hash(request2) + assert hash(request1) != hash(request3) + + def test_build_cert(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] + + def test_build_cert_private_type_encoding(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + name = x509.Name([ + x509.NameAttribute( + NameOID.STATE_OR_PROVINCE_NAME, u'Texas', + _ASN1Type.PrintableString), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute( + NameOID.COMMON_NAME, u'cryptography.io', _ASN1Type.IA5String), + ]) + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name( + name + ).subject_name( + name + ).public_key( + subject_private_key.public_key() + ).not_valid_before( + not_valid_before + ).not_valid_after(not_valid_after) + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + + for dn in (cert.subject, cert.issuer): + assert dn.get_attributes_for_oid( + NameOID.STATE_OR_PROVINCE_NAME + )[0]._type == _ASN1Type.PrintableString + assert dn.get_attributes_for_oid( + NameOID.STATE_OR_PROVINCE_NAME + )[0]._type == _ASN1Type.PrintableString + assert dn.get_attributes_for_oid( + NameOID.LOCALITY_NAME + )[0]._type == _ASN1Type.UTF8String + + def test_build_cert_printable_string_country_name(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.JURISDICTION_COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.JURISDICTION_COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + + parsed = _parse_cert(cert.public_bytes(serialization.Encoding.DER)) + subject = parsed.subject + issuer = parsed.issuer + + def read_next_rdn_value_tag(reader): + # Assume each RDN has a single attribute. + with reader.read_element(SET) as rdn: + attribute = rdn.read_element(SEQUENCE) + + with attribute: + _ = attribute.read_element(OBJECT_IDENTIFIER) + tag, value = attribute.read_any_element() + return tag + + # Check that each value was encoded as an ASN.1 PRINTABLESTRING. + assert read_next_rdn_value_tag(subject) == PRINTABLE_STRING + assert read_next_rdn_value_tag(issuer) == PRINTABLE_STRING + if ( + # This only works correctly in OpenSSL 1.1.0f+ and 1.0.2l+ + backend._lib.CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER or ( + backend._lib.CRYPTOGRAPHY_OPENSSL_102L_OR_GREATER and + not backend._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER + ) + ): + assert read_next_rdn_value_tag(subject) == PRINTABLE_STRING + assert read_next_rdn_value_tag(issuer) == PRINTABLE_STRING + + +class TestCertificateBuilder(object): + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_checks_for_unsupported_extensions(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + private_key.public_key() + ).serial_number( + 777 + ).not_valid_before( + datetime.datetime(1999, 1, 1) + ).not_valid_after( + datetime.datetime(2020, 1, 1) + ).add_extension( + DummyExtension(), False + ) + + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA1(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_encode_nonstandard_aia(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + x509.ObjectIdentifier("2.999.7"), + x509.UniformResourceIdentifier(u"http://example.com") + ), + ]) + + builder = x509.CertificateBuilder().subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + private_key.public_key() + ).serial_number( + 777 + ).not_valid_before( + datetime.datetime(1999, 1, 1) + ).not_valid_after( + datetime.datetime(2020, 1, 1) + ).add_extension( + aia, False + ) + + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_subject_dn_asn1_types(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + name = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"mysite.com"), + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.LOCALITY_NAME, u"value"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"value"), + x509.NameAttribute(NameOID.STREET_ADDRESS, u"value"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"value"), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"value"), + x509.NameAttribute(NameOID.SERIAL_NUMBER, u"value"), + x509.NameAttribute(NameOID.SURNAME, u"value"), + x509.NameAttribute(NameOID.GIVEN_NAME, u"value"), + x509.NameAttribute(NameOID.TITLE, u"value"), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u"value"), + x509.NameAttribute(NameOID.X500_UNIQUE_IDENTIFIER, u"value"), + x509.NameAttribute(NameOID.DN_QUALIFIER, u"value"), + x509.NameAttribute(NameOID.PSEUDONYM, u"value"), + x509.NameAttribute(NameOID.USER_ID, u"value"), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u"value"), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, u"value"), + x509.NameAttribute(NameOID.JURISDICTION_COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.JURISDICTION_LOCALITY_NAME, u"value"), + x509.NameAttribute( + NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME, u"value" + ), + x509.NameAttribute(NameOID.BUSINESS_CATEGORY, u"value"), + x509.NameAttribute(NameOID.POSTAL_ADDRESS, u"value"), + x509.NameAttribute(NameOID.POSTAL_CODE, u"value"), + ]) + cert = x509.CertificateBuilder().subject_name( + name + ).issuer_name( + name + ).public_key( + private_key.public_key() + ).serial_number( + 777 + ).not_valid_before( + datetime.datetime(1999, 1, 1) + ).not_valid_after( + datetime.datetime(2020, 1, 1) + ).sign(private_key, hashes.SHA256(), backend) + + for dn in (cert.subject, cert.issuer): + for oid, asn1_type in TestNameAttribute.EXPECTED_TYPES: + assert dn.get_attributes_for_oid( + oid + )[0]._type == asn1_type + + @pytest.mark.parametrize( + ("not_valid_before", "not_valid_after"), + [ + [datetime.datetime(1970, 2, 1), datetime.datetime(9999, 1, 1)], + [datetime.datetime(1970, 2, 1), datetime.datetime(9999, 12, 31)], + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_extreme_times(self, not_valid_before, not_valid_after, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + private_key.public_key() + ).serial_number( + 777 + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + cert = builder.sign(private_key, hashes.SHA256(), backend) + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + parsed = _parse_cert(cert.public_bytes(serialization.Encoding.DER)) + assert parsed.not_before_tag == UTC_TIME + assert parsed.not_after_tag == GENERALIZED_TIME + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_subject_name(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_issuer_name(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + 777 + ).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_public_key(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_not_valid_before(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_not_valid_after(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_serial_number(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + def test_issuer_name_must_be_a_name_type(self): + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.issuer_name("subject") + + with pytest.raises(TypeError): + builder.issuer_name(object) + + def test_issuer_name_may_only_be_set_once(self): + name = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + builder = x509.CertificateBuilder().issuer_name(name) + + with pytest.raises(ValueError): + builder.issuer_name(name) + + def test_subject_name_must_be_a_name_type(self): + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.subject_name("subject") + + with pytest.raises(TypeError): + builder.subject_name(object) + + def test_subject_name_may_only_be_set_once(self): + name = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + builder = x509.CertificateBuilder().subject_name(name) + + with pytest.raises(ValueError): + builder.subject_name(name) + + def test_not_valid_before_after_not_valid_after(self): + builder = x509.CertificateBuilder() + + builder = builder.not_valid_after( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.not_valid_before( + datetime.datetime(2003, 1, 1, 12, 1) + ) + + def test_not_valid_after_before_not_valid_before(self): + builder = x509.CertificateBuilder() + + builder = builder.not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.not_valid_after( + datetime.datetime(2001, 1, 1, 12, 1) + ) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_public_key_must_be_public_key(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.public_key(private_key) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_public_key_may_only_be_set_once(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + public_key = private_key.public_key() + builder = x509.CertificateBuilder().public_key(public_key) + + with pytest.raises(ValueError): + builder.public_key(public_key) + + def test_serial_number_must_be_an_integer_type(self): + with pytest.raises(TypeError): + x509.CertificateBuilder().serial_number(10.0) + + def test_serial_number_must_be_non_negative(self): + with pytest.raises(ValueError): + x509.CertificateBuilder().serial_number(-1) + + def test_serial_number_must_be_positive(self): + with pytest.raises(ValueError): + x509.CertificateBuilder().serial_number(0) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_minimal_serial_number(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + 1 + ).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'RU'), + ])).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'RU'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + cert = builder.sign(subject_private_key, hashes.SHA256(), backend) + assert cert.serial_number == 1 + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_biggest_serial_number(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + (1 << 159) - 1 + ).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'RU'), + ])).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'RU'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + cert = builder.sign(subject_private_key, hashes.SHA256(), backend) + assert cert.serial_number == (1 << 159) - 1 + + def test_serial_number_must_be_less_than_160_bits_long(self): + with pytest.raises(ValueError): + x509.CertificateBuilder().serial_number(1 << 159) + + def test_serial_number_may_only_be_set_once(self): + builder = x509.CertificateBuilder().serial_number(10) + + with pytest.raises(ValueError): + builder.serial_number(20) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_aware_not_valid_after(self, backend): + time = datetime.datetime(2012, 1, 16, 22, 43) + tz = pytz.timezone("US/Pacific") + time = tz.localize(time) + utc_time = datetime.datetime(2012, 1, 17, 6, 43) + private_key = RSA_KEY_2048.private_key(backend) + cert_builder = x509.CertificateBuilder().not_valid_after(time) + cert_builder = cert_builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_before( + utc_time - datetime.timedelta(days=365) + ) + + cert = cert_builder.sign(private_key, hashes.SHA256(), backend) + assert cert.not_valid_after == utc_time + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_earliest_time(self, backend): + time = datetime.datetime(1950, 1, 1) + private_key = RSA_KEY_2048.private_key(backend) + cert_builder = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_before( + time + ).not_valid_after( + time + ) + cert = cert_builder.sign(private_key, hashes.SHA256(), backend) + assert cert.not_valid_before == time + assert cert.not_valid_after == time + parsed = _parse_cert(cert.public_bytes(serialization.Encoding.DER)) + assert parsed.not_before_tag == UTC_TIME + assert parsed.not_after_tag == UTC_TIME + + def test_invalid_not_valid_after(self): + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_after(104204304504) + + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_after(datetime.time()) + + with pytest.raises(ValueError): + x509.CertificateBuilder().not_valid_after( + datetime.datetime(1940, 8, 10) + ) + + def test_not_valid_after_may_only_be_set_once(self): + builder = x509.CertificateBuilder().not_valid_after( + datetime.datetime.now() + ) + + with pytest.raises(ValueError): + builder.not_valid_after( + datetime.datetime.now() + ) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_aware_not_valid_before(self, backend): + time = datetime.datetime(2012, 1, 16, 22, 43) + tz = pytz.timezone("US/Pacific") + time = tz.localize(time) + utc_time = datetime.datetime(2012, 1, 17, 6, 43) + private_key = RSA_KEY_2048.private_key(backend) + cert_builder = x509.CertificateBuilder().not_valid_before(time) + cert_builder = cert_builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_after( + utc_time + datetime.timedelta(days=366) + ) + + cert = cert_builder.sign(private_key, hashes.SHA256(), backend) + assert cert.not_valid_before == utc_time + + def test_invalid_not_valid_before(self): + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_before(104204304504) + + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_before(datetime.time()) + + with pytest.raises(ValueError): + x509.CertificateBuilder().not_valid_before( + datetime.datetime(1940, 8, 10) + ) + + def test_not_valid_before_may_only_be_set_once(self): + builder = x509.CertificateBuilder().not_valid_before( + datetime.datetime.now() + ) + + with pytest.raises(ValueError): + builder.not_valid_before( + datetime.datetime.now() + ) + + def test_add_extension_checks_for_duplicates(self): + builder = x509.CertificateBuilder().add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ) + + with pytest.raises(ValueError): + builder.add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ) + + def test_add_invalid_extension_type(self): + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.add_extension(object(), False) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + @pytest.mark.parametrize( + "algorithm", + [ + object(), None + ] + ) + def test_sign_with_unsupported_hash(self, algorithm, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2032, 1, 1, 12, 1) + ) + + with pytest.raises(TypeError): + builder.sign(private_key, algorithm, backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_unsupported_hash_ed25519(self, backend): + private_key = ed25519.Ed25519PrivateKey.generate() + builder = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2032, 1, 1, 12, 1) + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_unsupported_hash_ed448(self, backend): + private_key = ed448.Ed448PrivateKey.generate() + builder = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2032, 1, 1, 12, 1) + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.MD5()), + skip_message="Requires OpenSSL with MD5 support" + ) + def test_sign_rsa_with_md5(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2032, 1, 1, 12, 1) + ) + cert = builder.sign(private_key, hashes.MD5(), backend) + assert isinstance(cert.signature_hash_algorithm, hashes.MD5) + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.MD5()), + skip_message="Requires OpenSSL with MD5 support" + ) + def test_sign_dsa_with_md5(self, backend): + private_key = DSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2032, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.MD5(), backend) + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.MD5()), + skip_message="Requires OpenSSL with MD5 support" + ) + def test_sign_ec_with_md5(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = EC_KEY_SECP256R1.private_key(backend) + builder = x509.CertificateBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2032, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.MD5(), backend) + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_dsa_private_key(self, backend): + issuer_private_key = DSA_KEY_2048.private_key(backend) + subject_private_key = DSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_ec_private_key(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + issuer_private_key = ec.generate_private_key(ec.SECP256R1(), backend) + subject_private_key = ec.generate_private_key(ec.SECP256R1(), backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] + + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_ed25519(self, backend): + issuer_private_key = ed25519.Ed25519PrivateKey.generate() + subject_private_key = ed25519.Ed25519PrivateKey.generate() + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, None, backend) + issuer_private_key.public_key().verify( + cert.signature, cert.tbs_certificate_bytes + ) + assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED25519 + assert cert.signature_hash_algorithm is None + assert isinstance(cert.public_key(), ed25519.Ed25519PublicKey) + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] + + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_build_cert_with_public_ed25519_rsa_sig(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = ed25519.Ed25519PrivateKey.generate() + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + issuer_private_key.public_key().verify( + cert.signature, cert.tbs_certificate_bytes, padding.PKCS1v15(), + cert.signature_hash_algorithm + ) + assert cert.signature_algorithm_oid == ( + SignatureAlgorithmOID.RSA_WITH_SHA256 + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) + assert isinstance(cert.public_key(), ed25519.Ed25519PublicKey) + + @pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_ed448(self, backend): + issuer_private_key = ed448.Ed448PrivateKey.generate() + subject_private_key = ed448.Ed448PrivateKey.generate() + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, None, backend) + issuer_private_key.public_key().verify( + cert.signature, cert.tbs_certificate_bytes + ) + assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED448 + assert cert.signature_hash_algorithm is None + assert isinstance(cert.public_key(), ed448.Ed448PublicKey) + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] + + @pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_build_cert_with_public_ed448_rsa_sig(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = ed448.Ed448PrivateKey.generate() + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + issuer_private_key.public_key().verify( + cert.signature, cert.tbs_certificate_bytes, padding.PKCS1v15(), + cert.signature_hash_algorithm + ) + assert cert.signature_algorithm_oid == ( + SignatureAlgorithmOID.RSA_WITH_SHA256 + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) + assert isinstance(cert.public_key(), ed448.Ed448PublicKey) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_rsa_key_too_small(self, backend): + issuer_private_key = RSA_KEY_512.private_key(backend) + subject_private_key = RSA_KEY_512.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + with pytest.raises(ValueError): + builder.sign(issuer_private_key, hashes.SHA512(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + @pytest.mark.parametrize( + "add_ext", + [ + x509.SubjectAlternativeName( + [ + # These examples exist to verify compatibility with + # certificates that have utf8 encoded data in the ia5string + x509.DNSName._init_without_validation(u'a\xedt\xe1s.test'), + x509.RFC822Name._init_without_validation( + u'test@a\xedt\xe1s.test' + ), + x509.UniformResourceIdentifier._init_without_validation( + u'http://a\xedt\xe1s.test' + ), + ] + ), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [u"http://other.com/cps"] + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + None + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + u"http://example.com/cps", + u"http://other.com/cps", + x509.UserNotice( + x509.NoticeReference(u"my org", [1, 2, 3, 4]), + u"thing" + ) + ] + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + u"http://example.com/cps", + x509.UserNotice( + x509.NoticeReference(u"UTF8\u2122'", [1, 2, 3, 4]), + u"We heart UTF8!\u2122" + ) + ] + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [x509.UserNotice(None, u"thing")] + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + x509.UserNotice( + x509.NoticeReference(u"my org", [1, 2, 3, 4]), + None + ) + ] + ) + ]), + x509.IssuerAlternativeName([ + x509.DNSName(u"myissuer"), + x509.RFC822Name(u"email@domain.com"), + ]), + x509.ExtendedKeyUsage([ + ExtendedKeyUsageOID.CLIENT_AUTH, + ExtendedKeyUsageOID.SERVER_AUTH, + ExtendedKeyUsageOID.CODE_SIGNING, + ]), + x509.InhibitAnyPolicy(3), + x509.TLSFeature([x509.TLSFeatureType.status_request]), + x509.TLSFeature([x509.TLSFeatureType.status_request_v2]), + x509.TLSFeature([ + x509.TLSFeatureType.status_request, + x509.TLSFeatureType.status_request_v2 + ]), + x509.NameConstraints( + permitted_subtrees=[ + x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24")), + x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/29")), + x509.IPAddress(ipaddress.IPv4Network(u"127.0.0.1/32")), + x509.IPAddress(ipaddress.IPv4Network(u"8.0.0.0/8")), + x509.IPAddress(ipaddress.IPv4Network(u"0.0.0.0/0")), + x509.IPAddress( + ipaddress.IPv6Network(u"FF:0:0:0:0:0:0:0/96") + ), + x509.IPAddress( + ipaddress.IPv6Network(u"FF:FF:0:0:0:0:0:0/128") + ), + ], + excluded_subtrees=[x509.DNSName(u"name.local")] + ), + x509.NameConstraints( + permitted_subtrees=[ + x509.IPAddress(ipaddress.IPv4Network(u"0.0.0.0/0")), + ], + excluded_subtrees=None + ), + x509.NameConstraints( + permitted_subtrees=None, + excluded_subtrees=[x509.DNSName(u"name.local")] + ), + x509.PolicyConstraints( + require_explicit_policy=None, + inhibit_policy_mapping=1 + ), + x509.PolicyConstraints( + require_explicit_policy=3, + inhibit_policy_mapping=1 + ), + x509.PolicyConstraints( + require_explicit_policy=0, + inhibit_policy_mapping=None + ), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + NameOID.COMMON_NAME, + u"indirect CRL for indirectCRL CA3" + ), + ]), + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, + u"Test Certificates 2011" + ), + x509.NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, + u"indirectCRL CA3 cRLIssuer" + ), + ]) + )], + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + ]) + )], + relative_name=None, + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, + u"cryptography Testing" + ), + ]) + )], + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl" + ), + x509.UniformResourceIdentifier( + u"http://backup.myhost.com/myca.crl" + ) + ], + relative_name=None, + reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise + ]), + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ), + ]) + )], + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://domain.com/some.crl" + )], + relative_name=None, + reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + x509.ReasonFlags.affiliation_changed, + x509.ReasonFlags.superseded, + x509.ReasonFlags.privilege_withdrawn, + x509.ReasonFlags.cessation_of_operation, + x509.ReasonFlags.aa_compromise, + x509.ReasonFlags.certificate_hold, + ]), + crl_issuer=None + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=None, + relative_name=None, + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ), + ]) + )], + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://domain.com/some.crl" + )], + relative_name=None, + reasons=frozenset([x509.ReasonFlags.aa_compromise]), + crl_issuer=None + ) + ]), + x509.FreshestCRL([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://domain.com/some.crl" + )], + relative_name=None, + reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + x509.ReasonFlags.affiliation_changed, + x509.ReasonFlags.superseded, + x509.ReasonFlags.privilege_withdrawn, + x509.ReasonFlags.cessation_of_operation, + x509.ReasonFlags.aa_compromise, + x509.ReasonFlags.certificate_hold, + ]), + crl_issuer=None + ) + ]), + x509.FreshestCRL([ + x509.DistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + NameOID.COMMON_NAME, + u"indirect CRL for indirectCRL CA3" + ), + ]), + reasons=None, + crl_issuer=None, + ) + ]), + x509.FreshestCRL([ + x509.DistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + NameOID.COMMON_NAME, + u"indirect CRL for indirectCRL CA3" + ), + x509.NameAttribute( + NameOID.COUNTRY_NAME, + u"US" + ), + ]), + reasons=None, + crl_issuer=None, + ) + ]), + ] + ) + def test_ext(self, add_ext, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).public_key( + subject_private_key.public_key() + ).serial_number( + 123 + ).add_extension( + add_ext, critical=False + ).sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_class(type(add_ext)) + assert ext.critical is False + assert ext.value == add_ext + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_key_usage(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).public_key( + subject_private_key.public_key() + ).serial_number( + 123 + ).add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ), + critical=False + ).sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) + assert ext.critical is False + assert ext.value == x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_ca_request_with_path_length_none(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u'PyCA'), + ]) + ).add_extension( + x509.BasicConstraints(ca=True, path_length=None), critical=True + ).sign(private_key, hashes.SHA1(), backend) + + loaded_request = x509.load_pem_x509_csr( + request.public_bytes(encoding=serialization.Encoding.PEM), backend + ) + subject = loaded_request.subject + assert isinstance(subject, x509.Name) + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.path_length is None + + @pytest.mark.parametrize( + "unrecognized", [ + x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4.5"), + b"abcdef", + ) + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_unrecognized_extension(self, backend, unrecognized): + private_key = RSA_KEY_2048.private_key(backend) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US')]) + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ).public_key( + private_key.public_key() + ).serial_number( + 123 + ).add_extension( + unrecognized, critical=False + ).sign(private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid(unrecognized.oid) + + assert ext.value == unrecognized + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestCertificateSigningRequestBuilder(object): + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_sign_invalid_hash_algorithm(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([]) + ) + with pytest.raises(TypeError): + builder.sign(private_key, 'NotAHash', backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_request_with_unsupported_hash_ed25519(self, backend): + private_key = ed25519.Ed25519PrivateKey.generate() + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_request_with_unsupported_hash_ed448(self, backend): + private_key = ed448.Ed448PrivateKey.generate() + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.MD5()), + skip_message="Requires OpenSSL with MD5 support" + ) + def test_sign_rsa_with_md5(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + ]) + ) + request = builder.sign(private_key, hashes.MD5(), backend) + assert isinstance(request.signature_hash_algorithm, hashes.MD5) + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.MD5()), + skip_message="Requires OpenSSL with MD5 support" + ) + def test_sign_dsa_with_md5(self, backend): + private_key = DSA_KEY_2048.private_key(backend) + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + ]) + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.MD5(), backend) + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.MD5()), + skip_message="Requires OpenSSL with MD5 support" + ) + def test_sign_ec_with_md5(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = EC_KEY_SECP256R1.private_key(backend) + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + ]) + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.MD5(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_no_subject_name(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + builder = x509.CertificateSigningRequestBuilder() + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_build_ca_request_with_rsa(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + ]) + ).add_extension( + x509.BasicConstraints(ca=True, path_length=2), critical=True + ).sign(private_key, hashes.SHA1(), backend) + + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + ] + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is True + assert basic_constraints.value.path_length == 2 + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_build_ca_request_with_unicode(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u'PyCA\U0001f37a'), + ]) + ).add_extension( + x509.BasicConstraints(ca=True, path_length=2), critical=True + ).sign(private_key, hashes.SHA1(), backend) + + loaded_request = x509.load_pem_x509_csr( + request.public_bytes(encoding=serialization.Encoding.PEM), backend + ) + subject = loaded_request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA\U0001f37a'), + ] + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_subject_dn_asn1_types(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"mysite.com"), + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.LOCALITY_NAME, u"value"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"value"), + x509.NameAttribute(NameOID.STREET_ADDRESS, u"value"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"value"), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"value"), + x509.NameAttribute(NameOID.SERIAL_NUMBER, u"value"), + x509.NameAttribute(NameOID.SURNAME, u"value"), + x509.NameAttribute(NameOID.GIVEN_NAME, u"value"), + x509.NameAttribute(NameOID.TITLE, u"value"), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u"value"), + x509.NameAttribute(NameOID.X500_UNIQUE_IDENTIFIER, u"value"), + x509.NameAttribute(NameOID.DN_QUALIFIER, u"value"), + x509.NameAttribute(NameOID.PSEUDONYM, u"value"), + x509.NameAttribute(NameOID.USER_ID, u"value"), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u"value"), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, u"value"), + x509.NameAttribute(NameOID.JURISDICTION_COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.JURISDICTION_LOCALITY_NAME, u"value" + ), + x509.NameAttribute( + NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME, u"value" + ), + x509.NameAttribute(NameOID.BUSINESS_CATEGORY, u"value"), + x509.NameAttribute(NameOID.POSTAL_ADDRESS, u"value"), + x509.NameAttribute(NameOID.POSTAL_CODE, u"value"), + ]) + ).sign(private_key, hashes.SHA256(), backend) + for oid, asn1_type in TestNameAttribute.EXPECTED_TYPES: + assert request.subject.get_attributes_for_oid( + oid + )[0]._type == asn1_type + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_build_ca_request_with_multivalue_rdns(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + subject = x509.Name([ + x509.RelativeDistinguishedName([ + x509.NameAttribute(NameOID.TITLE, u'Test'), + x509.NameAttribute(NameOID.COMMON_NAME, u'Multivalue'), + x509.NameAttribute(NameOID.SURNAME, u'RDNs'), + ]), + x509.RelativeDistinguishedName([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA') + ]), + ]) + + request = x509.CertificateSigningRequestBuilder().subject_name( + subject + ).sign(private_key, hashes.SHA1(), backend) + + loaded_request = x509.load_pem_x509_csr( + request.public_bytes(encoding=serialization.Encoding.PEM), backend + ) + assert isinstance(loaded_request.subject, x509.Name) + assert loaded_request.subject == subject + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_build_nonca_request_with_rsa(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), critical=True, + ).sign(private_key, hashes.SHA1(), backend) + + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ] + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + def test_build_ca_request_with_ec(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ]) + ).add_extension( + x509.BasicConstraints(ca=True, path_length=2), critical=True + ).sign(private_key, hashes.SHA1(), backend) + + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, ec.EllipticCurvePublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ] + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is True + assert basic_constraints.value.path_length == 2 + + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_ca_request_with_ed25519(self, backend): + private_key = ed25519.Ed25519PrivateKey.generate() + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ]) + ).add_extension( + x509.BasicConstraints(ca=True, path_length=2), critical=True + ).sign(private_key, None, backend) + + assert request.signature_hash_algorithm is None + public_key = request.public_key() + assert isinstance(public_key, ed25519.Ed25519PublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ] + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is True + assert basic_constraints.value.path_length == 2 + + @pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_ca_request_with_ed448(self, backend): + private_key = ed448.Ed448PrivateKey.generate() + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ]) + ).add_extension( + x509.BasicConstraints(ca=True, path_length=2), critical=True + ).sign(private_key, None, backend) + + assert request.signature_hash_algorithm is None + public_key = request.public_key() + assert isinstance(public_key, ed448.Ed448PublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ] + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is True + assert basic_constraints.value.path_length == 2 + + @pytest.mark.requires_backend_interface(interface=DSABackend) + def test_build_ca_request_with_dsa(self, backend): + private_key = DSA_KEY_2048.private_key(backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ).add_extension( + x509.BasicConstraints(ca=True, path_length=2), critical=True + ).sign(private_key, hashes.SHA1(), backend) + + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, dsa.DSAPublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ] + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is True + assert basic_constraints.value.path_length == 2 + + def test_add_duplicate_extension(self): + builder = x509.CertificateSigningRequestBuilder().add_extension( + x509.BasicConstraints(True, 2), critical=True, + ) + with pytest.raises(ValueError): + builder.add_extension( + x509.BasicConstraints(True, 2), critical=True, + ) + + def test_set_invalid_subject(self): + builder = x509.CertificateSigningRequestBuilder() + with pytest.raises(TypeError): + builder.subject_name('NotAName') + + def test_add_invalid_extension_type(self): + builder = x509.CertificateSigningRequestBuilder() + + with pytest.raises(TypeError): + builder.add_extension(object(), False) + + def test_add_unsupported_extension(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateSigningRequestBuilder() + builder = builder.subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).add_extension( + DummyExtension(), False + ) + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA256(), backend) + + def test_key_usage(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateSigningRequestBuilder() + request = builder.subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ).add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ), + critical=False + ).sign(private_key, hashes.SHA256(), backend) + assert len(request.extensions) == 1 + ext = request.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) + assert ext.critical is False + assert ext.value == x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + + def test_key_usage_key_agreement_bit(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateSigningRequestBuilder() + request = builder.subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ).add_extension( + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=True + ), + critical=False + ).sign(private_key, hashes.SHA256(), backend) + assert len(request.extensions) == 1 + ext = request.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) + assert ext.critical is False + assert ext.value == x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + + def test_add_two_extensions(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateSigningRequestBuilder() + request = builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).add_extension( + x509.BasicConstraints(ca=True, path_length=2), critical=True + ).sign(private_key, hashes.SHA1(), backend) + + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is True + assert basic_constraints.value.path_length == 2 + ext = request.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert list(ext.value) == [x509.DNSName(u"cryptography.io")] + + def test_set_subject_twice(self): + builder = x509.CertificateSigningRequestBuilder() + builder = builder.subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ) + with pytest.raises(ValueError): + builder.subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ) + + def test_subject_alt_names(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + san = x509.SubjectAlternativeName([ + x509.DNSName(u"example.com"), + x509.DNSName(u"*.example.com"), + x509.RegisteredID(x509.ObjectIdentifier("1.2.3.4.5.6.7")), + x509.DirectoryName(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'PyCA'), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u'We heart UTF8!\u2122' + ) + ])), + x509.IPAddress(ipaddress.ip_address(u"127.0.0.1")), + x509.IPAddress(ipaddress.ip_address(u"ff::")), + x509.OtherName( + type_id=x509.ObjectIdentifier("1.2.3.3.3.3"), + value=b"0\x03\x02\x01\x05" + ), + x509.RFC822Name(u"test@example.com"), + x509.RFC822Name(u"email"), + x509.RFC822Name(u"email@xn--eml-vla4c.com"), + x509.UniformResourceIdentifier( + u"https://xn--80ato2c.cryptography" + ), + x509.UniformResourceIdentifier( + u"gopher://cryptography:70/some/path" + ), + ]) + + csr = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"SAN"), + ]) + ).add_extension( + san, + critical=False, + ).sign(private_key, hashes.SHA256(), backend) + + assert len(csr.extensions) == 1 + ext = csr.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert not ext.critical + assert ext.oid == ExtensionOID.SUBJECT_ALTERNATIVE_NAME + assert ext.value == san + + def test_invalid_asn1_othername(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"SAN"), + ]) + ).add_extension( + x509.SubjectAlternativeName([ + x509.OtherName( + type_id=x509.ObjectIdentifier("1.2.3.3.3.3"), + value=b"\x01\x02\x01\x05" + ), + ]), + critical=False, + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + def test_subject_alt_name_unsupported_general_name(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"SAN"), + ]) + ).add_extension( + x509.SubjectAlternativeName([FakeGeneralName("")]), + critical=False, + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + def test_extended_key_usage(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + eku = x509.ExtendedKeyUsage([ + ExtendedKeyUsageOID.CLIENT_AUTH, + ExtendedKeyUsageOID.SERVER_AUTH, + ExtendedKeyUsageOID.CODE_SIGNING, + ]) + builder = x509.CertificateSigningRequestBuilder() + request = builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).add_extension( + eku, critical=False + ).sign(private_key, hashes.SHA256(), backend) + + ext = request.extensions.get_extension_for_oid( + ExtensionOID.EXTENDED_KEY_USAGE + ) + assert ext.critical is False + assert ext.value == eku + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_rsa_key_too_small(self, backend): + private_key = RSA_KEY_512.private_key(backend) + builder = x509.CertificateSigningRequestBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ) + + with pytest.raises(ValueError) as exc: + builder.sign(private_key, hashes.SHA512(), backend) + + assert str(exc.value) == "Digest too big for RSA key" + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_aia(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + aia, critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_INFORMATION_ACCESS + ) + assert ext.value == aia + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_ski(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + ski = x509.SubjectKeyIdentifier.from_public_key( + subject_private_key.public_key() + ) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + ski, critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + assert ext.value == ski + + @pytest.mark.parametrize( + "aki", + [ + x509.AuthorityKeyIdentifier( + b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" + b"\xcbY", + None, + None + ), + x509.AuthorityKeyIdentifier( + b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" + b"\xcbY", + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u"PyCA" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ) + ]) + ) + ], + 333 + ), + x509.AuthorityKeyIdentifier( + None, + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u"PyCA" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ) + ]) + ) + ], + 333 + ), + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_aki(self, aki, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + aki, critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + assert ext.value == aki + + def test_ocsp_nocheck(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.OCSPNoCheck(), critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.OCSP_NO_CHECK + ) + assert isinstance(ext.value, x509.OCSPNoCheck) + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestDSACertificate(object): + def test_load_dsa_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) + public_key = cert.public_key() + assert isinstance(public_key, dsa.DSAPublicKey) + num = public_key.public_numbers() + assert num.y == int( + "4c08bfe5f2d76649c80acf7d431f6ae2124b217abc8c9f6aca776ddfa94" + "53b6656f13e543684cd5f6431a314377d2abfa068b7080cb8ddc065afc2" + "dea559f0b584c97a2b235b9b69b46bc6de1aed422a6f341832618bcaae2" + "198aba388099dafb05ff0b5efecb3b0ae169a62e1c72022af50ae68af3b" + "033c18e6eec1f7df4692c456ccafb79cc7e08da0a5786e9816ceda651d6" + "1b4bb7b81c2783da97cea62df67af5e85991fdc13aff10fc60e06586386" + "b96bb78d65750f542f86951e05a6d81baadbcd35a2e5cad4119923ae6a2" + "002091a3d17017f93c52970113cdc119970b9074ca506eac91c3dd37632" + "5df4af6b3911ef267d26623a5a1c5df4a6d13f1c", 16 + ) + assert num.parameter_numbers.g == int( + "4b7ced71dc353965ecc10d441a9a06fc24943a32d66429dd5ef44d43e67" + "d789d99770aec32c0415dc92970880872da45fef8dd1e115a3e4801387b" + "a6d755861f062fd3b6e9ea8e2641152339b828315b1528ee6c7b79458d2" + "1f3db973f6fc303f9397174c2799dd2351282aa2d8842c357a73495bbaa" + "c4932786414c55e60d73169f5761036fba29e9eebfb049f8a3b1b7cee6f" + "3fbfa136205f130bee2cf5b9c38dc1095d4006f2e73335c07352c64130a" + "1ab2b89f13b48f628d3cc3868beece9bb7beade9f830eacc6fa241425c0" + "b3fcc0df416a0c89f7bf35668d765ec95cdcfbe9caff49cfc156c668c76" + "fa6247676a6d3ac945844a083509c6a1b436baca", 16 + ) + assert num.parameter_numbers.p == int( + "bfade6048e373cd4e48b677e878c8e5b08c02102ae04eb2cb5c46a523a3" + "af1c73d16b24f34a4964781ae7e50500e21777754a670bd19a7420d6330" + "84e5556e33ca2c0e7d547ea5f46a07a01bf8669ae3bdec042d9b2ae5e6e" + "cf49f00ba9dac99ab6eff140d2cedf722ee62c2f9736857971444c25d0a" + "33d2017dc36d682a1054fe2a9428dda355a851ce6e6d61e03e419fd4ca4" + "e703313743d86caa885930f62ed5bf342d8165627681e9cc3244ba72aa2" + "2148400a6bbe80154e855d042c9dc2a3405f1e517be9dea50562f56da93" + "f6085f844a7e705c1f043e65751c583b80d29103e590ccb26efdaa0893d" + "833e36468f3907cfca788a3cb790f0341c8a31bf", 16 + ) + assert num.parameter_numbers.q == int( + "822ff5d234e073b901cf5941f58e1f538e71d40d", 16 + ) + + def test_signature(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.signature == binascii.unhexlify( + b"302c021425c4a84a936ab311ee017d3cbd9a3c650bb3ae4a02145d30c64b4326" + b"86bdf925716b4ed059184396bcce" + ) + r, s = decode_dss_signature(cert.signature) + assert r == 215618264820276283222494627481362273536404860490 + assert s == 532023851299196869156027211159466197586787351758 + + def test_tbs_certificate_bytes(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.tbs_certificate_bytes == binascii.unhexlify( + b"3082051aa003020102020900a37352e0b2142f86300906072a8648ce3804033" + b"067310b3009060355040613025553310e300c06035504081305546578617331" + b"0f300d0603550407130641757374696e3121301f060355040a1318496e74657" + b"26e6574205769646769747320507479204c7464311430120603550403130b50" + b"79434120445341204341301e170d3134313132373035313431375a170d31343" + b"13232373035313431375a3067310b3009060355040613025553310e300c0603" + b"55040813055465786173310f300d0603550407130641757374696e3121301f0" + b"60355040a1318496e7465726e6574205769646769747320507479204c746431" + b"1430120603550403130b50794341204453412043413082033a3082022d06072" + b"a8648ce380401308202200282010100bfade6048e373cd4e48b677e878c8e5b" + b"08c02102ae04eb2cb5c46a523a3af1c73d16b24f34a4964781ae7e50500e217" + b"77754a670bd19a7420d633084e5556e33ca2c0e7d547ea5f46a07a01bf8669a" + b"e3bdec042d9b2ae5e6ecf49f00ba9dac99ab6eff140d2cedf722ee62c2f9736" + b"857971444c25d0a33d2017dc36d682a1054fe2a9428dda355a851ce6e6d61e0" + b"3e419fd4ca4e703313743d86caa885930f62ed5bf342d8165627681e9cc3244" + b"ba72aa22148400a6bbe80154e855d042c9dc2a3405f1e517be9dea50562f56d" + b"a93f6085f844a7e705c1f043e65751c583b80d29103e590ccb26efdaa0893d8" + b"33e36468f3907cfca788a3cb790f0341c8a31bf021500822ff5d234e073b901" + b"cf5941f58e1f538e71d40d028201004b7ced71dc353965ecc10d441a9a06fc2" + b"4943a32d66429dd5ef44d43e67d789d99770aec32c0415dc92970880872da45" + b"fef8dd1e115a3e4801387ba6d755861f062fd3b6e9ea8e2641152339b828315" + b"b1528ee6c7b79458d21f3db973f6fc303f9397174c2799dd2351282aa2d8842" + b"c357a73495bbaac4932786414c55e60d73169f5761036fba29e9eebfb049f8a" + b"3b1b7cee6f3fbfa136205f130bee2cf5b9c38dc1095d4006f2e73335c07352c" + b"64130a1ab2b89f13b48f628d3cc3868beece9bb7beade9f830eacc6fa241425" + b"c0b3fcc0df416a0c89f7bf35668d765ec95cdcfbe9caff49cfc156c668c76fa" + b"6247676a6d3ac945844a083509c6a1b436baca0382010500028201004c08bfe" + b"5f2d76649c80acf7d431f6ae2124b217abc8c9f6aca776ddfa9453b6656f13e" + b"543684cd5f6431a314377d2abfa068b7080cb8ddc065afc2dea559f0b584c97" + b"a2b235b9b69b46bc6de1aed422a6f341832618bcaae2198aba388099dafb05f" + b"f0b5efecb3b0ae169a62e1c72022af50ae68af3b033c18e6eec1f7df4692c45" + b"6ccafb79cc7e08da0a5786e9816ceda651d61b4bb7b81c2783da97cea62df67" + b"af5e85991fdc13aff10fc60e06586386b96bb78d65750f542f86951e05a6d81" + b"baadbcd35a2e5cad4119923ae6a2002091a3d17017f93c52970113cdc119970" + b"b9074ca506eac91c3dd376325df4af6b3911ef267d26623a5a1c5df4a6d13f1" + b"ca381cc3081c9301d0603551d0e04160414a4fb887a13fcdeb303bbae9a1dec" + b"a72f125a541b3081990603551d2304819130818e8014a4fb887a13fcdeb303b" + b"bae9a1deca72f125a541ba16ba4693067310b3009060355040613025553310e" + b"300c060355040813055465786173310f300d0603550407130641757374696e3" + b"121301f060355040a1318496e7465726e657420576964676974732050747920" + b"4c7464311430120603550403130b5079434120445341204341820900a37352e" + b"0b2142f86300c0603551d13040530030101ff" + ) + cert.public_key().verify( + cert.signature, cert.tbs_certificate_bytes, + cert.signature_hash_algorithm + ) + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestDSACertificateRequest(object): + @pytest.mark.parametrize( + ("path", "loader_func"), + [ + [ + os.path.join("x509", "requests", "dsa_sha1.pem"), + x509.load_pem_x509_csr + ], + [ + os.path.join("x509", "requests", "dsa_sha1.der"), + x509.load_der_x509_csr + ], + ] + ) + def test_load_dsa_request(self, path, loader_func, backend): + request = _load_cert(path, loader_func, backend) + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, dsa.DSAPublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + ] + + def test_signature(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "dsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.signature == binascii.unhexlify( + b"302c021461d58dc028d0110818a7d817d74235727c4acfdf0214097b52e198e" + b"ce95de17273f0a924df23ce9d8188" + ) + + def test_tbs_certrequest_bytes(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "dsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.tbs_certrequest_bytes == binascii.unhexlify( + b"3082021802010030573118301606035504030c0f63727970746f677261706879" + b"2e696f310d300b060355040a0c0450794341310b300906035504061302555331" + b"0e300c06035504080c055465786173310f300d06035504070c0641757374696e" + b"308201b63082012b06072a8648ce3804013082011e028181008d7fadbc09e284" + b"aafa69154cea24177004909e519f8b35d685cde5b4ecdc9583e74d370a0f88ad" + b"a98f026f27762fb3d5da7836f986dfcdb3589e5b925bea114defc03ef81dae30" + b"c24bbc6df3d588e93427bba64203d4a5b1687b2b5e3b643d4c614976f89f95a3" + b"8d3e4c89065fba97514c22c50adbbf289163a74b54859b35b7021500835de56b" + b"d07cf7f82e2032fe78949aed117aa2ef0281801f717b5a07782fc2e4e68e311f" + b"ea91a54edd36b86ac634d14f05a68a97eae9d2ef31fb1ef3de42c3d100df9ca6" + b"4f5bdc2aec7bfdfb474cf831fea05853b5e059f2d24980a0ac463f1e818af352" + b"3e3cb79a39d45fa92731897752842469cf8540b01491024eaafbce6018e8a1f4" + b"658c343f4ba7c0b21e5376a21f4beb8491961e038184000281800713f07641f6" + b"369bb5a9545274a2d4c01998367fb371bb9e13436363672ed68f82174c2de05c" + b"8e839bc6de568dd50ba28d8d9d8719423aaec5557df10d773ab22d6d65cbb878" + b"04a697bc8fd965b952f9f7e850edf13c8acdb5d753b6d10e59e0b5732e3c82ba" + b"fa140342bc4a3bba16bd0681c8a6a2dbbb7efe6ce2b8463b170ba000" + ) + request.public_key().verify( + request.signature, + request.tbs_certrequest_bytes, + request.signature_hash_algorithm + ) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestECDSACertificate(object): + def test_load_ecdsa_cert(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + cert = _load_cert( + os.path.join("x509", "ecdsa_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA384) + public_key = cert.public_key() + assert isinstance(public_key, ec.EllipticCurvePublicKey) + num = public_key.public_numbers() + assert num.x == int( + "dda7d9bb8ab80bfb0b7f21d2f0bebe73f3335d1abc34eadec69bbcd095f" + "6f0ccd00bba615b51467e9e2d9fee8e630c17", 16 + ) + assert num.y == int( + "ec0770f5cf842e40839ce83f416d3badd3a4145936789d0343ee10136c7" + "2deae88a7a16bb543ce67dc23ff031ca3e23e", 16 + ) + assert isinstance(num.curve, ec.SECP384R1) + + def test_signature(self, backend): + cert = _load_cert( + os.path.join("x509", "ecdsa_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.signature == binascii.unhexlify( + b"3065023100adbcf26c3f124ad12d39c30a099773f488368c8827bbe6888d5085" + b"a763f99e32de66930ff1ccb1098fdd6cabfa6b7fa0023039665bc2648db89e50" + b"dca8d549a2edc7dcd1497f1701b8c8868f4e8c882ba89aa98ac5d100bdf854e2" + b"9ae55b7cb32717" + ) + r, s = decode_dss_signature(cert.signature) + assert r == int( + "adbcf26c3f124ad12d39c30a099773f488368c8827bbe6888d5085a763f99e32" + "de66930ff1ccb1098fdd6cabfa6b7fa0", + 16 + ) + assert s == int( + "39665bc2648db89e50dca8d549a2edc7dcd1497f1701b8c8868f4e8c882ba89a" + "a98ac5d100bdf854e29ae55b7cb32717", + 16 + ) + + def test_tbs_certificate_bytes(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + cert = _load_cert( + os.path.join("x509", "ecdsa_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.tbs_certificate_bytes == binascii.unhexlify( + b"308201c5a0030201020210055556bcf25ea43535c3a40fd5ab4572300a06082" + b"a8648ce3d0403033061310b300906035504061302555331153013060355040a" + b"130c446967694365727420496e6331193017060355040b13107777772e64696" + b"769636572742e636f6d3120301e06035504031317446967694365727420476c" + b"6f62616c20526f6f74204733301e170d3133303830313132303030305a170d3" + b"338303131353132303030305a3061310b300906035504061302555331153013" + b"060355040a130c446967694365727420496e6331193017060355040b1310777" + b"7772e64696769636572742e636f6d3120301e06035504031317446967694365" + b"727420476c6f62616c20526f6f742047333076301006072a8648ce3d0201060" + b"52b8104002203620004dda7d9bb8ab80bfb0b7f21d2f0bebe73f3335d1abc34" + b"eadec69bbcd095f6f0ccd00bba615b51467e9e2d9fee8e630c17ec0770f5cf8" + b"42e40839ce83f416d3badd3a4145936789d0343ee10136c72deae88a7a16bb5" + b"43ce67dc23ff031ca3e23ea3423040300f0603551d130101ff040530030101f" + b"f300e0603551d0f0101ff040403020186301d0603551d0e04160414b3db48a4" + b"f9a1c5d8ae3641cc1163696229bc4bc6" + ) + cert.public_key().verify( + cert.signature, cert.tbs_certificate_bytes, + ec.ECDSA(cert.signature_hash_algorithm) + ) + + def test_load_ecdsa_no_named_curve(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + cert = _load_cert( + os.path.join("x509", "custom", "ec_no_named_curve.pem"), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(NotImplementedError): + cert.public_key() + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +class TestECDSACertificateRequest(object): + @pytest.mark.parametrize( + ("path", "loader_func"), + [ + [ + os.path.join("x509", "requests", "ec_sha256.pem"), + x509.load_pem_x509_csr + ], + [ + os.path.join("x509", "requests", "ec_sha256.der"), + x509.load_der_x509_csr + ], + ] + ) + def test_load_ecdsa_certificate_request(self, path, loader_func, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + request = _load_cert(path, loader_func, backend) + assert isinstance(request.signature_hash_algorithm, hashes.SHA256) + public_key = request.public_key() + assert isinstance(public_key, ec.EllipticCurvePublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + ] + + def test_signature(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + request = _load_cert( + os.path.join("x509", "requests", "ec_sha256.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.signature == binascii.unhexlify( + b"306502302c1a9f7de8c1787332d2307a886b476a59f172b9b0e250262f3238b1" + b"b45ee112bb6eb35b0fb56a123b9296eb212dffc302310094cf440c95c52827d5" + b"56ae6d76500e3008255d47c29f7ee782ed7558e51bfd76aa45df6d999ed5c463" + b"347fe2382d1751" + ) + + def test_tbs_certrequest_bytes(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + request = _load_cert( + os.path.join("x509", "requests", "ec_sha256.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.tbs_certrequest_bytes == binascii.unhexlify( + b"3081d602010030573118301606035504030c0f63727970746f6772617068792" + b"e696f310d300b060355040a0c0450794341310b300906035504061302555331" + b"0e300c06035504080c055465786173310f300d06035504070c0641757374696" + b"e3076301006072a8648ce3d020106052b8104002203620004de19b514c0b3c3" + b"ae9b398ea3e26b5e816bdcf9102cad8f12fe02f9e4c9248724b39297ed7582e" + b"04d8b32a551038d09086803a6d3fb91a1a1167ec02158b00efad39c9396462f" + b"accff0ffaf7155812909d3726bd59fde001cff4bb9b2f5af8cbaa000" + ) + request.public_key().verify( + request.signature, request.tbs_certrequest_bytes, + ec.ECDSA(request.signature_hash_algorithm) + ) + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestOtherCertificate(object): + def test_unsupported_subject_public_key_info(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "unsupported_subject_public_key_info.pem" + ), + x509.load_pem_x509_certificate, + backend, + ) + + with pytest.raises(ValueError): + cert.public_key() + + def test_bad_time_in_validity(self, backend): + cert = _load_cert( + os.path.join( + "x509", "badasn1time.pem" + ), + x509.load_pem_x509_certificate, + backend, + ) + + with pytest.raises(ValueError, match='19020701025736Z'): + cert.not_valid_after + + +class TestNameAttribute(object): + EXPECTED_TYPES = [ + (NameOID.COMMON_NAME, _ASN1Type.UTF8String), + (NameOID.COUNTRY_NAME, _ASN1Type.PrintableString), + (NameOID.LOCALITY_NAME, _ASN1Type.UTF8String), + (NameOID.STATE_OR_PROVINCE_NAME, _ASN1Type.UTF8String), + (NameOID.STREET_ADDRESS, _ASN1Type.UTF8String), + (NameOID.ORGANIZATION_NAME, _ASN1Type.UTF8String), + (NameOID.ORGANIZATIONAL_UNIT_NAME, _ASN1Type.UTF8String), + (NameOID.SERIAL_NUMBER, _ASN1Type.PrintableString), + (NameOID.SURNAME, _ASN1Type.UTF8String), + (NameOID.GIVEN_NAME, _ASN1Type.UTF8String), + (NameOID.TITLE, _ASN1Type.UTF8String), + (NameOID.GENERATION_QUALIFIER, _ASN1Type.UTF8String), + (NameOID.X500_UNIQUE_IDENTIFIER, _ASN1Type.UTF8String), + (NameOID.DN_QUALIFIER, _ASN1Type.PrintableString), + (NameOID.PSEUDONYM, _ASN1Type.UTF8String), + (NameOID.USER_ID, _ASN1Type.UTF8String), + (NameOID.DOMAIN_COMPONENT, _ASN1Type.IA5String), + (NameOID.EMAIL_ADDRESS, _ASN1Type.IA5String), + (NameOID.JURISDICTION_COUNTRY_NAME, _ASN1Type.PrintableString), + (NameOID.JURISDICTION_LOCALITY_NAME, _ASN1Type.UTF8String), + ( + NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME, + _ASN1Type.UTF8String + ), + (NameOID.BUSINESS_CATEGORY, _ASN1Type.UTF8String), + (NameOID.POSTAL_ADDRESS, _ASN1Type.UTF8String), + (NameOID.POSTAL_CODE, _ASN1Type.UTF8String), + ] + + def test_default_types(self): + for oid, asn1_type in TestNameAttribute.EXPECTED_TYPES: + na = x509.NameAttribute(oid, u"US") + assert na._type == asn1_type + + def test_alternate_type(self): + na2 = x509.NameAttribute( + NameOID.COMMON_NAME, u"common", _ASN1Type.IA5String + ) + assert na2._type == _ASN1Type.IA5String + + def test_init_bad_oid(self): + with pytest.raises(TypeError): + x509.NameAttribute(None, u'value') + + def test_init_bad_value(self): + with pytest.raises(TypeError): + x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), + b'bytes' + ) + + def test_init_none_value(self): + with pytest.raises(TypeError): + x509.NameAttribute(NameOID.ORGANIZATION_NAME, None) + + def test_init_bad_country_code_value(self): + with pytest.raises(ValueError): + x509.NameAttribute( + NameOID.COUNTRY_NAME, + u'United States' + ) + + # unicode string of length 2, but > 2 bytes + with pytest.raises(ValueError): + x509.NameAttribute( + NameOID.COUNTRY_NAME, + u'\U0001F37A\U0001F37A' + ) + + def test_invalid_type(self): + with pytest.raises(TypeError): + x509.NameAttribute(NameOID.COMMON_NAME, u"common", "notanenum") + + def test_eq(self): + assert x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), u'value' + ) == x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), u'value' + ) + + def test_ne(self): + assert x509.NameAttribute( + x509.ObjectIdentifier('2.5.4.3'), u'value' + ) != x509.NameAttribute( + x509.ObjectIdentifier('2.5.4.5'), u'value' + ) + assert x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), u'value' + ) != x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), u'value2' + ) + assert x509.NameAttribute( + x509.ObjectIdentifier('2.999.2'), u'value' + ) != object() + + def test_repr(self): + na = x509.NameAttribute(x509.ObjectIdentifier('2.5.4.3'), u'value') + if not six.PY2: + assert repr(na) == ( + "<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commo" + "nName)>, value='value')>" + ) + else: + assert repr(na) == ( + "<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commo" + "nName)>, value=u'value')>" + ) + + def test_distinugished_name(self): + # Escaping + na = x509.NameAttribute(NameOID.COMMON_NAME, u'James "Jim" Smith, III') + assert na.rfc4514_string() == r'CN=James \"Jim\" Smith\, III' + na = x509.NameAttribute(NameOID.USER_ID, u'# escape+,;\0this ') + assert na.rfc4514_string() == r'UID=\# escape\+\,\;\00this\ ' + + # Nonstandard attribute OID + na = x509.NameAttribute(NameOID.EMAIL_ADDRESS, u'somebody@example.com') + assert (na.rfc4514_string() == + '1.2.840.113549.1.9.1=somebody@example.com') + + def test_empty_value(self): + na = x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'') + assert na.rfc4514_string() == r'ST=' + + +class TestRelativeDistinguishedName(object): + def test_init_empty(self): + with pytest.raises(ValueError): + x509.RelativeDistinguishedName([]) + + def test_init_not_nameattribute(self): + with pytest.raises(TypeError): + x509.RelativeDistinguishedName(["not-a-NameAttribute"]) + + def test_init_duplicate_attribute(self): + with pytest.raises(ValueError): + x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'val1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'val1'), + ]) + + def test_hash(self): + rdn1 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), + ]) + rdn2 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + ]) + rdn3 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value3'), + ]) + assert hash(rdn1) == hash(rdn2) + assert hash(rdn1) != hash(rdn3) + + def test_eq(self): + rdn1 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), + ]) + rdn2 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + ]) + assert rdn1 == rdn2 + + def test_ne(self): + rdn1 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), + ]) + rdn2 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value3'), + ]) + assert rdn1 != rdn2 + assert rdn1 != object() + + def test_iter_input(self): + # Order must be preserved too + attrs = [ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value2'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value3') + ] + rdn = x509.RelativeDistinguishedName(iter(attrs)) + assert list(rdn) == attrs + assert list(rdn) == attrs + + def test_get_attributes_for_oid(self): + oid = x509.ObjectIdentifier('2.999.1') + attr = x509.NameAttribute(oid, u'value1') + rdn = x509.RelativeDistinguishedName([attr]) + assert rdn.get_attributes_for_oid(oid) == [attr] + assert rdn.get_attributes_for_oid(x509.ObjectIdentifier('1.2.3')) == [] + + +class TestObjectIdentifier(object): + def test_eq(self): + oid1 = x509.ObjectIdentifier('2.999.1') + oid2 = x509.ObjectIdentifier('2.999.1') + assert oid1 == oid2 + + def test_ne(self): + oid1 = x509.ObjectIdentifier('2.999.1') + assert oid1 != x509.ObjectIdentifier('2.999.2') + assert oid1 != object() + + def test_repr(self): + oid = x509.ObjectIdentifier("2.5.4.3") + assert repr(oid) == "<ObjectIdentifier(oid=2.5.4.3, name=commonName)>" + oid = x509.ObjectIdentifier("2.999.1") + assert repr(oid) == "<ObjectIdentifier(oid=2.999.1, name=Unknown OID)>" + + def test_name_property(self): + oid = x509.ObjectIdentifier("2.5.4.3") + assert oid._name == 'commonName' + oid = x509.ObjectIdentifier("2.999.1") + assert oid._name == 'Unknown OID' + + def test_too_short(self): + with pytest.raises(ValueError): + x509.ObjectIdentifier("1") + + def test_invalid_input(self): + with pytest.raises(ValueError): + x509.ObjectIdentifier("notavalidform") + + def test_invalid_node1(self): + with pytest.raises(ValueError): + x509.ObjectIdentifier("7.1.37") + + def test_invalid_node2(self): + with pytest.raises(ValueError): + x509.ObjectIdentifier("1.50.200") + + def test_valid(self): + x509.ObjectIdentifier("0.35.200") + x509.ObjectIdentifier("1.39.999") + x509.ObjectIdentifier("2.5.29.3") + x509.ObjectIdentifier("2.999.37.5.22.8") + x509.ObjectIdentifier("2.25.305821105408246119474742976030998643995") + + +class TestName(object): + def test_eq(self): + ava1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ava2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + name1 = x509.Name([ava1, ava2]) + name2 = x509.Name([ + x509.RelativeDistinguishedName([ava1]), + x509.RelativeDistinguishedName([ava2]), + ]) + name3 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) + name4 = x509.Name([x509.RelativeDistinguishedName([ava2, ava1])]) + assert name1 == name2 + assert name3 == name4 + + def test_ne(self): + ava1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ava2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + name1 = x509.Name([ava1, ava2]) + name2 = x509.Name([ava2, ava1]) + name3 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) + assert name1 != name2 + assert name1 != name3 + assert name1 != object() + + def test_hash(self): + ava1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ava2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + name1 = x509.Name([ava1, ava2]) + name2 = x509.Name([ + x509.RelativeDistinguishedName([ava1]), + x509.RelativeDistinguishedName([ava2]), + ]) + name3 = x509.Name([ava2, ava1]) + name4 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) + name5 = x509.Name([x509.RelativeDistinguishedName([ava2, ava1])]) + assert hash(name1) == hash(name2) + assert hash(name1) != hash(name3) + assert hash(name1) != hash(name4) + assert hash(name4) == hash(name5) + + def test_iter_input(self): + attrs = [ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ] + name = x509.Name(iter(attrs)) + assert list(name) == attrs + assert list(name) == attrs + + def test_rdns(self): + rdn1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + rdn2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + name1 = x509.Name([rdn1, rdn2]) + assert name1.rdns == [ + x509.RelativeDistinguishedName([rdn1]), + x509.RelativeDistinguishedName([rdn2]), + ] + name2 = x509.Name([x509.RelativeDistinguishedName([rdn1, rdn2])]) + assert name2.rdns == [x509.RelativeDistinguishedName([rdn1, rdn2])] + + @pytest.mark.parametrize( + ("common_name", "org_name", "expected_repr"), + [ + ( + u'cryptography.io', + u'PyCA', + "<Name(CN=cryptography.io,O=PyCA)>", + ), + ( + u'Certificación', + u'Certificación', + "<Name(CN=Certificación,O=Certificación)>", + ), + ]) + def test_repr(self, common_name, org_name, expected_repr): + name = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, common_name), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, org_name), + ]) + + assert repr(name) == expected_repr + + def test_rfc4514_string(self): + n = x509.Name([ + x509.RelativeDistinguishedName([ + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'net'), + ]), + x509.RelativeDistinguishedName([ + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'example'), + ]), + x509.RelativeDistinguishedName([ + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'Sales'), + x509.NameAttribute(NameOID.COMMON_NAME, u'J. Smith'), + ]), + ]) + assert (n.rfc4514_string() == + 'OU=Sales+CN=J. Smith,DC=example,DC=net') + + def test_rfc4514_string_empty_values(self): + n = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u''), + x509.NameAttribute(NameOID.LOCALITY_NAME, u''), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ]) + assert (n.rfc4514_string() == 'CN=cryptography.io,O=PyCA,L=,ST=,C=US') + + def test_not_nameattribute(self): + with pytest.raises(TypeError): + x509.Name(["not-a-NameAttribute"]) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_bytes(self, backend): + name = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + ]) + assert name.public_bytes(backend) == binascii.unhexlify( + b"30293118301606035504030c0f63727970746f6772617068792e696f310d300" + b"b060355040a0c0450794341" + ) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_bmpstring_bytes(self, backend): + # For this test we need an odd length string. BMPString is UCS-2 + # encoded so it will always be even length and OpenSSL will error if + # you pass an odd length string without encoding it properly first. + name = x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, + u'cryptography.io', + _ASN1Type.BMPString + ), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + ]) + assert name.public_bytes(backend) == binascii.unhexlify( + b"30383127302506035504031e1e00630072007900700074006f00670072006100" + b"7000680079002e0069006f310d300b060355040a0c0450794341" + ) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_universalstring_bytes(self, backend): + # UniversalString is UCS-4 + name = x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, + u'cryptography.io', + _ASN1Type.UniversalString + ), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + ]) + assert name.public_bytes(backend) == binascii.unhexlify( + b"30563145304306035504031c3c00000063000000720000007900000070000000" + b"740000006f000000670000007200000061000000700000006800000079000000" + b"2e000000690000006f310d300b060355040a0c0450794341" + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" +) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestEd25519Certificate(object): + def test_load_pem_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "ed25519", "root-ed25519.pem"), + x509.load_pem_x509_certificate, + backend + ) + # self-signed, so this will work + cert.public_key().verify(cert.signature, cert.tbs_certificate_bytes) + assert isinstance(cert, x509.Certificate) + assert cert.serial_number == 9579446940964433301 + assert cert.signature_hash_algorithm is None + assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED25519 + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" +) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestEd448Certificate(object): + def test_load_pem_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "ed448", "root-ed448.pem"), + x509.load_pem_x509_certificate, + backend + ) + # self-signed, so this will work + cert.public_key().verify(cert.signature, cert.tbs_certificate_bytes) + assert isinstance(cert, x509.Certificate) + assert cert.serial_number == 448 + assert cert.signature_hash_algorithm is None + assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED448 + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestSignatureRejection(object): + """Test if signing rejects DH keys properly. + """ + def load_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "DH", "dhkey.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + return serialization.load_pem_private_key(data, None, backend) + + def test_crt_signing_check(self, backend): + issuer_private_key = self.load_key(backend) + public_key = RSA_KEY_2048.private_key(backend).public_key() + not_valid_before = datetime.datetime(2020, 1, 1, 1, 1) + not_valid_after = datetime.datetime(2050, 12, 31, 8, 30) + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + public_key + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + with pytest.raises(TypeError): + builder.sign(issuer_private_key, hashes.SHA256(), backend) + + def test_csr_signing_check(self, backend): + private_key = self.load_key(backend) + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ) + + with pytest.raises(TypeError): + builder.sign(private_key, hashes.SHA256(), backend) + + def test_crl_signing_check(self, backend): + private_key = self.load_key(backend) + last_time = datetime.datetime.utcnow().replace(microsecond=0) + next_time = last_time + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"CA")]) + ).last_update(last_time).next_update(next_time) + + with pytest.raises(TypeError): + builder.sign(private_key, hashes.SHA256(), backend) + + +def test_random_serial_number(monkeypatch): + sample_data = os.urandom(20) + + def notrandom(size): + assert size == len(sample_data) + return sample_data + + monkeypatch.setattr(os, "urandom", notrandom) + + serial_number = x509.random_serial_number() + + assert ( + serial_number == utils.int_from_bytes(sample_data, "big") >> 1 + ) + assert serial_number.bit_length() < 160 diff --git a/tests/x509/test_x509_crlbuilder.py b/tests/x509/test_x509_crlbuilder.py new file mode 100644 index 00000000..04244c1b --- /dev/null +++ b/tests/x509/test_x509_crlbuilder.py @@ -0,0 +1,697 @@ +# 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 datetime + +import pytest + +import pytz + +from cryptography import x509 +from cryptography.hazmat.backends.interfaces import ( + DSABackend, EllipticCurveBackend, RSABackend, X509Backend +) +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec, ed25519, ed448 +from cryptography.x509.oid import ( + AuthorityInformationAccessOID, NameOID, SignatureAlgorithmOID +) + +from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048 +from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 +from ..hazmat.primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 +from ..hazmat.primitives.test_ec import _skip_curve_unsupported + + +class TestCertificateRevocationListBuilder(object): + def test_issuer_name_invalid(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(TypeError): + builder.issuer_name("notanx509name") + + def test_set_issuer_name_twice(self): + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ) + with pytest.raises(ValueError): + builder.issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_aware_last_update(self, backend): + last_time = datetime.datetime(2012, 1, 16, 22, 43) + tz = pytz.timezone("US/Pacific") + last_time = tz.localize(last_time) + utc_last = datetime.datetime(2012, 1, 17, 6, 43) + next_time = datetime.datetime(2022, 1, 17, 6, 43) + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update(last_time).next_update(next_time) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert crl.last_update == utc_last + + def test_last_update_invalid(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(TypeError): + builder.last_update("notadatetime") + + def test_last_update_before_1950(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(ValueError): + builder.last_update(datetime.datetime(1940, 8, 10)) + + def test_set_last_update_twice(self): + builder = x509.CertificateRevocationListBuilder().last_update( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.last_update(datetime.datetime(2002, 1, 1, 12, 1)) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_aware_next_update(self, backend): + next_time = datetime.datetime(2022, 1, 16, 22, 43) + tz = pytz.timezone("US/Pacific") + next_time = tz.localize(next_time) + utc_next = datetime.datetime(2022, 1, 17, 6, 43) + last_time = datetime.datetime(2012, 1, 17, 6, 43) + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update(last_time).next_update(next_time) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert crl.next_update == utc_next + + def test_next_update_invalid(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(TypeError): + builder.next_update("notadatetime") + + def test_next_update_before_1950(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(ValueError): + builder.next_update(datetime.datetime(1940, 8, 10)) + + def test_set_next_update_twice(self): + builder = x509.CertificateRevocationListBuilder().next_update( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.next_update(datetime.datetime(2002, 1, 1, 12, 1)) + + def test_last_update_after_next_update(self): + builder = x509.CertificateRevocationListBuilder() + + builder = builder.next_update( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.last_update(datetime.datetime(2003, 1, 1, 12, 1)) + + def test_next_update_after_last_update(self): + builder = x509.CertificateRevocationListBuilder() + + builder = builder.last_update( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.next_update(datetime.datetime(2001, 1, 1, 12, 1)) + + def test_add_extension_checks_for_duplicates(self): + builder = x509.CertificateRevocationListBuilder().add_extension( + x509.CRLNumber(1), False + ) + + with pytest.raises(ValueError): + builder.add_extension(x509.CRLNumber(2), False) + + def test_add_invalid_extension(self): + builder = x509.CertificateRevocationListBuilder() + + with pytest.raises(TypeError): + builder.add_extension( + object(), False + ) + + def test_add_invalid_revoked_certificate(self): + builder = x509.CertificateRevocationListBuilder() + + with pytest.raises(TypeError): + builder.add_revoked_certificate(object()) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_issuer_name(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateRevocationListBuilder().last_update( + datetime.datetime(2002, 1, 1, 12, 1) + ).next_update( + datetime.datetime(2030, 1, 1, 12, 1) + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_last_update(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).next_update( + datetime.datetime(2030, 1, 1, 12, 1) + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_next_update(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).last_update( + datetime.datetime(2030, 1, 1, 12, 1) + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_empty_list(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update(last_update).next_update(next_update) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert len(crl) == 0 + assert crl.last_update == last_update + assert crl.next_update == next_update + + @pytest.mark.parametrize( + "extension", + [ + x509.CRLNumber(13), + x509.DeltaCRLIndicator(12345678901234567890), + x509.AuthorityKeyIdentifier( + b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" + b"\xcbY", + None, + None + ), + x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.DNSName(u"cryptography.io") + ) + ]), + x509.IssuerAlternativeName([ + x509.UniformResourceIdentifier(u"https://cryptography.io"), + ]) + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_extensions(self, backend, extension): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_extension( + extension, False + ) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert len(crl) == 0 + assert len(crl.extensions) == 1 + ext = crl.extensions.get_extension_for_class(type(extension)) + assert ext.critical is False + assert ext.value == extension + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_multiple_extensions_critical(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + ian = x509.IssuerAlternativeName([ + x509.UniformResourceIdentifier(u"https://cryptography.io"), + ]) + crl_number = x509.CRLNumber(13) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_extension( + crl_number, False + ).add_extension( + ian, True + ) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert len(crl) == 0 + assert len(crl.extensions) == 2 + ext1 = crl.extensions.get_extension_for_class(x509.CRLNumber) + assert ext1.critical is False + assert ext1.value == crl_number + ext2 = crl.extensions.get_extension_for_class( + x509.IssuerAlternativeName + ) + assert ext2.critical is True + assert ext2.value == ian + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_freshestcrl_extension(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + freshest = x509.FreshestCRL([ + x509.DistributionPoint([ + x509.UniformResourceIdentifier(u"http://d.om/delta"), + ], None, None, None) + ]) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_extension( + freshest, False + ) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert len(crl) == 0 + assert len(crl.extensions) == 1 + ext1 = crl.extensions.get_extension_for_class(x509.FreshestCRL) + assert ext1.critical is False + assert isinstance(ext1.value[0], x509.DistributionPoint) + uri = ext1.value[0].full_name[0] + assert isinstance(uri, x509.UniformResourceIdentifier) + assert uri.value == u"http://d.om/delta" + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_add_unsupported_extension(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_extension( + x509.OCSPNoCheck(), False + ) + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_rsa_key_too_small(self, backend): + private_key = RSA_KEY_512.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA512(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_invalid_hash(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ) + + with pytest.raises(TypeError): + builder.sign(private_key, object(), backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_invalid_hash_ed25519(self, backend): + private_key = ed25519.Ed25519PrivateKey.generate() + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ) + + with pytest.raises(ValueError): + builder.sign(private_key, object(), backend) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_invalid_hash_ed448(self, backend): + private_key = ed448.Ed448PrivateKey.generate() + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ) + + with pytest.raises(ValueError): + builder.sign(private_key, object(), backend) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_dsa_key(self, backend): + private_key = DSA_KEY_2048.private_key(backend) + invalidity_date = x509.InvalidityDate( + datetime.datetime(2002, 1, 1, 0, 0) + ) + ian = x509.IssuerAlternativeName([ + x509.UniformResourceIdentifier(u"https://cryptography.io"), + ]) + revoked_cert0 = x509.RevokedCertificateBuilder().serial_number( + 2 + ).revocation_date( + datetime.datetime(2012, 1, 1, 1, 1) + ).add_extension( + invalidity_date, False + ).build(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_revoked_certificate( + revoked_cert0 + ).add_extension( + ian, False + ) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert crl.extensions.get_extension_for_class( + x509.IssuerAlternativeName + ).value == ian + assert crl[0].serial_number == revoked_cert0.serial_number + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert len(crl[0].extensions) == 1 + ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) + assert ext.critical is False + assert ext.value == invalidity_date + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_ec_key(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + invalidity_date = x509.InvalidityDate( + datetime.datetime(2002, 1, 1, 0, 0) + ) + ian = x509.IssuerAlternativeName([ + x509.UniformResourceIdentifier(u"https://cryptography.io"), + ]) + revoked_cert0 = x509.RevokedCertificateBuilder().serial_number( + 2 + ).revocation_date( + datetime.datetime(2012, 1, 1, 1, 1) + ).add_extension( + invalidity_date, False + ).build(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_revoked_certificate( + revoked_cert0 + ).add_extension( + ian, False + ) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert crl.extensions.get_extension_for_class( + x509.IssuerAlternativeName + ).value == ian + assert crl[0].serial_number == revoked_cert0.serial_number + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert len(crl[0].extensions) == 1 + ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) + assert ext.critical is False + assert ext.value == invalidity_date + + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_ed25519_key(self, backend): + private_key = ed25519.Ed25519PrivateKey.generate() + invalidity_date = x509.InvalidityDate( + datetime.datetime(2002, 1, 1, 0, 0) + ) + ian = x509.IssuerAlternativeName([ + x509.UniformResourceIdentifier(u"https://cryptography.io"), + ]) + revoked_cert0 = x509.RevokedCertificateBuilder().serial_number( + 2 + ).revocation_date( + datetime.datetime(2012, 1, 1, 1, 1) + ).add_extension( + invalidity_date, False + ).build(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_revoked_certificate( + revoked_cert0 + ).add_extension( + ian, False + ) + + crl = builder.sign(private_key, None, backend) + assert crl.signature_hash_algorithm is None + assert crl.signature_algorithm_oid == SignatureAlgorithmOID.ED25519 + assert crl.extensions.get_extension_for_class( + x509.IssuerAlternativeName + ).value == ian + assert crl[0].serial_number == revoked_cert0.serial_number + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert len(crl[0].extensions) == 1 + ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) + assert ext.critical is False + assert ext.value == invalidity_date + + @pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_ed448_key(self, backend): + private_key = ed448.Ed448PrivateKey.generate() + invalidity_date = x509.InvalidityDate( + datetime.datetime(2002, 1, 1, 0, 0) + ) + ian = x509.IssuerAlternativeName([ + x509.UniformResourceIdentifier(u"https://cryptography.io"), + ]) + revoked_cert0 = x509.RevokedCertificateBuilder().serial_number( + 2 + ).revocation_date( + datetime.datetime(2012, 1, 1, 1, 1) + ).add_extension( + invalidity_date, False + ).build(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_revoked_certificate( + revoked_cert0 + ).add_extension( + ian, False + ) + + crl = builder.sign(private_key, None, backend) + assert crl.signature_hash_algorithm is None + assert crl.signature_algorithm_oid == SignatureAlgorithmOID.ED448 + assert crl.extensions.get_extension_for_class( + x509.IssuerAlternativeName + ).value == ian + assert crl[0].serial_number == revoked_cert0.serial_number + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert len(crl[0].extensions) == 1 + ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) + assert ext.critical is False + assert ext.value == invalidity_date + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_dsa_key_sign_md5(self, backend): + private_key = DSA_KEY_2048.private_key(backend) + last_time = datetime.datetime(2012, 1, 16, 22, 43) + next_time = datetime.datetime(2022, 1, 17, 6, 43) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update(last_time).next_update(next_time) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.MD5(), backend) + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_ec_key_sign_md5(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = EC_KEY_SECP256R1.private_key(backend) + last_time = datetime.datetime(2012, 1, 16, 22, 43) + next_time = datetime.datetime(2022, 1, 17, 6, 43) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update(last_time).next_update(next_time) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.MD5(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_revoked_certificates(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + invalidity_date = x509.InvalidityDate( + datetime.datetime(2002, 1, 1, 0, 0) + ) + revoked_cert0 = x509.RevokedCertificateBuilder().serial_number( + 38 + ).revocation_date( + datetime.datetime(2011, 1, 1, 1, 1) + ).build(backend) + revoked_cert1 = x509.RevokedCertificateBuilder().serial_number( + 2 + ).revocation_date( + datetime.datetime(2012, 1, 1, 1, 1) + ).add_extension( + invalidity_date, False + ).build(backend) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_revoked_certificate( + revoked_cert0 + ).add_revoked_certificate( + revoked_cert1 + ) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert len(crl) == 2 + assert crl.last_update == last_update + assert crl.next_update == next_update + assert crl[0].serial_number == revoked_cert0.serial_number + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert len(crl[0].extensions) == 0 + assert crl[1].serial_number == revoked_cert1.serial_number + assert crl[1].revocation_date == revoked_cert1.revocation_date + assert len(crl[1].extensions) == 1 + ext = crl[1].extensions.get_extension_for_class(x509.InvalidityDate) + assert ext.critical is False + assert ext.value == invalidity_date diff --git a/tests/x509/test_x509_ext.py b/tests/x509/test_x509_ext.py new file mode 100644 index 00000000..19ce4363 --- /dev/null +++ b/tests/x509/test_x509_ext.py @@ -0,0 +1,5289 @@ +# 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 datetime +import ipaddress +import os + +import pretend + +import pytest + +import six + +from cryptography import utils, x509 +from cryptography.hazmat.backends.interfaces import ( + DSABackend, EllipticCurveBackend, RSABackend, X509Backend +) +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.x509 import DNSName, NameConstraints, SubjectAlternativeName +from cryptography.x509.extensions import _key_identifier_from_public_key +from cryptography.x509.general_name import _lazy_import_idna +from cryptography.x509.oid import ( + AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID, + NameOID, ObjectIdentifier, _OID_NAMES +) + +from .test_x509 import _load_cert +from ..hazmat.primitives.fixtures_rsa import RSA_KEY_2048 +from ..hazmat.primitives.test_ec import _skip_curve_unsupported +from ..utils import load_vectors_from_file + + +def _make_certbuilder(private_key): + name = x509.Name( + [x509.NameAttribute(NameOID.COMMON_NAME, u'example.org')]) + return ( + x509.CertificateBuilder() + .subject_name(name) + .issuer_name(name) + .public_key(private_key.public_key()) + .serial_number(777) + .not_valid_before(datetime.datetime(1999, 1, 1)) + .not_valid_after(datetime.datetime(2020, 1, 1)) + ) + + +def test_lazy_idna_import(): + try: + __import__("idna") + pytest.skip("idna is installed") + except ImportError: + pass + + with pytest.raises(ImportError): + _lazy_import_idna() + + +class TestExtension(object): + def test_not_an_oid(self): + bc = x509.BasicConstraints(ca=False, path_length=None) + with pytest.raises(TypeError): + x509.Extension("notanoid", True, bc) + + def test_critical_not_a_bool(self): + bc = x509.BasicConstraints(ca=False, path_length=None) + with pytest.raises(TypeError): + x509.Extension(ExtensionOID.BASIC_CONSTRAINTS, "notabool", bc) + + def test_repr(self): + bc = x509.BasicConstraints(ca=False, path_length=None) + ext = x509.Extension(ExtensionOID.BASIC_CONSTRAINTS, True, bc) + assert repr(ext) == ( + "<Extension(oid=<ObjectIdentifier(oid=2.5.29.19, name=basicConst" + "raints)>, critical=True, value=<BasicConstraints(ca=False, path" + "_length=None)>)>" + ) + + def test_eq(self): + ext1 = x509.Extension( + x509.ObjectIdentifier('1.2.3.4'), False, 'value' + ) + ext2 = x509.Extension( + x509.ObjectIdentifier('1.2.3.4'), False, 'value' + ) + assert ext1 == ext2 + + def test_ne(self): + ext1 = x509.Extension( + x509.ObjectIdentifier('1.2.3.4'), False, 'value' + ) + ext2 = x509.Extension( + x509.ObjectIdentifier('1.2.3.5'), False, 'value' + ) + ext3 = x509.Extension( + x509.ObjectIdentifier('1.2.3.4'), True, 'value' + ) + ext4 = x509.Extension( + x509.ObjectIdentifier('1.2.3.4'), False, 'value4' + ) + assert ext1 != ext2 + assert ext1 != ext3 + assert ext1 != ext4 + assert ext1 != object() + + def test_hash(self): + ext1 = x509.Extension( + ExtensionOID.BASIC_CONSTRAINTS, + False, + x509.BasicConstraints(ca=False, path_length=None) + ) + ext2 = x509.Extension( + ExtensionOID.BASIC_CONSTRAINTS, + False, + x509.BasicConstraints(ca=False, path_length=None) + ) + ext3 = x509.Extension( + ExtensionOID.BASIC_CONSTRAINTS, + False, + x509.BasicConstraints(ca=True, path_length=None) + ) + assert hash(ext1) == hash(ext2) + assert hash(ext1) != hash(ext3) + + +class TestTLSFeature(object): + def test_not_enum_type(self): + with pytest.raises(TypeError): + x509.TLSFeature([3]) + + def test_empty_list(self): + with pytest.raises(TypeError): + x509.TLSFeature([]) + + def test_repr(self): + ext1 = x509.TLSFeature([x509.TLSFeatureType.status_request]) + assert repr(ext1) == ( + "<TLSFeature(features=[<TLSFeatureType.status_request: 5>])>" + ) + + def test_eq(self): + ext1 = x509.TLSFeature([x509.TLSFeatureType.status_request]) + ext2 = x509.TLSFeature([x509.TLSFeatureType.status_request]) + assert ext1 == ext2 + + def test_ne(self): + ext1 = x509.TLSFeature([x509.TLSFeatureType.status_request]) + ext2 = x509.TLSFeature([x509.TLSFeatureType.status_request_v2]) + ext3 = x509.TLSFeature([ + x509.TLSFeatureType.status_request, + x509.TLSFeatureType.status_request_v2 + ]) + assert ext1 != ext2 + assert ext1 != ext3 + assert ext1 != object() + + def test_hash(self): + ext1 = x509.TLSFeature([x509.TLSFeatureType.status_request]) + ext2 = x509.TLSFeature([x509.TLSFeatureType.status_request]) + ext3 = x509.TLSFeature([ + x509.TLSFeatureType.status_request, + x509.TLSFeatureType.status_request_v2 + ]) + assert hash(ext1) == hash(ext2) + assert hash(ext1) != hash(ext3) + + def test_iter(self): + ext1_features = [x509.TLSFeatureType.status_request] + ext1 = x509.TLSFeature(ext1_features) + assert len(ext1) == 1 + assert list(ext1) == ext1_features + ext2_features = [ + x509.TLSFeatureType.status_request, + x509.TLSFeatureType.status_request_v2, + ] + ext2 = x509.TLSFeature(ext2_features) + assert len(ext2) == 2 + assert list(ext2) == ext2_features + + def test_indexing(self): + ext = x509.TLSFeature([ + x509.TLSFeatureType.status_request, + x509.TLSFeatureType.status_request_v2, + ]) + assert ext[-1] == ext[1] + assert ext[0] == x509.TLSFeatureType.status_request + + +class TestUnrecognizedExtension(object): + def test_invalid_oid(self): + with pytest.raises(TypeError): + x509.UnrecognizedExtension("notanoid", b"somedata") + + def test_eq(self): + ext1 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" + ) + ext2 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" + ) + assert ext1 == ext2 + + def test_ne(self): + ext1 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" + ) + ext2 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x02" + ) + ext3 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.5"), b"\x03\x02\x01" + ) + assert ext1 != ext2 + assert ext1 != ext3 + assert ext1 != object() + + def test_repr(self): + ext1 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" + ) + if not six.PY2: + assert repr(ext1) == ( + "<UnrecognizedExtension(oid=<ObjectIdentifier(oid=1.2.3.4, " + "name=Unknown OID)>, value=b'\\x03\\x02\\x01')>" + ) + else: + assert repr(ext1) == ( + "<UnrecognizedExtension(oid=<ObjectIdentifier(oid=1.2.3.4, " + "name=Unknown OID)>, value='\\x03\\x02\\x01')>" + ) + + def test_hash(self): + ext1 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" + ) + ext2 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" + ) + ext3 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.5"), b"\x03\x02\x01" + ) + assert hash(ext1) == hash(ext2) + assert hash(ext1) != hash(ext3) + + +class TestCertificateIssuer(object): + def test_iter_names(self): + ci = x509.CertificateIssuer([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ]) + assert len(ci) == 2 + assert list(ci) == [ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ] + + def test_indexing(self): + ci = x509.CertificateIssuer([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + x509.DNSName(u"another.local"), + x509.RFC822Name(u"email@another.local"), + x509.UniformResourceIdentifier(u"http://another.local"), + ]) + assert ci[-1] == ci[4] + assert ci[2:6:2] == [ci[2], ci[4]] + + def test_eq(self): + ci1 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + ci2 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + assert ci1 == ci2 + + def test_ne(self): + ci1 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + ci2 = x509.CertificateIssuer([x509.DNSName(u"somethingelse.tld")]) + assert ci1 != ci2 + assert ci1 != object() + + def test_repr(self): + ci = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + if not six.PY2: + assert repr(ci) == ( + "<CertificateIssuer(<GeneralNames([<DNSName(value=" + "'cryptography.io')>])>)>" + ) + else: + assert repr(ci) == ( + "<CertificateIssuer(<GeneralNames([<DNSName(value=" + "u'cryptography.io')>])>)>" + ) + + def test_get_values_for_type(self): + ci = x509.CertificateIssuer( + [x509.DNSName(u"cryptography.io")] + ) + names = ci.get_values_for_type(x509.DNSName) + assert names == [u"cryptography.io"] + + def test_hash(self): + ci1 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + ci2 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + ci3 = x509.CertificateIssuer( + [x509.UniformResourceIdentifier(u"http://something")] + ) + assert hash(ci1) == hash(ci2) + assert hash(ci1) != hash(ci3) + + +class TestCRLReason(object): + def test_invalid_reason_flags(self): + with pytest.raises(TypeError): + x509.CRLReason("notareason") + + def test_eq(self): + reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) + reason2 = x509.CRLReason(x509.ReasonFlags.unspecified) + assert reason1 == reason2 + + def test_ne(self): + reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) + reason2 = x509.CRLReason(x509.ReasonFlags.ca_compromise) + assert reason1 != reason2 + assert reason1 != object() + + def test_hash(self): + reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) + reason2 = x509.CRLReason(x509.ReasonFlags.unspecified) + reason3 = x509.CRLReason(x509.ReasonFlags.ca_compromise) + + assert hash(reason1) == hash(reason2) + assert hash(reason1) != hash(reason3) + + def test_repr(self): + reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) + assert repr(reason1) == ( + "<CRLReason(reason=ReasonFlags.unspecified)>" + ) + + +class TestDeltaCRLIndicator(object): + def test_not_int(self): + with pytest.raises(TypeError): + x509.DeltaCRLIndicator("notanint") + + def test_eq(self): + delta1 = x509.DeltaCRLIndicator(1) + delta2 = x509.DeltaCRLIndicator(1) + assert delta1 == delta2 + + def test_ne(self): + delta1 = x509.DeltaCRLIndicator(1) + delta2 = x509.DeltaCRLIndicator(2) + assert delta1 != delta2 + assert delta1 != object() + + def test_repr(self): + delta1 = x509.DeltaCRLIndicator(2) + assert repr(delta1) == ( + "<DeltaCRLIndicator(crl_number=2)>" + ) + + def test_hash(self): + delta1 = x509.DeltaCRLIndicator(1) + delta2 = x509.DeltaCRLIndicator(1) + delta3 = x509.DeltaCRLIndicator(2) + assert hash(delta1) == hash(delta2) + assert hash(delta1) != hash(delta3) + + +class TestInvalidityDate(object): + def test_invalid_invalidity_date(self): + with pytest.raises(TypeError): + x509.InvalidityDate("notadate") + + def test_eq(self): + invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + invalid2 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + assert invalid1 == invalid2 + + def test_ne(self): + invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + invalid2 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 2)) + assert invalid1 != invalid2 + assert invalid1 != object() + + def test_repr(self): + invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + assert repr(invalid1) == ( + "<InvalidityDate(invalidity_date=2015-01-01 01:01:00)>" + ) + + def test_hash(self): + invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + invalid2 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + invalid3 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 2)) + assert hash(invalid1) == hash(invalid2) + assert hash(invalid1) != hash(invalid3) + + +class TestNoticeReference(object): + def test_notice_numbers_not_all_int(self): + with pytest.raises(TypeError): + x509.NoticeReference("org", [1, 2, "three"]) + + def test_notice_numbers_none(self): + with pytest.raises(TypeError): + x509.NoticeReference("org", None) + + def test_iter_input(self): + numbers = [1, 3, 4] + nr = x509.NoticeReference(u"org", iter(numbers)) + assert list(nr.notice_numbers) == numbers + + def test_repr(self): + nr = x509.NoticeReference(u"org", [1, 3, 4]) + + if not six.PY2: + assert repr(nr) == ( + "<NoticeReference(organization='org', notice_numbers=[1, 3, 4" + "])>" + ) + else: + assert repr(nr) == ( + "<NoticeReference(organization=u'org', notice_numbers=[1, 3, " + "4])>" + ) + + def test_eq(self): + nr = x509.NoticeReference("org", [1, 2]) + nr2 = x509.NoticeReference("org", [1, 2]) + assert nr == nr2 + + def test_ne(self): + nr = x509.NoticeReference("org", [1, 2]) + nr2 = x509.NoticeReference("org", [1]) + nr3 = x509.NoticeReference(None, [1, 2]) + assert nr != nr2 + assert nr != nr3 + assert nr != object() + + def test_hash(self): + nr = x509.NoticeReference("org", [1, 2]) + nr2 = x509.NoticeReference("org", [1, 2]) + nr3 = x509.NoticeReference(None, [1, 2]) + assert hash(nr) == hash(nr2) + assert hash(nr) != hash(nr3) + + +class TestUserNotice(object): + def test_notice_reference_invalid(self): + with pytest.raises(TypeError): + x509.UserNotice("invalid", None) + + def test_notice_reference_none(self): + un = x509.UserNotice(None, "text") + assert un.notice_reference is None + assert un.explicit_text == "text" + + def test_repr(self): + un = x509.UserNotice(x509.NoticeReference(u"org", [1]), u"text") + if not six.PY2: + assert repr(un) == ( + "<UserNotice(notice_reference=<NoticeReference(organization='" + "org', notice_numbers=[1])>, explicit_text='text')>" + ) + else: + assert repr(un) == ( + "<UserNotice(notice_reference=<NoticeReference(organization=u" + "'org', notice_numbers=[1])>, explicit_text=u'text')>" + ) + + def test_eq(self): + nr = x509.NoticeReference("org", [1, 2]) + nr2 = x509.NoticeReference("org", [1, 2]) + un = x509.UserNotice(nr, "text") + un2 = x509.UserNotice(nr2, "text") + assert un == un2 + + def test_ne(self): + nr = x509.NoticeReference("org", [1, 2]) + nr2 = x509.NoticeReference("org", [1]) + un = x509.UserNotice(nr, "text") + un2 = x509.UserNotice(nr2, "text") + un3 = x509.UserNotice(nr, "text3") + assert un != un2 + assert un != un3 + assert un != object() + + def test_hash(self): + nr = x509.NoticeReference("org", [1, 2]) + nr2 = x509.NoticeReference("org", [1, 2]) + un = x509.UserNotice(nr, "text") + un2 = x509.UserNotice(nr2, "text") + un3 = x509.UserNotice(None, "text") + assert hash(un) == hash(un2) + assert hash(un) != hash(un3) + + +class TestPolicyInformation(object): + def test_invalid_policy_identifier(self): + with pytest.raises(TypeError): + x509.PolicyInformation("notanoid", None) + + def test_none_policy_qualifiers(self): + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), None) + assert pi.policy_identifier == x509.ObjectIdentifier("1.2.3") + assert pi.policy_qualifiers is None + + def test_policy_qualifiers(self): + pq = [u"string"] + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) + assert pi.policy_identifier == x509.ObjectIdentifier("1.2.3") + assert pi.policy_qualifiers == pq + + def test_invalid_policy_identifiers(self): + with pytest.raises(TypeError): + x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), [1, 2]) + + def test_iter_input(self): + qual = [u"foo", u"bar"] + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), iter(qual)) + assert list(pi.policy_qualifiers) == qual + + def test_repr(self): + pq = [u"string", x509.UserNotice(None, u"hi")] + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) + if not six.PY2: + assert repr(pi) == ( + "<PolicyInformation(policy_identifier=<ObjectIdentifier(oid=1." + "2.3, name=Unknown OID)>, policy_qualifiers=['string', <UserNo" + "tice(notice_reference=None, explicit_text='hi')>])>" + ) + else: + assert repr(pi) == ( + "<PolicyInformation(policy_identifier=<ObjectIdentifier(oid=1." + "2.3, name=Unknown OID)>, policy_qualifiers=[u'string', <UserN" + "otice(notice_reference=None, explicit_text=u'hi')>])>" + ) + + def test_eq(self): + pi = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), + [u"string", x509.UserNotice(None, u"hi")] + ) + pi2 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), + [u"string", x509.UserNotice(None, u"hi")] + ) + assert pi == pi2 + + def test_ne(self): + pi = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string"] + ) + pi2 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string2"] + ) + pi3 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3.4"), [u"string"] + ) + assert pi != pi2 + assert pi != pi3 + assert pi != object() + + def test_hash(self): + pi = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), + [u"string", x509.UserNotice(None, u"hi")] + ) + pi2 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), + [u"string", x509.UserNotice(None, u"hi")] + ) + pi3 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), None) + assert hash(pi) == hash(pi2) + assert hash(pi) != hash(pi3) + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestCertificatePolicies(object): + def test_invalid_policies(self): + pq = [u"string"] + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) + with pytest.raises(TypeError): + x509.CertificatePolicies([1, pi]) + + def test_iter_len(self): + pq = [u"string"] + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) + cp = x509.CertificatePolicies([pi]) + assert len(cp) == 1 + for policyinfo in cp: + assert policyinfo == pi + + def test_iter_input(self): + policies = [ + x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), [u"string"]) + ] + cp = x509.CertificatePolicies(iter(policies)) + assert list(cp) == policies + + def test_repr(self): + pq = [u"string"] + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) + cp = x509.CertificatePolicies([pi]) + if not six.PY2: + assert repr(cp) == ( + "<CertificatePolicies([<PolicyInformation(policy_identifier=<O" + "bjectIdentifier(oid=1.2.3, name=Unknown OID)>, policy_qualifi" + "ers=['string'])>])>" + ) + else: + assert repr(cp) == ( + "<CertificatePolicies([<PolicyInformation(policy_identifier=<O" + "bjectIdentifier(oid=1.2.3, name=Unknown OID)>, policy_qualifi" + "ers=[u'string'])>])>" + ) + + def test_eq(self): + pi = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string"] + ) + cp = x509.CertificatePolicies([pi]) + pi2 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string"] + ) + cp2 = x509.CertificatePolicies([pi2]) + assert cp == cp2 + + def test_ne(self): + pi = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string"] + ) + cp = x509.CertificatePolicies([pi]) + pi2 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string2"] + ) + cp2 = x509.CertificatePolicies([pi2]) + assert cp != cp2 + assert cp != object() + + def test_indexing(self): + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), [u"test"]) + pi2 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.4"), [u"test"]) + pi3 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.5"), [u"test"]) + pi4 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.6"), [u"test"]) + pi5 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.7"), [u"test"]) + cp = x509.CertificatePolicies([pi, pi2, pi3, pi4, pi5]) + assert cp[-1] == cp[4] + assert cp[2:6:2] == [cp[2], cp[4]] + + def test_long_oid(self, backend): + """ + Test that parsing a CertificatePolicies ext with + a very long OID succeeds. + """ + cert = _load_cert( + os.path.join("x509", "bigoid.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_class( + x509.CertificatePolicies) + + oid = x509.ObjectIdentifier( + "1.3.6.1.4.1.311.21.8.8950086.10656446.2706058" + ".12775672.480128.147.13466065.13029902" + ) + + assert ext.value[0].policy_identifier == oid + + def test_hash(self): + pi = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string"] + ) + cp = x509.CertificatePolicies([pi]) + pi2 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string"] + ) + cp2 = x509.CertificatePolicies([pi2]) + pi3 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [x509.UserNotice(None, b"text")] + ) + cp3 = x509.CertificatePolicies([pi3]) + assert hash(cp) == hash(cp2) + assert hash(cp) != hash(cp3) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestCertificatePoliciesExtension(object): + def test_cps_uri_policy_qualifier(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "cp_cps_uri.pem"), + x509.load_pem_x509_certificate, + backend + ) + + cp = cert.extensions.get_extension_for_oid( + ExtensionOID.CERTIFICATE_POLICIES + ).value + + assert cp == x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [u"http://other.com/cps"] + ) + ]) + + def test_user_notice_with_notice_reference(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cp_user_notice_with_notice_reference.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cp = cert.extensions.get_extension_for_oid( + ExtensionOID.CERTIFICATE_POLICIES + ).value + + assert cp == x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + u"http://example.com/cps", + u"http://other.com/cps", + x509.UserNotice( + x509.NoticeReference(u"my org", [1, 2, 3, 4]), + u"thing" + ) + ] + ) + ]) + + def test_user_notice_with_explicit_text(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cp_user_notice_with_explicit_text.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cp = cert.extensions.get_extension_for_oid( + ExtensionOID.CERTIFICATE_POLICIES + ).value + + assert cp == x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [x509.UserNotice(None, u"thing")] + ) + ]) + + def test_user_notice_no_explicit_text(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cp_user_notice_no_explicit_text.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cp = cert.extensions.get_extension_for_oid( + ExtensionOID.CERTIFICATE_POLICIES + ).value + + assert cp == x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + x509.UserNotice( + x509.NoticeReference(u"my org", [1, 2, 3, 4]), + None + ) + ] + ) + ]) + + +class TestKeyUsage(object): + def test_key_agreement_false_encipher_decipher_true(self): + with pytest.raises(ValueError): + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=True, + decipher_only=False + ) + + with pytest.raises(ValueError): + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=True, + decipher_only=True + ) + + with pytest.raises(ValueError): + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + + def test_properties_key_agreement_true(self): + ku = x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + assert ku.digital_signature is True + assert ku.content_commitment is True + assert ku.key_encipherment is False + assert ku.data_encipherment is False + assert ku.key_agreement is False + assert ku.key_cert_sign is True + assert ku.crl_sign is False + + def test_key_agreement_true_properties(self): + ku = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + assert ku.key_agreement is True + assert ku.encipher_only is False + assert ku.decipher_only is True + + def test_key_agreement_false_properties(self): + ku = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + assert ku.key_agreement is False + with pytest.raises(ValueError): + ku.encipher_only + + with pytest.raises(ValueError): + ku.decipher_only + + def test_repr_key_agreement_false(self): + ku = x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + assert repr(ku) == ( + "<KeyUsage(digital_signature=True, content_commitment=True, key_en" + "cipherment=False, data_encipherment=False, key_agreement=False, k" + "ey_cert_sign=True, crl_sign=False, encipher_only=None, decipher_o" + "nly=None)>" + ) + + def test_repr_key_agreement_true(self): + ku = x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + assert repr(ku) == ( + "<KeyUsage(digital_signature=True, content_commitment=True, key_en" + "cipherment=False, data_encipherment=False, key_agreement=True, k" + "ey_cert_sign=True, crl_sign=False, encipher_only=False, decipher_" + "only=False)>" + ) + + def test_eq(self): + ku = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + ku2 = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + assert ku == ku2 + + def test_ne(self): + ku = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + ku2 = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + assert ku != ku2 + assert ku != object() + + def test_hash(self): + ku = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + ku2 = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + ku3 = x509.KeyUsage( + digital_signature=False, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + assert hash(ku) == hash(ku2) + assert hash(ku) != hash(ku3) + + +class TestSubjectKeyIdentifier(object): + def test_properties(self): + value = binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ski = x509.SubjectKeyIdentifier(value) + assert ski.digest == value + + def test_repr(self): + ski = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + ext = x509.Extension(ExtensionOID.SUBJECT_KEY_IDENTIFIER, False, ski) + if not six.PY2: + assert repr(ext) == ( + "<Extension(oid=<ObjectIdentifier(oid=2.5.29.14, name=subjectK" + "eyIdentifier)>, critical=False, value=<SubjectKeyIdentifier(d" + "igest=b\'\\t#\\x84\\x93\"0I\\x8b\\xc9\\x80\\xaa\\x80\\x98Eoo" + "\\xf7\\xff:\\xc9\')>)>" + ) + else: + assert repr(ext) == ( + "<Extension(oid=<ObjectIdentifier(oid=2.5.29.14, name=subjectK" + "eyIdentifier)>, critical=False, value=<SubjectKeyIdentifier(d" + "igest=\'\\t#\\x84\\x93\"0I\\x8b\\xc9\\x80\\xaa\\x80\\x98Eoo" + "\\xf7\\xff:\\xc9\')>)>" + ) + + def test_eq(self): + ski = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + ski2 = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + assert ski == ski2 + + def test_ne(self): + ski = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + ski2 = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"aa8098456f6ff7ff3ac9092384932230498bc980") + ) + assert ski != ski2 + assert ski != object() + + def test_hash(self): + ski1 = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + ski2 = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + ski3 = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"aa8098456f6ff7ff3ac9092384932230498bc980") + ) + assert hash(ski1) == hash(ski2) + assert hash(ski1) != hash(ski3) + + +class TestAuthorityKeyIdentifier(object): + def test_authority_cert_issuer_not_generalname(self): + with pytest.raises(TypeError): + x509.AuthorityKeyIdentifier(b"identifier", ["notname"], 3) + + def test_authority_cert_serial_number_not_integer(self): + dirname = x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), + u'value1' + ), + x509.NameAttribute( + x509.ObjectIdentifier('2.999.2'), + u'value2' + ), + ]) + ) + with pytest.raises(TypeError): + x509.AuthorityKeyIdentifier(b"identifier", [dirname], "notanint") + + def test_authority_issuer_none_serial_not_none(self): + with pytest.raises(ValueError): + x509.AuthorityKeyIdentifier(b"identifier", None, 3) + + def test_authority_issuer_not_none_serial_none(self): + dirname = x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), + u'value1' + ), + x509.NameAttribute( + x509.ObjectIdentifier('2.999.2'), + u'value2' + ), + ]) + ) + with pytest.raises(ValueError): + x509.AuthorityKeyIdentifier(b"identifier", [dirname], None) + + def test_authority_cert_serial_and_issuer_none(self): + aki = x509.AuthorityKeyIdentifier(b"id", None, None) + assert aki.key_identifier == b"id" + assert aki.authority_cert_issuer is None + assert aki.authority_cert_serial_number is None + + def test_authority_cert_serial_zero(self): + dns = x509.DNSName(u"SomeIssuer") + aki = x509.AuthorityKeyIdentifier(b"id", [dns], 0) + assert aki.key_identifier == b"id" + assert aki.authority_cert_issuer == [dns] + assert aki.authority_cert_serial_number == 0 + + def test_iter_input(self): + dirnames = [ + x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'myCN')]) + ) + ] + aki = x509.AuthorityKeyIdentifier(b"digest", iter(dirnames), 1234) + assert list(aki.authority_cert_issuer) == dirnames + + def test_repr(self): + dirname = x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'myCN')]) + ) + aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) + + if not six.PY2: + assert repr(aki) == ( + "<AuthorityKeyIdentifier(key_identifier=b'digest', authority_" + "cert_issuer=[<DirectoryName(value=<Name(CN=myCN)>)>], author" + "ity_cert_serial_number=1234)>" + ) + else: + assert repr(aki) == ( + "<AuthorityKeyIdentifier(key_identifier='digest', authority_" + "cert_issuer=[<DirectoryName(value=<Name(CN=myCN)>)>], author" + "ity_cert_serial_number=1234)>" + ) + + def test_eq(self): + dirname = x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'myCN')]) + ) + aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) + dirname2 = x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'myCN')]) + ) + aki2 = x509.AuthorityKeyIdentifier(b"digest", [dirname2], 1234) + assert aki == aki2 + + def test_ne(self): + dirname = x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'myCN')]) + ) + dirname5 = x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'aCN')]) + ) + aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) + aki2 = x509.AuthorityKeyIdentifier(b"diges", [dirname], 1234) + aki3 = x509.AuthorityKeyIdentifier(b"digest", None, None) + aki4 = x509.AuthorityKeyIdentifier(b"digest", [dirname], 12345) + aki5 = x509.AuthorityKeyIdentifier(b"digest", [dirname5], 12345) + assert aki != aki2 + assert aki != aki3 + assert aki != aki4 + assert aki != aki5 + assert aki != object() + + def test_hash(self): + dirname = x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'myCN')]) + ) + aki1 = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) + aki2 = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) + aki3 = x509.AuthorityKeyIdentifier(b"digest", None, None) + assert hash(aki1) == hash(aki2) + assert hash(aki1) != hash(aki3) + + +class TestBasicConstraints(object): + def test_ca_not_boolean(self): + with pytest.raises(TypeError): + x509.BasicConstraints(ca="notbool", path_length=None) + + def test_path_length_not_ca(self): + with pytest.raises(ValueError): + x509.BasicConstraints(ca=False, path_length=0) + + def test_path_length_not_int(self): + with pytest.raises(TypeError): + x509.BasicConstraints(ca=True, path_length=1.1) + + with pytest.raises(TypeError): + x509.BasicConstraints(ca=True, path_length="notint") + + def test_path_length_negative(self): + with pytest.raises(TypeError): + x509.BasicConstraints(ca=True, path_length=-1) + + def test_repr(self): + na = x509.BasicConstraints(ca=True, path_length=None) + assert repr(na) == ( + "<BasicConstraints(ca=True, path_length=None)>" + ) + + def test_hash(self): + na = x509.BasicConstraints(ca=True, path_length=None) + na2 = x509.BasicConstraints(ca=True, path_length=None) + na3 = x509.BasicConstraints(ca=True, path_length=0) + assert hash(na) == hash(na2) + assert hash(na) != hash(na3) + + def test_eq(self): + na = x509.BasicConstraints(ca=True, path_length=None) + na2 = x509.BasicConstraints(ca=True, path_length=None) + assert na == na2 + + def test_ne(self): + na = x509.BasicConstraints(ca=True, path_length=None) + na2 = x509.BasicConstraints(ca=True, path_length=1) + na3 = x509.BasicConstraints(ca=False, path_length=None) + assert na != na2 + assert na != na3 + assert na != object() + + +class TestExtendedKeyUsage(object): + def test_not_all_oids(self): + with pytest.raises(TypeError): + x509.ExtendedKeyUsage(["notoid"]) + + def test_iter_len(self): + eku = x509.ExtendedKeyUsage([ + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), + ]) + assert len(eku) == 2 + assert list(eku) == [ + ExtendedKeyUsageOID.SERVER_AUTH, + ExtendedKeyUsageOID.CLIENT_AUTH + ] + + def test_iter_input(self): + usages = [ + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), + ] + aia = x509.ExtendedKeyUsage(iter(usages)) + assert list(aia) == usages + + def test_repr(self): + eku = x509.ExtendedKeyUsage([ + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), + ]) + assert repr(eku) == ( + "<ExtendedKeyUsage([<ObjectIdentifier(oid=1.3.6.1.5.5.7.3.1, name=" + "serverAuth)>, <ObjectIdentifier(oid=1.3.6.1.5.5.7.3.2, name=clien" + "tAuth)>])>" + ) + + def test_eq(self): + eku = x509.ExtendedKeyUsage([ + x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7") + ]) + eku2 = x509.ExtendedKeyUsage([ + x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7") + ]) + assert eku == eku2 + + def test_ne(self): + eku = x509.ExtendedKeyUsage([x509.ObjectIdentifier("1.3.6")]) + eku2 = x509.ExtendedKeyUsage([x509.ObjectIdentifier("1.3.6.1")]) + assert eku != eku2 + assert eku != object() + + def test_hash(self): + eku = x509.ExtendedKeyUsage([ + x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7") + ]) + eku2 = x509.ExtendedKeyUsage([ + x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7") + ]) + eku3 = x509.ExtendedKeyUsage([x509.ObjectIdentifier("1.3.6")]) + assert hash(eku) == hash(eku2) + assert hash(eku) != hash(eku3) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestExtensions(object): + def test_no_extensions(self, backend): + cert = _load_cert( + os.path.join("x509", "verisign_md2_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions + assert len(ext) == 0 + assert list(ext) == [] + with pytest.raises(x509.ExtensionNotFound) as exc: + ext.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS) + + assert exc.value.oid == ExtensionOID.BASIC_CONSTRAINTS + + def test_one_extension(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "basic_constraints_not_critical.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + extensions = cert.extensions + ext = extensions.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS) + assert ext is not None + assert ext.value.ca is False + + def test_duplicate_extension(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "two_basic_constraints.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(x509.DuplicateExtension) as exc: + cert.extensions + + assert exc.value.oid == ExtensionOID.BASIC_CONSTRAINTS + + def test_unsupported_critical_extension(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "unsupported_extension_critical.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + x509.ObjectIdentifier("1.2.3.4") + ) + assert ext.value.value == b"value" + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + def test_unsupported_extension(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "unsupported_extension_2.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + extensions = cert.extensions + assert len(extensions) == 2 + assert extensions[0].critical is False + assert extensions[0].oid == x509.ObjectIdentifier( + "1.3.6.1.4.1.41482.2" + ) + assert extensions[0].value == x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.3.6.1.4.1.41482.2"), + b"1.3.6.1.4.1.41482.1.2" + ) + assert extensions[1].critical is False + assert extensions[1].oid == x509.ObjectIdentifier( + "1.3.6.1.4.1.45724.2.1.1" + ) + assert extensions[1].value == x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.3.6.1.4.1.45724.2.1.1"), + b"\x03\x02\x040" + ) + + def test_no_extensions_get_for_class(self, backend): + cert = _load_cert( + os.path.join( + "x509", "cryptography.io.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + exts = cert.extensions + with pytest.raises(x509.ExtensionNotFound) as exc: + exts.get_extension_for_class(x509.IssuerAlternativeName) + assert exc.value.oid == ExtensionOID.ISSUER_ALTERNATIVE_NAME + + def test_unrecognized_extension_for_class(self): + exts = x509.Extensions([]) + with pytest.raises(TypeError): + exts.get_extension_for_class(x509.UnrecognizedExtension) + + def test_indexing(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + exts = cert.extensions + assert exts[-1] == exts[7] + assert exts[2:6:2] == [exts[2], exts[4]] + + def test_one_extension_get_for_class(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "basic_constraints_not_critical.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) + assert ext is not None + assert isinstance(ext.value, x509.BasicConstraints) + + def test_repr(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "basic_constraints_not_critical.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + assert repr(cert.extensions) == ( + "<Extensions([<Extension(oid=<ObjectIdentifier(oid=2.5.29.19, name" + "=basicConstraints)>, critical=False, value=<BasicConstraints(ca=F" + "alse, path_length=None)>)>])>" + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestBasicConstraintsExtension(object): + def test_ca_true_pathlen_6(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", "pathLenConstraint6CACert.crt" + ), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is True + assert ext.value.ca is True + assert ext.value.path_length == 6 + + def test_path_length_zero(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "bc_path_length_zero.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is True + assert ext.value.ca is True + assert ext.value.path_length == 0 + + def test_ca_true_no_pathlen(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is True + assert ext.value.ca is True + assert ext.value.path_length is None + + def test_ca_false(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is True + assert ext.value.ca is False + assert ext.value.path_length is None + + def test_no_basic_constraints(self, backend): + cert = _load_cert( + os.path.join( + "x509", + "PKITS_data", + "certs", + "ValidCertificatePathTest1EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + with pytest.raises(x509.ExtensionNotFound): + cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + + def test_basic_constraint_not_critical(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "basic_constraints_not_critical.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is False + assert ext.value.ca is False + + +class TestSubjectKeyIdentifierExtension(object): + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_subject_key_identifier(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + ski = ext.value + assert ext is not None + assert ext.critical is False + assert ski.digest == binascii.unhexlify( + b"580184241bbc2b52944a3da510721451f5af3ac9" + ) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_subject_key_identifier(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "bc_path_length_zero.pem"), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(x509.ExtensionNotFound): + cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_from_rsa_public_key(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + ski = x509.SubjectKeyIdentifier.from_public_key( + cert.public_key() + ) + assert ext.value == ski + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_from_dsa_public_key(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), + x509.load_pem_x509_certificate, + backend + ) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + ski = x509.SubjectKeyIdentifier.from_public_key( + cert.public_key() + ) + assert ext.value == ski + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_invalid_bit_string_padding_from_public_key(self, backend): + data = load_vectors_from_file( + filename=os.path.join( + "asymmetric", "DER_Serialization", + "dsa_public_key_invalid_bit_string.der" + ), loader=lambda data: data.read(), mode="rb" + ) + pretend_key = pretend.stub(public_bytes=lambda x, y: data) + with pytest.raises(ValueError): + _key_identifier_from_public_key(pretend_key) + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_optional_params_allowed_from_public_key(self, backend): + data = load_vectors_from_file( + filename=os.path.join( + "asymmetric", "DER_Serialization", + "dsa_public_key_no_params.der" + ), loader=lambda data: data.read(), mode="rb" + ) + pretend_key = pretend.stub(public_bytes=lambda x, y: data) + key_identifier = _key_identifier_from_public_key(pretend_key) + assert key_identifier == binascii.unhexlify( + b"24c0133a6a492f2c48a18c7648e515db5ac76749" + ) + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_from_ec_public_key(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + cert = _load_cert( + os.path.join("x509", "ecdsa_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + ski = x509.SubjectKeyIdentifier.from_public_key( + cert.public_key() + ) + assert ext.value == ski + + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_from_ed25519_public_key(self, backend): + cert = _load_cert( + os.path.join("x509", "ed25519", "root-ed25519.pem"), + x509.load_pem_x509_certificate, + backend + ) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + ski = x509.SubjectKeyIdentifier.from_public_key( + cert.public_key() + ) + assert ext.value == ski + + @pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_from_ed448_public_key(self, backend): + cert = _load_cert( + os.path.join("x509", "ed448", "root-ed448.pem"), + x509.load_pem_x509_certificate, + backend + ) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + ski = x509.SubjectKeyIdentifier.from_public_key( + cert.public_key() + ) + assert ext.value == ski + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestKeyUsageExtension(object): + def test_no_key_usage(self, backend): + cert = _load_cert( + os.path.join("x509", "verisign_md2_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions + with pytest.raises(x509.ExtensionNotFound) as exc: + ext.get_extension_for_oid(ExtensionOID.KEY_USAGE) + + assert exc.value.oid == ExtensionOID.KEY_USAGE + + def test_all_purposes(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "all_key_usages.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + extensions = cert.extensions + ext = extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) + assert ext is not None + + ku = ext.value + assert ku.digital_signature is True + assert ku.content_commitment is True + assert ku.key_encipherment is True + assert ku.data_encipherment is True + assert ku.key_agreement is True + assert ku.key_cert_sign is True + assert ku.crl_sign is True + assert ku.encipher_only is True + assert ku.decipher_only is True + + def test_key_cert_sign_crl_sign(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", "pathLenConstraint6CACert.crt" + ), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) + assert ext is not None + assert ext.critical is True + + ku = ext.value + assert ku.digital_signature is False + assert ku.content_commitment is False + assert ku.key_encipherment is False + assert ku.data_encipherment is False + assert ku.key_agreement is False + assert ku.key_cert_sign is True + assert ku.crl_sign is True + + +class TestDNSName(object): + def test_init_deprecated(self): + pytest.importorskip("idna") + with pytest.warns(utils.CryptographyDeprecationWarning): + name = x509.DNSName(u".\xf5\xe4\xf6\xfc.example.com") + assert name.value == u".xn--4ca7aey.example.com" + + with pytest.warns(utils.CryptographyDeprecationWarning): + name = x509.DNSName(u"\xf5\xe4\xf6\xfc.example.com") + assert name.value == u"xn--4ca7aey.example.com" + + def test_init(self): + name = x509.DNSName(u"*.xn--4ca7aey.example.com") + assert name.value == u"*.xn--4ca7aey.example.com" + + with pytest.raises(TypeError): + x509.DNSName(1.3) + + with pytest.raises(TypeError): + x509.DNSName(b"bytes not allowed") + + def test_ne(self): + n1 = x509.DNSName(u"test1") + n2 = x509.DNSName(u"test2") + n3 = x509.DNSName(u"test2") + assert n1 != n2 + assert not (n2 != n3) + + def test_hash(self): + n1 = x509.DNSName(u"test1") + n2 = x509.DNSName(u"test2") + n3 = x509.DNSName(u"test2") + assert hash(n1) != hash(n2) + assert hash(n2) == hash(n3) + + +class TestDirectoryName(object): + def test_not_name(self): + with pytest.raises(TypeError): + x509.DirectoryName(b"notaname") + + with pytest.raises(TypeError): + x509.DirectoryName(1.3) + + def test_repr(self): + name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'value1')]) + gn = x509.DirectoryName(name) + assert repr(gn) == "<DirectoryName(value=<Name(CN=value1)>)>" + + def test_eq(self): + name = x509.Name([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ]) + name2 = x509.Name([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ]) + gn = x509.DirectoryName(name) + gn2 = x509.DirectoryName(name2) + assert gn == gn2 + + def test_ne(self): + name = x509.Name([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ]) + name2 = x509.Name([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + ]) + gn = x509.DirectoryName(name) + gn2 = x509.DirectoryName(name2) + assert gn != gn2 + assert gn != object() + + def test_hash(self): + name = x509.Name([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ]) + name2 = x509.Name([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + ]) + gn = x509.DirectoryName(name) + gn2 = x509.DirectoryName(name) + gn3 = x509.DirectoryName(name2) + assert hash(gn) == hash(gn2) + assert hash(gn) != hash(gn3) + + +class TestRFC822Name(object): + def test_repr(self): + gn = x509.RFC822Name(u"string") + if not six.PY2: + assert repr(gn) == "<RFC822Name(value='string')>" + else: + assert repr(gn) == "<RFC822Name(value=u'string')>" + + def test_equality(self): + gn = x509.RFC822Name(u"string") + gn2 = x509.RFC822Name(u"string2") + gn3 = x509.RFC822Name(u"string") + assert gn != gn2 + assert gn != object() + assert gn == gn3 + + def test_not_text(self): + with pytest.raises(TypeError): + x509.RFC822Name(1.3) + + with pytest.raises(TypeError): + x509.RFC822Name(b"bytes") + + def test_invalid_email(self): + with pytest.raises(ValueError): + x509.RFC822Name(u"Name <email>") + + with pytest.raises(ValueError): + x509.RFC822Name(u"") + + def test_single_label(self): + gn = x509.RFC822Name(u"administrator") + assert gn.value == u"administrator" + + def test_idna(self): + pytest.importorskip("idna") + with pytest.warns(utils.CryptographyDeprecationWarning): + gn = x509.RFC822Name(u"email@em\xe5\xefl.com") + + assert gn.value == u"email@xn--eml-vla4c.com" + + gn2 = x509.RFC822Name(u"email@xn--eml-vla4c.com") + assert gn2.value == u"email@xn--eml-vla4c.com" + + def test_hash(self): + g1 = x509.RFC822Name(u"email@host.com") + g2 = x509.RFC822Name(u"email@host.com") + g3 = x509.RFC822Name(u"admin@host.com") + + assert hash(g1) == hash(g2) + assert hash(g1) != hash(g3) + + +class TestUniformResourceIdentifier(object): + def test_equality(self): + gn = x509.UniformResourceIdentifier(u"string") + gn2 = x509.UniformResourceIdentifier(u"string2") + gn3 = x509.UniformResourceIdentifier(u"string") + assert gn != gn2 + assert gn != object() + assert gn == gn3 + + def test_not_text(self): + with pytest.raises(TypeError): + x509.UniformResourceIdentifier(1.3) + + def test_no_parsed_hostname(self): + gn = x509.UniformResourceIdentifier(u"singlelabel") + assert gn.value == u"singlelabel" + + def test_with_port(self): + gn = x509.UniformResourceIdentifier(u"singlelabel:443/test") + assert gn.value == u"singlelabel:443/test" + + def test_idna_no_port(self): + pytest.importorskip("idna") + with pytest.warns(utils.CryptographyDeprecationWarning): + gn = x509.UniformResourceIdentifier( + u"http://\u043f\u044b\u043a\u0430.cryptography" + ) + + assert gn.value == u"http://xn--80ato2c.cryptography" + + def test_idna_with_port(self): + pytest.importorskip("idna") + with pytest.warns(utils.CryptographyDeprecationWarning): + gn = x509.UniformResourceIdentifier( + u"gopher://\u043f\u044b\u043a\u0430.cryptography:70/some/path" + ) + + assert gn.value == ( + u"gopher://xn--80ato2c.cryptography:70/some/path" + ) + + def test_empty_hostname(self): + gn = x509.UniformResourceIdentifier(u"ldap:///some-nonsense") + assert gn.value == "ldap:///some-nonsense" + + def test_query_and_fragment(self): + pytest.importorskip("idna") + with pytest.warns(utils.CryptographyDeprecationWarning): + gn = x509.UniformResourceIdentifier( + u"ldap://\u043f\u044b\u043a\u0430.cryptography:90/path?query=" + u"true#somedata" + ) + assert gn.value == ( + u"ldap://xn--80ato2c.cryptography:90/path?query=true#somedata" + ) + + def test_hash(self): + g1 = x509.UniformResourceIdentifier(u"http://host.com") + g2 = x509.UniformResourceIdentifier(u"http://host.com") + g3 = x509.UniformResourceIdentifier(u"http://other.com") + + assert hash(g1) == hash(g2) + assert hash(g1) != hash(g3) + + def test_repr(self): + gn = x509.UniformResourceIdentifier(u"string") + if not six.PY2: + assert repr(gn) == ( + "<UniformResourceIdentifier(value='string')>" + ) + else: + assert repr(gn) == ( + "<UniformResourceIdentifier(value=u'string')>" + ) + + +class TestRegisteredID(object): + def test_not_oid(self): + with pytest.raises(TypeError): + x509.RegisteredID(b"notanoid") + + with pytest.raises(TypeError): + x509.RegisteredID(1.3) + + def test_repr(self): + gn = x509.RegisteredID(NameOID.COMMON_NAME) + assert repr(gn) == ( + "<RegisteredID(value=<ObjectIdentifier(oid=2.5.4.3, name=commonNam" + "e)>)>" + ) + + def test_eq(self): + gn = x509.RegisteredID(NameOID.COMMON_NAME) + gn2 = x509.RegisteredID(NameOID.COMMON_NAME) + assert gn == gn2 + + def test_ne(self): + gn = x509.RegisteredID(NameOID.COMMON_NAME) + gn2 = x509.RegisteredID(ExtensionOID.BASIC_CONSTRAINTS) + assert gn != gn2 + assert gn != object() + + def test_hash(self): + gn = x509.RegisteredID(NameOID.COMMON_NAME) + gn2 = x509.RegisteredID(NameOID.COMMON_NAME) + gn3 = x509.RegisteredID(ExtensionOID.BASIC_CONSTRAINTS) + assert hash(gn) == hash(gn2) + assert hash(gn) != hash(gn3) + + +class TestIPAddress(object): + def test_not_ipaddress(self): + with pytest.raises(TypeError): + x509.IPAddress(b"notanipaddress") + + with pytest.raises(TypeError): + x509.IPAddress(1.3) + + def test_repr(self): + gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + assert repr(gn) == "<IPAddress(value=127.0.0.1)>" + + gn2 = x509.IPAddress(ipaddress.IPv6Address(u"ff::")) + assert repr(gn2) == "<IPAddress(value=ff::)>" + + gn3 = x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24")) + assert repr(gn3) == "<IPAddress(value=192.168.0.0/24)>" + + gn4 = x509.IPAddress(ipaddress.IPv6Network(u"ff::/96")) + assert repr(gn4) == "<IPAddress(value=ff::/96)>" + + def test_eq(self): + gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + gn2 = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + assert gn == gn2 + + def test_ne(self): + gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + gn2 = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.2")) + assert gn != gn2 + assert gn != object() + + def test_hash(self): + gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + gn2 = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + gn3 = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.2")) + assert hash(gn) == hash(gn2) + assert hash(gn) != hash(gn3) + + +class TestOtherName(object): + def test_invalid_args(self): + with pytest.raises(TypeError): + x509.OtherName(b"notanobjectidentifier", b"derdata") + + with pytest.raises(TypeError): + x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), u"notderdata") + + def test_repr(self): + gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") + if not six.PY2: + assert repr(gn) == ( + "<OtherName(type_id=<ObjectIdentifier(oid=1.2.3.4, " + "name=Unknown OID)>, value=b'derdata')>" + ) + else: + assert repr(gn) == ( + "<OtherName(type_id=<ObjectIdentifier(oid=1.2.3.4, " + "name=Unknown OID)>, value='derdata')>" + ) + + gn = x509.OtherName(x509.ObjectIdentifier("2.5.4.65"), b"derdata") + if not six.PY2: + assert repr(gn) == ( + "<OtherName(type_id=<ObjectIdentifier(oid=2.5.4.65, " + "name=pseudonym)>, value=b'derdata')>" + ) + else: + assert repr(gn) == ( + "<OtherName(type_id=<ObjectIdentifier(oid=2.5.4.65, " + "name=pseudonym)>, value='derdata')>" + ) + + def test_eq(self): + gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") + gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") + assert gn == gn2 + + def test_ne(self): + gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") + assert gn != object() + + gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata2") + assert gn != gn2 + + gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.5"), b"derdata") + assert gn != gn2 + + def test_hash(self): + gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") + gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") + gn3 = x509.OtherName(x509.ObjectIdentifier("1.2.3.5"), b"derdata") + assert hash(gn) == hash(gn2) + assert hash(gn) != hash(gn3) + + +class TestGeneralNames(object): + def test_get_values_for_type(self): + gns = x509.GeneralNames( + [x509.DNSName(u"cryptography.io")] + ) + names = gns.get_values_for_type(x509.DNSName) + assert names == [u"cryptography.io"] + + def test_iter_names(self): + gns = x509.GeneralNames([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ]) + assert len(gns) == 2 + assert list(gns) == [ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ] + + def test_iter_input(self): + names = [ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ] + gns = x509.GeneralNames(iter(names)) + assert list(gns) == names + + def test_indexing(self): + gn = x509.GeneralNames([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + x509.DNSName(u"another.local"), + x509.RFC822Name(u"email@another.local"), + x509.UniformResourceIdentifier(u"http://another.local"), + ]) + assert gn[-1] == gn[4] + assert gn[2:6:2] == [gn[2], gn[4]] + + def test_invalid_general_names(self): + with pytest.raises(TypeError): + x509.GeneralNames( + [x509.DNSName(u"cryptography.io"), "invalid"] + ) + + def test_repr(self): + gns = x509.GeneralNames( + [ + x509.DNSName(u"cryptography.io") + ] + ) + if not six.PY2: + assert repr(gns) == ( + "<GeneralNames([<DNSName(value='cryptography.io')>])>" + ) + else: + assert repr(gns) == ( + "<GeneralNames([<DNSName(value=u'cryptography.io')>])>" + ) + + def test_eq(self): + gns = x509.GeneralNames( + [x509.DNSName(u"cryptography.io")] + ) + gns2 = x509.GeneralNames( + [x509.DNSName(u"cryptography.io")] + ) + assert gns == gns2 + + def test_ne(self): + gns = x509.GeneralNames( + [x509.DNSName(u"cryptography.io")] + ) + gns2 = x509.GeneralNames( + [x509.RFC822Name(u"admin@cryptography.io")] + ) + assert gns != gns2 + assert gns != object() + + def test_hash(self): + gns = x509.GeneralNames([x509.DNSName(u"cryptography.io")]) + gns2 = x509.GeneralNames([x509.DNSName(u"cryptography.io")]) + gns3 = x509.GeneralNames([x509.RFC822Name(u"admin@cryptography.io")]) + assert hash(gns) == hash(gns2) + assert hash(gns) != hash(gns3) + + +class TestIssuerAlternativeName(object): + def test_get_values_for_type(self): + san = x509.IssuerAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + names = san.get_values_for_type(x509.DNSName) + assert names == [u"cryptography.io"] + + def test_iter_names(self): + san = x509.IssuerAlternativeName([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ]) + assert len(san) == 2 + assert list(san) == [ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ] + + def test_indexing(self): + ian = x509.IssuerAlternativeName([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + x509.DNSName(u"another.local"), + x509.RFC822Name(u"email@another.local"), + x509.UniformResourceIdentifier(u"http://another.local"), + ]) + assert ian[-1] == ian[4] + assert ian[2:6:2] == [ian[2], ian[4]] + + def test_invalid_general_names(self): + with pytest.raises(TypeError): + x509.IssuerAlternativeName( + [x509.DNSName(u"cryptography.io"), "invalid"] + ) + + def test_repr(self): + san = x509.IssuerAlternativeName( + [ + x509.DNSName(u"cryptography.io") + ] + ) + if not six.PY2: + assert repr(san) == ( + "<IssuerAlternativeName(" + "<GeneralNames([<DNSName(value='cryptography.io')>])>)>" + ) + else: + assert repr(san) == ( + "<IssuerAlternativeName(" + "<GeneralNames([<DNSName(value=u'cryptography.io')>])>)>" + ) + + def test_eq(self): + san = x509.IssuerAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + san2 = x509.IssuerAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + assert san == san2 + + def test_ne(self): + san = x509.IssuerAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + san2 = x509.IssuerAlternativeName( + [x509.RFC822Name(u"admin@cryptography.io")] + ) + assert san != san2 + assert san != object() + + def test_hash(self): + ian = x509.IssuerAlternativeName([x509.DNSName(u"cryptography.io")]) + ian2 = x509.IssuerAlternativeName([x509.DNSName(u"cryptography.io")]) + ian3 = x509.IssuerAlternativeName( + [x509.RFC822Name(u"admin@cryptography.io")] + ) + assert hash(ian) == hash(ian2) + assert hash(ian) != hash(ian3) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRSAIssuerAlternativeNameExtension(object): + def test_uri(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "ian_uri.pem"), + x509.load_pem_x509_certificate, + backend, + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.ISSUER_ALTERNATIVE_NAME + ) + assert list(ext.value) == [ + x509.UniformResourceIdentifier(u"http://path.to.root/root.crt"), + ] + + +class TestCRLNumber(object): + def test_eq(self): + crl_number = x509.CRLNumber(15) + assert crl_number == x509.CRLNumber(15) + + def test_ne(self): + crl_number = x509.CRLNumber(15) + assert crl_number != x509.CRLNumber(14) + assert crl_number != object() + + def test_repr(self): + crl_number = x509.CRLNumber(15) + assert repr(crl_number) == "<CRLNumber(15)>" + + def test_invalid_number(self): + with pytest.raises(TypeError): + x509.CRLNumber("notanumber") + + def test_hash(self): + c1 = x509.CRLNumber(1) + c2 = x509.CRLNumber(1) + c3 = x509.CRLNumber(2) + assert hash(c1) == hash(c2) + assert hash(c1) != hash(c3) + + +class TestSubjectAlternativeName(object): + def test_get_values_for_type(self): + san = x509.SubjectAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + names = san.get_values_for_type(x509.DNSName) + assert names == [u"cryptography.io"] + + def test_iter_names(self): + san = x509.SubjectAlternativeName([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ]) + assert len(san) == 2 + assert list(san) == [ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ] + + def test_indexing(self): + san = x509.SubjectAlternativeName([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + x509.DNSName(u"another.local"), + x509.RFC822Name(u"email@another.local"), + x509.UniformResourceIdentifier(u"http://another.local"), + ]) + assert san[-1] == san[4] + assert san[2:6:2] == [san[2], san[4]] + + def test_invalid_general_names(self): + with pytest.raises(TypeError): + x509.SubjectAlternativeName( + [x509.DNSName(u"cryptography.io"), "invalid"] + ) + + def test_repr(self): + san = x509.SubjectAlternativeName( + [ + x509.DNSName(u"cryptography.io") + ] + ) + if not six.PY2: + assert repr(san) == ( + "<SubjectAlternativeName(" + "<GeneralNames([<DNSName(value='cryptography.io')>])>)>" + ) + else: + assert repr(san) == ( + "<SubjectAlternativeName(" + "<GeneralNames([<DNSName(value=u'cryptography.io')>])>)>" + ) + + def test_eq(self): + san = x509.SubjectAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + san2 = x509.SubjectAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + assert san == san2 + + def test_ne(self): + san = x509.SubjectAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + san2 = x509.SubjectAlternativeName( + [x509.RFC822Name(u"admin@cryptography.io")] + ) + assert san != san2 + assert san != object() + + def test_hash(self): + san = x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]) + san2 = x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]) + san3 = x509.SubjectAlternativeName( + [x509.RFC822Name(u"admin@cryptography.io")] + ) + assert hash(san) == hash(san2) + assert hash(san) != hash(san3) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRSASubjectAlternativeNameExtension(object): + def test_dns_name(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + san = ext.value + + dns = san.get_values_for_type(x509.DNSName) + assert dns == [u"www.cryptography.io", u"cryptography.io"] + + def test_wildcard_dns_name(self, backend): + cert = _load_cert( + os.path.join("x509", "wildcard_san.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + + dns = ext.value.get_values_for_type(x509.DNSName) + assert dns == [ + u'*.langui.sh', + u'langui.sh', + u'*.saseliminator.com', + u'saseliminator.com' + ] + + def test_san_empty_hostname(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_empty_hostname.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + san = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + + dns = san.value.get_values_for_type(x509.DNSName) + assert dns == [u''] + + def test_san_wildcard_idna_dns_name(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "san_wildcard_idna.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + + dns = ext.value.get_values_for_type(x509.DNSName) + assert dns == [u'*.xn--80ato2c.cryptography'] + + def test_unsupported_gn(self, backend): + cert = _load_cert( + os.path.join("x509", "san_x400address.der"), + x509.load_der_x509_certificate, + backend + ) + with pytest.raises(x509.UnsupportedGeneralNameType) as exc: + cert.extensions + + assert exc.value.type == 3 + + def test_registered_id(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_registered_id.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + san = ext.value + rid = san.get_values_for_type(x509.RegisteredID) + assert rid == [x509.ObjectIdentifier("1.2.3.4")] + + def test_uri(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_uri_with_port.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + uri = ext.value.get_values_for_type( + x509.UniformResourceIdentifier + ) + assert uri == [ + u"gopher://xn--80ato2c.cryptography:70/path?q=s#hel" + u"lo", + u"http://someregulardomain.com", + ] + + def test_ipaddress(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_ipaddr.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + san = ext.value + + ip = san.get_values_for_type(x509.IPAddress) + assert [ + ipaddress.ip_address(u"127.0.0.1"), + ipaddress.ip_address(u"ff::") + ] == ip + + def test_dirname(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_dirname.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + san = ext.value + + dirname = san.get_values_for_type(x509.DirectoryName) + assert [ + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'test'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Org'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ]) + ] == dirname + + def test_rfc822name(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_rfc822_idna.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + san = ext.value + + rfc822name = san.get_values_for_type(x509.RFC822Name) + assert [u"email@xn--eml-vla4c.com"] == rfc822name + + def test_idna2003_invalid(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_idna2003_dnsname.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + san = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName + ).value + + assert len(san) == 1 + [name] = san + assert name.value == u"xn--k4h.ws" + + def test_unicode_rfc822_name_dns_name_uri(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_idna_names.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + rfc822_name = ext.value.get_values_for_type(x509.RFC822Name) + dns_name = ext.value.get_values_for_type(x509.DNSName) + uri = ext.value.get_values_for_type(x509.UniformResourceIdentifier) + assert rfc822_name == [u"email@xn--80ato2c.cryptography"] + assert dns_name == [u"xn--80ato2c.cryptography"] + assert uri == [u"https://www.xn--80ato2c.cryptography"] + + def test_rfc822name_dnsname_ipaddress_directoryname_uri(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_email_dns_ip_dirname_uri.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + san = ext.value + + rfc822_name = san.get_values_for_type(x509.RFC822Name) + uri = san.get_values_for_type(x509.UniformResourceIdentifier) + dns = san.get_values_for_type(x509.DNSName) + ip = san.get_values_for_type(x509.IPAddress) + dirname = san.get_values_for_type(x509.DirectoryName) + assert [u"user@cryptography.io"] == rfc822_name + assert [u"https://cryptography.io"] == uri + assert [u"cryptography.io"] == dns + assert [ + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'dirCN'), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u'Cryptographic Authority' + ), + ]) + ] == dirname + assert [ + ipaddress.ip_address(u"127.0.0.1"), + ipaddress.ip_address(u"ff::") + ] == ip + + def test_invalid_rfc822name(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_rfc822_names.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + san = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName + ).value + values = san.get_values_for_type(x509.RFC822Name) + assert values == [ + u'email', u'email <email>', u'email <email@email>', + u'email <email@xn--eml-vla4c.com>', u'myemail:' + ] + + def test_other_name(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_other_name.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + expected = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), + b'\x16\x0bHello World') + assert len(ext.value) == 1 + assert list(ext.value)[0] == expected + + othernames = ext.value.get_values_for_type(x509.OtherName) + assert othernames == [expected] + + def test_certbuilder(self, backend): + sans = [u'*.example.org', u'*.xn--4ca7aey.example.com', + u'foobar.example.net'] + private_key = RSA_KEY_2048.private_key(backend) + builder = _make_certbuilder(private_key) + builder = builder.add_extension( + SubjectAlternativeName(list(map(DNSName, sans))), True) + + cert = builder.sign(private_key, hashes.SHA1(), backend) + result = [ + x.value + for x in cert.extensions.get_extension_for_class( + SubjectAlternativeName + ).value + ] + assert result == sans + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestExtendedKeyUsageExtension(object): + def test_eku(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "extended_key_usage.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.EXTENDED_KEY_USAGE + ) + assert ext is not None + assert ext.critical is False + + assert [ + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.3"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.4"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.9"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.8"), + x509.ObjectIdentifier("2.5.29.37.0"), + x509.ObjectIdentifier("2.16.840.1.113730.4.1"), + ] == list(ext.value) + + +class TestAccessDescription(object): + def test_invalid_access_method(self): + with pytest.raises(TypeError): + x509.AccessDescription("notanoid", x509.DNSName(u"test")) + + def test_invalid_access_location(self): + with pytest.raises(TypeError): + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, "invalid" + ) + + def test_valid_nonstandard_method(self): + ad = x509.AccessDescription( + ObjectIdentifier("2.999.1"), + x509.UniformResourceIdentifier(u"http://example.com") + ) + assert ad is not None + + def test_repr(self): + ad = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + if not six.PY2: + assert repr(ad) == ( + "<AccessDescription(access_method=<ObjectIdentifier(oid=1.3.6" + ".1.5.5.7.48.1, name=OCSP)>, access_location=<UniformResource" + "Identifier(value='http://ocsp.domain.com')>)>" + ) + else: + assert repr(ad) == ( + "<AccessDescription(access_method=<ObjectIdentifier(oid=1.3.6" + ".1.5.5.7.48.1, name=OCSP)>, access_location=<UniformResource" + "Identifier(value=u'http://ocsp.domain.com')>)>" + ) + + def test_eq(self): + ad = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad2 = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + assert ad == ad2 + + def test_ne(self): + ad = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad2 = x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad3 = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://notthesame") + ) + assert ad != ad2 + assert ad != ad3 + assert ad != object() + + def test_hash(self): + ad = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad2 = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad3 = x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + assert hash(ad) == hash(ad2) + assert hash(ad) != hash(ad3) + + +class TestPolicyConstraints(object): + def test_invalid_explicit_policy(self): + with pytest.raises(TypeError): + x509.PolicyConstraints("invalid", None) + + def test_invalid_inhibit_policy(self): + with pytest.raises(TypeError): + x509.PolicyConstraints(None, "invalid") + + def test_both_none(self): + with pytest.raises(ValueError): + x509.PolicyConstraints(None, None) + + def test_repr(self): + pc = x509.PolicyConstraints(0, None) + + assert repr(pc) == ( + u"<PolicyConstraints(require_explicit_policy=0, inhibit_policy_ma" + u"pping=None)>" + ) + + def test_eq(self): + pc = x509.PolicyConstraints(2, 1) + pc2 = x509.PolicyConstraints(2, 1) + assert pc == pc2 + + def test_ne(self): + pc = x509.PolicyConstraints(2, 1) + pc2 = x509.PolicyConstraints(2, 2) + pc3 = x509.PolicyConstraints(3, 1) + assert pc != pc2 + assert pc != pc3 + assert pc != object() + + def test_hash(self): + pc = x509.PolicyConstraints(2, 1) + pc2 = x509.PolicyConstraints(2, 1) + pc3 = x509.PolicyConstraints(2, None) + assert hash(pc) == hash(pc2) + assert hash(pc) != hash(pc3) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestPolicyConstraintsExtension(object): + def test_inhibit_policy_mapping(self, backend): + cert = _load_cert( + os.path.join("x509", "department-of-state-root.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.POLICY_CONSTRAINTS, + ) + assert ext.critical is True + + assert ext.value == x509.PolicyConstraints( + require_explicit_policy=None, inhibit_policy_mapping=0, + ) + + def test_require_explicit_policy(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "policy_constraints_explicit.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.POLICY_CONSTRAINTS + ) + assert ext.critical is True + assert ext.value == x509.PolicyConstraints( + require_explicit_policy=1, inhibit_policy_mapping=None, + ) + + +class TestAuthorityInformationAccess(object): + def test_invalid_descriptions(self): + with pytest.raises(TypeError): + x509.AuthorityInformationAccess(["notanAccessDescription"]) + + def test_iter_len(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + assert len(aia) == 2 + assert list(aia) == [ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ] + + def test_iter_input(self): + desc = [ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ] + aia = x509.AuthorityInformationAccess(iter(desc)) + assert list(aia) == desc + + def test_repr(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + if not six.PY2: + assert repr(aia) == ( + "<AuthorityInformationAccess([<AccessDescription(access_method" + "=<ObjectIdentifier(oid=1.3.6.1.5.5.7.48.1, name=OCSP)>, acces" + "s_location=<UniformResourceIdentifier(value='http://oc" + "sp.domain.com')>)>, <AccessDescription(access_method=<ObjectI" + "dentifier(oid=1.3.6.1.5.5.7.48.2, name=caIssuers)>, access_lo" + "cation=<UniformResourceIdentifier(value='http://domain" + ".com/ca.crt')>)>])>" + ) + else: + assert repr(aia) == ( + "<AuthorityInformationAccess([<AccessDescription(access_method" + "=<ObjectIdentifier(oid=1.3.6.1.5.5.7.48.1, name=OCSP)>, acces" + "s_location=<UniformResourceIdentifier(value=u'http://oc" + "sp.domain.com')>)>, <AccessDescription(access_method=<ObjectI" + "dentifier(oid=1.3.6.1.5.5.7.48.2, name=caIssuers)>, access_lo" + "cation=<UniformResourceIdentifier(value=u'http://domain" + ".com/ca.crt')>)>])>" + ) + + def test_eq(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + aia2 = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + assert aia == aia2 + + def test_ne(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + aia2 = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + ]) + + assert aia != aia2 + assert aia != object() + + def test_indexing(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp2.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp3.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp4.domain.com") + ), + ]) + assert aia[-1] == aia[4] + assert aia[2:6:2] == [aia[2], aia[4]] + + def test_hash(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + aia2 = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + aia3 = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.other.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + assert hash(aia) == hash(aia2) + assert hash(aia) != hash(aia3) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestAuthorityInformationAccessExtension(object): + def test_aia_ocsp_ca_issuers(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_INFORMATION_ACCESS + ) + assert ext is not None + assert ext.critical is False + + assert ext.value == x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://gv.symcd.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://gv.symcb.com/gv.crt") + ), + ]) + + def test_aia_multiple_ocsp_ca_issuers(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "aia_ocsp_ca_issuers.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_INFORMATION_ACCESS + ) + assert ext is not None + assert ext.critical is False + + assert ext.value == x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp2.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.DirectoryName(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"myCN"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u"some Org"), + ])) + ), + ]) + + def test_aia_ocsp_only(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "aia_ocsp.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_INFORMATION_ACCESS + ) + assert ext is not None + assert ext.critical is False + + assert ext.value == x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + ]) + + def test_aia_ca_issuers_only(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "aia_ca_issuers.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_INFORMATION_ACCESS + ) + assert ext is not None + assert ext.critical is False + + assert ext.value == x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.DirectoryName(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"myCN"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u"some Org"), + ])) + ), + ]) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestAuthorityKeyIdentifierExtension(object): + def test_aki_keyid(self, backend): + cert = _load_cert( + os.path.join( + "x509", "cryptography.io.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + assert ext is not None + assert ext.critical is False + + assert ext.value.key_identifier == ( + b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08\xcbY" + ) + assert ext.value.authority_cert_issuer is None + assert ext.value.authority_cert_serial_number is None + + def test_aki_all_fields(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "authority_key_identifier.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + assert ext is not None + assert ext.critical is False + + assert ext.value.key_identifier == ( + b"9E>\xca=b\x1d\xea\x86I\xf6Z\xab@\xb7\xa4p\x98\xf1\xec" + ) + assert ext.value.authority_cert_issuer == [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u"PyCA" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography.io" + ) + ]) + ) + ] + assert ext.value.authority_cert_serial_number == 3 + + def test_aki_no_keyid(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "authority_key_identifier_no_keyid.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + assert ext is not None + assert ext.critical is False + + assert ext.value.key_identifier is None + assert ext.value.authority_cert_issuer == [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u"PyCA" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography.io" + ) + ]) + ) + ] + assert ext.value.authority_cert_serial_number == 3 + + def test_from_certificate(self, backend): + issuer_cert = _load_cert( + os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + aki = x509.AuthorityKeyIdentifier.from_issuer_public_key( + issuer_cert.public_key() + ) + assert ext.value == aki + + def test_from_issuer_subject_key_identifier(self, backend): + issuer_cert = _load_cert( + os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + ski_ext = issuer_cert.extensions.get_extension_for_class( + x509.SubjectKeyIdentifier + ) + aki = x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( + ski_ext.value + ) + assert ext.value == aki + + +class TestNameConstraints(object): + def test_ipaddress_wrong_type(self): + with pytest.raises(TypeError): + x509.NameConstraints( + permitted_subtrees=[ + x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + ], + excluded_subtrees=None + ) + + with pytest.raises(TypeError): + x509.NameConstraints( + permitted_subtrees=None, + excluded_subtrees=[ + x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + ] + ) + + def test_ipaddress_allowed_type(self): + permitted = [x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/29"))] + excluded = [x509.IPAddress(ipaddress.IPv4Network(u"10.10.0.0/24"))] + nc = x509.NameConstraints( + permitted_subtrees=permitted, + excluded_subtrees=excluded + ) + assert nc.permitted_subtrees == permitted + assert nc.excluded_subtrees == excluded + + def test_invalid_permitted_subtrees(self): + with pytest.raises(TypeError): + x509.NameConstraints("badpermitted", None) + + def test_invalid_excluded_subtrees(self): + with pytest.raises(TypeError): + x509.NameConstraints(None, "badexcluded") + + def test_no_subtrees(self): + with pytest.raises(ValueError): + x509.NameConstraints(None, None) + + def test_permitted_none(self): + excluded = [x509.DNSName(u"name.local")] + nc = x509.NameConstraints( + permitted_subtrees=None, excluded_subtrees=excluded + ) + assert nc.permitted_subtrees is None + assert nc.excluded_subtrees is not None + + def test_excluded_none(self): + permitted = [x509.DNSName(u"name.local")] + nc = x509.NameConstraints( + permitted_subtrees=permitted, excluded_subtrees=None + ) + assert nc.permitted_subtrees is not None + assert nc.excluded_subtrees is None + + def test_iter_input(self): + subtrees = [x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24"))] + nc = x509.NameConstraints(iter(subtrees), iter(subtrees)) + assert list(nc.permitted_subtrees) == subtrees + assert list(nc.excluded_subtrees) == subtrees + + def test_repr(self): + permitted = [x509.DNSName(u"name.local"), x509.DNSName(u"name2.local")] + nc = x509.NameConstraints( + permitted_subtrees=permitted, + excluded_subtrees=None + ) + if not six.PY2: + assert repr(nc) == ( + "<NameConstraints(permitted_subtrees=[<DNSName(" + "value='name.local')>, <DNSName(value=" + "'name2.local')>], excluded_subtrees=None)>" + ) + else: + assert repr(nc) == ( + "<NameConstraints(permitted_subtrees=[<DNSName(" + "value=u'name.local')>, <DNSName(value=" + "u'name2.local')>], excluded_subtrees=None)>" + ) + + def test_eq(self): + nc = x509.NameConstraints( + permitted_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=[x509.DNSName(u"name2.local")] + ) + nc2 = x509.NameConstraints( + permitted_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=[x509.DNSName(u"name2.local")] + ) + assert nc == nc2 + + def test_ne(self): + nc = x509.NameConstraints( + permitted_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=[x509.DNSName(u"name2.local")] + ) + nc2 = x509.NameConstraints( + permitted_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=None + ) + nc3 = x509.NameConstraints( + permitted_subtrees=None, + excluded_subtrees=[x509.DNSName(u"name2.local")] + ) + + assert nc != nc2 + assert nc != nc3 + assert nc != object() + + def test_hash(self): + nc = x509.NameConstraints( + permitted_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=[x509.DNSName(u"name2.local")] + ) + nc2 = x509.NameConstraints( + permitted_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=[x509.DNSName(u"name2.local")] + ) + nc3 = x509.NameConstraints( + permitted_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=None + ) + nc4 = x509.NameConstraints( + permitted_subtrees=None, + excluded_subtrees=[x509.DNSName(u"name.local")] + ) + assert hash(nc) == hash(nc2) + assert hash(nc) != hash(nc3) + assert hash(nc3) != hash(nc4) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestNameConstraintsExtension(object): + def test_permitted_excluded(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_permitted_excluded_2.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=[ + x509.DNSName(u"zombo.local"), + ], + excluded_subtrees=[ + x509.DirectoryName(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"zombo") + ])) + ] + ) + + def test_permitted(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_permitted_2.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=[ + x509.DNSName(u"zombo.local"), + ], + excluded_subtrees=None + ) + + def test_permitted_with_leading_period(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_permitted.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=[ + x509.DNSName(u".cryptography.io"), + x509.UniformResourceIdentifier(u"ftp://cryptography.test") + ], + excluded_subtrees=None + ) + + def test_excluded_with_leading_period(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_excluded.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=None, + excluded_subtrees=[ + x509.DNSName(u".cryptography.io"), + x509.UniformResourceIdentifier(u"gopher://cryptography.test") + ] + ) + + def test_permitted_excluded_with_ips(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_permitted_excluded.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=[ + x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24")), + x509.IPAddress(ipaddress.IPv6Network(u"FF:0:0:0:0:0:0:0/96")), + ], + excluded_subtrees=[ + x509.DNSName(u".domain.com"), + x509.UniformResourceIdentifier(u"http://test.local"), + ] + ) + + def test_single_ip_netmask(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_single_ip_netmask.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=[ + x509.IPAddress(ipaddress.IPv6Network(u"FF:0:0:0:0:0:0:0/128")), + x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.1/32")), + ], + excluded_subtrees=None + ) + + def test_invalid_netmask(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_invalid_ip_netmask.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(ValueError): + cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ) + + def test_certbuilder(self, backend): + permitted = [u'.example.org', u'.xn--4ca7aey.example.com', + u'foobar.example.net'] + private_key = RSA_KEY_2048.private_key(backend) + builder = _make_certbuilder(private_key) + builder = builder.add_extension( + NameConstraints(permitted_subtrees=list(map(DNSName, permitted)), + excluded_subtrees=[]), True) + + cert = builder.sign(private_key, hashes.SHA1(), backend) + result = [ + x.value + for x in cert.extensions.get_extension_for_class( + NameConstraints + ).value.permitted_subtrees + ] + assert result == permitted + + +class TestDistributionPoint(object): + def test_distribution_point_full_name_not_general_names(self): + with pytest.raises(TypeError): + x509.DistributionPoint(["notgn"], None, None, None) + + def test_distribution_point_relative_name_not_name(self): + with pytest.raises(TypeError): + x509.DistributionPoint(None, "notname", None, None) + + def test_distribution_point_full_and_relative_not_none(self): + with pytest.raises(ValueError): + x509.DistributionPoint("data", "notname", None, None) + + def test_crl_issuer_not_general_names(self): + with pytest.raises(TypeError): + x509.DistributionPoint(None, None, None, ["notgn"]) + + def test_reason_not_reasonflags(self): + with pytest.raises(TypeError): + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset(["notreasonflags"]), + None + ) + + def test_reason_not_frozenset(self): + with pytest.raises(TypeError): + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + [x509.ReasonFlags.ca_compromise], + None + ) + + def test_disallowed_reasons(self): + with pytest.raises(ValueError): + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset([x509.ReasonFlags.unspecified]), + None + ) + + with pytest.raises(ValueError): + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset([x509.ReasonFlags.remove_from_crl]), + None + ) + + def test_reason_only(self): + with pytest.raises(ValueError): + x509.DistributionPoint( + None, + None, + frozenset([x509.ReasonFlags.aa_compromise]), + None + ) + + def test_eq(self): + dp = x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset([x509.ReasonFlags.superseded]), + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"Important CA" + ) + ]) + ) + ], + ) + dp2 = x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset([x509.ReasonFlags.superseded]), + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"Important CA" + ) + ]) + ) + ], + ) + assert dp == dp2 + + def test_ne(self): + dp = x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset([x509.ReasonFlags.superseded]), + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"Important CA" + ) + ]) + ) + ], + ) + dp2 = x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + None, + None + ) + assert dp != dp2 + assert dp != object() + + def test_iter_input(self): + name = [x509.UniformResourceIdentifier(u"http://crypt.og/crl")] + issuer = [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"Important CA") + ]) + ) + ] + dp = x509.DistributionPoint( + iter(name), + None, + frozenset([x509.ReasonFlags.ca_compromise]), + iter(issuer), + ) + assert list(dp.full_name) == name + assert list(dp.crl_issuer) == issuer + + def test_repr(self): + dp = x509.DistributionPoint( + None, + x509.RelativeDistinguishedName([ + x509.NameAttribute(NameOID.COMMON_NAME, u"myCN") + ]), + frozenset([x509.ReasonFlags.ca_compromise]), + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"Important CA" + ) + ]) + ) + ], + ) + if not six.PY2: + assert repr(dp) == ( + "<DistributionPoint(full_name=None, relative_name=<RelativeDis" + "tinguishedName(CN=myCN)>, reasons=frozenset({<ReasonFlags.ca_" + "compromise: 'cACompromise'>}), crl_issuer=[<DirectoryName(val" + "ue=<Name(CN=Important CA)>)>])>" + ) + else: + assert repr(dp) == ( + "<DistributionPoint(full_name=None, relative_name=<RelativeDis" + "tinguishedName(CN=myCN)>, reasons=frozenset([<ReasonFlags.ca_" + "compromise: 'cACompromise'>]), crl_issuer=[<DirectoryName(val" + "ue=<Name(CN=Important CA)>)>])>" + ) + + def test_hash(self): + dp = x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset([x509.ReasonFlags.superseded]), + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"Important CA" + ) + ]) + ) + ], + ) + dp2 = x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset([x509.ReasonFlags.superseded]), + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"Important CA" + ) + ]) + ) + ], + ) + dp3 = x509.DistributionPoint( + None, + x509.RelativeDistinguishedName([ + x509.NameAttribute(NameOID.COMMON_NAME, u"myCN") + ]), + None, + None, + ) + assert hash(dp) == hash(dp2) + assert hash(dp) != hash(dp3) + + +class TestFreshestCRL(object): + def test_invalid_distribution_points(self): + with pytest.raises(TypeError): + x509.FreshestCRL(["notadistributionpoint"]) + + def test_iter_len(self): + fcrl = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://domain")], + None, None, None + ), + ]) + assert len(fcrl) == 1 + assert list(fcrl) == [ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://domain")], + None, None, None + ), + ] + + def test_iter_input(self): + points = [ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://domain")], + None, None, None + ), + ] + fcrl = x509.FreshestCRL(iter(points)) + assert list(fcrl) == points + + def test_repr(self): + fcrl = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([x509.ReasonFlags.key_compromise]), + None + ), + ]) + if not six.PY2: + assert repr(fcrl) == ( + "<FreshestCRL([<DistributionPoint(full_name=[<Unifo" + "rmResourceIdentifier(value='ftp://domain')>], relative" + "_name=None, reasons=frozenset({<ReasonFlags.key_compromise: " + "'keyCompromise'>}), crl_issuer=None)>])>" + ) + else: + assert repr(fcrl) == ( + "<FreshestCRL([<DistributionPoint(full_name=[<Unifo" + "rmResourceIdentifier(value=u'ftp://domain')>], relative" + "_name=None, reasons=frozenset([<ReasonFlags.key_compromise: " + "'keyCompromise'>]), crl_issuer=None)>])>" + ) + + def test_eq(self): + fcrl = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + fcrl2 = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + assert fcrl == fcrl2 + + def test_ne(self): + fcrl = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + fcrl2 = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain2")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + fcrl3 = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([x509.ReasonFlags.key_compromise]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + fcrl4 = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing2")], + ), + ]) + assert fcrl != fcrl2 + assert fcrl != fcrl3 + assert fcrl != fcrl4 + assert fcrl != object() + + def test_hash(self): + fcrl = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + fcrl2 = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + fcrl3 = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([x509.ReasonFlags.key_compromise]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + assert hash(fcrl) == hash(fcrl2) + assert hash(fcrl) != hash(fcrl3) + + def test_indexing(self): + fcrl = x509.FreshestCRL([ + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing2")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing3")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing4")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing5")], + ), + ]) + assert fcrl[-1] == fcrl[4] + assert fcrl[2:6:2] == [fcrl[2], fcrl[4]] + + +class TestCRLDistributionPoints(object): + def test_invalid_distribution_points(self): + with pytest.raises(TypeError): + x509.CRLDistributionPoints(["notadistributionpoint"]) + + def test_iter_len(self): + cdp = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://domain")], + None, + None, + None + ), + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + None + ), + ]) + assert len(cdp) == 2 + assert list(cdp) == [ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://domain")], + None, + None, + None + ), + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + None + ), + ] + + def test_iter_input(self): + points = [ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://domain")], + None, + None, + None + ), + ] + cdp = x509.CRLDistributionPoints(iter(points)) + assert list(cdp) == points + + def test_repr(self): + cdp = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([x509.ReasonFlags.key_compromise]), + None + ), + ]) + if not six.PY2: + assert repr(cdp) == ( + "<CRLDistributionPoints([<DistributionPoint(full_name=[<Unifo" + "rmResourceIdentifier(value='ftp://domain')>], relative" + "_name=None, reasons=frozenset({<ReasonFlags.key_compromise: " + "'keyCompromise'>}), crl_issuer=None)>])>" + ) + else: + assert repr(cdp) == ( + "<CRLDistributionPoints([<DistributionPoint(full_name=[<Unifo" + "rmResourceIdentifier(value=u'ftp://domain')>], relative" + "_name=None, reasons=frozenset([<ReasonFlags.key_compromise: " + "'keyCompromise'>]), crl_issuer=None)>])>" + ) + + def test_eq(self): + cdp = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + cdp2 = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + assert cdp == cdp2 + + def test_ne(self): + cdp = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + cdp2 = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain2")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + cdp3 = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([x509.ReasonFlags.key_compromise]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + cdp4 = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing2")], + ), + ]) + assert cdp != cdp2 + assert cdp != cdp3 + assert cdp != cdp4 + assert cdp != object() + + def test_hash(self): + cdp = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + cdp2 = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + cdp3 = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([x509.ReasonFlags.key_compromise]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + assert hash(cdp) == hash(cdp2) + assert hash(cdp) != hash(cdp3) + + def test_indexing(self): + ci = x509.CRLDistributionPoints([ + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing2")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing3")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing4")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing5")], + ), + ]) + assert ci[-1] == ci[4] + assert ci[2:6:2] == [ci[2], ci[4]] + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestCRLDistributionPointsExtension(object): + def test_fullname_and_crl_issuer(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", "ValidcRLIssuerTest28EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, + u"Test Certificates 2011" + ), + x509.NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, + u"indirectCRL CA3 cRLIssuer" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, + u"indirect CRL for indirectCRL CA3" + ), + ]) + )], + relative_name=None, + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, + u"Test Certificates 2011" + ), + x509.NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, + u"indirectCRL CA3 cRLIssuer" + ), + ]) + )], + ) + ]) + + def test_relativename_and_crl_issuer(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", "ValidcRLIssuerTest29EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + NameOID.COMMON_NAME, + u"indirect CRL for indirectCRL CA3" + ), + ]), + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, + u"Test Certificates 2011" + ), + x509.NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, + u"indirectCRL CA3 cRLIssuer" + ), + ]) + )], + ) + ]) + + def test_fullname_crl_issuer_reasons(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cdp_fullname_reasons_crl_issuer.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl" + )], + relative_name=None, + reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise + ]), + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u"PyCA" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ), + ]) + )], + ) + ]) + + def test_all_reasons(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cdp_all_reasons.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://domain.com/some.crl" + )], + relative_name=None, + reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + x509.ReasonFlags.affiliation_changed, + x509.ReasonFlags.superseded, + x509.ReasonFlags.privilege_withdrawn, + x509.ReasonFlags.cessation_of_operation, + x509.ReasonFlags.aa_compromise, + x509.ReasonFlags.certificate_hold, + ]), + crl_issuer=None + ) + ]) + + def test_single_reason(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cdp_reason_aa_compromise.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://domain.com/some.crl" + )], + relative_name=None, + reasons=frozenset([x509.ReasonFlags.aa_compromise]), + crl_issuer=None + ) + ]) + + def test_crl_issuer_only(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cdp_crl_issuer.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=None, + relative_name=None, + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ), + ]) + )], + ) + ]) + + def test_crl_empty_hostname(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cdp_empty_hostname.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"ldap:///CN=A,OU=B,dc=C,DC=D?E?F?G?H=I" + )], + relative_name=None, + reasons=None, + crl_issuer=None + ) + ]) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestFreshestCRLExtension(object): + def test_vector(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "freshestcrl.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + fcrl = cert.extensions.get_extension_for_class(x509.FreshestCRL).value + assert fcrl == x509.FreshestCRL([ + x509.DistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + u'http://myhost.com/myca.crl' + ), + x509.UniformResourceIdentifier( + u'http://backup.myhost.com/myca.crl' + ) + ], + relative_name=None, + reasons=frozenset([ + x509.ReasonFlags.ca_compromise, + x509.ReasonFlags.key_compromise + ]), + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ), + ]) + )] + ) + ]) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestOCSPNoCheckExtension(object): + def test_nocheck(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "ocsp_nocheck.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.OCSP_NO_CHECK + ) + assert isinstance(ext.value, x509.OCSPNoCheck) + + def test_eq(self): + onc1 = x509.OCSPNoCheck() + onc2 = x509.OCSPNoCheck() + + assert onc1 == onc2 + + def test_hash(self): + onc1 = x509.OCSPNoCheck() + onc2 = x509.OCSPNoCheck() + + assert hash(onc1) == hash(onc2) + + def test_ne(self): + onc1 = x509.OCSPNoCheck() + onc2 = x509.OCSPNoCheck() + + assert onc1 == onc2 + assert (onc1 != onc2) is False + assert onc1 != object() + + def test_repr(self): + onc = x509.OCSPNoCheck() + + assert repr(onc) == '<OCSPNoCheck()>' + + +class TestInhibitAnyPolicy(object): + def test_not_int(self): + with pytest.raises(TypeError): + x509.InhibitAnyPolicy("notint") + + def test_negative_int(self): + with pytest.raises(ValueError): + x509.InhibitAnyPolicy(-1) + + def test_repr(self): + iap = x509.InhibitAnyPolicy(0) + assert repr(iap) == "<InhibitAnyPolicy(skip_certs=0)>" + + def test_eq(self): + iap = x509.InhibitAnyPolicy(1) + iap2 = x509.InhibitAnyPolicy(1) + assert iap == iap2 + + def test_ne(self): + iap = x509.InhibitAnyPolicy(1) + iap2 = x509.InhibitAnyPolicy(4) + assert iap != iap2 + assert iap != object() + + def test_hash(self): + iap = x509.InhibitAnyPolicy(1) + iap2 = x509.InhibitAnyPolicy(1) + iap3 = x509.InhibitAnyPolicy(4) + assert hash(iap) == hash(iap2) + assert hash(iap) != hash(iap3) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestInhibitAnyPolicyExtension(object): + def test_inhibit_any_policy(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "inhibit_any_policy_5.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + iap = cert.extensions.get_extension_for_oid( + ExtensionOID.INHIBIT_ANY_POLICY + ).value + assert iap.skip_certs == 5 + + +class TestIssuingDistributionPointExtension(object): + @pytest.mark.parametrize( + ("filename", "expected"), + [ + ( + "crl_idp_fullname_indirect_crl.pem", + x509.IssuingDistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl") + ], + relative_name=None, + only_contains_user_certs=False, + only_contains_ca_certs=False, + only_some_reasons=None, + indirect_crl=True, + only_contains_attribute_certs=False, + ) + ), + ( + "crl_idp_fullname_only.pem", + x509.IssuingDistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl") + ], + relative_name=None, + only_contains_user_certs=False, + only_contains_ca_certs=False, + only_some_reasons=None, + indirect_crl=False, + only_contains_attribute_certs=False, + ) + ), + ( + "crl_idp_fullname_only_aa.pem", + x509.IssuingDistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl") + ], + relative_name=None, + only_contains_user_certs=False, + only_contains_ca_certs=False, + only_some_reasons=None, + indirect_crl=False, + only_contains_attribute_certs=True, + ) + ), + ( + "crl_idp_fullname_only_user.pem", + x509.IssuingDistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl") + ], + relative_name=None, + only_contains_user_certs=True, + only_contains_ca_certs=False, + only_some_reasons=None, + indirect_crl=False, + only_contains_attribute_certs=False, + ) + ), + ( + "crl_idp_only_ca.pem", + x509.IssuingDistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + ) + ]), + only_contains_user_certs=False, + only_contains_ca_certs=True, + only_some_reasons=None, + indirect_crl=False, + only_contains_attribute_certs=False, + ) + ), + ( + "crl_idp_reasons_only.pem", + x509.IssuingDistributionPoint( + full_name=None, + relative_name=None, + only_contains_user_certs=False, + only_contains_ca_certs=False, + only_some_reasons=frozenset([ + x509.ReasonFlags.key_compromise + ]), + indirect_crl=False, + only_contains_attribute_certs=False, + ) + ), + ( + "crl_idp_relative_user_all_reasons.pem", + x509.IssuingDistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + ) + ]), + only_contains_user_certs=True, + only_contains_ca_certs=False, + only_some_reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + x509.ReasonFlags.affiliation_changed, + x509.ReasonFlags.superseded, + x509.ReasonFlags.cessation_of_operation, + x509.ReasonFlags.certificate_hold, + x509.ReasonFlags.privilege_withdrawn, + x509.ReasonFlags.aa_compromise, + ]), + indirect_crl=False, + only_contains_attribute_certs=False, + ) + ), + ( + "crl_idp_relativename_only.pem", + x509.IssuingDistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + ) + ]), + only_contains_user_certs=False, + only_contains_ca_certs=False, + only_some_reasons=None, + indirect_crl=False, + only_contains_attribute_certs=False, + ) + ), + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_vectors(self, filename, expected, backend): + crl = _load_cert( + os.path.join("x509", "custom", filename), + x509.load_pem_x509_crl, backend + ) + idp = crl.extensions.get_extension_for_class( + x509.IssuingDistributionPoint + ).value + assert idp == expected + + @pytest.mark.parametrize( + ( + "error", "only_contains_user_certs", "only_contains_ca_certs", + "indirect_crl", "only_contains_attribute_certs", + "only_some_reasons", "full_name", "relative_name" + ), + [ + ( + TypeError, False, False, False, False, 'notafrozenset', None, + None + ), + ( + TypeError, False, False, False, False, frozenset(['bad']), + None, None + ), + ( + ValueError, False, False, False, False, + frozenset([x509.ReasonFlags.unspecified]), None, None + ), + ( + ValueError, False, False, False, False, + frozenset([x509.ReasonFlags.remove_from_crl]), None, None + ), + (TypeError, 'notabool', False, False, False, None, None, None), + (TypeError, False, 'notabool', False, False, None, None, None), + (TypeError, False, False, 'notabool', False, None, None, None), + (TypeError, False, False, False, 'notabool', None, None, None), + (ValueError, True, True, False, False, None, None, None), + (ValueError, False, False, True, True, None, None, None), + (ValueError, False, False, False, False, None, None, None), + ] + ) + def test_invalid_init(self, error, only_contains_user_certs, + only_contains_ca_certs, indirect_crl, + only_contains_attribute_certs, only_some_reasons, + full_name, relative_name): + with pytest.raises(error): + x509.IssuingDistributionPoint( + full_name, relative_name, only_contains_user_certs, + only_contains_ca_certs, only_some_reasons, indirect_crl, + only_contains_attribute_certs + ) + + def test_repr(self): + idp = x509.IssuingDistributionPoint( + None, None, False, False, + frozenset([x509.ReasonFlags.key_compromise]), False, False + ) + if not six.PY2: + assert repr(idp) == ( + "<IssuingDistributionPoint(full_name=None, relative_name=None," + " only_contains_user_certs=False, only_contains_ca_certs=False" + ", only_some_reasons=frozenset({<ReasonFlags.key_compromise: '" + "keyCompromise'>}), indirect_crl=False, only_contains_attribut" + "e_certs=False)>" + ) + else: + assert repr(idp) == ( + "<IssuingDistributionPoint(full_name=None, relative_name=None," + " only_contains_user_certs=False, only_contains_ca_certs=False" + ", only_some_reasons=frozenset([<ReasonFlags.key_compromise: '" + "keyCompromise'>]), indirect_crl=False, only_contains_attribut" + "e_certs=False)>" + ) + + def test_eq(self): + idp1 = x509.IssuingDistributionPoint( + only_contains_user_certs=False, + only_contains_ca_certs=False, + indirect_crl=False, + only_contains_attribute_certs=False, + only_some_reasons=None, + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA") + ]) + ) + idp2 = x509.IssuingDistributionPoint( + only_contains_user_certs=False, + only_contains_ca_certs=False, + indirect_crl=False, + only_contains_attribute_certs=False, + only_some_reasons=None, + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA") + ]) + ) + assert idp1 == idp2 + + def test_ne(self): + idp1 = x509.IssuingDistributionPoint( + only_contains_user_certs=False, + only_contains_ca_certs=False, + indirect_crl=False, + only_contains_attribute_certs=False, + only_some_reasons=None, + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA") + ]) + ) + idp2 = x509.IssuingDistributionPoint( + only_contains_user_certs=True, + only_contains_ca_certs=False, + indirect_crl=False, + only_contains_attribute_certs=False, + only_some_reasons=None, + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA") + ]) + ) + assert idp1 != idp2 + assert idp1 != object() + + def test_hash(self): + idp1 = x509.IssuingDistributionPoint( + None, None, True, False, None, False, False + ) + idp2 = x509.IssuingDistributionPoint( + None, None, True, False, None, False, False + ) + idp3 = x509.IssuingDistributionPoint( + None, + x509.RelativeDistinguishedName([ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA") + ]), + True, False, None, False, False + ) + assert hash(idp1) == hash(idp2) + assert hash(idp1) != hash(idp3) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + @pytest.mark.parametrize( + "idp", + [ + x509.IssuingDistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl" + ) + ], + relative_name=None, + only_contains_user_certs=False, + only_contains_ca_certs=False, + only_some_reasons=None, + indirect_crl=True, + only_contains_attribute_certs=False, + ), + x509.IssuingDistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl" + ) + ], + relative_name=None, + only_contains_user_certs=False, + only_contains_ca_certs=False, + only_some_reasons=None, + indirect_crl=False, + only_contains_attribute_certs=False, + ), + x509.IssuingDistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl" + ) + ], + relative_name=None, + only_contains_user_certs=False, + only_contains_ca_certs=False, + only_some_reasons=None, + indirect_crl=False, + only_contains_attribute_certs=True, + ), + x509.IssuingDistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl" + ) + ], + relative_name=None, + only_contains_user_certs=True, + only_contains_ca_certs=False, + only_some_reasons=None, + indirect_crl=False, + only_contains_attribute_certs=False, + ), + x509.IssuingDistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + ) + ]), + only_contains_user_certs=False, + only_contains_ca_certs=True, + only_some_reasons=None, + indirect_crl=False, + only_contains_attribute_certs=False, + ), + x509.IssuingDistributionPoint( + full_name=None, + relative_name=None, + only_contains_user_certs=False, + only_contains_ca_certs=True, + only_some_reasons=frozenset([x509.ReasonFlags.key_compromise]), + indirect_crl=False, + only_contains_attribute_certs=False, + ), + x509.IssuingDistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA"), + x509.NameAttribute( + oid=x509.NameOID.COMMON_NAME, value=u"cryptography") + ]), + only_contains_user_certs=True, + only_contains_ca_certs=False, + only_some_reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + x509.ReasonFlags.affiliation_changed, + x509.ReasonFlags.privilege_withdrawn, + x509.ReasonFlags.aa_compromise, + ]), + indirect_crl=False, + only_contains_attribute_certs=False, + ), + x509.IssuingDistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + ) + ]), + only_contains_user_certs=False, + only_contains_ca_certs=False, + only_some_reasons=None, + indirect_crl=False, + only_contains_attribute_certs=False, + ), + ] + ) + def test_generate(self, idp, backend): + key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_extension( + idp, True + ) + + crl = builder.sign(key, hashes.SHA256(), backend) + ext = crl.extensions.get_extension_for_class( + x509.IssuingDistributionPoint + ) + assert ext.critical is True + assert ext.value == idp + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestPrecertPoisonExtension(object): + def test_load(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.precert.pem"), + x509.load_pem_x509_certificate, + backend + ) + poison = cert.extensions.get_extension_for_oid( + ExtensionOID.PRECERT_POISON + ).value + assert isinstance(poison, x509.PrecertPoison) + poison = cert.extensions.get_extension_for_class( + x509.PrecertPoison + ).value + assert isinstance(poison, x509.PrecertPoison) + + def test_generate(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + cert = _make_certbuilder(private_key).add_extension( + x509.PrecertPoison(), critical=True + ).sign(private_key, hashes.SHA256(), backend) + poison = cert.extensions.get_extension_for_oid( + ExtensionOID.PRECERT_POISON + ).value + assert isinstance(poison, x509.PrecertPoison) + + def test_eq(self): + pcp1 = x509.PrecertPoison() + pcp2 = x509.PrecertPoison() + + assert pcp1 == pcp2 + + def test_hash(self): + pcp1 = x509.PrecertPoison() + pcp2 = x509.PrecertPoison() + + assert hash(pcp1) == hash(pcp2) + + def test_ne(self): + pcp1 = x509.PrecertPoison() + pcp2 = x509.PrecertPoison() + + assert pcp1 == pcp2 + assert (pcp1 != pcp2) is False + assert pcp1 != object() + + def test_repr(self): + pcp = x509.PrecertPoison() + + assert repr(pcp) == '<PrecertPoison()>' + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestSignedCertificateTimestamps(object): + @pytest.mark.supported( + only_if=lambda backend: ( + backend._lib.CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER), + skip_message="Requires OpenSSL 1.1.0f+", + ) + def test_eq(self, backend): + sct = _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value[0] + sct2 = _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value[0] + assert sct == sct2 + + @pytest.mark.supported( + only_if=lambda backend: ( + backend._lib.CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER), + skip_message="Requires OpenSSL 1.1.0f+", + ) + def test_ne(self, backend): + sct = _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value[0] + sct2 = _load_cert( + os.path.join("x509", "cryptography-scts.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value[0] + assert sct != sct2 + assert sct != object() + + @pytest.mark.supported( + only_if=lambda backend: ( + backend._lib.CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER), + skip_message="Requires OpenSSL 1.1.0f+", + ) + def test_hash(self, backend): + sct = _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value[0] + sct2 = _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value[0] + sct3 = _load_cert( + os.path.join("x509", "cryptography-scts.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value[0] + assert hash(sct) == hash(sct2) + assert hash(sct) != hash(sct3) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestPrecertificateSignedCertificateTimestampsExtension(object): + def test_init(self): + with pytest.raises(TypeError): + x509.PrecertificateSignedCertificateTimestamps([object()]) + + def test_repr(self): + assert repr(x509.PrecertificateSignedCertificateTimestamps([])) == ( + "<PrecertificateSignedCertificateTimestamps([])>" + ) + + @pytest.mark.supported( + only_if=lambda backend: ( + backend._lib.CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER), + skip_message="Requires OpenSSL 1.1.0f+", + ) + def test_eq(self, backend): + psct1 = _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value + psct2 = _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value + assert psct1 == psct2 + + @pytest.mark.supported( + only_if=lambda backend: ( + backend._lib.CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER), + skip_message="Requires OpenSSL 1.1.0f+", + ) + def test_ne(self, backend): + psct1 = _load_cert( + os.path.join("x509", "cryptography-scts.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value + psct2 = _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value + assert psct1 != psct2 + assert psct1 != object() + + @pytest.mark.supported( + only_if=lambda backend: ( + backend._lib.CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER), + skip_message="Requires OpenSSL 1.1.0f+", + ) + def test_hash(self, backend): + psct1 = _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value + psct2 = _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value + psct3 = _load_cert( + os.path.join("x509", "cryptography-scts.pem"), + x509.load_pem_x509_certificate, + backend + ).extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value + assert hash(psct1) == hash(psct2) + assert hash(psct1) != hash(psct3) + + @pytest.mark.supported( + only_if=lambda backend: ( + backend._lib.CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER), + skip_message="Requires OpenSSL 1.1.0f+", + ) + def test_simple(self, backend): + cert = _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + backend + ) + scts = cert.extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value + assert len(scts) == 1 + [sct] = scts + assert scts[0] == sct + assert sct.version == x509.certificate_transparency.Version.v1 + assert sct.log_id == ( + b"\xa7\xceJNb\x07\xe0\xad\xde\xe5\xfd\xaaK\x1f\x86v\x87g\xb5\xd0" + b"\x02\xa5]G1\x0e~g\n\x95\xea\xb2" + ) + assert sct.timestamp == datetime.datetime( + 2016, 11, 17, 1, 56, 25, 396000 + ) + assert ( + sct.entry_type == + x509.certificate_transparency.LogEntryType.PRE_CERTIFICATE + ) + + @pytest.mark.supported( + only_if=lambda backend: ( + not backend._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER), + skip_message="Requires OpenSSL < 1.1.0", + ) + def test_skips_scts_if_unsupported(self, backend): + cert = _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert len(cert.extensions) == 10 + with pytest.raises(x509.ExtensionNotFound): + cert.extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ) + + ext = cert.extensions.get_extension_for_oid( + x509.ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS + ) + assert isinstance(ext.value, x509.UnrecognizedExtension) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestInvalidExtension(object): + def test_invalid_certificate_policies_data(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cp_invalid.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(ValueError): + cert.extensions + + +class TestOCSPNonce(object): + def test_non_bytes(self): + with pytest.raises(TypeError): + x509.OCSPNonce(38) + + def test_eq(self): + nonce1 = x509.OCSPNonce(b"0" * 5) + nonce2 = x509.OCSPNonce(b"0" * 5) + assert nonce1 == nonce2 + + def test_ne(self): + nonce1 = x509.OCSPNonce(b"0" * 5) + nonce2 = x509.OCSPNonce(b"0" * 6) + assert nonce1 != nonce2 + assert nonce1 != object() + + def test_repr(self): + nonce1 = x509.OCSPNonce(b"nonce") + if not six.PY2: + assert repr(nonce1) == "<OCSPNonce(nonce=b'nonce')>" + else: + assert repr(nonce1) == "<OCSPNonce(nonce='nonce')>" + + def test_hash(self): + nonce1 = x509.OCSPNonce(b"0" * 5) + nonce2 = x509.OCSPNonce(b"0" * 5) + nonce3 = x509.OCSPNonce(b"1" * 5) + assert hash(nonce1) == hash(nonce2) + assert hash(nonce1) != hash(nonce3) + + +def test_all_extension_oid_members_have_names_defined(): + for oid in dir(ExtensionOID): + if oid.startswith('__'): + continue + assert getattr(ExtensionOID, oid) in _OID_NAMES diff --git a/tests/x509/test_x509_revokedcertbuilder.py b/tests/x509/test_x509_revokedcertbuilder.py new file mode 100644 index 00000000..75c6b269 --- /dev/null +++ b/tests/x509/test_x509_revokedcertbuilder.py @@ -0,0 +1,205 @@ +# 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 datetime + +import pytest + +import pytz + +from cryptography import x509 +from cryptography.hazmat.backends.interfaces import X509Backend + + +class TestRevokedCertificateBuilder(object): + def test_serial_number_must_be_integer(self): + with pytest.raises(TypeError): + x509.RevokedCertificateBuilder().serial_number("notanx509name") + + def test_serial_number_must_be_non_negative(self): + with pytest.raises(ValueError): + x509.RevokedCertificateBuilder().serial_number(-1) + + def test_serial_number_must_be_positive(self): + with pytest.raises(ValueError): + x509.RevokedCertificateBuilder().serial_number(0) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_minimal_serial_number(self, backend): + revocation_date = datetime.datetime(2002, 1, 1, 12, 1) + builder = x509.RevokedCertificateBuilder().serial_number( + 1 + ).revocation_date( + revocation_date + ) + + revoked_certificate = builder.build(backend) + assert revoked_certificate.serial_number == 1 + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_biggest_serial_number(self, backend): + revocation_date = datetime.datetime(2002, 1, 1, 12, 1) + builder = x509.RevokedCertificateBuilder().serial_number( + (1 << 159) - 1 + ).revocation_date( + revocation_date + ) + + revoked_certificate = builder.build(backend) + assert revoked_certificate.serial_number == (1 << 159) - 1 + + def test_serial_number_must_be_less_than_160_bits_long(self): + with pytest.raises(ValueError): + x509.RevokedCertificateBuilder().serial_number(1 << 159) + + def test_set_serial_number_twice(self): + builder = x509.RevokedCertificateBuilder().serial_number(3) + with pytest.raises(ValueError): + builder.serial_number(4) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_aware_revocation_date(self, backend): + time = datetime.datetime(2012, 1, 16, 22, 43) + tz = pytz.timezone("US/Pacific") + time = tz.localize(time) + utc_time = datetime.datetime(2012, 1, 17, 6, 43) + serial_number = 333 + builder = x509.RevokedCertificateBuilder().serial_number( + serial_number + ).revocation_date( + time + ) + + revoked_certificate = builder.build(backend) + assert revoked_certificate.revocation_date == utc_time + + def test_revocation_date_invalid(self): + with pytest.raises(TypeError): + x509.RevokedCertificateBuilder().revocation_date("notadatetime") + + def test_revocation_date_before_1950(self): + with pytest.raises(ValueError): + x509.RevokedCertificateBuilder().revocation_date( + datetime.datetime(1940, 8, 10) + ) + + def test_set_revocation_date_twice(self): + builder = x509.RevokedCertificateBuilder().revocation_date( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.revocation_date(datetime.datetime(2002, 1, 1, 12, 1)) + + def test_add_extension_checks_for_duplicates(self): + builder = x509.RevokedCertificateBuilder().add_extension( + x509.CRLReason(x509.ReasonFlags.ca_compromise), False + ) + + with pytest.raises(ValueError): + builder.add_extension( + x509.CRLReason(x509.ReasonFlags.ca_compromise), False + ) + + def test_add_invalid_extension(self): + with pytest.raises(TypeError): + x509.RevokedCertificateBuilder().add_extension( + "notanextension", False + ) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_serial_number(self, backend): + builder = x509.RevokedCertificateBuilder().revocation_date( + datetime.datetime(2002, 1, 1, 12, 1) + ) + + with pytest.raises(ValueError): + builder.build(backend) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_revocation_date(self, backend): + builder = x509.RevokedCertificateBuilder().serial_number(3) + + with pytest.raises(ValueError): + builder.build(backend) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_create_revoked(self, backend): + serial_number = 333 + revocation_date = datetime.datetime(2002, 1, 1, 12, 1) + builder = x509.RevokedCertificateBuilder().serial_number( + serial_number + ).revocation_date( + revocation_date + ) + + revoked_certificate = builder.build(backend) + assert revoked_certificate.serial_number == serial_number + assert revoked_certificate.revocation_date == revocation_date + assert len(revoked_certificate.extensions) == 0 + + @pytest.mark.parametrize( + "extension", + [ + x509.InvalidityDate(datetime.datetime(2015, 1, 1, 0, 0)), + x509.CRLReason(x509.ReasonFlags.ca_compromise), + x509.CertificateIssuer([ + x509.DNSName(u"cryptography.io"), + ]) + ] + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_add_extensions(self, backend, extension): + serial_number = 333 + revocation_date = datetime.datetime(2002, 1, 1, 12, 1) + builder = x509.RevokedCertificateBuilder().serial_number( + serial_number + ).revocation_date( + revocation_date + ).add_extension( + extension, False + ) + + revoked_certificate = builder.build(backend) + assert revoked_certificate.serial_number == serial_number + assert revoked_certificate.revocation_date == revocation_date + assert len(revoked_certificate.extensions) == 1 + ext = revoked_certificate.extensions.get_extension_for_class( + type(extension) + ) + assert ext.critical is False + assert ext.value == extension + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_add_multiple_extensions(self, backend): + serial_number = 333 + revocation_date = datetime.datetime(2002, 1, 1, 12, 1) + invalidity_date = x509.InvalidityDate( + datetime.datetime(2015, 1, 1, 0, 0) + ) + certificate_issuer = x509.CertificateIssuer([ + x509.DNSName(u"cryptography.io"), + ]) + crl_reason = x509.CRLReason(x509.ReasonFlags.aa_compromise) + builder = x509.RevokedCertificateBuilder().serial_number( + serial_number + ).revocation_date( + revocation_date + ).add_extension( + invalidity_date, True + ).add_extension( + crl_reason, True + ).add_extension( + certificate_issuer, True + ) + + revoked_certificate = builder.build(backend) + assert len(revoked_certificate.extensions) == 3 + for ext_data in [invalidity_date, certificate_issuer, crl_reason]: + ext = revoked_certificate.extensions.get_extension_for_class( + type(ext_data) + ) + assert ext.critical is True + assert ext.value == ext_data |
