aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2017-06-04 13:36:58 -0400
committerPaul Kehrer <paul.l.kehrer@gmail.com>2017-06-04 07:36:58 -1000
commit6a0718faddbc7b6b57f86417f6daa468c18ea248 (patch)
tree624fe16cf368a13cbbd7370b2a4780fa5da76c91
parent140ec5d6e2167692ba5619b368f44a1b07f96a4a (diff)
downloadcryptography-6a0718faddbc7b6b57f86417f6daa468c18ea248.tar.gz
cryptography-6a0718faddbc7b6b57f86417f6daa468c18ea248.tar.bz2
cryptography-6a0718faddbc7b6b57f86417f6daa468c18ea248.zip
Refs #3461 -- parse SCTs from x.509 extension (#3480)
* Stub API for SCTs, feedback wanted * grr, flake8 * finish up the __init__ * Initial implementation and tests * write a test. it fails because computer * get the tests passing and fix some TODOs * changelog entry * This can go now * Put a skip in this test * grump * Removed unreachable code * moved changelog to the correct section * Use the deocrator for expressing requirements * This needs f for the right entry_type * coverage * syntax error * tests for coverage * better sct eq tests * docs * technically correct, the most useless kind of correct * typo and more details * bug * drop __eq__
-rw-r--r--CHANGELOG.rst3
-rw-r--r--docs/x509/certificate-transparency.rst6
-rw-r--r--docs/x509/reference.rst26
-rw-r--r--src/cryptography/hazmat/backends/openssl/decode_asn1.py18
-rw-r--r--src/cryptography/hazmat/backends/openssl/x509.py41
-rw-r--r--src/cryptography/x509/__init__.py7
-rw-r--r--src/cryptography/x509/extensions.py36
-rw-r--r--tests/test_x509_ext.py42
8 files changed, 173 insertions, 6 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 22411d1f..080ebd66 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -18,6 +18,9 @@ Changelog
and
:class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`
in favor of ``verify``.
+* Added support for parsing
+ :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp`
+ objects from X.509 certificate extensions.
1.9 - 2017-05-29
~~~~~~~~~~~~~~~~
diff --git a/docs/x509/certificate-transparency.rst b/docs/x509/certificate-transparency.rst
index 0d344d2b..f9e651ed 100644
--- a/docs/x509/certificate-transparency.rst
+++ b/docs/x509/certificate-transparency.rst
@@ -11,7 +11,7 @@ issued.
.. class:: SignedCertificateTimestamp
- .. versionadded:: 1.9
+ .. versionadded:: 2.0
SignedCertificateTimestamps (SCTs) are small cryptographically signed
assertions that the specified certificate has been submitted to a
@@ -53,7 +53,7 @@ issued.
.. class:: Version
- .. versionadded:: 1.9
+ .. versionadded:: 2.0
An enumeration for SignedCertificateTimestamp versions.
@@ -63,7 +63,7 @@ issued.
.. class:: LogEntryType
- .. versionadded:: 1.9
+ .. versionadded:: 2.0
An enumeration for SignedCertificateTimestamp log entry types.
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index 24d1c07b..5a903b95 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -1814,6 +1814,32 @@ X.509 Extensions
:returns: A list of values extracted from the matched general names.
+.. class:: PrecertificateSignedCertificateTimestamps(scts)
+
+ .. versionadded:: 2.0
+
+ This extension contains
+ :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp`
+ instances which were issued for the pre-certificate corresponding to this
+ certificate. These can be used to verify that the certificate is included
+ in a public Certificate Transparency log.
+
+ It is an iterable containing one or more
+ :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp`
+ objects.
+
+ :param list scts: A ``list`` of
+ :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp`
+ objects.
+
+ .. attribute:: oid
+
+ :type: :class:`ObjectIdentifier`
+
+ Returns
+ :attr:`~cryptography.x509.oid.ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS`.
+
+
.. class:: AuthorityInformationAccess(descriptions)
.. versionadded:: 0.9
diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py
index 282e30f0..ab97dc19 100644
--- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py
+++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py
@@ -597,6 +597,21 @@ def _decode_inhibit_any_policy(backend, asn1_int):
return x509.InhibitAnyPolicy(skip_certs)
+def _decode_precert_signed_certificate_timestamps(backend, asn1_scts):
+ from cryptography.hazmat.backends.openssl.x509 import (
+ _SignedCertificateTimestamp
+ )
+ asn1_scts = backend._ffi.cast("Cryptography_STACK_OF_SCT *", asn1_scts)
+ asn1_scts = backend._ffi.gc(asn1_scts, backend._lib.SCT_LIST_free)
+
+ scts = []
+ for i in range(backend._lib.sk_SCT_num(asn1_scts)):
+ sct = backend._lib.sk_SCT_value(asn1_scts, i)
+
+ scts.append(_SignedCertificateTimestamp(backend, asn1_scts, sct))
+ return x509.PrecertificateSignedCertificateTimestamps(scts)
+
+
# CRLReason ::= ENUMERATED {
# unspecified (0),
# keyCompromise (1),
@@ -751,6 +766,9 @@ _EXTENSION_HANDLERS = {
ExtensionOID.ISSUER_ALTERNATIVE_NAME: _decode_issuer_alt_name,
ExtensionOID.NAME_CONSTRAINTS: _decode_name_constraints,
ExtensionOID.POLICY_CONSTRAINTS: _decode_policy_constraints,
+ ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS: (
+ _decode_precert_signed_certificate_timestamps
+ ),
}
_REVOKED_EXTENSION_HANDLERS = {
diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py
index 5b3304f3..43456382 100644
--- a/src/cryptography/hazmat/backends/openssl/x509.py
+++ b/src/cryptography/hazmat/backends/openssl/x509.py
@@ -4,6 +4,7 @@
from __future__ import absolute_import, division, print_function
+import datetime
import operator
import warnings
@@ -433,3 +434,43 @@ class _CertificateSigningRequest(object):
return False
return True
+
+
+@utils.register_interface(
+ x509.certificate_transparency.SignedCertificateTimestamp
+)
+class _SignedCertificateTimestamp(object):
+ def __init__(self, backend, sct_list, sct):
+ self._backend = backend
+ # Keep the SCT_LIST that this SCT came from alive.
+ self._sct_list = sct_list
+ self._sct = sct
+
+ @property
+ def version(self):
+ version = self._backend._lib.SCT_get_version(self._sct)
+ assert version == self._backend._lib.SCT_VERSION_V1
+ return x509.certificate_transparency.Version.v1
+
+ @property
+ def log_id(self):
+ out = self._backend._ffi.new("unsigned char **")
+ log_id_length = self._backend._lib.SCT_get0_log_id(self._sct, out)
+ assert log_id_length >= 0
+ return self._backend._ffi.buffer(out[0], log_id_length)[:]
+
+ @property
+ def timestamp(self):
+ timestamp = self._backend._lib.SCT_get_timestamp(self._sct)
+ milliseconds = timestamp % 1000
+ return datetime.datetime.utcfromtimestamp(
+ timestamp // 1000
+ ).replace(microsecond=milliseconds * 1000)
+
+ @property
+ def entry_type(self):
+ entry_type = self._backend._lib.SCT_get_log_entry_type(self._sct)
+ # We currently only support loading SCTs from the X.509 extension, so
+ # we only have precerts.
+ assert entry_type == self._backend._lib.CT_LOG_ENTRY_TYPE_PRECERT
+ return x509.certificate_transparency.LogEntryType.PRE_CERTIFICATE
diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py
index c5465fbb..b1a32ef6 100644
--- a/src/cryptography/x509/__init__.py
+++ b/src/cryptography/x509/__init__.py
@@ -23,9 +23,9 @@ from cryptography.x509.extensions import (
ExtensionNotFound, ExtensionType, Extensions, GeneralNames,
InhibitAnyPolicy, InvalidityDate, IssuerAlternativeName, KeyUsage,
NameConstraints, NoticeReference, OCSPNoCheck, PolicyConstraints,
- PolicyInformation, ReasonFlags, SubjectAlternativeName,
- SubjectKeyIdentifier, UnrecognizedExtension, UnsupportedExtension,
- UserNotice
+ PolicyInformation, PrecertificateSignedCertificateTimestamps, ReasonFlags,
+ SubjectAlternativeName, SubjectKeyIdentifier, UnrecognizedExtension,
+ UnsupportedExtension, UserNotice
)
from cryptography.x509.general_name import (
DNSName, DirectoryName, GeneralName, IPAddress, OtherName, RFC822Name,
@@ -185,4 +185,5 @@ __all__ = [
"InvalidityDate",
"UnrecognizedExtension",
"PolicyConstraints",
+ "PrecertificateSignedCertificateTimestamps",
]
diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py
index aa30f8ff..1b64f4a5 100644
--- a/src/cryptography/x509/extensions.py
+++ b/src/cryptography/x509/extensions.py
@@ -18,6 +18,9 @@ from cryptography import utils
from cryptography.hazmat.primitives import constant_time, serialization
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
+from cryptography.x509.certificate_transparency import (
+ SignedCertificateTimestamp
+)
from cryptography.x509.general_name import GeneralName, IPAddress, OtherName
from cryptography.x509.name import RelativeDistinguishedName
from cryptography.x509.oid import (
@@ -1152,6 +1155,39 @@ class InvalidityDate(object):
@utils.register_interface(ExtensionType)
+class PrecertificateSignedCertificateTimestamps(object):
+ oid = ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS
+
+ def __init__(self, signed_certificate_timestamps):
+ signed_certificate_timestamps = list(signed_certificate_timestamps)
+ if not all(
+ isinstance(sct, SignedCertificateTimestamp)
+ for sct in signed_certificate_timestamps
+ ):
+ raise TypeError(
+ "Every item in the signed_certificate_timestamps list must be "
+ "a SignedCertificateTimestamp"
+ )
+ self._signed_certificate_timestamps = signed_certificate_timestamps
+
+ def __iter__(self):
+ return iter(self._signed_certificate_timestamps)
+
+ def __len__(self):
+ return len(self._signed_certificate_timestamps)
+
+ def __getitem__(self, idx):
+ return self._signed_certificate_timestamps[idx]
+
+ def __repr__(self):
+ return (
+ "<PrecertificateSignedCertificateTimestamps({0})>".format(
+ list(self)
+ )
+ )
+
+
+@utils.register_interface(ExtensionType)
class UnrecognizedExtension(object):
def __init__(self, oid, value):
if not isinstance(oid, ObjectIdentifier):
diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py
index b89abdda..595ec703 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -3668,6 +3668,48 @@ class TestInhibitAnyPolicyExtension(object):
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
+@pytest.mark.supported(
+ only_if=lambda backend: backend._lib.CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER,
+ skip_message="Requires OpenSSL 1.1.0f+",
+)
+class TestPrecertificateSignedCertificateTimestampsExtension(object):
+ def test_init(self):
+ with pytest.raises(TypeError):
+ x509.PrecertificateSignedCertificateTimestamps([object()])
+
+ def test_repr(self):
+ assert repr(x509.PrecertificateSignedCertificateTimestamps([])) == (
+ "<PrecertificateSignedCertificateTimestamps([])>"
+ )
+
+ def test_simple(self, backend):
+ cert = _load_cert(
+ os.path.join("x509", "badssl-sct.pem"),
+ x509.load_pem_x509_certificate,
+ backend
+ )
+ scts = cert.extensions.get_extension_for_class(
+ x509.PrecertificateSignedCertificateTimestamps
+ ).value
+ assert len(scts) == 1
+ [sct] = scts
+ assert scts[0] == sct
+ assert sct.version == x509.certificate_transparency.Version.v1
+ assert sct.log_id == (
+ b"\xa7\xceJNb\x07\xe0\xad\xde\xe5\xfd\xaaK\x1f\x86v\x87g\xb5\xd0"
+ b"\x02\xa5]G1\x0e~g\n\x95\xea\xb2"
+ )
+ assert sct.timestamp == datetime.datetime(
+ 2016, 11, 17, 1, 56, 25, 396000
+ )
+ assert (
+ sct.entry_type ==
+ x509.certificate_transparency.LogEntryType.PRE_CERTIFICATE
+ )
+
+
+@pytest.mark.requires_backend_interface(interface=RSABackend)
+@pytest.mark.requires_backend_interface(interface=X509Backend)
class TestInvalidExtension(object):
def test_invalid_certificate_policies_data(self, backend):
cert = _load_cert(