From 8cf26425504d22dbcf463ff702a167cbe3567e6a Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 21 Mar 2015 09:50:24 -0500 Subject: basic constraints class & extensions interface --- docs/x509.rst | 43 +++++++++++++++++++++++++++++++++++++++++++ src/cryptography/x509.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test_x509_ext.py | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 tests/test_x509_ext.py diff --git a/docs/x509.rst b/docs/x509.rst index 27f1d544..89265df2 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -273,6 +273,49 @@ 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 + + All X.509 extensions are registered against this interface. + + .. attribute:: critical + + :type: bool + + Determines whether a given extension is critical or not. + +.. class:: BasicConstraints + + .. versionadded:: 0.9 + + Basic constraints is an X.509 extension that defines whether a given + certificate is allowed to sign additional certificates and what path + length restrictions may exist. + + .. attribute:: ca + + :type: bool + + Whether the certificate can sign certificates. + + .. attribute:: path_length + + :type: int, 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 + ``ca`` true certificates. + + Object Identifiers ~~~~~~~~~~~~~~~~~~ diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index ad7ebbe0..c053dd61 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -141,6 +141,52 @@ class Name(object): return len(self._attributes) +OID_BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19") + + +@six.add_metaclass(abc.ABCMeta) +class Extension(object): + @abc.abstractproperty + def critical(self): + """ + Returns the boolean value of the critical extension field. + """ + + +@utils.register_interface(Extension) +class BasicConstraints(object): + oid = OID_BASIC_CONSTRAINTS + + def __init__(self, ca, path_length, critical): + if not isinstance(ca, bool): + raise TypeError("ca must be a boolean value") + + if not isinstance(critical, bool): + raise TypeError("critical must be a boolean value") + + if path_length is not None and ca is False: + raise ValueError("path_length must be None when ca is False") + + if path_length is not None and (not isinstance(path_length, int) + or path_length < 0): + raise TypeError( + "path_length must be a non-negative integer or None" + ) + + self._ca = ca + self._path_length = path_length + self._critical = critical + + ca = utils.read_only_property("_ca") + path_length = utils.read_only_property("_path_length") + critical = utils.read_only_property("_critical") + + def __repr__(self): + return "".format( + self.ca, self.path_length, self.critical + ) + + 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..9fde1be1 --- /dev/null +++ b/tests/test_x509_ext.py @@ -0,0 +1,40 @@ +# 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 TestBasicConstraints(object): + def test_ca_not_boolean(self): + with pytest.raises(TypeError): + x509.BasicConstraints("notbool", None, False) + + def test_critical_not_boolean(self): + with pytest.raises(TypeError): + x509.BasicConstraints(False, None, "notbool") + + def test_path_length_not_ca(self): + with pytest.raises(ValueError): + x509.BasicConstraints(False, 0, True) + + def test_path_length_not_int(self): + with pytest.raises(TypeError): + x509.BasicConstraints(True, 1.1, True) + + with pytest.raises(TypeError): + x509.BasicConstraints(True, "notint", True) + + def test_path_length_negative(self): + with pytest.raises(TypeError): + x509.BasicConstraints(True, -1, True) + + def test_repr(self): + na = x509.BasicConstraints(True, None, True) + assert repr(na) == ( + "" + ) -- cgit v1.2.3 From 2bb9464c6eb3da36addd8c501f0a392c4339d1b8 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 21 Mar 2015 09:54:17 -0500 Subject: add docs for the OID_BASIC_CONSTRAINTS constant --- docs/x509.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/x509.rst b/docs/x509.rst index 89265df2..18f6ff18 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -473,6 +473,14 @@ 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 +~~~~~~~~~~~~~~ + +.. data:: OID_BASIC_CONSTRAINTS + + Corresponds to the dotted string ``"2.5.29.19"``. The identifier for the + basic constraints extension. + Exceptions ~~~~~~~~~~ -- cgit v1.2.3 From d258d378a388dde070ebe6bfb89400bcdce06e04 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 21 Mar 2015 09:58:07 -0500 Subject: right, {} format support was added in 2.7 --- src/cryptography/x509.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index c053dd61..510e9c6f 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -182,8 +182,10 @@ class BasicConstraints(object): critical = utils.read_only_property("_critical") def __repr__(self): - return "".format( - self.ca, self.path_length, self.critical + return ("").format( + ca=self.ca, path_length=self.path_length, critical=self.critical ) -- cgit v1.2.3 From fd1444cfa97bb897eaee6ceca35175357317623b Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 21 Mar 2015 19:47:05 -0500 Subject: review feedback doc updates --- docs/x509.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/x509.rst b/docs/x509.rst index 18f6ff18..80242581 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -304,7 +304,7 @@ X.509 Extensions .. attribute:: path_length - :type: int, None + :type: int or None The maximum path length for certificates subordinate to this certificate. This attribute only has meaning if ``ca`` is true. @@ -313,7 +313,7 @@ X.509 Extensions 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 - ``ca`` true certificates. + subordinates with ``ca`` set to true. Object Identifiers -- cgit v1.2.3 From 8589466c0a12835cda03bf91043cf51b657d9e46 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 22 Mar 2015 13:19:31 -0500 Subject: rework BasicConstraints and Extension. --- docs/x509.rst | 15 ++++++++++++--- src/cryptography/x509.py | 44 ++++++++++++++++++++++++++------------------ tests/test_x509_ext.py | 39 ++++++++++++++++++++++++++++----------- 3 files changed, 66 insertions(+), 32 deletions(-) diff --git a/docs/x509.rst b/docs/x509.rst index 80242581..7eb47a31 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -280,7 +280,11 @@ X.509 Extensions .. versionadded:: 0.9 - All X.509 extensions are registered against this interface. + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + The attribute OID. .. attribute:: critical @@ -288,13 +292,18 @@ X.509 Extensions Determines whether a given extension is critical or not. + .. 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 that defines whether a given + 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. + length restrictions may exist. It corresponds to + :data:`OID_BASIC_CONSTRAINTS`. .. attribute:: ca diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 510e9c6f..d64d61f0 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", } @@ -144,26 +145,36 @@ class Name(object): OID_BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19") -@six.add_metaclass(abc.ABCMeta) class Extension(object): - @abc.abstractproperty - def critical(self): - """ - Returns the boolean value of the critical extension field. - """ + 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") -@utils.register_interface(Extension) -class BasicConstraints(object): - oid = OID_BASIC_CONSTRAINTS + self._oid = oid + self._critical = critical + self._value = value - def __init__(self, ca, path_length, critical): + oid = utils.read_only_property("_oid") + critical = utils.read_only_property("_critical") + value = utils.read_only_property("_value") + + def __repr__(self): + return ("").format( + oid=self.oid, critical=self.critical, value=self.value + ) + + +class BasicConstraints(object): + def __init__(self, ca, path_length): if not isinstance(ca, bool): raise TypeError("ca must be a boolean value") - if not isinstance(critical, bool): - raise TypeError("critical must be a boolean value") - if path_length is not None and ca is False: raise ValueError("path_length must be None when ca is False") @@ -175,17 +186,14 @@ class BasicConstraints(object): self._ca = ca self._path_length = path_length - self._critical = critical ca = utils.read_only_property("_ca") path_length = utils.read_only_property("_path_length") - critical = utils.read_only_property("_critical") def __repr__(self): return ("").format( - ca=self.ca, path_length=self.path_length, critical=self.critical + "path_length={path_length})>").format( + ca=self.ca, path_length=self.path_length ) diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py index 9fde1be1..de3ca312 100644 --- a/tests/test_x509_ext.py +++ b/tests/test_x509_ext.py @@ -9,32 +9,49 @@ import pytest from cryptography import x509 -class TestBasicConstraints(object): - def test_ca_not_boolean(self): +class TestExtension(object): + def test_not_an_oid(self): + bc = x509.BasicConstraints(False, None) with pytest.raises(TypeError): - x509.BasicConstraints("notbool", None, False) + x509.Extension("notanoid", True, bc) + + def test_critical_not_a_bool(self): + bc = x509.BasicConstraints(False, None) + with pytest.raises(TypeError): + x509.Extension(x509.OID_BASIC_CONSTRAINTS, "notabool", bc) + + def test_repr(self): + bc = x509.BasicConstraints(False, None) + ext = x509.Extension(x509.OID_BASIC_CONSTRAINTS, True, bc) + assert repr(ext) == ( + ", critical=True, value=)>" + ) + - def test_critical_not_boolean(self): +class TestBasicConstraints(object): + def test_ca_not_boolean(self): with pytest.raises(TypeError): - x509.BasicConstraints(False, None, "notbool") + x509.BasicConstraints("notbool", None) def test_path_length_not_ca(self): with pytest.raises(ValueError): - x509.BasicConstraints(False, 0, True) + x509.BasicConstraints(False, 0) def test_path_length_not_int(self): with pytest.raises(TypeError): - x509.BasicConstraints(True, 1.1, True) + x509.BasicConstraints(True, 1.1) with pytest.raises(TypeError): - x509.BasicConstraints(True, "notint", True) + x509.BasicConstraints(True, "notint") def test_path_length_negative(self): with pytest.raises(TypeError): - x509.BasicConstraints(True, -1, True) + x509.BasicConstraints(True, -1) def test_repr(self): - na = x509.BasicConstraints(True, None, True) + na = x509.BasicConstraints(True, None) assert repr(na) == ( - "" + "" ) -- cgit v1.2.3 From 611d3d36fb1e33582eefc81cc241140d7a69f733 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 22 Mar 2015 13:31:18 -0500 Subject: doc update --- docs/x509.rst | 2 +- src/cryptography/x509.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/x509.rst b/docs/x509.rst index 7eb47a31..751e077c 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -488,7 +488,7 @@ Extension OIDs .. data:: OID_BASIC_CONSTRAINTS Corresponds to the dotted string ``"2.5.29.19"``. The identifier for the - basic constraints extension. + :class:`BasicConstraints` extension type. Exceptions diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index d64d61f0..4ba6956e 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -175,7 +175,7 @@ class BasicConstraints(object): if not isinstance(ca, bool): raise TypeError("ca must be a boolean value") - if path_length is not None and ca is False: + 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, int) -- cgit v1.2.3 From 58b756969211e2972fb6fda44582e55b98c02924 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 22 Mar 2015 23:24:58 -0500 Subject: doc updates and simplification of __repr__ --- docs/x509.rst | 7 +++++-- src/cryptography/x509.py | 12 ++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/x509.rst b/docs/x509.rst index 751e077c..36e9fab3 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -284,13 +284,16 @@ X.509 Extensions :type: :class:`ObjectIdentifier` - The attribute OID. + The extension OID. .. attribute:: critical :type: bool - Determines whether a given extension is critical or not. + 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 diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 4ba6956e..0c773dac 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -164,10 +164,8 @@ class Extension(object): value = utils.read_only_property("_value") def __repr__(self): - return ("").format( - oid=self.oid, critical=self.critical, value=self.value - ) + return ("").format(self) class BasicConstraints(object): @@ -191,10 +189,8 @@ class BasicConstraints(object): path_length = utils.read_only_property("_path_length") def __repr__(self): - return ("").format( - ca=self.ca, path_length=self.path_length - ) + return ("").format(self) OID_COMMON_NAME = ObjectIdentifier("2.5.4.3") -- cgit v1.2.3 From a5c6e9a89242bb42dbc98f29681d2f74aec12b02 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 23 Mar 2015 19:23:43 -0500 Subject: use kwargs for BasicConstraints creation Also check path_length using integer_types --- src/cryptography/x509.py | 7 +++++-- tests/test_x509_ext.py | 18 +++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 0c773dac..43ece920 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -176,8 +176,11 @@ class BasicConstraints(object): 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, int) - or path_length < 0): + 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" ) diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py index de3ca312..74d14c57 100644 --- a/tests/test_x509_ext.py +++ b/tests/test_x509_ext.py @@ -11,17 +11,17 @@ from cryptography import x509 class TestExtension(object): def test_not_an_oid(self): - bc = x509.BasicConstraints(False, None) + 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(False, None) + 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(False, None) + bc = x509.BasicConstraints(ca=False, path_length=None) ext = x509.Extension(x509.OID_BASIC_CONSTRAINTS, True, bc) assert repr(ext) == ( "" ) -- cgit v1.2.3 From 5553d576f3bc3f65b84de99a2561360f82fc110f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 23 Mar 2015 21:08:01 -0500 Subject: review feedback updates --- docs/x509.rst | 4 +++- src/cryptography/x509.py | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/x509.rst b/docs/x509.rst index 36e9fab3..13218914 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -284,7 +284,7 @@ X.509 Extensions :type: :class:`ObjectIdentifier` - The extension OID. + The :ref:`extension OID `. .. attribute:: critical @@ -485,6 +485,8 @@ 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 ~~~~~~~~~~~~~~ diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 43ece920..556e5a3c 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -177,9 +177,8 @@ class BasicConstraints(object): 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) + 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" -- cgit v1.2.3