From fa56a23061c8b3431aa32b7ffbd05a38fa6f77e4 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 17 Mar 2015 13:14:03 -0500 Subject: basicConstraints support for OpenSSL X509 backend --- docs/x509.rst | 34 +++++++ src/cryptography/hazmat/backends/openssl/x509.py | 37 +++++++- src/cryptography/x509.py | 13 +++ tests/test_x509_ext.py | 108 +++++++++++++++++++++++ 4 files changed, 188 insertions(+), 4 deletions(-) diff --git a/docs/x509.rst b/docs/x509.rst index 44d53a45..f17c3dae 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -205,6 +205,12 @@ X.509 Certificate Object :raises cryptography.x509.DuplicateExtension: If more than one extension of the same type is found within the certificate. + .. doctest:: + + >>> for ext in cert.extensions: + ... print(ext) + , critical=True, value=)> + .. class:: Name .. versionadded:: 0.8 @@ -292,6 +298,23 @@ X.509 Extensions An X.509 Extensions instance is an ordered list of extensions. The object is iterable to get every extension. + .. method:: get_extension_for_oid(oid) + + :param oid: An :class:`ObjectIdentifier` instance. + + :returns: An instance of the extension class. + + :raises cryptography.x509.ExtensionNotFound: If the certificate does + not have the extension requested. + + :raises cryptography.x509.UnsupportedExtension: If the certificate + contains an extension that is not supported. + + .. doctest:: + + >>> cert.extensions.get_extension_for_oid(x509.OID_BASIC_CONSTRAINTS) + , critical=True, value=)> + .. class:: Extension .. versionadded:: 0.9 @@ -546,6 +569,17 @@ Exceptions Returns the OID. +.. class:: ExtensionNotFound + + This is raised when calling :meth:`Extensions.get_extension_for_oid` with + an extension OID that is not present in the certificate. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns the OID. + .. _`public key infrastructure`: https://en.wikipedia.org/wiki/Public_key_infrastructure .. _`TLS`: https://en.wikipedia.org/wiki/Transport_Layer_Security diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 3502d122..1c9cf5cf 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -168,13 +168,16 @@ class _Certificate(object): raise x509.DuplicateExtension( "Duplicate {0} extension found".format(oid), oid ) - elif oid == x509.OID_BASIC_CONSTRAINTS and critical: + elif oid == x509.OID_BASIC_CONSTRAINTS: + value = self._build_basic_constraints(ext) + elif oid == x509.OID_KEY_USAGE and critical: # TODO: remove this obviously. warnings.warn( - "Extension support is not fully implemented. A basic " - "constraints extension with the critical flag was seen and" - " IGNORED." + "Extension support is not fully implemented. A key usage " + "extension with the critical flag was seen and IGNORED." ) + seen_oids.add(oid) + continue elif critical: raise x509.UnsupportedExtension( "{0} is not currently supported".format(oid), oid @@ -185,5 +188,31 @@ class _Certificate(object): continue seen_oids.add(oid) + extensions.append(x509.Extension(oid, critical, value)) return x509.Extensions(extensions) + + def _build_basic_constraints(self, ext): + bc_st = self._backend._lib.X509V3_EXT_d2i(ext) + assert bc_st != self._backend._ffi.NULL + basic_constraints = self._backend._ffi.cast( + "BASIC_CONSTRAINTS *", bc_st + ) + basic_constraints = self._backend._ffi.gc( + basic_constraints, self._backend._lib.BASIC_CONSTRAINTS_free + ) + # The byte representation of an ASN.1 boolean true is \xff. OpenSSL + # chooses to just map this to its ordinal value, so true is 255 and + # false is 0. + ca = basic_constraints.ca == 255 + if basic_constraints.pathlen == self._backend._ffi.NULL: + path_length = None + else: + bn = self._backend._lib.ASN1_INTEGER_to_BN( + basic_constraints.pathlen, self._backend._ffi.NULL + ) + assert bn != self._backend._ffi.NULL + bn = self._backend._ffi.gc(bn, self._backend._lib.BN_free) + path_length = self._backend._bn_to_int(bn) + + return x509.BasicConstraints(ca, path_length) diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 29602b3e..864736e8 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -78,6 +78,12 @@ class UnsupportedExtension(Exception): self.oid = oid +class ExtensionNotFound(Exception): + def __init__(self, msg, oid): + super(ExtensionNotFound, self).__init__(msg) + self.oid = oid + + class NameAttribute(object): def __init__(self, oid, value): if not isinstance(oid, ObjectIdentifier): @@ -163,6 +169,13 @@ class Extensions(object): def __init__(self, extensions): self._extensions = extensions + def get_extension_for_oid(self, oid): + for ext in self: + if ext.oid == oid: + return ext + + raise ExtensionNotFound("No {0} extension was found".format(oid), oid) + def __iter__(self): return iter(self._extensions) diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py index d8281526..324864d9 100644 --- a/tests/test_x509_ext.py +++ b/tests/test_x509_ext.py @@ -74,6 +74,23 @@ class TestExtensions(object): ext = cert.extensions assert len(ext) == 0 assert list(ext) == [] + with pytest.raises(x509.ExtensionNotFound) as exc: + ext.get_extension_for_oid(x509.OID_BASIC_CONSTRAINTS) + + assert exc.value.oid == x509.OID_BASIC_CONSTRAINTS + + def test_one_extension(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "basic_constraints_not_critical.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + extensions = cert.extensions + ext = extensions.get_extension_for_oid(x509.OID_BASIC_CONSTRAINTS) + assert ext is not None + assert ext.value.ca is False def test_duplicate_extension(self, backend): cert = _load_cert( @@ -112,3 +129,94 @@ class TestExtensions(object): ) extensions = cert.extensions assert len(extensions) == 0 + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRSABasicConstraintsExtension(object): + def test_ca_true_pathlen_6(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", "pathLenConstraint6CACert.crt" + ), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + x509.OID_BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is True + assert ext.value.ca is True + assert ext.value.path_length == 6 + + def test_path_length_zero(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "bc_path_length_zero.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + x509.OID_BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is True + assert ext.value.ca is True + assert ext.value.path_length == 0 + + def test_ca_true_no_pathlen(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + x509.OID_BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is True + assert ext.value.ca is True + assert ext.value.path_length is None + + def test_ca_false(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + x509.OID_BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is True + assert ext.value.ca is False + assert ext.value.path_length is None + + def test_no_basic_constraints(self, backend): + cert = _load_cert( + os.path.join( + "x509", + "PKITS_data", + "certs", + "ValidCertificatePathTest1EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + with pytest.raises(x509.ExtensionNotFound): + cert.extensions.get_extension_for_oid(x509.OID_BASIC_CONSTRAINTS) + + def test_basic_constraint_not_critical(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "basic_constraints_not_critical.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + x509.OID_BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is False + assert ext.value.ca is False -- cgit v1.2.3