aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.rst1
-rw-r--r--docs/x509/reference.rst23
-rw-r--r--src/cryptography/hazmat/backends/openssl/decode_asn1.py13
-rw-r--r--src/cryptography/hazmat/backends/openssl/encode_asn1.py7
-rw-r--r--src/cryptography/x509/__init__.py7
-rw-r--r--src/cryptography/x509/extensions.py41
-rw-r--r--tests/x509/test_x509.py32
-rw-r--r--tests/x509/test_x509_ext.py227
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(