aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2015-03-28 13:33:01 -0400
committerAlex Gaynor <alex.gaynor@gmail.com>2015-03-28 13:33:01 -0400
commit320050b92d98c9dd8f3949f04a13756a4018f85d (patch)
tree5c6715eb538ff5308045be4a342ddc687795570b
parent7d31b38ae1000d6691c62426219f8c8c03ac3f7d (diff)
parentfbb7ac804a769ff48cddde6fb1f36d8af0d56174 (diff)
downloadcryptography-320050b92d98c9dd8f3949f04a13756a4018f85d.tar.gz
cryptography-320050b92d98c9dd8f3949f04a13756a4018f85d.tar.bz2
cryptography-320050b92d98c9dd8f3949f04a13756a4018f85d.zip
Merge pull request #1793 from reaperhulk/x509-extensions-class
add x509 extensions class and basic tests (no extensions supported)
-rw-r--r--docs/x509.rst39
-rw-r--r--src/cryptography/hazmat/backends/openssl/x509.py36
-rw-r--r--src/cryptography/x509.py28
-rw-r--r--tests/test_x509_ext.py57
4 files changed, 159 insertions, 1 deletions
diff --git a/docs/x509.rst b/docs/x509.rst
index 13218914..44d53a45 100644
--- a/docs/x509.rst
+++ b/docs/x509.rst
@@ -196,6 +196,15 @@ X.509 Certificate Object
>>> isinstance(cert.signature_hash_algorithm, hashes.SHA256)
True
+ .. attribute:: extensions
+
+ :type: :class:`Extensions`
+
+ The extensions encoded in the certificate.
+
+ :raises cryptography.x509.DuplicateExtension: If more than one
+ extension of the same type is found within the certificate.
+
.. class:: Name
.. versionadded:: 0.8
@@ -276,6 +285,13 @@ X.509 Certificate Object
X.509 Extensions
~~~~~~~~~~~~~~~~
+.. class:: Extensions
+
+ .. versionadded:: 0.9
+
+ An X.509 Extensions instance is an ordered list of extensions. The object
+ is iterable to get every extension.
+
.. class:: Extension
.. versionadded:: 0.9
@@ -482,7 +498,7 @@ Signature Algorithm OIDs
.. data:: OID_DSA_WITH_SHA256
- Corresponds to the dotted string ``2.16.840.1.101.3.4.3.2"``. This is
+ 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:
@@ -509,6 +525,27 @@ Exceptions
Returns the raw version that was parsed from the certificate.
+.. class:: DuplicateExtension
+
+ This is raised when more than one X.509 extension of the same type is
+ found within a certificate.
+
+ .. attribute:: oid
+
+ :type: :class:`ObjectIdentifier`
+
+ Returns the OID.
+
+.. class:: UnsupportedExtension
+
+ This is raised when a certificate contains an unsupported extension type.
+
+ .. 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 89db016b..3502d122 100644
--- a/src/cryptography/hazmat/backends/openssl/x509.py
+++ b/src/cryptography/hazmat/backends/openssl/x509.py
@@ -14,6 +14,7 @@
from __future__ import absolute_import, division, print_function
import datetime
+import warnings
from cryptography import utils, x509
from cryptography.exceptions import UnsupportedAlgorithm
@@ -151,3 +152,38 @@ class _Certificate(object):
raise UnsupportedAlgorithm(
"Signature algorithm OID:{0} not recognized".format(oid)
)
+
+ @property
+ def extensions(self):
+ extensions = []
+ seen_oids = set()
+ extcount = self._backend._lib.X509_get_ext_count(self._x509)
+ for i in range(0, extcount):
+ ext = self._backend._lib.X509_get_ext(self._x509, i)
+ assert ext != self._backend._ffi.NULL
+ crit = self._backend._lib.X509_EXTENSION_get_critical(ext)
+ critical = crit == 1
+ oid = x509.ObjectIdentifier(_obj2txt(self._backend, ext.object))
+ if oid in seen_oids:
+ raise x509.DuplicateExtension(
+ "Duplicate {0} extension found".format(oid), oid
+ )
+ elif oid == x509.OID_BASIC_CONSTRAINTS 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."
+ )
+ elif critical:
+ raise x509.UnsupportedExtension(
+ "{0} is not currently supported".format(oid), oid
+ )
+ else:
+ # Unsupported non-critical extension, silently skipping for now
+ seen_oids.add(oid)
+ continue
+
+ seen_oids.add(oid)
+
+ return x509.Extensions(extensions)
diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py
index 1ad7028d..29602b3e 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -43,6 +43,7 @@ _OID_NAMES = {
"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",
+ "2.5.29.15": "keyUsage",
}
@@ -65,6 +66,18 @@ class InvalidVersion(Exception):
self.parsed_version = parsed_version
+class DuplicateExtension(Exception):
+ def __init__(self, msg, oid):
+ super(DuplicateExtension, self).__init__(msg)
+ self.oid = oid
+
+
+class UnsupportedExtension(Exception):
+ def __init__(self, msg, oid):
+ super(UnsupportedExtension, self).__init__(msg)
+ self.oid = oid
+
+
class NameAttribute(object):
def __init__(self, oid, value):
if not isinstance(oid, ObjectIdentifier):
@@ -113,6 +126,9 @@ class ObjectIdentifier(object):
_OID_NAMES.get(self._dotted_string, "Unknown OID")
)
+ def __hash__(self):
+ return hash(self.dotted_string)
+
dotted_string = utils.read_only_property("_dotted_string")
@@ -139,9 +155,21 @@ class Name(object):
return len(self._attributes)
+OID_KEY_USAGE = ObjectIdentifier("2.5.29.15")
OID_BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19")
+class Extensions(object):
+ def __init__(self, extensions):
+ self._extensions = extensions
+
+ def __iter__(self):
+ return iter(self._extensions)
+
+ def __len__(self):
+ return len(self._extensions)
+
+
class Extension(object):
def __init__(self, oid, critical, value):
if not isinstance(oid, ObjectIdentifier):
diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py
index 74d14c57..d8281526 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -4,9 +4,14 @@
from __future__ import absolute_import, division, print_function
+import os
+
import pytest
from cryptography import x509
+from cryptography.hazmat.backends.interfaces import RSABackend, X509Backend
+
+from .test_x509 import _load_cert
class TestExtension(object):
@@ -55,3 +60,55 @@ class TestBasicConstraints(object):
assert repr(na) == (
"<BasicConstraints(ca=True, path_length=None)>"
)
+
+
+@pytest.mark.requires_backend_interface(interface=RSABackend)
+@pytest.mark.requires_backend_interface(interface=X509Backend)
+class TestExtensions(object):
+ def test_no_extensions(self, backend):
+ cert = _load_cert(
+ os.path.join("x509", "verisign_md2_root.pem"),
+ x509.load_pem_x509_certificate,
+ backend
+ )
+ ext = cert.extensions
+ assert len(ext) == 0
+ assert list(ext) == []
+
+ def test_duplicate_extension(self, backend):
+ cert = _load_cert(
+ os.path.join(
+ "x509", "custom", "two_basic_constraints.pem"
+ ),
+ x509.load_pem_x509_certificate,
+ backend
+ )
+ with pytest.raises(x509.DuplicateExtension) as exc:
+ cert.extensions
+
+ assert exc.value.oid == x509.OID_BASIC_CONSTRAINTS
+
+ def test_unsupported_critical_extension(self, backend):
+ cert = _load_cert(
+ os.path.join(
+ "x509", "custom", "unsupported_extension_critical.pem"
+ ),
+ x509.load_pem_x509_certificate,
+ backend
+ )
+ with pytest.raises(x509.UnsupportedExtension) as exc:
+ cert.extensions
+
+ assert exc.value.oid == x509.ObjectIdentifier("1.2.3.4")
+
+ def test_unsupported_extension(self, backend):
+ # TODO: this will raise an exception when all extensions are complete
+ cert = _load_cert(
+ os.path.join(
+ "x509", "custom", "unsupported_extension.pem"
+ ),
+ x509.load_pem_x509_certificate,
+ backend
+ )
+ extensions = cert.extensions
+ assert len(extensions) == 0