diff options
author | Paul Kehrer <paul.l.kehrer@gmail.com> | 2015-12-25 23:55:47 -0600 |
---|---|---|
committer | Paul Kehrer <paul.l.kehrer@gmail.com> | 2015-12-27 08:36:38 -0600 |
commit | e5f152b0a93b105cc32fe5adf06899f4f5cd0936 (patch) | |
tree | 1332ab20e70a057dc5fd5a69ab5144ed5fc76286 | |
parent | 28077b621390965fbe1bca3409691974c894251d (diff) | |
download | cryptography-e5f152b0a93b105cc32fe5adf06899f4f5cd0936.tar.gz cryptography-e5f152b0a93b105cc32fe5adf06899f4f5cd0936.tar.bz2 cryptography-e5f152b0a93b105cc32fe5adf06899f4f5cd0936.zip |
support CRL entry extension encoding in the RevokedCertificateBuilder
-rw-r--r-- | docs/x509/reference.rst | 12 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 80 | ||||
-rw-r--r-- | src/cryptography/x509/base.py | 15 | ||||
-rw-r--r-- | tests/test_x509_crlbuilder.py | 10 | ||||
-rw-r--r-- | tests/test_x509_revokedcertbuilder.py | 74 |
5 files changed, 187 insertions, 4 deletions
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 72fd44be..bbea490e 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -947,6 +947,16 @@ X.509 Revoked Certificate Builder :param time: The :class:`datetime.datetime` object (in UTC) that marks the revocation time for the certificate. + .. method:: add_extension(extension, critical) + + Adds an X.509 extension to this revoked certificate. + + :param extension: An instance of one of the + :ref:`CRL entry extensions <crl_entry_extensions>`. + + :param critical: Set to ``True`` if the extension must be understood and + handled. + .. method:: build(backend) Create a revoked certificate object using the provided backend. @@ -1956,6 +1966,8 @@ These classes may be present within a :class:`CertificatePolicies` instance. A list of integers. +.. _crl_entry_extensions: + CRL Entry Extensions ~~~~~~~~~~~~~~~~~~~~ diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index c0c9ebe2..6c7a6840 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -53,7 +53,7 @@ from cryptography.hazmat.primitives.ciphers.algorithms import ( from cryptography.hazmat.primitives.ciphers.modes import ( CBC, CFB, CFB8, CTR, ECB, GCM, OFB ) -from cryptography.x509.oid import ExtensionOID, NameOID +from cryptography.x509.oid import CRLEntryExtensionOID, ExtensionOID, NameOID _MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"]) @@ -164,6 +164,68 @@ def _encode_crl_number(backend, crl_number): return pp, r +_CRL_ENTRY_REASONFLAGS = { + x509.ReasonFlags.unspecified: 0, + x509.ReasonFlags.key_compromise: 1, + x509.ReasonFlags.ca_compromise: 2, + x509.ReasonFlags.affiliation_changed: 3, + x509.ReasonFlags.superseded: 4, + x509.ReasonFlags.cessation_of_operation: 5, + x509.ReasonFlags.certificate_hold: 6, + x509.ReasonFlags.remove_from_crl: 8, + x509.ReasonFlags.privilege_withdrawn: 9, + x509.ReasonFlags.aa_compromise: 10 +} + + +def _encode_crl_reason(backend, crl_reason): + asn1enum = backend._lib.ASN1_ENUMERATED_new() + backend.openssl_assert(asn1enum != backend._ffi.NULL) + asn1enum = backend._ffi.gc(asn1enum, backend._lib.ASN1_ENUMERATED_free) + res = backend._lib.ASN1_ENUMERATED_set( + asn1enum, _CRL_ENTRY_REASONFLAGS[crl_reason.reason] + ) + backend.openssl_assert(res == 1) + pp = backend._ffi.new('unsigned char **') + r = backend._lib.i2d_ASN1_ENUMERATED(asn1enum, pp) + backend.openssl_assert(r > 0) + pp = backend._ffi.gc( + pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + ) + return pp, r + + +def _encode_invalidity_date(backend, invalidity_date): + time = backend._lib.ASN1_GENERALIZEDTIME_set( + backend._ffi.NULL, calendar.timegm( + invalidity_date.invalidity_date.timetuple() + ) + ) + backend.openssl_assert(time != backend._ffi.NULL) + time = backend._ffi.gc(time, backend._lib.ASN1_GENERALIZEDTIME_free) + pp = backend._ffi.new('unsigned char **') + r = backend._lib.i2d_ASN1_GENERALIZEDTIME(time, pp) + backend.openssl_assert(r > 0) + pp = backend._ffi.gc( + pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + ) + return pp, r + + +def _encode_certificate_issuer(backend, certificate_issuer): + general_names = _encode_general_names(backend, certificate_issuer) + general_names = backend._ffi.gc( + general_names, backend._lib.GENERAL_NAMES_free + ) + pp = backend._ffi.new("unsigned char **") + r = backend._lib.i2d_GENERAL_NAMES(general_names, pp) + backend.openssl_assert(r > 0) + pp = backend._ffi.gc( + pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + ) + return pp, r + + def _encode_certificate_policies(backend, certificate_policies): cp = backend._lib.sk_POLICYINFO_new_null() backend.openssl_assert(cp != backend._ffi.NULL) @@ -645,6 +707,12 @@ _CRL_EXTENSION_ENCODE_HANDLERS = { ExtensionOID.CRL_NUMBER: _encode_crl_number, } +_CRL_ENTRY_EXTENSION_ENCODE_HANDLERS = { + CRLEntryExtensionOID.CERTIFICATE_ISSUER: _encode_certificate_issuer, + CRLEntryExtensionOID.CRL_REASON: _encode_crl_reason, + CRLEntryExtensionOID.INVALIDITY_DATE: _encode_invalidity_date, +} + class _PasswordUserdata(object): def __init__(self, password): @@ -1505,7 +1573,6 @@ class Backend(object): next_update = self._ffi.gc(next_update, self._lib.ASN1_TIME_free) res = self._lib.X509_CRL_set_nextUpdate(x509_crl, next_update) self.openssl_assert(res == 1) - # TODO: support revoked certificates # Add extensions. self._create_x509_extensions( @@ -1583,7 +1650,14 @@ class Backend(object): calendar.timegm(builder._revocation_date.timetuple()) ) self.openssl_assert(res != self._ffi.NULL) - # TODO: add crl entry extensions + # add CRL entry extensions + self._create_x509_extensions( + extensions=builder._extensions, + handlers=_CRL_ENTRY_EXTENSION_ENCODE_HANDLERS, + x509_obj=x509_revoked, + add_func=self._lib.X509_REVOKED_add_ext, + gc=True + ) return _RevokedCertificate(self, None, x509_revoked) def load_pem_private_key(self, data, password): diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py index bc927e87..55e965f7 100644 --- a/src/cryptography/x509/base.py +++ b/src/cryptography/x509/base.py @@ -650,6 +650,21 @@ class RevokedCertificateBuilder(object): self._serial_number, time, self._extensions ) + def add_extension(self, extension, critical): + if not isinstance(extension, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extension.oid, critical, extension) + + # TODO: This is quadratic in the number of extensions + for e in self._extensions: + if e.oid == extension.oid: + raise ValueError('This extension has already been set.') + return RevokedCertificateBuilder( + self._serial_number, self._revocation_date, + self._extensions + [extension] + ) + def build(self, backend): if self._serial_number is None: raise ValueError("A revoked certificate must have a serial number") diff --git a/tests/test_x509_crlbuilder.py b/tests/test_x509_crlbuilder.py index de3adcd4..763a6472 100644 --- a/tests/test_x509_crlbuilder.py +++ b/tests/test_x509_crlbuilder.py @@ -351,6 +351,9 @@ class TestCertificateRevocationListBuilder(object): 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( @@ -360,6 +363,8 @@ class TestCertificateRevocationListBuilder(object): 2 ).revocation_date( datetime.datetime(2012, 1, 1, 1, 1) + ).add_extension( + invalidity_date, False ).build(backend) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ @@ -384,4 +389,7 @@ class TestCertificateRevocationListBuilder(object): 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) == 0 + 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_revokedcertbuilder.py b/tests/test_x509_revokedcertbuilder.py index 9f79387b..0ef92ff6 100644 --- a/tests/test_x509_revokedcertbuilder.py +++ b/tests/test_x509_revokedcertbuilder.py @@ -48,6 +48,16 @@ class TestRevokedCertificateBuilder(object): 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 + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) def test_no_serial_number(self, backend): builder = x509.RevokedCertificateBuilder().revocation_date( @@ -78,3 +88,67 @@ class TestRevokedCertificateBuilder(object): 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 |