From a4d5babf26b606c501f81f08712756871b29b65c Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 3 Aug 2015 21:54:43 +0100 Subject: support CRLDistributionPoints in the CertificateBuilder --- .../hazmat/backends/openssl/backend.py | 93 ++++++++++++++++ src/cryptography/x509.py | 4 + tests/test_x509.py | 118 +++++++++++++++++++++ 3 files changed, 215 insertions(+) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 0038ddb0..c2f4ba6d 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -359,6 +359,95 @@ def _encode_extended_key_usage(backend, extended_key_usage): return pp, r +def _encode_crl_distribution_points(backend, crl_distribution_points): + cdp = backend._lib.sk_DIST_POINT_new_null() + cdp = backend._ffi.gc(cdp, backend._lib.sk_DIST_POINT_free) + for point in crl_distribution_points: + dp = backend._lib.DIST_POINT_new() + assert dp != backend._ffi.NULL + + if point.reasons: + # TODO: determining reason flag is quadratic + bitmask = backend._lib.ASN1_BIT_STRING_new() + res = backend._lib.ASN1_BIT_STRING_set_bit( + bitmask, 1, x509.ReasonFlags.key_compromise in point.reasons + ) + assert res == 1 + res = backend._lib.ASN1_BIT_STRING_set_bit( + bitmask, 2, x509.ReasonFlags.ca_compromise in point.reasons + ) + assert res == 1 + res = backend._lib.ASN1_BIT_STRING_set_bit( + bitmask, + 3, + x509.ReasonFlags.affiliation_changed in point.reasons + ) + assert res == 1 + res = backend._lib.ASN1_BIT_STRING_set_bit( + bitmask, 4, x509.ReasonFlags.superseded in point.reasons + ) + assert res == 1 + res = backend._lib.ASN1_BIT_STRING_set_bit( + bitmask, + 5, + x509.ReasonFlags.cessation_of_operation in point.reasons + ) + assert res == 1 + res = backend._lib.ASN1_BIT_STRING_set_bit( + bitmask, 6, x509.ReasonFlags.certificate_hold in point.reasons + ) + assert res == 1 + res = backend._lib.ASN1_BIT_STRING_set_bit( + bitmask, + 7, + x509.ReasonFlags.privilege_withdrawn in point.reasons + ) + assert res == 1 + res = backend._lib.ASN1_BIT_STRING_set_bit( + bitmask, 8, x509.ReasonFlags.aa_compromise in point.reasons + ) + assert res == 1 + + dp.reasons = bitmask + + if point.full_name: + # Type 0 is fullName, there is no #define for it in the code. + dpn = backend._lib.DIST_POINT_NAME_new() + assert dpn != backend._ffi.NULL + dpn.type = 0 + for name in point.full_name: + gns = backend._lib.GENERAL_NAMES_new() + assert gns != backend._ffi.NULL + for name in point.full_name: + gn = _encode_general_name(backend, name) + res = backend._lib.sk_GENERAL_NAME_push(gns, gn) + assert res >= 1 + + dpn.name.fullname = gns + dp.distpoint = dpn + + if point.relative_name: + # TODO: don't duplicate this with fullname above + dpn = backend._lib.DIST_POINT_NAME_new() + assert dpn != backend._ffi.NULL + dpn.name.relativename = _encode_name(backend, point.relative_name) + dp.distpoint = dpn + + if point.crl_issuer: + dp.CRLissuer = _encode_general_names(backend, point.crl_issuer) + + res = backend._lib.sk_DIST_POINT_push(cdp, dp) + assert res >= 1 + + pp = backend._ffi.new('unsigned char **') + r = backend._lib.i2d_CRL_DIST_POINTS(cdp, pp) + assert r > 0 + pp = backend._ffi.gc( + pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + ) + return pp, r + + @utils.register_interface(CipherBackend) @utils.register_interface(CMACBackend) @utils.register_interface(DERSerializationBackend) @@ -1177,6 +1266,10 @@ class Backend(object): pp, r = _encode_authority_information_access( self, extension.value ) + elif isinstance(extension.value, x509.CRLDistributionPoints): + pp, r = _encode_crl_distribution_points( + self, extension.value + ) else: raise NotImplementedError('Extension not yet supported.') diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 08a0c7c9..0245fad4 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1780,6 +1780,10 @@ class CertificateBuilder(object): ) elif isinstance(extension, InhibitAnyPolicy): extension = Extension(OID_INHIBIT_ANY_POLICY, critical, extension) + elif isinstance(extension, CRLDistributionPoints): + extension = Extension( + OID_CRL_DISTRIBUTION_POINTS, critical, extension + ) else: raise NotImplementedError('Unsupported X.509 extension.') diff --git a/tests/test_x509.py b/tests/test_x509.py index 5e0342cb..71c29f75 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -1143,6 +1143,124 @@ class TestCertificateBuilder(object): with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA512(), backend) + @pytest.mark.parametrize( + "cdp", + [ + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), + ]) + )], + relative_name=None, + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + x509.OID_ORGANIZATION_NAME, + u"cryptography Testing" + ), + ]) + )], + ) + ]), + 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_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( + x509.OID_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 + ) + ]) + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_crl_distribution_points(self, backend, cdp): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + builder = x509.CertificateBuilder().serial_number( + 4444444 + ).issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + ])).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + cdp, + critical=False, + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + ext = cert.extensions.get_extension_for_oid( + x509.OID_CRL_DISTRIBUTION_POINTS + ) + assert ext.critical is False + assert ext.value == cdp + @pytest.mark.requires_backend_interface(interface=DSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_build_cert_with_dsa_private_key(self, backend): -- cgit v1.2.3 From 1cd8fee9c6fa746d395c88df1c6fd59d32280eb5 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 4 Aug 2015 07:55:40 +0100 Subject: add missing test --- tests/test_x509.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_x509.py b/tests/test_x509.py index 71c29f75..5fe6eb40 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -1146,6 +1146,31 @@ class TestCertificateBuilder(object): @pytest.mark.parametrize( "cdp", [ + 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" + ), + ]) + )], + ) + ]), x509.CRLDistributionPoints([ x509.DistributionPoint( full_name=[x509.DirectoryName( -- cgit v1.2.3 From bdf425cb183764c23aa7551909369aa8a57a2d65 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 4 Aug 2015 22:35:54 +0100 Subject: support relativename encoding using X509_NAME X509_NAME contains a STACK_OF(X509_NAME_ENTRY) which we duplicate --- src/cryptography/hazmat/backends/openssl/backend.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index c2f4ba6d..941f8c38 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -411,9 +411,9 @@ def _encode_crl_distribution_points(backend, crl_distribution_points): dp.reasons = bitmask if point.full_name: - # Type 0 is fullName, there is no #define for it in the code. dpn = backend._lib.DIST_POINT_NAME_new() assert dpn != backend._ffi.NULL + # Type 0 is fullName, there is no #define for it in the code. dpn.type = 0 for name in point.full_name: gns = backend._lib.GENERAL_NAMES_new() @@ -427,10 +427,13 @@ def _encode_crl_distribution_points(backend, crl_distribution_points): dp.distpoint = dpn if point.relative_name: - # TODO: don't duplicate this with fullname above dpn = backend._lib.DIST_POINT_NAME_new() assert dpn != backend._ffi.NULL - dpn.name.relativename = _encode_name(backend, point.relative_name) + dpn.type = 1 + name = _encode_name_gc(backend, point.relative_name) + relativename = backend._lib.sk_X509_NAME_ENTRY_dup(name.entries) + assert relativename != backend._ffi.NULL + dpn.name.relativename = relativename dp.distpoint = dpn if point.crl_issuer: -- cgit v1.2.3 From 93decfb272864655f0b88a119cf8d2986bfd656e Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 8 Aug 2015 06:50:39 -0500 Subject: switch ReasonFlags bit string setting to use a dict mapping --- .../hazmat/backends/openssl/backend.py | 58 +++++++--------------- 1 file changed, 18 insertions(+), 40 deletions(-) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 941f8c38..393ddfc8 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -359,6 +359,18 @@ def _encode_extended_key_usage(backend, extended_key_usage): return pp, r +_CRLREASONFLAGS = { + 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.privilege_withdrawn: 7, + x509.ReasonFlags.aa_compromise: 8, +} + + def _encode_crl_distribution_points(backend, crl_distribution_points): cdp = backend._lib.sk_DIST_POINT_new_null() cdp = backend._ffi.gc(cdp, backend._lib.sk_DIST_POINT_free) @@ -367,48 +379,14 @@ def _encode_crl_distribution_points(backend, crl_distribution_points): assert dp != backend._ffi.NULL if point.reasons: - # TODO: determining reason flag is quadratic bitmask = backend._lib.ASN1_BIT_STRING_new() - res = backend._lib.ASN1_BIT_STRING_set_bit( - bitmask, 1, x509.ReasonFlags.key_compromise in point.reasons - ) - assert res == 1 - res = backend._lib.ASN1_BIT_STRING_set_bit( - bitmask, 2, x509.ReasonFlags.ca_compromise in point.reasons - ) - assert res == 1 - res = backend._lib.ASN1_BIT_STRING_set_bit( - bitmask, - 3, - x509.ReasonFlags.affiliation_changed in point.reasons - ) - assert res == 1 - res = backend._lib.ASN1_BIT_STRING_set_bit( - bitmask, 4, x509.ReasonFlags.superseded in point.reasons - ) - assert res == 1 - res = backend._lib.ASN1_BIT_STRING_set_bit( - bitmask, - 5, - x509.ReasonFlags.cessation_of_operation in point.reasons - ) - assert res == 1 - res = backend._lib.ASN1_BIT_STRING_set_bit( - bitmask, 6, x509.ReasonFlags.certificate_hold in point.reasons - ) - assert res == 1 - res = backend._lib.ASN1_BIT_STRING_set_bit( - bitmask, - 7, - x509.ReasonFlags.privilege_withdrawn in point.reasons - ) - assert res == 1 - res = backend._lib.ASN1_BIT_STRING_set_bit( - bitmask, 8, x509.ReasonFlags.aa_compromise in point.reasons - ) - assert res == 1 - + assert bitmask != backend._ffi.NULL dp.reasons = bitmask + for reason in point.reasons: + res = backend._lib.ASN1_BIT_STRING_set_bit( + bitmask, _CRLREASONFLAGS[reason], 1 + ) + assert res == 1 if point.full_name: dpn = backend._lib.DIST_POINT_NAME_new() -- cgit v1.2.3 From c6cf8f384e3851548296cf874c3278e824585bb2 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 8 Aug 2015 09:47:44 -0500 Subject: modify a CRL encode test to have multiple full_names --- tests/test_x509.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_x509.py b/tests/test_x509.py index 5fe6eb40..4e763e36 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -1192,9 +1192,14 @@ class TestCertificateBuilder(object): ]), x509.CRLDistributionPoints([ x509.DistributionPoint( - full_name=[x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" - )], + 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, -- cgit v1.2.3 From 3b9b1f3fb2529b381d630b4ae4224a2a41f510a1 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 8 Aug 2015 09:47:56 -0500 Subject: remove a double for loop that made literally no sense --- src/cryptography/hazmat/backends/openssl/backend.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 393ddfc8..17448146 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -393,13 +393,12 @@ def _encode_crl_distribution_points(backend, crl_distribution_points): assert dpn != backend._ffi.NULL # Type 0 is fullName, there is no #define for it in the code. dpn.type = 0 + gns = backend._lib.GENERAL_NAMES_new() + assert gns != backend._ffi.NULL for name in point.full_name: - gns = backend._lib.GENERAL_NAMES_new() - assert gns != backend._ffi.NULL - for name in point.full_name: - gn = _encode_general_name(backend, name) - res = backend._lib.sk_GENERAL_NAME_push(gns, gn) - assert res >= 1 + gn = _encode_general_name(backend, name) + res = backend._lib.sk_GENERAL_NAME_push(gns, gn) + assert res >= 1 dpn.name.fullname = gns dp.distpoint = dpn -- cgit v1.2.3 From d298484f44929f85ac7951ded5270911df11f6c3 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 8 Aug 2015 09:50:41 -0500 Subject: switch to _encode_general_names. I knew I made that for a reason, thanks Alex --- src/cryptography/hazmat/backends/openssl/backend.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 17448146..7f90bd8f 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -393,14 +393,7 @@ def _encode_crl_distribution_points(backend, crl_distribution_points): assert dpn != backend._ffi.NULL # Type 0 is fullName, there is no #define for it in the code. dpn.type = 0 - gns = backend._lib.GENERAL_NAMES_new() - assert gns != backend._ffi.NULL - for name in point.full_name: - gn = _encode_general_name(backend, name) - res = backend._lib.sk_GENERAL_NAME_push(gns, gn) - assert res >= 1 - - dpn.name.fullname = gns + dpn.name.fullname = _encode_general_names(backend, point.full_name) dp.distpoint = dpn if point.relative_name: -- cgit v1.2.3 From 0777325061deb2773e74dfe242d5150d0ce8b378 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 8 Aug 2015 10:32:03 -0500 Subject: move distpoint fullname/relativename to consts in backends.openssl.x509 --- src/cryptography/hazmat/backends/openssl/backend.py | 8 ++++---- src/cryptography/hazmat/backends/openssl/x509.py | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 7f90bd8f..2752d98d 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -38,7 +38,8 @@ from cryptography.hazmat.backends.openssl.rsa import ( _RSAPrivateKey, _RSAPublicKey ) from cryptography.hazmat.backends.openssl.x509 import ( - _Certificate, _CertificateSigningRequest + _Certificate, _CertificateSigningRequest, _DISTPOINT_TYPE_FULLNAME, + _DISTPOINT_TYPE_RELATIVENAME ) from cryptography.hazmat.bindings.openssl.binding import Binding from cryptography.hazmat.primitives import hashes, serialization @@ -391,15 +392,14 @@ def _encode_crl_distribution_points(backend, crl_distribution_points): if point.full_name: dpn = backend._lib.DIST_POINT_NAME_new() assert dpn != backend._ffi.NULL - # Type 0 is fullName, there is no #define for it in the code. - dpn.type = 0 + dpn.type = _DISTPOINT_TYPE_FULLNAME dpn.name.fullname = _encode_general_names(backend, point.full_name) dp.distpoint = dpn if point.relative_name: dpn = backend._lib.DIST_POINT_NAME_new() assert dpn != backend._ffi.NULL - dpn.type = 1 + dpn.type = _DISTPOINT_TYPE_RELATIVENAME name = _encode_name_gc(backend, point.relative_name) relativename = backend._lib.sk_X509_NAME_ENTRY_dup(name.entries) assert relativename != backend._ffi.NULL diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index ee9a3bbf..564b2680 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -592,6 +592,10 @@ def _decode_extended_key_usage(backend, sk): return x509.ExtendedKeyUsage(ekus) +_DISTPOINT_TYPE_FULLNAME = 0 +_DISTPOINT_TYPE_RELATIVENAME = 1 + + def _decode_crl_distribution_points(backend, cdps): cdps = backend._ffi.cast("Cryptography_STACK_OF_DIST_POINT *", cdps) cdps = backend._ffi.gc(cdps, backend._lib.sk_DIST_POINT_free) @@ -651,7 +655,7 @@ def _decode_crl_distribution_points(backend, cdps): # point so make sure it's not null. if cdp.distpoint != backend._ffi.NULL: # Type 0 is fullName, there is no #define for it in the code. - if cdp.distpoint.type == 0: + if cdp.distpoint.type == _DISTPOINT_TYPE_FULLNAME: full_name = _decode_general_names( backend, cdp.distpoint.name.fullname ) -- cgit v1.2.3