diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/hazmat/backends/test_multibackend.py | 14 | ||||
-rw-r--r-- | tests/hazmat/backends/test_openssl.py | 105 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_rsa.py | 37 | ||||
-rw-r--r-- | tests/test_x509.py | 116 | ||||
-rw-r--r-- | tests/test_x509_crlbuilder.py | 449 | ||||
-rw-r--r-- | tests/test_x509_ext.py | 328 | ||||
-rw-r--r-- | tests/test_x509_revokedcertbuilder.py | 160 |
7 files changed, 1137 insertions, 72 deletions
diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py index 81a64ce0..74835716 100644 --- a/tests/hazmat/backends/test_multibackend.py +++ b/tests/hazmat/backends/test_multibackend.py @@ -218,6 +218,12 @@ class DummyX509Backend(object): def create_x509_certificate(self, builder, private_key, algorithm): pass + def create_x509_crl(self, builder, private_key, algorithm): + pass + + def create_x509_revoked_certificate(self, builder): + pass + class TestMultiBackend(object): def test_ciphers(self): @@ -514,6 +520,8 @@ class TestMultiBackend(object): backend.load_der_x509_csr(b"reqdata") backend.create_x509_csr(object(), b"privatekey", hashes.SHA1()) backend.create_x509_certificate(object(), b"privatekey", hashes.SHA1()) + backend.create_x509_crl(object(), b"privatekey", hashes.SHA1()) + backend.create_x509_revoked_certificate(object()) backend = MultiBackend([]) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): @@ -534,3 +542,9 @@ class TestMultiBackend(object): backend.create_x509_certificate( object(), b"privatekey", hashes.SHA1() ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.create_x509_crl( + object(), b"privatekey", hashes.SHA1() + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.create_x509_revoked_certificate(object()) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index d048fe68..ad2daf7d 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function +import datetime import os import subprocess import sys @@ -13,7 +14,7 @@ import pretend import pytest -from cryptography import utils +from cryptography import utils, x509 from cryptography.exceptions import InternalError, _Reasons from cryptography.hazmat.backends.interfaces import RSABackend from cryptography.hazmat.backends.openssl.backend import ( @@ -500,8 +501,108 @@ class TestOpenSSLSignX509Certificate(object): with pytest.raises(TypeError): backend.create_x509_certificate(object(), private_key, DummyHash()) + @pytest.mark.skipif( + backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000, + reason="Requires an older OpenSSL. Must be < 1.0.1" + ) + def test_sign_with_dsa_private_key_is_unsupported(self): + private_key = DSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(x509.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(NotImplementedError): + builder.sign(private_key, hashes.SHA512(), backend) + + @pytest.mark.skipif( + backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000, + reason="Requires an older OpenSSL. Must be < 1.0.1" + ) + def test_sign_with_ec_private_key_is_unsupported(self): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + builder = x509.CertificateBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(x509.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(NotImplementedError): + builder.sign(private_key, hashes.SHA512(), backend) + + +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()) + + @pytest.mark.skipif( + backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000, + reason="Requires an older OpenSSL. Must be < 1.0.1" + ) + def test_sign_with_dsa_private_key_is_unsupported(self): + private_key = DSA_KEY_2048.private_key(backend) + builder = x509.CertificateRevocationListBuilder() + builder = builder.issuer_name( + x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')]) + ).last_update( + datetime.datetime(2002, 1, 1, 12, 1) + ).next_update( + datetime.datetime(2032, 1, 1, 12, 1) + ) + + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA1(), backend) + + @pytest.mark.skipif( + backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000, + reason="Requires an older OpenSSL. Must be < 1.0.1" + ) + def test_sign_with_ec_private_key_is_unsupported(self): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + builder = x509.CertificateRevocationListBuilder() + builder = builder.issuer_name( + x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')]) + ).last_update( + datetime.datetime(2002, 1, 1, 12, 1) + ).next_update( + datetime.datetime(2032, 1, 1, 12, 1) + ) + + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA512(), backend) + + +class TestOpenSSLCreateRevokedCertificate(object): + def test_invalid_builder(self): + with pytest.raises(TypeError): + backend.create_x509_revoked_certificate(object()) + -class TestOpenSSLSerialisationWithOpenSSL(object): +class TestOpenSSLSerializationWithOpenSSL(object): def test_pem_password_cb_buffer_too_small(self): ffi_cb, userdata = backend._pem_password_cb(b"aa") handle = backend._ffi.new_handle(userdata) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 0b83fd65..b6213d6d 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -1194,6 +1194,43 @@ 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.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 + ) + ) + def test_unsupported_oaep_mgf(self, backend): private_key = RSA_KEY_512.private_key(backend) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): diff --git a/tests/test_x509.py b/tests/test_x509.py index ae2746e3..6145edb1 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -173,6 +173,20 @@ class TestCertificateRevocationList(object): # Check that len() works for CRLs. assert len(crl) == 12 + 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"), @@ -318,7 +332,6 @@ class TestCertificateRevocationList(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"), @@ -342,12 +355,12 @@ class TestRevokedCertificate(object): backend ) - exp_issuer = x509.GeneralNames([ + 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. @@ -366,29 +379,28 @@ class TestRevokedCertificate(object): rev1 = crl[1] assert isinstance(rev1.extensions, x509.Extensions) - reason = rev1.extensions.get_extension_for_oid( - x509.OID_CRL_REASON).value - assert reason == x509.ReasonFlags.unspecified + reason = rev1.extensions.get_extension_for_class( + x509.CRLReason).value + assert reason == x509.CRLReason(x509.ReasonFlags.unspecified) - issuer = rev1.extensions.get_extension_for_oid( - x509.OID_CERTIFICATE_ISSUER).value - assert issuer == exp_issuer + issuer = rev1.extensions.get_extension_for_class( + x509.CertificateIssuer).value + assert issuer == x509.CertificateIssuer(exp_issuer) - date = rev1.extensions.get_extension_for_oid( - x509.OID_INVALIDITY_DATE).value - assert isinstance(date, datetime.datetime) - assert date.isoformat() == "2015-01-01T00:00:00" + 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_oid(x509.OID_CRL_REASON) + 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) + flags.discard(r.value.reason) assert len(flags) == 0 @@ -446,6 +458,23 @@ class TestRevokedCertificate(object): 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 + @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) @@ -1064,7 +1093,11 @@ class TestRSACertificateRequest(object): backend ) extensions = request.extensions - assert len(extensions) == 0 + 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( @@ -1710,57 +1743,6 @@ class TestCertificateBuilder(object): with pytest.raises(TypeError): builder.sign(private_key, object(), backend) - @pytest.mark.requires_backend_interface(interface=DSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_sign_with_dsa_private_key_is_unsupported(self, backend): - if backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000: - pytest.skip("Requires an older OpenSSL. Must be < 1.0.1") - - 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(NotImplementedError): - builder.sign(private_key, hashes.SHA512(), backend) - - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_sign_with_ec_private_key_is_unsupported(self, backend): - if backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000: - pytest.skip("Requires an older OpenSSL. Must be < 1.0.1") - - _skip_curve_unsupported(backend, ec.SECP256R1()) - private_key = ec.generate_private_key(ec.SECP256R1(), 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(NotImplementedError): - builder.sign(private_key, hashes.SHA512(), backend) - @pytest.mark.parametrize( "cdp", [ diff --git a/tests/test_x509_crlbuilder.py b/tests/test_x509_crlbuilder.py new file mode 100644 index 00000000..32a07487 --- /dev/null +++ b/tests/test_x509_crlbuilder.py @@ -0,0 +1,449 @@ +# 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 + +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 +from cryptography.x509.oid import AuthorityInformationAccessOID, NameOID + +from .hazmat.primitives.fixtures_dsa import DSA_KEY_2048 +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')]) + ) + + def test_last_update_invalid(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(TypeError): + builder.last_update("notadatetime") + + def test_last_update_before_unix_epoch(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(ValueError): + builder.last_update(datetime.datetime(1960, 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)) + + def test_next_update_invalid(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(TypeError): + builder.next_update("notadatetime") + + def test_next_update_before_unix_epoch(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(ValueError): + builder.next_update(datetime.datetime(1960, 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.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_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.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_dsa_key(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) + 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_unsupported(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) + 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=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/test_x509_ext.py b/tests/test_x509_ext.py index d9743c8e..03a3730a 100644 --- a/tests/test_x509_ext.py +++ b/tests/test_x509_ext.py @@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function import binascii +import datetime import ipaddress import os @@ -74,6 +75,173 @@ class TestExtension(object): assert ext1 != object() +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 six.PY3: + 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")]) + assert repr(ci) == ( + "<CertificateIssuer(<GeneralNames([<DNSName(value=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"] + + +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 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): @@ -269,6 +437,16 @@ class TestCertificatePolicies(object): 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]] + @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) @@ -595,6 +773,20 @@ class TestSubjectKeyIdentifier(object): 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): @@ -720,6 +912,13 @@ class TestBasicConstraints(object): "<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) @@ -833,17 +1032,33 @@ class TestExtensions(object): assert exc.value.oid == x509.ObjectIdentifier("1.2.3.4") + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) 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", "custom", "unsupported_extension_2.pem" ), x509.load_pem_x509_certificate, backend ) extensions = cert.extensions - assert len(extensions) == 0 + 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( @@ -858,6 +1073,21 @@ class TestExtensions(object): 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( @@ -1379,6 +1609,17 @@ class TestGeneralNames(object): x509.DNSName(u"crypto.local"), ] + 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( @@ -1434,6 +1675,17 @@ class TestIssuerAlternativeName(object): 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( @@ -1506,6 +1758,13 @@ class TestCRLNumber(object): 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): @@ -1526,6 +1785,17 @@ class TestSubjectAlternativeName(object): 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( @@ -2028,6 +2298,32 @@ class TestAuthorityInformationAccess(object): 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]] + @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) @@ -2756,6 +3052,32 @@ class TestCRLDistributionPoints(object): assert cdp != cdp4 assert cdp != object() + 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) diff --git a/tests/test_x509_revokedcertbuilder.py b/tests/test_x509_revokedcertbuilder.py new file mode 100644 index 00000000..5aa90630 --- /dev/null +++ b/tests/test_x509_revokedcertbuilder.py @@ -0,0 +1,160 @@ +# 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 + +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_less_than_160_bits_long(self): + with pytest.raises(ValueError): + # 2 raised to the 160th power is actually 161 bits + x509.RevokedCertificateBuilder().serial_number(2 ** 160) + + def test_set_serial_number_twice(self): + builder = x509.RevokedCertificateBuilder().serial_number(3) + with pytest.raises(ValueError): + builder.serial_number(4) + + def test_revocation_date_invalid(self): + with pytest.raises(TypeError): + x509.RevokedCertificateBuilder().revocation_date("notadatetime") + + def test_revocation_date_before_unix_epoch(self): + with pytest.raises(ValueError): + x509.RevokedCertificateBuilder().revocation_date( + datetime.datetime(1960, 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 |