From 6a0718faddbc7b6b57f86417f6daa468c18ea248 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 4 Jun 2017 13:36:58 -0400 Subject: 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__ --- .../hazmat/backends/openssl/decode_asn1.py | 18 ++++++++++ src/cryptography/hazmat/backends/openssl/x509.py | 41 ++++++++++++++++++++++ src/cryptography/x509/__init__.py | 7 ++-- src/cryptography/x509/extensions.py | 36 +++++++++++++++++++ 4 files changed, 99 insertions(+), 3 deletions(-) (limited to 'src') 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 ( @@ -1151,6 +1154,39 @@ class InvalidityDate(object): invalidity_date = utils.read_only_property("_invalidity_date") +@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 ( + "".format( + list(self) + ) + ) + + @utils.register_interface(ExtensionType) class UnrecognizedExtension(object): def __init__(self, oid, value): -- cgit v1.2.3