aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2015-03-17 13:14:03 -0500
committerPaul Kehrer <paul.l.kehrer@gmail.com>2015-03-28 12:33:34 -0500
commitfa56a23061c8b3431aa32b7ffbd05a38fa6f77e4 (patch)
tree6c884d20d1f165cf7c42548d4877f9ed4e44cd79
parent320050b92d98c9dd8f3949f04a13756a4018f85d (diff)
downloadcryptography-fa56a23061c8b3431aa32b7ffbd05a38fa6f77e4.tar.gz
cryptography-fa56a23061c8b3431aa32b7ffbd05a38fa6f77e4.tar.bz2
cryptography-fa56a23061c8b3431aa32b7ffbd05a38fa6f77e4.zip
basicConstraints support for OpenSSL X509 backend
-rw-r--r--docs/x509.rst34
-rw-r--r--src/cryptography/hazmat/backends/openssl/x509.py37
-rw-r--r--src/cryptography/x509.py13
-rw-r--r--tests/test_x509_ext.py108
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)
+ <Extension(oid=<ObjectIdentifier(oid=2.5.29.19, name=basicConstraints)>, critical=True, value=<BasicConstraints(ca=True, path_length=None)>)>
+
.. 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)
+ <Extension(oid=<ObjectIdentifier(oid=2.5.29.19, name=basicConstraints)>, critical=True, value=<BasicConstraints(ca=True, path_length=None)>)>
+
.. 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