aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2015-03-23 21:49:25 -0500
committerAlex Gaynor <alex.gaynor@gmail.com>2015-03-23 21:49:25 -0500
commit8c8ee123cbe76fc7cdfca9c9313b16e11059b511 (patch)
treeff769206a473cd59c12979efc4530b7cf2c7e191
parenta1f968aec9fa396739fbe0280c60262a8fbb6675 (diff)
parent5553d576f3bc3f65b84de99a2561360f82fc110f (diff)
downloadcryptography-8c8ee123cbe76fc7cdfca9c9313b16e11059b511.tar.gz
cryptography-8c8ee123cbe76fc7cdfca9c9313b16e11059b511.tar.bz2
cryptography-8c8ee123cbe76fc7cdfca9c9313b16e11059b511.zip
Merge pull request #1768 from reaperhulk/basic-constraints
basic constraints class & extensions interface
-rw-r--r--docs/x509.rst65
-rw-r--r--src/cryptography/x509.py54
-rw-r--r--tests/test_x509_ext.py57
3 files changed, 176 insertions, 0 deletions
diff --git a/docs/x509.rst b/docs/x509.rst
index 27f1d544..13218914 100644
--- a/docs/x509.rst
+++ b/docs/x509.rst
@@ -273,6 +273,61 @@ X.509 Certificate Object
The dotted string value of the OID (e.g. ``"2.5.4.3"``)
+X.509 Extensions
+~~~~~~~~~~~~~~~~
+
+.. class:: Extension
+
+ .. versionadded:: 0.9
+
+ .. attribute:: oid
+
+ :type: :class:`ObjectIdentifier`
+
+ The :ref:`extension OID <extension_oids>`.
+
+ .. attribute:: critical
+
+ :type: bool
+
+ Determines whether a given extension is critical or not. :rfc:`5280`
+ requires that "A certificate-using system MUST reject the certificate
+ if it encounters a critical extension it does not recognize or a
+ critical extension that contains information that it cannot process".
+
+ .. attribute:: value
+
+ Returns an instance of the extension type corresponding to the OID.
+
+.. class:: BasicConstraints
+
+ .. versionadded:: 0.9
+
+ Basic constraints is an X.509 extension type that defines whether a given
+ certificate is allowed to sign additional certificates and what path
+ length restrictions may exist. It corresponds to
+ :data:`OID_BASIC_CONSTRAINTS`.
+
+ .. attribute:: ca
+
+ :type: bool
+
+ Whether the certificate can sign certificates.
+
+ .. attribute:: path_length
+
+ :type: int or None
+
+ The maximum path length for certificates subordinate to this
+ certificate. This attribute only has meaning if ``ca`` is true.
+ If ``ca`` is true then a path length of None means there's no
+ restriction on the number of subordinate CAs in the certificate chain.
+ If it is zero or greater then that number defines the maximum length.
+ For example, a ``path_length`` of 1 means the certificate can sign a
+ subordinate CA, but the subordinate CA is not allowed to create
+ subordinates with ``ca`` set to true.
+
+
Object Identifiers
~~~~~~~~~~~~~~~~~~
@@ -430,6 +485,16 @@ Signature Algorithm OIDs
Corresponds to the dotted string ``2.16.840.1.101.3.4.3.2"``. This is
a SHA256 digest signed by a DSA key.
+.. _extension_oids:
+
+Extension OIDs
+~~~~~~~~~~~~~~
+
+.. data:: OID_BASIC_CONSTRAINTS
+
+ Corresponds to the dotted string ``"2.5.29.19"``. The identifier for the
+ :class:`BasicConstraints` extension type.
+
Exceptions
~~~~~~~~~~
diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py
index 1d2a9489..1ad7028d 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -42,6 +42,7 @@ _OID_NAMES = {
"1.2.840.10040.4.3": "dsa-with-sha1",
"2.16.840.1.101.3.4.3.1": "dsa-with-sha224",
"2.16.840.1.101.3.4.3.2": "dsa-with-sha256",
+ "2.5.29.19": "basicConstraints",
}
@@ -138,6 +139,59 @@ class Name(object):
return len(self._attributes)
+OID_BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19")
+
+
+class Extension(object):
+ def __init__(self, oid, critical, value):
+ if not isinstance(oid, ObjectIdentifier):
+ raise TypeError(
+ "oid argument must be an ObjectIdentifier instance."
+ )
+
+ if not isinstance(critical, bool):
+ raise TypeError("critical must be a boolean value")
+
+ self._oid = oid
+ self._critical = critical
+ self._value = value
+
+ oid = utils.read_only_property("_oid")
+ critical = utils.read_only_property("_critical")
+ value = utils.read_only_property("_value")
+
+ def __repr__(self):
+ return ("<Extension(oid={0.oid}, critical={0.critical}, "
+ "value={0.value})>").format(self)
+
+
+class BasicConstraints(object):
+ def __init__(self, ca, path_length):
+ if not isinstance(ca, bool):
+ raise TypeError("ca must be a boolean value")
+
+ if path_length is not None and not ca:
+ raise ValueError("path_length must be None when ca is False")
+
+ if (
+ path_length is not None and
+ (not isinstance(path_length, six.integer_types) or path_length < 0)
+ ):
+ raise TypeError(
+ "path_length must be a non-negative integer or None"
+ )
+
+ self._ca = ca
+ self._path_length = path_length
+
+ ca = utils.read_only_property("_ca")
+ path_length = utils.read_only_property("_path_length")
+
+ def __repr__(self):
+ return ("<BasicConstraints(ca={0.ca}, "
+ "path_length={0.path_length})>").format(self)
+
+
OID_COMMON_NAME = ObjectIdentifier("2.5.4.3")
OID_COUNTRY_NAME = ObjectIdentifier("2.5.4.6")
OID_LOCALITY_NAME = ObjectIdentifier("2.5.4.7")
diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py
new file mode 100644
index 00000000..74d14c57
--- /dev/null
+++ b/tests/test_x509_ext.py
@@ -0,0 +1,57 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import absolute_import, division, print_function
+
+import pytest
+
+from cryptography import x509
+
+
+class TestExtension(object):
+ def test_not_an_oid(self):
+ bc = x509.BasicConstraints(ca=False, path_length=None)
+ with pytest.raises(TypeError):
+ x509.Extension("notanoid", True, bc)
+
+ def test_critical_not_a_bool(self):
+ bc = x509.BasicConstraints(ca=False, path_length=None)
+ with pytest.raises(TypeError):
+ x509.Extension(x509.OID_BASIC_CONSTRAINTS, "notabool", bc)
+
+ def test_repr(self):
+ bc = x509.BasicConstraints(ca=False, path_length=None)
+ ext = x509.Extension(x509.OID_BASIC_CONSTRAINTS, True, bc)
+ assert repr(ext) == (
+ "<Extension(oid=<ObjectIdentifier(oid=2.5.29.19, name=basicConst"
+ "raints)>, critical=True, value=<BasicConstraints(ca=False, path"
+ "_length=None)>)>"
+ )
+
+
+class TestBasicConstraints(object):
+ def test_ca_not_boolean(self):
+ with pytest.raises(TypeError):
+ x509.BasicConstraints(ca="notbool", path_length=None)
+
+ def test_path_length_not_ca(self):
+ with pytest.raises(ValueError):
+ x509.BasicConstraints(ca=False, path_length=0)
+
+ def test_path_length_not_int(self):
+ with pytest.raises(TypeError):
+ x509.BasicConstraints(ca=True, path_length=1.1)
+
+ with pytest.raises(TypeError):
+ x509.BasicConstraints(ca=True, path_length="notint")
+
+ def test_path_length_negative(self):
+ with pytest.raises(TypeError):
+ x509.BasicConstraints(ca=True, path_length=-1)
+
+ def test_repr(self):
+ na = x509.BasicConstraints(ca=True, path_length=None)
+ assert repr(na) == (
+ "<BasicConstraints(ca=True, path_length=None)>"
+ )