aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.rst1
-rw-r--r--docs/x509/reference.rst71
-rw-r--r--src/cryptography/hazmat/backends/openssl/decode_asn1.py25
-rw-r--r--src/cryptography/x509/__init__.py7
-rw-r--r--src/cryptography/x509/extensions.py130
-rw-r--r--tests/x509/test_x509_ext.py288
6 files changed, 519 insertions, 3 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0cc468c5..b75836d4 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -18,6 +18,7 @@ Changelog
1.1.1.
* Added initial support for parsing PKCS12 files with
:func:`~cryptography.hazmat.primitives.serialization.pkcs12.load_key_and_certificates`.
+* Added support for :class:`~cryptography.x509.IssuingDistributionPoint`.
.. _v2-4-2:
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index 5e814916..15891059 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -2319,6 +2319,77 @@ X.509 Extensions
:type: int
+.. class:: IssuingDistributionPoint(full_name, relative_name,\
+ only_contains_user_certs, only_contains_ca_certs, only_some_reasons,\
+ indirect_crl, only_contains_attribute_certs)
+
+ .. versionadded:: 2.5
+
+ Issuing distribution point is a CRL extension that identifies the CRL
+ distribution point and scope for a particular CRL. It indicates whether
+ the CRL covers revocation for end entity certificates only, CA certificates
+ only, attribute certificates only, or a limited set of reason codes. For
+ specific details on the way this extension should be processed see
+ :rfc:`5280`.
+
+ .. attribute:: oid
+
+ :type: :class:`ObjectIdentifier`
+
+ Returns
+ :attr:`~cryptography.x509.oid.ExtensionOID.ISSUING_DISTRIBUTION_POINT`.
+
+ .. attribute:: only_contains_user_certs
+
+ :type: bool
+
+ Set to ``True`` if the CRL this extension is embedded within only
+ contains information about user certificates.
+
+ .. attribute:: only_contains_ca_certs
+
+ :type: bool
+
+ Set to ``True`` if the CRL this extension is embedded within only
+ contains information about CA certificates.
+
+ .. attribute:: indirect_crl
+
+ :type: bool
+
+ Set to ``True`` if the CRL this extension is embedded within includes
+ certificates issued by one or more authorities other than the CRL
+ issuer.
+
+ .. attribute:: only_contains_attribute_certs
+
+ :type: bool
+
+ Set to ``True`` if the CRL this extension is embedded within only
+ contains information about attribute certificates.
+
+ .. attribute:: only_some_reasons
+
+ :type: frozenset of :class:`ReasonFlags` or None
+
+ The reasons for which the issuing distribution point is valid. None
+ indicates that it is valid for all reasons.
+
+ .. attribute:: full_name
+
+ :type: list of :class:`GeneralName` instances or None
+
+ This field describes methods to retrieve the CRL. At most one of
+ ``full_name`` or ``relative_name`` will be non-None.
+
+ .. attribute:: relative_name
+
+ :type: :class:`RelativeDistinguishedName` or None
+
+ This field describes methods to retrieve the CRL relative to the CRL
+ issuer. At most one of ``full_name`` or ``relative_name`` will be
+ non-None.
+
.. class:: UnrecognizedExtension
.. versionadded:: 1.2
diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py
index e06e8cd6..007675d4 100644
--- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py
+++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py
@@ -464,6 +464,30 @@ def _decode_general_subtrees(backend, stack_subtrees):
return subtrees
+def _decode_issuing_dist_point(backend, idp):
+ idp = backend._ffi.cast("ISSUING_DIST_POINT *", idp)
+ idp = backend._ffi.gc(idp, backend._lib.ISSUING_DIST_POINT_free)
+ if idp.distpoint != backend._ffi.NULL:
+ full_name, relative_name = _decode_distpoint(backend, idp.distpoint)
+ else:
+ full_name = None
+ relative_name = None
+
+ only_user = idp.onlyuser == 255
+ only_ca = idp.onlyCA == 255
+ indirect_crl = idp.indirectCRL == 255
+ only_attr = idp.onlyattr == 255
+ if idp.onlysomereasons != backend._ffi.NULL:
+ only_some_reasons = _decode_reasons(backend, idp.onlysomereasons)
+ else:
+ only_some_reasons = None
+
+ return x509.IssuingDistributionPoint(
+ full_name, relative_name, only_user, only_ca, only_some_reasons,
+ indirect_crl, only_attr
+ )
+
+
def _decode_policy_constraints(backend, pc):
pc = backend._ffi.cast("POLICY_CONSTRAINTS *", pc)
pc = backend._ffi.gc(pc, backend._lib.POLICY_CONSTRAINTS_free)
@@ -814,6 +838,7 @@ _CRL_EXTENSION_HANDLERS = {
ExtensionOID.AUTHORITY_INFORMATION_ACCESS: (
_decode_authority_information_access
),
+ ExtensionOID.ISSUING_DISTRIBUTION_POINT: _decode_issuing_dist_point,
}
_OCSP_REQ_EXTENSION_HANDLERS = {
diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py
index fd019455..b761e264 100644
--- a/src/cryptography/x509/__init__.py
+++ b/src/cryptography/x509/__init__.py
@@ -21,9 +21,9 @@ from cryptography.x509.extensions import (
DeltaCRLIndicator, DistributionPoint, DuplicateExtension, ExtendedKeyUsage,
Extension, ExtensionNotFound, ExtensionType, Extensions, FreshestCRL,
GeneralNames, InhibitAnyPolicy, InvalidityDate, IssuerAlternativeName,
- KeyUsage, NameConstraints, NoticeReference, OCSPNoCheck, OCSPNonce,
- PolicyConstraints, PolicyInformation, PrecertPoison,
- PrecertificateSignedCertificateTimestamps, ReasonFlags,
+ IssuingDistributionPoint, KeyUsage, NameConstraints, NoticeReference,
+ OCSPNoCheck, OCSPNonce, PolicyConstraints, PolicyInformation,
+ PrecertPoison, PrecertificateSignedCertificateTimestamps, ReasonFlags,
SubjectAlternativeName, SubjectKeyIdentifier, TLSFeature, TLSFeatureType,
UnrecognizedExtension, UserNotice
)
@@ -134,6 +134,7 @@ __all__ = [
"Extension",
"ExtendedKeyUsage",
"FreshestCRL",
+ "IssuingDistributionPoint",
"TLSFeature",
"TLSFeatureType",
"OCSPNoCheck",
diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py
index fc5c17a9..12071b66 100644
--- a/src/cryptography/x509/extensions.py
+++ b/src/cryptography/x509/extensions.py
@@ -1447,6 +1447,136 @@ class OCSPNonce(object):
@utils.register_interface(ExtensionType)
+class IssuingDistributionPoint(object):
+ oid = ExtensionOID.ISSUING_DISTRIBUTION_POINT
+
+ def __init__(self, full_name, relative_name, only_contains_user_certs,
+ only_contains_ca_certs, only_some_reasons, indirect_crl,
+ only_contains_attribute_certs):
+ if (
+ only_some_reasons and (
+ not isinstance(only_some_reasons, frozenset) or not all(
+ isinstance(x, ReasonFlags) for x in only_some_reasons
+ )
+ )
+ ):
+ raise TypeError(
+ "only_some_reasons must be None or frozenset of ReasonFlags"
+ )
+
+ if only_some_reasons and (
+ ReasonFlags.unspecified in only_some_reasons or
+ ReasonFlags.remove_from_crl in only_some_reasons
+ ):
+ raise ValueError(
+ "unspecified and remove_from_crl are not valid reasons in an "
+ "IssuingDistributionPoint"
+ )
+
+ if not (
+ isinstance(only_contains_user_certs, bool) and
+ isinstance(only_contains_ca_certs, bool) and
+ isinstance(indirect_crl, bool) and
+ isinstance(only_contains_attribute_certs, bool)
+ ):
+ raise TypeError(
+ "only_contains_user_certs, only_contains_ca_certs, "
+ "indirect_crl and only_contains_attribute_certs "
+ "must all be boolean."
+ )
+
+ crl_constraints = [
+ only_contains_user_certs, only_contains_ca_certs,
+ indirect_crl, only_contains_attribute_certs
+ ]
+
+ if len([x for x in crl_constraints if x]) > 1:
+ raise ValueError(
+ "Only one of the following can be set to True: "
+ "only_contains_user_certs, only_contains_ca_certs, "
+ "indirect_crl, only_contains_attribute_certs"
+ )
+
+ if (
+ not any([
+ only_contains_user_certs, only_contains_ca_certs,
+ indirect_crl, only_contains_attribute_certs, full_name,
+ relative_name, only_some_reasons
+ ])
+ ):
+ raise ValueError(
+ "Cannot create empty extension: "
+ "if only_contains_user_certs, only_contains_ca_certs, "
+ "indirect_crl, and only_contains_attribute_certs are all False"
+ ", then either full_name, relative_name, or only_some_reasons "
+ "must have a value."
+ )
+
+ self._only_contains_user_certs = only_contains_user_certs
+ self._only_contains_ca_certs = only_contains_ca_certs
+ self._indirect_crl = indirect_crl
+ self._only_contains_attribute_certs = only_contains_attribute_certs
+ self._only_some_reasons = only_some_reasons
+ self._full_name = full_name
+ self._relative_name = relative_name
+
+ def __repr__(self):
+ return (
+ "<IssuingDistributionPoint(full_name={0.full_name}, "
+ "relative_name={0.relative_name}, "
+ "only_contains_user_certs={0.only_contains_user_certs}, "
+ "only_contains_ca_certs={0.only_contains_ca_certs}, "
+ "only_some_reasons={0.only_some_reasons}, "
+ "indirect_crl={0.indirect_crl}, "
+ "only_contains_attribute_certs="
+ "{0.only_contains_attribute_certs})>".format(self)
+ )
+
+ def __eq__(self, other):
+ if not isinstance(other, IssuingDistributionPoint):
+ return NotImplemented
+
+ return (
+ self.full_name == other.full_name and
+ self.relative_name == other.relative_name and
+ self.only_contains_user_certs == other.only_contains_user_certs and
+ self.only_contains_ca_certs == other.only_contains_ca_certs and
+ self.only_some_reasons == other.only_some_reasons and
+ self.indirect_crl == other.indirect_crl and
+ self.only_contains_attribute_certs ==
+ other.only_contains_attribute_certs
+ )
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ return hash((
+ self.full_name,
+ self.relative_name,
+ self.only_contains_user_certs,
+ self.only_contains_ca_certs,
+ self.only_some_reasons,
+ self.indirect_crl,
+ self.only_contains_attribute_certs,
+ ))
+
+ full_name = utils.read_only_property("_full_name")
+ relative_name = utils.read_only_property("_relative_name")
+ only_contains_user_certs = utils.read_only_property(
+ "_only_contains_user_certs"
+ )
+ only_contains_ca_certs = utils.read_only_property(
+ "_only_contains_ca_certs"
+ )
+ only_some_reasons = utils.read_only_property("_only_some_reasons")
+ indirect_crl = utils.read_only_property("_indirect_crl")
+ only_contains_attribute_certs = utils.read_only_property(
+ "_only_contains_attribute_certs"
+ )
+
+
+@utils.register_interface(ExtensionType)
class UnrecognizedExtension(object):
def __init__(self, oid, value):
if not isinstance(oid, ObjectIdentifier):
diff --git a/tests/x509/test_x509_ext.py b/tests/x509/test_x509_ext.py
index 9eac9a27..5ff3bdd6 100644
--- a/tests/x509/test_x509_ext.py
+++ b/tests/x509/test_x509_ext.py
@@ -4440,6 +4440,294 @@ class TestInhibitAnyPolicyExtension(object):
assert iap.skip_certs == 5
+class TestIssuingDistributionPointExtension(object):
+ @pytest.mark.parametrize(
+ ("filename", "expected"),
+ [
+ (
+ "crl_idp_fullname_indirect_crl.pem",
+ x509.IssuingDistributionPoint(
+ full_name=[
+ x509.UniformResourceIdentifier(
+ u"http://myhost.com/myca.crl")
+ ],
+ relative_name=None,
+ only_contains_user_certs=False,
+ only_contains_ca_certs=False,
+ only_some_reasons=None,
+ indirect_crl=True,
+ only_contains_attribute_certs=False,
+ )
+ ),
+ (
+ "crl_idp_fullname_only.pem",
+ x509.IssuingDistributionPoint(
+ full_name=[
+ x509.UniformResourceIdentifier(
+ u"http://myhost.com/myca.crl")
+ ],
+ relative_name=None,
+ only_contains_user_certs=False,
+ only_contains_ca_certs=False,
+ only_some_reasons=None,
+ indirect_crl=False,
+ only_contains_attribute_certs=False,
+ )
+ ),
+ (
+ "crl_idp_fullname_only_aa.pem",
+ x509.IssuingDistributionPoint(
+ full_name=[
+ x509.UniformResourceIdentifier(
+ u"http://myhost.com/myca.crl")
+ ],
+ relative_name=None,
+ only_contains_user_certs=False,
+ only_contains_ca_certs=False,
+ only_some_reasons=None,
+ indirect_crl=False,
+ only_contains_attribute_certs=True,
+ )
+ ),
+ (
+ "crl_idp_fullname_only_user.pem",
+ x509.IssuingDistributionPoint(
+ full_name=[
+ x509.UniformResourceIdentifier(
+ u"http://myhost.com/myca.crl")
+ ],
+ relative_name=None,
+ only_contains_user_certs=True,
+ only_contains_ca_certs=False,
+ only_some_reasons=None,
+ indirect_crl=False,
+ only_contains_attribute_certs=False,
+ )
+ ),
+ (
+ "crl_idp_only_ca.pem",
+ x509.IssuingDistributionPoint(
+ full_name=None,
+ relative_name=x509.RelativeDistinguishedName([
+ x509.NameAttribute(
+ oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA"
+ )
+ ]),
+ only_contains_user_certs=False,
+ only_contains_ca_certs=True,
+ only_some_reasons=None,
+ indirect_crl=False,
+ only_contains_attribute_certs=False,
+ )
+ ),
+ (
+ "crl_idp_reasons_only.pem",
+ x509.IssuingDistributionPoint(
+ full_name=None,
+ relative_name=None,
+ only_contains_user_certs=False,
+ only_contains_ca_certs=False,
+ only_some_reasons=frozenset([
+ x509.ReasonFlags.key_compromise
+ ]),
+ indirect_crl=False,
+ only_contains_attribute_certs=False,
+ )
+ ),
+ (
+ "crl_idp_relative_user_all_reasons.pem",
+ x509.IssuingDistributionPoint(
+ full_name=None,
+ relative_name=x509.RelativeDistinguishedName([
+ x509.NameAttribute(
+ oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA"
+ )
+ ]),
+ only_contains_user_certs=True,
+ only_contains_ca_certs=False,
+ only_some_reasons=frozenset([
+ x509.ReasonFlags.key_compromise,
+ x509.ReasonFlags.ca_compromise,
+ x509.ReasonFlags.affiliation_changed,
+ x509.ReasonFlags.superseded,
+ x509.ReasonFlags.cessation_of_operation,
+ x509.ReasonFlags.certificate_hold,
+ x509.ReasonFlags.privilege_withdrawn,
+ x509.ReasonFlags.aa_compromise,
+ ]),
+ indirect_crl=False,
+ only_contains_attribute_certs=False,
+ )
+ ),
+ (
+ "crl_idp_relativename_only.pem",
+ x509.IssuingDistributionPoint(
+ full_name=None,
+ relative_name=x509.RelativeDistinguishedName([
+ x509.NameAttribute(
+ oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA"
+ )
+ ]),
+ only_contains_user_certs=False,
+ only_contains_ca_certs=False,
+ only_some_reasons=None,
+ indirect_crl=False,
+ only_contains_attribute_certs=False,
+ )
+ ),
+ ]
+ )
+ @pytest.mark.requires_backend_interface(interface=RSABackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_vectors(self, filename, expected, backend):
+ crl = _load_cert(
+ os.path.join("x509", "custom", filename),
+ x509.load_pem_x509_crl, backend
+ )
+ idp = crl.extensions.get_extension_for_class(
+ x509.IssuingDistributionPoint
+ ).value
+ assert idp == expected
+
+ @pytest.mark.parametrize(
+ (
+ "error", "only_contains_user_certs", "only_contains_ca_certs",
+ "indirect_crl", "only_contains_attribute_certs",
+ "only_some_reasons", "full_name", "relative_name"
+ ),
+ [
+ (
+ TypeError, False, False, False, False, 'notafrozenset', None,
+ None
+ ),
+ (
+ TypeError, False, False, False, False, frozenset(['bad']),
+ None, None
+ ),
+ (
+ ValueError, False, False, False, False,
+ frozenset([x509.ReasonFlags.unspecified]), None, None
+ ),
+ (
+ ValueError, False, False, False, False,
+ frozenset([x509.ReasonFlags.remove_from_crl]), None, None
+ ),
+ (TypeError, 'notabool', False, False, False, None, None, None),
+ (TypeError, False, 'notabool', False, False, None, None, None),
+ (TypeError, False, False, 'notabool', False, None, None, None),
+ (TypeError, False, False, False, 'notabool', None, None, None),
+ (ValueError, True, True, False, False, None, None, None),
+ (ValueError, False, False, True, True, None, None, None),
+ (ValueError, False, False, False, False, None, None, None),
+ ]
+ )
+ def test_invalid_init(self, error, only_contains_user_certs,
+ only_contains_ca_certs, indirect_crl,
+ only_contains_attribute_certs, only_some_reasons,
+ full_name, relative_name):
+ with pytest.raises(error):
+ x509.IssuingDistributionPoint(
+ full_name, relative_name, only_contains_user_certs,
+ only_contains_ca_certs, only_some_reasons, indirect_crl,
+ only_contains_attribute_certs
+ )
+
+ def test_repr(self):
+ idp = x509.IssuingDistributionPoint(
+ None, None, False, False,
+ frozenset([x509.ReasonFlags.key_compromise]), False, False
+ )
+ if not six.PY2:
+ assert repr(idp) == (
+ "<IssuingDistributionPoint(full_name=None, relative_name=None,"
+ " only_contains_user_certs=False, only_contains_ca_certs=False"
+ ", only_some_reasons=frozenset({<ReasonFlags.key_compromise: '"
+ "keyCompromise'>}), indirect_crl=False, only_contains_attribut"
+ "e_certs=False)>"
+ )
+ else:
+ assert repr(idp) == (
+ "<IssuingDistributionPoint(full_name=None, relative_name=None,"
+ " only_contains_user_certs=False, only_contains_ca_certs=False"
+ ", only_some_reasons=frozenset([<ReasonFlags.key_compromise: '"
+ "keyCompromise'>]), indirect_crl=False, only_contains_attribut"
+ "e_certs=False)>"
+ )
+
+ def test_eq(self):
+ idp1 = x509.IssuingDistributionPoint(
+ only_contains_user_certs=False,
+ only_contains_ca_certs=False,
+ indirect_crl=False,
+ only_contains_attribute_certs=False,
+ only_some_reasons=None,
+ full_name=None,
+ relative_name=x509.RelativeDistinguishedName([
+ x509.NameAttribute(
+ oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA")
+ ])
+ )
+ idp2 = x509.IssuingDistributionPoint(
+ only_contains_user_certs=False,
+ only_contains_ca_certs=False,
+ indirect_crl=False,
+ only_contains_attribute_certs=False,
+ only_some_reasons=None,
+ full_name=None,
+ relative_name=x509.RelativeDistinguishedName([
+ x509.NameAttribute(
+ oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA")
+ ])
+ )
+ assert idp1 == idp2
+
+ def test_ne(self):
+ idp1 = x509.IssuingDistributionPoint(
+ only_contains_user_certs=False,
+ only_contains_ca_certs=False,
+ indirect_crl=False,
+ only_contains_attribute_certs=False,
+ only_some_reasons=None,
+ full_name=None,
+ relative_name=x509.RelativeDistinguishedName([
+ x509.NameAttribute(
+ oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA")
+ ])
+ )
+ idp2 = x509.IssuingDistributionPoint(
+ only_contains_user_certs=True,
+ only_contains_ca_certs=False,
+ indirect_crl=False,
+ only_contains_attribute_certs=False,
+ only_some_reasons=None,
+ full_name=None,
+ relative_name=x509.RelativeDistinguishedName([
+ x509.NameAttribute(
+ oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA")
+ ])
+ )
+ assert idp1 != idp2
+ assert idp1 != object()
+
+ def test_hash(self):
+ idp1 = x509.IssuingDistributionPoint(
+ None, None, True, False, None, False, False
+ )
+ idp2 = x509.IssuingDistributionPoint(
+ None, None, True, False, None, False, False
+ )
+ idp3 = x509.IssuingDistributionPoint(
+ None,
+ x509.RelativeDistinguishedName([
+ x509.NameAttribute(
+ oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA")
+ ]),
+ True, False, None, False, False
+ )
+ assert hash(idp1) == hash(idp2)
+ assert hash(idp1) != hash(idp3)
+
+
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
class TestPrecertPoisonExtension(object):