diff options
-rw-r--r-- | CHANGELOG.rst | 1 | ||||
-rw-r--r-- | docs/x509/reference.rst | 23 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/decode_asn1.py | 13 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/encode_asn1.py | 7 | ||||
-rw-r--r-- | src/cryptography/x509/__init__.py | 7 | ||||
-rw-r--r-- | src/cryptography/x509/extensions.py | 41 | ||||
-rw-r--r-- | tests/x509/test_x509.py | 32 | ||||
-rw-r--r-- | tests/x509/test_x509_ext.py | 227 |
8 files changed, 344 insertions, 7 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d239d754..a56c67b9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -45,6 +45,7 @@ Changelog * Add support for the :class:`~cryptography.x509.TLSFeature` extension. This is commonly used for enabling ``OCSP Must-Staple`` in certificates. +* Add support for the :class:`~cryptography.x509.FreshestCRL` extension. .. _v2-0-3: diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index dea7ee3b..951e6b7d 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -2027,6 +2027,24 @@ X.509 Extensions Where to access the information defined by the access method. +.. class:: FreshestCRL(distribution_points) + + .. versionadded:: 2.1 + + The freshest CRL extension (also known as Delta CRL Distribution Point) + identifies how delta CRL information is obtained. It is an iterable, + containing one or more :class:`DistributionPoint` instances. + + :param list distribution_points: A list of :class:`DistributionPoint` + instances. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.FRESHEST_CRL`. + .. class:: CRLDistributionPoints(distribution_points) .. versionadded:: 0.9 @@ -2792,6 +2810,11 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"2.5.29.36"``. The identifier for the :class:`~cryptography.x509.PolicyConstraints` extension type. + .. attribute:: FRESHEST_CRL + + Corresponds to the dotted string ``"2.5.29.46"``. The identifier for the + :class:`~cryptography.x509.FreshestCRL` extension type. + .. class:: CRLEntryExtensionOID diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py index 1326a94e..ec55a9e8 100644 --- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py @@ -463,7 +463,7 @@ _DISTPOINT_TYPE_FULLNAME = 0 _DISTPOINT_TYPE_RELATIVENAME = 1 -def _decode_crl_distribution_points(backend, cdps): +def _decode_dist_points(backend, cdps): cdps = backend._ffi.cast("Cryptography_STACK_OF_DIST_POINT *", cdps) cdps = backend._ffi.gc(cdps, backend._lib.CRL_DIST_POINTS_free) @@ -554,9 +554,19 @@ def _decode_crl_distribution_points(backend, cdps): ) ) + return dist_points + + +def _decode_crl_distribution_points(backend, cdps): + dist_points = _decode_dist_points(backend, cdps) return x509.CRLDistributionPoints(dist_points) +def _decode_freshest_crl(backend, cdps): + dist_points = _decode_dist_points(backend, cdps) + return x509.FreshestCRL(dist_points) + + def _decode_inhibit_any_policy(backend, asn1_int): asn1_int = backend._ffi.cast("ASN1_INTEGER *", asn1_int) asn1_int = backend._ffi.gc(asn1_int, backend._lib.ASN1_INTEGER_free) @@ -728,6 +738,7 @@ _EXTENSION_HANDLERS_NO_SCT = { ), ExtensionOID.CERTIFICATE_POLICIES: _decode_certificate_policies, ExtensionOID.CRL_DISTRIBUTION_POINTS: _decode_crl_distribution_points, + ExtensionOID.FRESHEST_CRL: _decode_freshest_crl, ExtensionOID.OCSP_NO_CHECK: _decode_ocsp_no_check, ExtensionOID.INHIBIT_ANY_POLICY: _decode_inhibit_any_policy, ExtensionOID.ISSUER_ALTERNATIVE_NAME: _decode_issuer_alt_name, diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py index 5ceb29c0..6b867683 100644 --- a/src/cryptography/hazmat/backends/openssl/encode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/encode_asn1.py @@ -484,10 +484,10 @@ _CRLREASONFLAGS = { } -def _encode_crl_distribution_points(backend, crl_distribution_points): +def _encode_cdps_freshest_crl(backend, cdps): 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: + for point in cdps: dp = backend._lib.DIST_POINT_new() backend.openssl_assert(dp != backend._ffi.NULL) @@ -585,7 +585,8 @@ _EXTENSION_ENCODE_HANDLERS = { ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( _encode_authority_information_access ), - ExtensionOID.CRL_DISTRIBUTION_POINTS: _encode_crl_distribution_points, + ExtensionOID.CRL_DISTRIBUTION_POINTS: _encode_cdps_freshest_crl, + ExtensionOID.FRESHEST_CRL: _encode_cdps_freshest_crl, ExtensionOID.INHIBIT_ANY_POLICY: _encode_inhibit_any_policy, ExtensionOID.OCSP_NO_CHECK: _encode_ocsp_nocheck, ExtensionOID.NAME_CONSTRAINTS: _encode_name_constraints, diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index e168adb7..224c9af6 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -19,9 +19,9 @@ from cryptography.x509.extensions import ( AuthorityKeyIdentifier, BasicConstraints, CRLDistributionPoints, CRLNumber, CRLReason, CertificateIssuer, CertificatePolicies, DeltaCRLIndicator, DistributionPoint, DuplicateExtension, ExtendedKeyUsage, - Extension, ExtensionNotFound, ExtensionType, Extensions, GeneralNames, - InhibitAnyPolicy, InvalidityDate, IssuerAlternativeName, KeyUsage, - NameConstraints, NoticeReference, OCSPNoCheck, PolicyConstraints, + Extension, ExtensionNotFound, ExtensionType, Extensions, FreshestCRL, + GeneralNames, InhibitAnyPolicy, InvalidityDate, IssuerAlternativeName, + KeyUsage, NameConstraints, NoticeReference, OCSPNoCheck, PolicyConstraints, PolicyInformation, PrecertificateSignedCertificateTimestamps, ReasonFlags, SubjectAlternativeName, SubjectKeyIdentifier, TLSFeature, TLSFeatureType, UnrecognizedExtension, UserNotice @@ -131,6 +131,7 @@ __all__ = [ "Extensions", "Extension", "ExtendedKeyUsage", + "FreshestCRL", "TLSFeature", "TLSFeatureType", "OCSPNoCheck", diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py index beb20bad..eb4b927f 100644 --- a/src/cryptography/x509/extensions.py +++ b/src/cryptography/x509/extensions.py @@ -444,6 +444,47 @@ class CRLDistributionPoints(object): return hash(tuple(self._distribution_points)) +@utils.register_interface(ExtensionType) +class FreshestCRL(object): + oid = ExtensionOID.FRESHEST_CRL + + def __init__(self, distribution_points): + distribution_points = list(distribution_points) + if not all( + isinstance(x, DistributionPoint) for x in distribution_points + ): + raise TypeError( + "distribution_points must be a list of DistributionPoint " + "objects" + ) + + self._distribution_points = distribution_points + + def __iter__(self): + return iter(self._distribution_points) + + def __len__(self): + return len(self._distribution_points) + + def __repr__(self): + return "<FreshestCRL({0})>".format(self._distribution_points) + + def __eq__(self, other): + if not isinstance(other, FreshestCRL): + return NotImplemented + + return self._distribution_points == other._distribution_points + + def __ne__(self, other): + return not self == other + + def __getitem__(self, idx): + return self._distribution_points[idx] + + def __hash__(self): + return hash(tuple(self._distribution_points)) + + class DistributionPoint(object): def __init__(self, full_name, relative_name, reasons, crl_issuer): if full_name and relative_name: diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index d0ce46d8..06aef666 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -2406,6 +2406,38 @@ class TestCertificateBuilder(object): 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, + ) + ]), ] ) def test_ext(self, add_ext, backend): diff --git a/tests/x509/test_x509_ext.py b/tests/x509/test_x509_ext.py index 9f0b1b0b..11e06eaf 100644 --- a/tests/x509/test_x509_ext.py +++ b/tests/x509/test_x509_ext.py @@ -3700,6 +3700,193 @@ class TestDistributionPoint(object): 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(b"http://domain")], + None, None, None + ), + ]) + assert len(fcrl) == 1 + assert list(fcrl) == [ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(b"http://domain")], + None, None, None + ), + ] + + def test_iter_input(self): + points = [ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(b"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(b"ftp://domain")], + None, + frozenset([x509.ReasonFlags.key_compromise]), + None + ), + ]) + if six.PY3: + assert repr(fcrl) == ( + "<FreshestCRL([<DistributionPoint(full_name=[<Unifo" + "rmResourceIdentifier(bytes_value=b'ftp://domain')>], relative" + "_name=None, reasons=frozenset({<ReasonFlags.key_compromise: " + "'keyCompromise'>}), crl_issuer=None)>])>" + ) + else: + assert repr(fcrl) == ( + "<FreshestCRL([<DistributionPoint(full_name=[<Unifo" + "rmResourceIdentifier(bytes_value='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(b"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(b"uri://thing")], + ), + ]) + fcrl2 = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(b"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(b"uri://thing")], + ), + ]) + assert fcrl == fcrl2 + + def test_ne(self): + fcrl = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(b"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(b"uri://thing")], + ), + ]) + fcrl2 = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(b"ftp://domain2")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(b"uri://thing")], + ), + ]) + fcrl3 = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(b"ftp://domain")], + None, + frozenset([x509.ReasonFlags.key_compromise]), + [x509.UniformResourceIdentifier(b"uri://thing")], + ), + ]) + fcrl4 = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(b"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(b"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(b"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(b"uri://thing")], + ), + ]) + fcrl2 = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(b"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(b"uri://thing")], + ), + ]) + fcrl3 = x509.FreshestCRL([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(b"ftp://domain")], + None, + frozenset([x509.ReasonFlags.key_compromise]), + [x509.UniformResourceIdentifier(b"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(b"uri://thing")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(b"uri://thing2")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(b"uri://thing3")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(b"uri://thing4")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(b"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): @@ -4152,6 +4339,46 @@ class TestCRLDistributionPointsExtension(object): @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( + b'http://myhost.com/myca.crl' + ), + x509.UniformResourceIdentifier( + b'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( |