From 9bbfcea022820e9783e22f5a8f1fe959c9b245eb Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Mon, 18 May 2015 20:55:29 -0400 Subject: Adds certificate builder. --- docs/x509/reference.rst | 83 +++++++++++++++++++ .../hazmat/backends/openssl/backend.py | 96 ++++++++++++++++++++++ src/cryptography/x509.py | 90 ++++++++++++++++++++ tests/test_x509.py | 50 +++++++++++ 4 files changed, 319 insertions(+) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 9179468f..65e3880d 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -388,6 +388,89 @@ X.509 CRL (Certificate Revocation List) Object The extensions encoded in the CRL. +X.509 Certificate Builder +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: CertificateBuilder + + .. method:: __init__() + + Creates an empty certificate (version 1). + + .. method:: set_version(version) + + Sets the X.509 version that will be used in the certificate. + + :param version: The :class:`~cryptography.x509.Version` that will be + used by the certificate. + + .. method:: set_issuer_name(name) + + Sets the issuer's distinguished name. + + :param public_key: The :class:`~cryptography.x509.Name` that describes + the issuer (CA). + + .. method:: set_subject_name(name) + + Sets the subject's distinguished name. + + :param public_key: The :class:`~cryptography.x509.Name` that describes + the subject (requester). + + .. method:: set_public_key(public_key) + + Sets the subject's public key. + + :param public_key: The subject's public key. + + .. method:: set_serial_number(serial_number) + + Sets the certificate's serial number (an integer). The CA's policy + determines how it attributes serial numbers to certificates. The only + requirement is that this number uniquely identify the certificate given + the issuer. + + :param serial_number: Integer number that will be used by the CA to + identify this certificate (most notably during certificate + revocation checking). + + .. method:: set_not_valid_before(time) + + Sets the certificate's activation time. This is the time from which + clients can start trusting the certificate. It may be different from + the time at which the certificate was created. + + :param time: The `datetime.datetime` object (in UTC) that marks the + activation time for the certificate. The certificate may not be + trusted clients if it is used before this time. + + .. method:: set_not_valid_after(time) + + Sets the certificate's expiration time. This is the time from which + clients should no longer trust the certificate. The CA's policy will + determine how long the certificate should remain in use. + + :param time: The `datetime.datetime` object (in UTC) that marks the + expiration time for the certificate. The certificate may not be + trusted clients if it is used after this time. + + .. method:: add_extension(extension) + + Adds an X.509 extension to the certificate. + + :param extension: The :class:`~cryptography.x509.Extension` to add to + the certificate. + + .. method:: sign(backend, private_key, algorithm) + + Sign the certificate using the CA's private key. + + :param algorithm: The + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that + will be used to generate the signature. + + X.509 CSR (Certificate Signing Request) Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 7ccb39a4..04f631f9 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function +import calendar import collections import itertools from contextlib import contextmanager @@ -94,6 +95,22 @@ def _encode_asn1_str_gc(backend, data, length): return s +def _make_asn1_int(backend, x): + i = backend._lib.ASN1_INTEGER_new() + # i = backend._ffi.gc(i, backend._lib.ASN1_INTEGER_free) + backend._lib.ASN1_INTEGER_set(i, x) + return i + + +def _make_asn1_str(backend, x, n=None): + if n is None: + n = len(x) + s = backend._lib.ASN1_OCTET_STRING_new() + # s = backend._ffi.gc(s, backend._lib.ASN1_OCTET_STRING_free) + backend._lib.ASN1_OCTET_STRING_set(s, x, n) + return s + + def _encode_name(backend, attributes): """ The X509_NAME created will not be gc'd. Use _encode_name_gc if needed. @@ -988,6 +1005,85 @@ class Backend(object): return _CertificateSigningRequest(self, x509_req) + def sign_x509_certificate(self, builder, private_key, algorithm): + # TODO: check type of private key parameter. + if not isinstance(builder, x509.CertificateBuilder): + raise TypeError('Builder type mismatch.') + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError('Algorithm must be a registered hash algorithm.') + + # Resolve the signature algorithm. + evp_md = self._lib.EVP_get_digestbyname( + algorithm.name.encode('ascii') + ) + assert evp_md != self._ffi.NULL + + # Create an empty certificate. + x509_cert = self._lib.X509_new() + x509_cert = self._ffi.gc(x509_cert, backend._lib.X509_free) + + # Set the x509 version. + res = self._lib.X509_set_version(x509_cert, builder._version.value) + assert res == 1 + + # Set the subject's name. + res = self._lib.X509_set_subject_name( + x509_cert, _encode_name(self, list(builder._subject_name)) + ) + assert res == 1 + + # Set the subject's public key. + res = self._lib.X509_set_pubkey( + x509_cert, builder._public_key._evp_pkey + ) + assert res == 1 + + # Set the certificate serial number. + serial_number = _make_asn1_int(self, builder._serial_number) + self._lib.X509_set_serialNumber(x509_cert, serial_number) + + # Set the "not before" time. + res = self._lib.ASN1_TIME_set( + self._lib.X509_get_notBefore(x509_cert), + calendar.timegm(builder._not_valid_before.timetuple()) + ) + assert res != self._ffi.NULL + + # Set the "not after" time. + res = self._lib.ASN1_TIME_set( + self._lib.X509_get_notAfter(x509_cert), + calendar.timegm(builder._not_valid_after.timetuple()) + ) + assert res != self._ffi.NULL + + # Add extensions. + for i, extension in enumerate(builder._extensions): + if isinstance(extension.value, x509.BasicConstraints): + extension = _encode_basic_constraints( + self, + extension.value.ca, + extension.value.path_length, + extension.critical + ) + else: + raise ValueError('Extension not yet supported.') + res = self._lib.X509_add_ext(x509_cert, extension, i) + assert res == 1 + + # Set the issuer name. + res = self._lib.X509_set_issuer_name( + x509_cert, _encode_name(self, list(builder._issuer_name)) + ) + assert res == 1 + + # Sign the certificate with the issuer's private key. + res = self._lib.X509_sign( + x509_cert, private_key._evp_pkey, evp_md + ) + assert res > 0 + + return _Certificate(self, x509_cert) + def load_pem_private_key(self, data, password): return self._load_key( self._lib.PEM_read_bio_PrivateKey, diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 58e1a37c..7cb42f57 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function import abc +import datetime import ipaddress from email.utils import parseaddr from enum import Enum @@ -1594,3 +1595,92 @@ class CertificateSigningRequestBuilder(object): if self._subject_name is None: raise ValueError("A CertificateSigningRequest must have a subject") return backend.create_x509_csr(self, private_key, algorithm) + + +class CertificateBuilder(object): + def __init__(self): + """ + Creates an empty X.509 certificate (version 1). + """ + self._version = Version.v1 + self._issuer_name = None + self._subject_name = None + self._public_key = None + self._serial_number = None + self._not_valid_before = None + self._not_valid_after = None + self._extensions = [] + + def set_version(self, version): + """ + Sets the X.509 version required by decoders. + """ + if not isinstance(version, Version): + raise TypeError('Expecting x509.Version object.') + self._version = version + + def set_issuer_name(self, name): + """ + Sets the CA's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError('Expecting x509.Name object.') + self._issuer_name = name + + def set_subject_name(self, name): + """ + Sets the requestor's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError('Expecting x509.Name object.') + self._subject_name = name + + def set_public_key(self, public_key): + """ + Sets the requestor's public key (as found in the signing request). + """ + # TODO: check type. + self._public_key = public_key + + def set_serial_number(self, serial_number): + """ + Sets the certificate serial number. + """ + if not isinstance(serial_number, six.integer_types): + raise TypeError('Serial number must be of integral type.') + self._serial_number = serial_number + + def set_not_valid_before(self, time): + """ + Sets the certificate activation time. + """ + # TODO: require UTC datetime? + if not isinstance(time, datetime.datetime): + raise TypeError('Expecting datetime object.') + self._not_valid_before = time + + def set_not_valid_after(self, time): + """ + Sets the certificate expiration time. + """ + # TODO: require UTC datetime? + if not isinstance(time, datetime.datetime): + raise TypeError('Expecting datetime object.') + self._not_valid_after = time + + def add_extension(self, extension): + """ + Adds an X.509 extension to the certificate. + """ + if not isinstance(extension, Extension): + raise TypeError('Expecting x509.Extension object.') + for e in self._extensions: + if e.oid == extension.oid: + raise ValueError('This extension has already been set.') + self._extensions.append(extension) + + def sign(self, backend, private_key, algorithm): + """ + Signs the certificate using the CA's private key. + """ + return backend.sign_x509_certificate(self, private_key, algorithm) diff --git a/tests/test_x509.py b/tests/test_x509.py index 94eeab2b..92f40473 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -775,6 +775,56 @@ class TestRSACertificateRequest(object): assert hash(request1) == hash(request2) assert hash(request1) != hash(request3) + def test_build_cert(self, backend): + issuer_private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=backend, + ) + subject_private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=backend, + ) + + builder = x509.CertificateBuilder() + builder.set_version(x509.Version.v3) + builder.set_serial_number(777) + builder.set_issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, 'cryptography.io'), + ])) + builder.set_subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, 'cryptography.io'), + ])) + builder.set_public_key(subject_private_key.public_key()) + builder.add_extension(x509.Extension( + x509.OID_BASIC_CONSTRAINTS, + True, + x509.BasicConstraints(False, None), + )) + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + builder.set_not_valid_before(not_valid_before) + builder.set_not_valid_after(not_valid_after) + cert = builder.sign(backend, issuer_private_key, hashes.SHA1()) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + x509.OID_BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + @pytest.mark.requires_backend_interface(interface=X509Backend) class TestCertificateSigningRequestBuilder(object): -- cgit v1.2.3 From b3ed4849b632835f73e059d605738559c6839c03 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 1 Jul 2015 22:46:03 -0500 Subject: Make the CertificateBuilder interface more like the CSRBuilder --- src/cryptography/x509.py | 119 ++++++++++++++++++++++++++++++++++++----------- tests/test_x509.py | 36 +++++++------- 2 files changed, 111 insertions(+), 44 deletions(-) diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 7cb42f57..c04b8c9c 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -18,6 +18,7 @@ from six.moves import urllib_parse from cryptography import utils from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa _OID_NAMES = { @@ -1598,89 +1599,153 @@ class CertificateSigningRequestBuilder(object): class CertificateBuilder(object): - def __init__(self): + def __init__(self, version=None, issuer_name=None, subject_name=None, + public_key=None, serial_number=None, not_valid_before=None, + not_valid_after=None, extensions=[]): """ Creates an empty X.509 certificate (version 1). """ - self._version = Version.v1 - self._issuer_name = None - self._subject_name = None - self._public_key = None - self._serial_number = None - self._not_valid_before = None - self._not_valid_after = None - self._extensions = [] + self._version = version + self._issuer_name = issuer_name + self._subject_name = subject_name + self._public_key = public_key + self._serial_number = serial_number + self._not_valid_before = not_valid_before + self._not_valid_after = not_valid_after + self._extensions = extensions - def set_version(self, version): + def version(self, version): """ Sets the X.509 version required by decoders. """ if not isinstance(version, Version): raise TypeError('Expecting x509.Version object.') - self._version = version + if self._version is not None: + raise ValueError('The version may only be set once.') + return CertificateBuilder( + version, self._issuer_name, self._subject_name, self._public_key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) - def set_issuer_name(self, name): + def issuer_name(self, name): """ Sets the CA's distinguished name. """ if not isinstance(name, Name): raise TypeError('Expecting x509.Name object.') - self._issuer_name = name + if self._issuer_name is not None: + raise ValueError('The issuer name may only be set once.') + return CertificateBuilder( + self._version, name, self._subject_name, self._public_key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) - def set_subject_name(self, name): + def subject_name(self, name): """ Sets the requestor's distinguished name. """ if not isinstance(name, Name): raise TypeError('Expecting x509.Name object.') - self._subject_name = name + if self._issuer_name is not None: + raise ValueError('The subject name may only be set once.') + return CertificateBuilder( + self._version, self._issuer_name, name, self._public_key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) - def set_public_key(self, public_key): + def public_key(self, key): """ Sets the requestor's public key (as found in the signing request). """ - # TODO: check type. - self._public_key = public_key + if not isinstance(key, (dsa.DSAPublicKey, rsa.RSAPublicKey, + ec.EllipticCurvePublicKey)): + raise TypeError('Expecting one of DSAPublicKey, RSAPublicKey,' + ' or EllipticCurvePublicKey.') + if self._public_key is not None: + raise ValueError('The public key may only be set once.') + return CertificateBuilder( + self._version, self._issuer_name, self._subject_name, key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) - def set_serial_number(self, serial_number): + def serial_number(self, number): """ Sets the certificate serial number. """ - if not isinstance(serial_number, six.integer_types): + if not isinstance(number, six.integer_types): raise TypeError('Serial number must be of integral type.') - self._serial_number = serial_number + if self._public_key is not None: + raise ValueError('The serial number may only be set once.') + return CertificateBuilder( + self._version, self._issuer_name, self._subject_name, + self._public_key, number, self._not_valid_before, + self._not_valid_after, self._extensions + ) - def set_not_valid_before(self, time): + def not_valid_before(self, time): """ Sets the certificate activation time. """ # TODO: require UTC datetime? if not isinstance(time, datetime.datetime): raise TypeError('Expecting datetime object.') - self._not_valid_before = time + if self._not_valid_before is not None: + raise ValueError('The not valid before may only be set once.') + return CertificateBuilder( + self._version, self._issuer_name, self._subject_name, + self._public_key, self._serial_number, time, + self._not_valid_after, self._extensions + ) - def set_not_valid_after(self, time): + def not_valid_after(self, time): """ Sets the certificate expiration time. """ # TODO: require UTC datetime? if not isinstance(time, datetime.datetime): raise TypeError('Expecting datetime object.') - self._not_valid_after = time + if self._not_valid_before is not None: + raise ValueError('The not valid after may only be set once.') + return CertificateBuilder( + self._version, self._issuer_name, self._subject_name, + self._public_key, self._serial_number, self._not_valid_before, + time, self._extensions + ) - def add_extension(self, extension): + def add_extension(self, extension, critical): """ Adds an X.509 extension to the certificate. """ + if isinstance(extension, BasicConstraints): + extension = Extension(OID_BASIC_CONSTRAINTS, critical, extension) + elif isinstance(extension, SubjectAlternativeName): + extension = Extension( + OID_SUBJECT_ALTERNATIVE_NAME, critical, extension + ) + else: + raise NotImplementedError('Unsupported X.509 extension.') if not isinstance(extension, Extension): raise TypeError('Expecting x509.Extension object.') + + # TODO: This is quadratic in the number of extensions for e in self._extensions: if e.oid == extension.oid: raise ValueError('This extension has already been set.') - self._extensions.append(extension) + + return CertificateBuilder( + self._version, self._issuer_name, self._subject_name, + self._public_key, self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + [extension] + ) def sign(self, backend, private_key, algorithm): """ Signs the certificate using the CA's private key. """ + if self._version is None: + self._version = Version.v1 return backend.sign_x509_certificate(self, private_key, algorithm) diff --git a/tests/test_x509.py b/tests/test_x509.py index 92f40473..7719833c 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -787,33 +787,35 @@ class TestRSACertificateRequest(object): backend=backend, ) - builder = x509.CertificateBuilder() - builder.set_version(x509.Version.v3) - builder.set_serial_number(777) - builder.set_issuer_name(x509.Name([ + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().version( + x509.Version.v3 + ).serial_number( + 777 + ).issuer_name(x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'), x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'), x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'), x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'), x509.NameAttribute(x509.OID_COMMON_NAME, 'cryptography.io'), - ])) - builder.set_subject_name(x509.Name([ + ])).subject_name(x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'), x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'), x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'), x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'), x509.NameAttribute(x509.OID_COMMON_NAME, 'cryptography.io'), - ])) - builder.set_public_key(subject_private_key.public_key()) - builder.add_extension(x509.Extension( - x509.OID_BASIC_CONSTRAINTS, - True, - x509.BasicConstraints(False, None), - )) - not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) - not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) - builder.set_not_valid_before(not_valid_before) - builder.set_not_valid_after(not_valid_after) + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(False, None), True, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + cert = builder.sign(backend, issuer_private_key, hashes.SHA1()) assert cert.version is x509.Version.v3 -- cgit v1.2.3 From 0092a0bb57590ce0946fdbd37513787bfa6d80b4 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 18 Jul 2015 21:46:41 -0500 Subject: Remove unnecessary helper functions - Update documented methods - Do not mute the CertificateBuilder object if no version is set --- docs/x509/reference.rst | 16 ++++++---------- src/cryptography/hazmat/backends/openssl/backend.py | 18 +----------------- src/cryptography/x509.py | 5 +++-- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 65e3880d..b6c2f8a8 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -393,10 +393,6 @@ X.509 Certificate Builder .. class:: CertificateBuilder - .. method:: __init__() - - Creates an empty certificate (version 1). - .. method:: set_version(version) Sets the X.509 version that will be used in the certificate. @@ -404,27 +400,27 @@ X.509 Certificate Builder :param version: The :class:`~cryptography.x509.Version` that will be used by the certificate. - .. method:: set_issuer_name(name) + .. method:: issuer_name(name) Sets the issuer's distinguished name. :param public_key: The :class:`~cryptography.x509.Name` that describes the issuer (CA). - .. method:: set_subject_name(name) + .. method:: subject_name(name) Sets the subject's distinguished name. :param public_key: The :class:`~cryptography.x509.Name` that describes the subject (requester). - .. method:: set_public_key(public_key) + .. method:: public_key(public_key) Sets the subject's public key. :param public_key: The subject's public key. - .. method:: set_serial_number(serial_number) + .. method:: serial_number(serial_number) Sets the certificate's serial number (an integer). The CA's policy determines how it attributes serial numbers to certificates. The only @@ -435,7 +431,7 @@ X.509 Certificate Builder identify this certificate (most notably during certificate revocation checking). - .. method:: set_not_valid_before(time) + .. method:: not_valid_before(time) Sets the certificate's activation time. This is the time from which clients can start trusting the certificate. It may be different from @@ -445,7 +441,7 @@ X.509 Certificate Builder activation time for the certificate. The certificate may not be trusted clients if it is used before this time. - .. method:: set_not_valid_after(time) + .. method:: not_valid_after(time) Sets the certificate's expiration time. This is the time from which clients should no longer trust the certificate. The CA's policy will diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 04f631f9..1c912e6c 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -95,22 +95,6 @@ def _encode_asn1_str_gc(backend, data, length): return s -def _make_asn1_int(backend, x): - i = backend._lib.ASN1_INTEGER_new() - # i = backend._ffi.gc(i, backend._lib.ASN1_INTEGER_free) - backend._lib.ASN1_INTEGER_set(i, x) - return i - - -def _make_asn1_str(backend, x, n=None): - if n is None: - n = len(x) - s = backend._lib.ASN1_OCTET_STRING_new() - # s = backend._ffi.gc(s, backend._lib.ASN1_OCTET_STRING_free) - backend._lib.ASN1_OCTET_STRING_set(s, x, n) - return s - - def _encode_name(backend, attributes): """ The X509_NAME created will not be gc'd. Use _encode_name_gc if needed. @@ -1039,7 +1023,7 @@ class Backend(object): assert res == 1 # Set the certificate serial number. - serial_number = _make_asn1_int(self, builder._serial_number) + serial_number = _encode_asn1_int(self, builder._serial_number) self._lib.X509_set_serialNumber(x509_cert, serial_number) # Set the "not before" time. diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index c04b8c9c..a9d4430d 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1746,6 +1746,7 @@ class CertificateBuilder(object): """ Signs the certificate using the CA's private key. """ + builder = self if self._version is None: - self._version = Version.v1 - return backend.sign_x509_certificate(self, private_key, algorithm) + builder = self.version(Version.v3) + return backend.sign_x509_certificate(builder, private_key, algorithm) -- cgit v1.2.3 From be9985b14602fc8bc535d78675c0f11ce5ceebc3 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 18 Jul 2015 23:22:19 -0500 Subject: Fix x509.Name creation in CertificateBuilder tests --- tests/test_x509.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_x509.py b/tests/test_x509.py index 7719833c..fc0ccdef 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -795,17 +795,17 @@ class TestRSACertificateRequest(object): ).serial_number( 777 ).issuer_name(x509.Name([ - x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, 'cryptography.io'), + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), ])).subject_name(x509.Name([ - x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, 'cryptography.io'), + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), ])).public_key( subject_private_key.public_key() ).add_extension( -- cgit v1.2.3 From 43ae7387cc20b70ea71e262813d2d24af99f0b08 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 18 Jul 2015 23:27:31 -0500 Subject: Fix copy-paste errors --- src/cryptography/x509.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index a9d4430d..e73721b5 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1648,7 +1648,7 @@ class CertificateBuilder(object): """ if not isinstance(name, Name): raise TypeError('Expecting x509.Name object.') - if self._issuer_name is not None: + if self._subject_name is not None: raise ValueError('The subject name may only be set once.') return CertificateBuilder( self._version, self._issuer_name, name, self._public_key, @@ -1678,7 +1678,7 @@ class CertificateBuilder(object): """ if not isinstance(number, six.integer_types): raise TypeError('Serial number must be of integral type.') - if self._public_key is not None: + if self._serial_number is not None: raise ValueError('The serial number may only be set once.') return CertificateBuilder( self._version, self._issuer_name, self._subject_name, @@ -1708,7 +1708,7 @@ class CertificateBuilder(object): # TODO: require UTC datetime? if not isinstance(time, datetime.datetime): raise TypeError('Expecting datetime object.') - if self._not_valid_before is not None: + if self._not_valid_after is not None: raise ValueError('The not valid after may only be set once.') return CertificateBuilder( self._version, self._issuer_name, self._subject_name, -- cgit v1.2.3 From e8fd93c2083281395984abe4e49c63958427d918 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 19 Jul 2015 10:05:40 -0500 Subject: Construct extensions like a CSR - Use _encode_basic_constraints appropriately - Create an appropriate object from the oid dotted string - Create the X509 Extension appropriately --- src/cryptography/hazmat/backends/openssl/backend.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 1c912e6c..5b9f0759 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -1043,14 +1043,17 @@ class Backend(object): # Add extensions. for i, extension in enumerate(builder._extensions): if isinstance(extension.value, x509.BasicConstraints): - extension = _encode_basic_constraints( - self, - extension.value.ca, - extension.value.path_length, - extension.critical - ) + pp, r = _encode_basic_constraints(self, extension.value) else: raise ValueError('Extension not yet supported.') + + obj = _txt2obj(self, extension.oid.dotted_string) + extension = self._lib.X509_EXTENSION_create_by_OBJ( + self._ffi.NULL, + obj, + 1 if extension.critical else 0, + _encode_asn1_str_gc(self, pp[0], r) + ) res = self._lib.X509_add_ext(x509_cert, extension, i) assert res == 1 -- cgit v1.2.3 From e4e52a4d3e866f65d20045c6f505d3264db06ee7 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 19 Jul 2015 10:15:37 -0500 Subject: Use test fixtures instead of generating private keys --- tests/test_x509.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/test_x509.py b/tests/test_x509.py index fc0ccdef..7edef7ed 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -776,16 +776,8 @@ class TestRSACertificateRequest(object): assert hash(request1) != hash(request3) def test_build_cert(self, backend): - issuer_private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=2048, - backend=backend, - ) - subject_private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=2048, - backend=backend, - ) + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) -- cgit v1.2.3 From 8d6733091322f87a3bbcff2c9ed414c52eeab746 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 19 Jul 2015 10:26:45 -0500 Subject: Handle SubjectAlternativeName extensions in the backend They are handled in cryptography.x509 so they need to be handled here --- src/cryptography/hazmat/backends/openssl/backend.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 5b9f0759..80b73f42 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -1044,6 +1044,8 @@ class Backend(object): for i, extension in enumerate(builder._extensions): if isinstance(extension.value, x509.BasicConstraints): pp, r = _encode_basic_constraints(self, extension.value) + elif isinstance(extension.value, x509.SubjectAlternativeName): + pp, r = _encode_subject_alt_name(self, extension.value) else: raise ValueError('Extension not yet supported.') -- cgit v1.2.3 From 8887a57bc4be41657d174c371798232b976dfa5b Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 19 Jul 2015 10:26:59 -0500 Subject: Use explicit keyword args in the tests --- tests/test_x509.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_x509.py b/tests/test_x509.py index 7edef7ed..91b4f2b3 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -801,7 +801,7 @@ class TestRSACertificateRequest(object): ])).public_key( subject_private_key.public_key() ).add_extension( - x509.BasicConstraints(False, None), True, + x509.BasicConstraints(ca=False, path_length=None), True, ).not_valid_before( not_valid_before ).not_valid_after( -- cgit v1.2.3 From 3934385ee9684439abf27589e14f9338f85ed656 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 19 Jul 2015 10:28:10 -0500 Subject: Remove unnecessary type check --- src/cryptography/x509.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index e73721b5..6f7aeeed 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1728,8 +1728,6 @@ class CertificateBuilder(object): ) else: raise NotImplementedError('Unsupported X.509 extension.') - if not isinstance(extension, Extension): - raise TypeError('Expecting x509.Extension object.') # TODO: This is quadratic in the number of extensions for e in self._extensions: -- cgit v1.2.3 From 747a21726dcff6579121ce2364134fa91e10c2de Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 19 Jul 2015 11:00:14 -0500 Subject: Add test coverage for x509.CertificateBuilder --- tests/test_x509.py | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/tests/test_x509.py b/tests/test_x509.py index 91b4f2b3..16d040f0 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -820,6 +820,139 @@ class TestRSACertificateRequest(object): assert basic_constraints.value.path_length is None +class TestCertificateBuilder(object): + def test_version_must_be_a_version_type(self): + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.version("v1") + + def test_version_may_only_be_set_once(self): + builder = x509.CertificateBuilder().version( + x509.Version.v3 + ) + + with pytest.raises(ValueError): + builder.version(x509.Version.v1) + + def test_issuer_name_must_be_a_name_type(self): + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.issuer_name("subject") + + with pytest.raises(TypeError): + builder.issuer_name(object) + + def test_issuer_name_may_only_be_set_once(self): + name = x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ]) + builder = x509.CertificateBuilder().issuer_name(name) + + with pytest.raises(ValueError): + builder.issuer_name(name) + + def test_subject_name_must_be_a_name_type(self): + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.subject_name("subject") + + with pytest.raises(TypeError): + builder.subject_name(object) + + def test_subject_name_may_only_be_set_once(self): + name = x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ]) + builder = x509.CertificateBuilder().subject_name(name) + + with pytest.raises(ValueError): + builder.subject_name(name) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_public_key_must_be_public_key(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.public_key(private_key) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_public_key_may_only_be_set_once(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + public_key = private_key.public_key() + builder = x509.CertificateBuilder().public_key(public_key) + + with pytest.raises(ValueError): + builder.public_key(public_key) + + def test_serial_number_must_be_an_integer_type(self): + with pytest.raises(TypeError): + x509.CertificateBuilder().serial_number(10.0) + + def test_serial_number_may_only_be_set_once(self): + builder = x509.CertificateBuilder().serial_number(10) + + with pytest.raises(ValueError): + builder.serial_number(20) + + def test_invalid_not_valid_after(self): + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_after(104204304504) + + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_after(datetime.time()) + + def test_not_valid_after_may_only_be_set_once(self): + builder = x509.CertificateBuilder().not_valid_after( + datetime.datetime.now() + ) + + with pytest.raises(ValueError): + builder.not_valid_after( + datetime.datetime.now() + ) + + def test_invalid_not_valid_before(self): + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_before(104204304504) + + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_before(datetime.time()) + + def test_not_valid_before_may_only_be_set_once(self): + builder = x509.CertificateBuilder().not_valid_before( + datetime.datetime.now() + ) + + with pytest.raises(ValueError): + builder.not_valid_before( + datetime.datetime.now() + ) + + def test_add_extension_checks_for_duplicates(self): + builder = x509.CertificateBuilder().add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ) + + with pytest.raises(ValueError): + builder.add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ) + + @pytest.mark.requires_backend_interface(interface=X509Backend) class TestCertificateSigningRequestBuilder(object): @pytest.mark.requires_backend_interface(interface=RSABackend) -- cgit v1.2.3 From 4e7bdc785b29e637d44ecb5a0aefc401ee6b1c14 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 20 Jul 2015 11:42:27 -0500 Subject: Use correct exception class in openssl backend --- src/cryptography/hazmat/backends/openssl/backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 80b73f42..69a8d87e 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -1047,7 +1047,7 @@ class Backend(object): elif isinstance(extension.value, x509.SubjectAlternativeName): pp, r = _encode_subject_alt_name(self, extension.value) else: - raise ValueError('Extension not yet supported.') + raise NotImplementedError('Extension not yet supported.') obj = _txt2obj(self, extension.oid.dotted_string) extension = self._lib.X509_EXTENSION_create_by_OBJ( -- cgit v1.2.3 From 9e0666e0bdd7b8357c0f95b46e8cdad8cfea7a75 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 20 Jul 2015 11:42:51 -0500 Subject: Add another extension to our CertificateBuilder test --- tests/test_x509.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_x509.py b/tests/test_x509.py index 16d040f0..7a069136 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -802,6 +802,9 @@ class TestRSACertificateRequest(object): subject_private_key.public_key() ).add_extension( x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, ).not_valid_before( not_valid_before ).not_valid_after( @@ -952,6 +955,12 @@ class TestCertificateBuilder(object): x509.BasicConstraints(ca=False, path_length=None), True, ) + def test_add_unsupported_extension(self): + builder = x509.CertificateBuilder() + + with pytest.raises(NotImplementedError): + builder.add_extension(object(), False) + @pytest.mark.requires_backend_interface(interface=X509Backend) class TestCertificateSigningRequestBuilder(object): -- cgit v1.2.3 From b77c716a2935b2fc1de30092ebacdaefae184414 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 20 Jul 2015 21:22:33 -0500 Subject: Add tests to test_openssl backend for extra coverage --- tests/hazmat/backends/test_openssl.py | 39 +++++++++++++++++++++++++++++++++++ tests/test_x509.py | 9 ++++++++ 2 files changed, 48 insertions(+) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 6a2e8a77..5505c630 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function +import datetime import os import subprocess import sys @@ -14,6 +15,7 @@ import pretend import pytest from cryptography import utils +from cryptography import x509 from cryptography.exceptions import InternalError, _Reasons from cryptography.hazmat.backends.interfaces import RSABackend from cryptography.hazmat.backends.openssl.backend import ( @@ -478,6 +480,43 @@ class TestOpenSSLCreateX509CSR(object): backend.create_x509_csr(object(), private_key, hashes.SHA1()) +class TestOpenSSLSignX509Certificate(object): + def test_requires_certificate_builder(self): + private_key = RSA_KEY_2048.private_key(backend) + + with pytest.raises(TypeError): + backend.sign_x509_certificate(object(), private_key, DummyHash()) + + def test_checks_for_unsupported_extensions(self): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().version( + x509.Version.v3 + ).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ])).public_key( + private_key.public_key() + ).serial_number( + 777 + ).not_valid_before( + datetime.datetime(1999, 1, 1) + ).not_valid_after( + datetime.datetime(2020, 1, 1) + ) + + builder._extensions.append(x509.Extension( + oid=x509.OID_COUNTRY_NAME, + critical=False, + value=object() + )) + + with pytest.raises(NotImplementedError): + backend.sign_x509_certificate(builder, private_key, hashes.SHA1()) + + class TestOpenSSLSerialisationWithOpenSSL(object): def test_pem_password_cb_buffer_too_small(self): ffi_cb, cb = backend._pem_password_cb(b"aa") diff --git a/tests/test_x509.py b/tests/test_x509.py index 7a069136..c4a423aa 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -961,6 +961,15 @@ class TestCertificateBuilder(object): with pytest.raises(NotImplementedError): builder.add_extension(object(), False) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_unsupported_hash(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.sign(backend, private_key, object()) + @pytest.mark.requires_backend_interface(interface=X509Backend) class TestCertificateSigningRequestBuilder(object): -- cgit v1.2.3 From 7644383e13f2dcc1e70e19a157b28608cc072830 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 21 Jul 2015 09:18:05 -0500 Subject: Fix documentation referring to CertificateBuilder.set_version --- docs/x509/reference.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index b6c2f8a8..e4ea252d 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -393,7 +393,7 @@ X.509 Certificate Builder .. class:: CertificateBuilder - .. method:: set_version(version) + .. method:: version(version) Sets the X.509 version that will be used in the certificate. -- cgit v1.2.3 From 893246fd6b6dcefa270777e7cb8261a3131a2745 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 24 Jul 2015 14:52:18 -0500 Subject: Remove CertificateBuilder.version Default CertificateBuilder to Version.v3 --- docs/x509/reference.rst | 7 ------- src/cryptography/x509.py | 21 ++------------------- tests/hazmat/backends/test_openssl.py | 4 +--- tests/test_x509.py | 18 +----------------- 4 files changed, 4 insertions(+), 46 deletions(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index e4ea252d..5a809847 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -393,13 +393,6 @@ X.509 Certificate Builder .. class:: CertificateBuilder - .. method:: version(version) - - Sets the X.509 version that will be used in the certificate. - - :param version: The :class:`~cryptography.x509.Version` that will be - used by the certificate. - .. method:: issuer_name(name) Sets the issuer's distinguished name. diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 6f7aeeed..4b13fce1 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1605,7 +1605,7 @@ class CertificateBuilder(object): """ Creates an empty X.509 certificate (version 1). """ - self._version = version + self._version = Version.v3 self._issuer_name = issuer_name self._subject_name = subject_name self._public_key = public_key @@ -1614,20 +1614,6 @@ class CertificateBuilder(object): self._not_valid_after = not_valid_after self._extensions = extensions - def version(self, version): - """ - Sets the X.509 version required by decoders. - """ - if not isinstance(version, Version): - raise TypeError('Expecting x509.Version object.') - if self._version is not None: - raise ValueError('The version may only be set once.') - return CertificateBuilder( - version, self._issuer_name, self._subject_name, self._public_key, - self._serial_number, self._not_valid_before, - self._not_valid_after, self._extensions - ) - def issuer_name(self, name): """ Sets the CA's distinguished name. @@ -1744,7 +1730,4 @@ class CertificateBuilder(object): """ Signs the certificate using the CA's private key. """ - builder = self - if self._version is None: - builder = self.version(Version.v3) - return backend.sign_x509_certificate(builder, private_key, algorithm) + return backend.sign_x509_certificate(self, private_key, algorithm) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 5505c630..daa37874 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -489,9 +489,7 @@ class TestOpenSSLSignX509Certificate(object): def test_checks_for_unsupported_extensions(self): private_key = RSA_KEY_2048.private_key(backend) - builder = x509.CertificateBuilder().version( - x509.Version.v3 - ).subject_name(x509.Name([ + builder = x509.CertificateBuilder().subject_name(x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), diff --git a/tests/test_x509.py b/tests/test_x509.py index c4a423aa..e052b4d9 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -782,9 +782,7 @@ class TestRSACertificateRequest(object): not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) - builder = x509.CertificateBuilder().version( - x509.Version.v3 - ).serial_number( + builder = x509.CertificateBuilder().serial_number( 777 ).issuer_name(x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), @@ -824,20 +822,6 @@ class TestRSACertificateRequest(object): class TestCertificateBuilder(object): - def test_version_must_be_a_version_type(self): - builder = x509.CertificateBuilder() - - with pytest.raises(TypeError): - builder.version("v1") - - def test_version_may_only_be_set_once(self): - builder = x509.CertificateBuilder().version( - x509.Version.v3 - ) - - with pytest.raises(ValueError): - builder.version(x509.Version.v1) - def test_issuer_name_must_be_a_name_type(self): builder = x509.CertificateBuilder() -- cgit v1.2.3 From 56561b12894bca3309bea4596278e844b0d567d0 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 24 Jul 2015 16:38:50 -0500 Subject: Check result of setting the serial number - Add checks for private key types - Add tests around new checks for types of private keys --- .../hazmat/backends/openssl/backend.py | 16 ++- tests/test_x509.py | 126 +++++++++++++++++++++ 2 files changed, 140 insertions(+), 2 deletions(-) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 69a8d87e..3beb716d 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -990,12 +990,23 @@ class Backend(object): return _CertificateSigningRequest(self, x509_req) def sign_x509_certificate(self, builder, private_key, algorithm): - # TODO: check type of private key parameter. if not isinstance(builder, x509.CertificateBuilder): raise TypeError('Builder type mismatch.') if not isinstance(algorithm, hashes.HashAlgorithm): raise TypeError('Algorithm must be a registered hash algorithm.') + if self._lib.OPENSSL_VERSION_NUMBER <= 0x10001000: + if isinstance(private_key, _DSAPrivateKey): + raise NotImplementedError( + "Certificate signatures aren't implemented for DSA" + " keys on OpenSSL versions less than 1.0.1." + ) + if isinstance(private_key, _EllipticCurvePrivateKey): + raise NotImplementedError( + "Certificate signatures aren't implemented for EC" + " keys on OpenSSL versions less than 1.0.1." + ) + # Resolve the signature algorithm. evp_md = self._lib.EVP_get_digestbyname( algorithm.name.encode('ascii') @@ -1024,7 +1035,8 @@ class Backend(object): # Set the certificate serial number. serial_number = _encode_asn1_int(self, builder._serial_number) - self._lib.X509_set_serialNumber(x509_cert, serial_number) + res = self._lib.X509_set_serialNumber(x509_cert, serial_number) + assert res == 1 # Set the "not before" time. res = self._lib.ASN1_TIME_set( diff --git a/tests/test_x509.py b/tests/test_x509.py index e052b4d9..19cb83bf 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -954,6 +954,132 @@ class TestCertificateBuilder(object): with pytest.raises(TypeError): builder.sign(backend, private_key, object()) + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_dsa_private_key_is_unsupported(self, backend): + if backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000: + pytest.skip("Requires an older OpenSSL. Must be < 1.0.1") + + private_key = DSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + + with pytest.raises(NotImplementedError): + builder.sign(backend, private_key, hashes.SHA512()) + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_ec_private_key_is_unsupported(self, backend): + if backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000: + pytest.skip("Requires an older OpenSSL. Must be < 1.0.1") + + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + builder = x509.CertificateBuilder() + + with pytest.raises(NotImplementedError): + builder.sign(backend, private_key, hashes.SHA512()) + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_dsa_private_key(self, backend): + if backend._lib.OPENSSL_VERSION_NUMBER < 0x10001000: + pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") + + issuer_private_key = DSA_KEY_2048.private_key(backend) + subject_private_key = DSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ])).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(backend, issuer_private_key, hashes.SHA1()) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + x509.OID_BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_dsa_private_key(self, backend): + if backend._lib.OPENSSL_VERSION_NUMBER < 0x10001000: + pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") + + _skip_curve_unsupported(backend, ec.SECP256R1()) + issuer_private_key = ec.generate_private_key(ec.SECP256R1(), backend) + subject_private_key = ec.generate_private_key(ec.SECP256R1(), backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ])).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(backend, issuer_private_key, hashes.SHA1()) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + x509.OID_BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + @pytest.mark.requires_backend_interface(interface=X509Backend) class TestCertificateSigningRequestBuilder(object): -- cgit v1.2.3 From 8690effbb812f944ea4d730e73dc60e9d77dae17 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 24 Jul 2015 16:42:58 -0500 Subject: Add extra CertificateBuilder test using SHA512 and 512-bit RSA key --- tests/test_x509.py | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/tests/test_x509.py b/tests/test_x509.py index 19cb83bf..c3381d5f 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -22,7 +22,7 @@ from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa from .hazmat.primitives.fixtures_dsa import DSA_KEY_2048 -from .hazmat.primitives.fixtures_rsa import RSA_KEY_2048 +from .hazmat.primitives.fixtures_rsa import RSA_KEY_512, RSA_KEY_2048 from .hazmat.primitives.test_ec import _skip_curve_unsupported from .utils import load_vectors_from_file @@ -1080,6 +1080,54 @@ class TestCertificateBuilder(object): assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_sha512_and_rsa512(self, backend): + # TODO(sigmavirus24): Give this a better name + issuer_private_key = RSA_KEY_512.private_key(backend) + subject_private_key = RSA_KEY_512.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ])).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(backend, issuer_private_key, hashes.SHA512()) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + x509.OID_BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + @pytest.mark.requires_backend_interface(interface=X509Backend) class TestCertificateSigningRequestBuilder(object): -- cgit v1.2.3 From c5e1c254ba4bc9bb94e8ddcc66f4dc8eb62ce218 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 31 Jul 2015 23:33:35 -0500 Subject: Document other two parameters from sign method - Remove incorrect CertificateBuilder doc-string - Check that serial numbers are non-negative and < 160 bits - Check that dates passed aren't earlier than the unix epoch - Remove version from CertificateBuilder.__init__ and version method --- docs/x509/reference.rst | 11 +++++++++++ src/cryptography/x509.py | 32 +++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 5a809847..b7fef940 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -455,6 +455,17 @@ X.509 Certificate Builder Sign the certificate using the CA's private key. + :param backend: Backend that will be used to build the certificate. + Must support the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :param private_key: The + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + that will be used to sign the certificate. + :param algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that will be used to generate the signature. diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 4b13fce1..11ce6cf0 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -97,6 +97,8 @@ _GENERAL_NAMES = { 8: "registeredID", } +_UNIX_EPOCH = datetime.datetime(1970, 1, 1) + class Version(Enum): v1 = 0 @@ -1599,12 +1601,9 @@ class CertificateSigningRequestBuilder(object): class CertificateBuilder(object): - def __init__(self, version=None, issuer_name=None, subject_name=None, + def __init__(self, issuer_name=None, subject_name=None, public_key=None, serial_number=None, not_valid_before=None, not_valid_after=None, extensions=[]): - """ - Creates an empty X.509 certificate (version 1). - """ self._version = Version.v3 self._issuer_name = issuer_name self._subject_name = subject_name @@ -1623,7 +1622,7 @@ class CertificateBuilder(object): if self._issuer_name is not None: raise ValueError('The issuer name may only be set once.') return CertificateBuilder( - self._version, name, self._subject_name, self._public_key, + name, self._subject_name, self._public_key, self._serial_number, self._not_valid_before, self._not_valid_after, self._extensions ) @@ -1637,7 +1636,7 @@ class CertificateBuilder(object): if self._subject_name is not None: raise ValueError('The subject name may only be set once.') return CertificateBuilder( - self._version, self._issuer_name, name, self._public_key, + self._issuer_name, name, self._public_key, self._serial_number, self._not_valid_before, self._not_valid_after, self._extensions ) @@ -1653,7 +1652,7 @@ class CertificateBuilder(object): if self._public_key is not None: raise ValueError('The public key may only be set once.') return CertificateBuilder( - self._version, self._issuer_name, self._subject_name, key, + self._issuer_name, self._subject_name, key, self._serial_number, self._not_valid_before, self._not_valid_after, self._extensions ) @@ -1666,8 +1665,13 @@ class CertificateBuilder(object): raise TypeError('Serial number must be of integral type.') if self._serial_number is not None: raise ValueError('The serial number may only be set once.') + if number < 0: + raise ValueError('The serial number should be non-negative.') + if utils.bit_length(number) > 160: # As defined in RFC 5280 + raise ValueError('The serial number should not be more than 160 ' + 'bits.') return CertificateBuilder( - self._version, self._issuer_name, self._subject_name, + self._issuer_name, self._subject_name, self._public_key, number, self._not_valid_before, self._not_valid_after, self._extensions ) @@ -1681,8 +1685,11 @@ class CertificateBuilder(object): raise TypeError('Expecting datetime object.') if self._not_valid_before is not None: raise ValueError('The not valid before may only be set once.') + if time <= _UNIX_EPOCH: + raise ValueError('The not valid before date must be after the unix' + ' epoch (1970 January 1).') return CertificateBuilder( - self._version, self._issuer_name, self._subject_name, + self._issuer_name, self._subject_name, self._public_key, self._serial_number, time, self._not_valid_after, self._extensions ) @@ -1696,8 +1703,11 @@ class CertificateBuilder(object): raise TypeError('Expecting datetime object.') if self._not_valid_after is not None: raise ValueError('The not valid after may only be set once.') + if time <= _UNIX_EPOCH: + raise ValueError('The not valid after date must be after the unix' + ' epoch (1970 January 1).') return CertificateBuilder( - self._version, self._issuer_name, self._subject_name, + self._issuer_name, self._subject_name, self._public_key, self._serial_number, self._not_valid_before, time, self._extensions ) @@ -1721,7 +1731,7 @@ class CertificateBuilder(object): raise ValueError('This extension has already been set.') return CertificateBuilder( - self._version, self._issuer_name, self._subject_name, + self._issuer_name, self._subject_name, self._public_key, self._serial_number, self._not_valid_before, self._not_valid_after, self._extensions + [extension] ) -- cgit v1.2.3 From 19f5a49d413bd9c7b81f29511f4c983bb9408968 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 1 Aug 2015 11:06:17 -0500 Subject: Add check for an RSA Key being too small - Remove outdated/unnecessary/illegitimate TODOs - Fix up test for an RSA key that is too small --- src/cryptography/hazmat/backends/openssl/backend.py | 6 +++++- src/cryptography/x509.py | 2 -- tests/test_x509.py | 15 +++------------ 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 3beb716d..eae31cd1 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -1081,7 +1081,11 @@ class Backend(object): res = self._lib.X509_sign( x509_cert, private_key._evp_pkey, evp_md ) - assert res > 0 + if res == 0: + errors = self._consume_errors() + assert errors[0][1] == self._lib.ERR_LIB_RSA + assert errors[0][3] == self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY + raise ValueError("Digest too big for RSA key") return _Certificate(self, x509_cert) diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 11ce6cf0..5760aae7 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1680,7 +1680,6 @@ class CertificateBuilder(object): """ Sets the certificate activation time. """ - # TODO: require UTC datetime? if not isinstance(time, datetime.datetime): raise TypeError('Expecting datetime object.') if self._not_valid_before is not None: @@ -1698,7 +1697,6 @@ class CertificateBuilder(object): """ Sets the certificate expiration time. """ - # TODO: require UTC datetime? if not isinstance(time, datetime.datetime): raise TypeError('Expecting datetime object.') if self._not_valid_after is not None: diff --git a/tests/test_x509.py b/tests/test_x509.py index c3381d5f..341818af 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -1082,8 +1082,7 @@ class TestCertificateBuilder(object): @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_build_cert_with_sha512_and_rsa512(self, backend): - # TODO(sigmavirus24): Give this a better name + def test_build_cert_with_rsa_key_too_small(self, backend): issuer_private_key = RSA_KEY_512.private_key(backend) subject_private_key = RSA_KEY_512.private_key(backend) @@ -1117,16 +1116,8 @@ class TestCertificateBuilder(object): not_valid_after ) - cert = builder.sign(backend, issuer_private_key, hashes.SHA512()) - - assert cert.version is x509.Version.v3 - assert cert.not_valid_before == not_valid_before - assert cert.not_valid_after == not_valid_after - basic_constraints = cert.extensions.get_extension_for_oid( - x509.OID_BASIC_CONSTRAINTS - ) - assert basic_constraints.value.ca is False - assert basic_constraints.value.path_length is None + with pytest.raises(ValueError): + builder.sign(backend, issuer_private_key, hashes.SHA512()) @pytest.mark.requires_backend_interface(interface=X509Backend) -- cgit v1.2.3 From c9682adaf78cc0983ef639279d38e8bdccc97321 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 1 Aug 2015 12:11:13 -0500 Subject: Fix up parameter names in the docs --- docs/x509/reference.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index b7fef940..9d5006ae 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -397,15 +397,15 @@ X.509 Certificate Builder Sets the issuer's distinguished name. - :param public_key: The :class:`~cryptography.x509.Name` that describes - the issuer (CA). + :param name: The :class:`~cryptography.x509.Name` that describes the + issuer (CA). .. method:: subject_name(name) Sets the subject's distinguished name. - :param public_key: The :class:`~cryptography.x509.Name` that describes - the subject (requester). + :param name: The :class:`~cryptography.x509.Name` that describes the + subject (requester). .. method:: public_key(public_key) -- cgit v1.2.3 From 6fdc89517a6466a8ab3632f1caac872a8ba6d9de Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 1 Aug 2015 16:16:16 -0500 Subject: Add _encode_asn1_int_gc Ensure the certificate serial number is freed --- src/cryptography/hazmat/backends/openssl/backend.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index eae31cd1..c190f591 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -79,6 +79,12 @@ def _encode_asn1_int(backend, x): return i +def _encode_asn1_int_gc(backend, x): + i = _encode_asn1_int(backend, x) + i = backend._ffi.gc(i, backend._lib.ASN1_INTEGER_free) + return i + + def _encode_asn1_str(backend, data, length): """ Create an ASN1_OCTET_STRING from a Python byte string. @@ -1034,7 +1040,7 @@ class Backend(object): assert res == 1 # Set the certificate serial number. - serial_number = _encode_asn1_int(self, builder._serial_number) + serial_number = _encode_asn1_int_gc(self, builder._serial_number) res = self._lib.X509_set_serialNumber(x509_cert, serial_number) assert res == 1 -- cgit v1.2.3 From 85fc4d51635e96adb5781a571acad062b4aa0d88 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 1 Aug 2015 20:29:31 -0500 Subject: Minor pep8 and doc fixes --- docs/x509/reference.rst | 2 +- tests/test_x509.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 9d5006ae..2029c08f 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -405,7 +405,7 @@ X.509 Certificate Builder Sets the subject's distinguished name. :param name: The :class:`~cryptography.x509.Name` that describes the - subject (requester). + subject. .. method:: public_key(public_key) diff --git a/tests/test_x509.py b/tests/test_x509.py index 341818af..2aea2f53 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -22,7 +22,7 @@ from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa from .hazmat.primitives.fixtures_dsa import DSA_KEY_2048 -from .hazmat.primitives.fixtures_rsa import RSA_KEY_512, RSA_KEY_2048 +from .hazmat.primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 from .hazmat.primitives.test_ec import _skip_curve_unsupported from .utils import load_vectors_from_file @@ -1031,7 +1031,7 @@ class TestCertificateBuilder(object): @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_build_cert_with_dsa_private_key(self, backend): + def test_build_cert_with_ec_private_key(self, backend): if backend._lib.OPENSSL_VERSION_NUMBER < 0x10001000: pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") -- cgit v1.2.3 From 91a461e39d943b0672dbb920cc85bf3f85aa04c1 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 1 Aug 2015 22:03:16 -0500 Subject: Slim tests by removing extra NameAttributes --- tests/test_x509.py | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/tests/test_x509.py b/tests/test_x509.py index 2aea2f53..7274fd7e 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -834,10 +834,6 @@ class TestCertificateBuilder(object): def test_issuer_name_may_only_be_set_once(self): name = x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), ]) builder = x509.CertificateBuilder().issuer_name(name) @@ -856,10 +852,6 @@ class TestCertificateBuilder(object): def test_subject_name_may_only_be_set_once(self): name = x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), ]) builder = x509.CertificateBuilder().subject_name(name) @@ -995,16 +987,8 @@ class TestCertificateBuilder(object): 777 ).issuer_name(x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), ])).subject_name(x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), ])).public_key( subject_private_key.public_key() ).add_extension( @@ -1046,16 +1030,8 @@ class TestCertificateBuilder(object): 777 ).issuer_name(x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), ])).subject_name(x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), ])).public_key( subject_private_key.public_key() ).add_extension( @@ -1093,16 +1069,8 @@ class TestCertificateBuilder(object): 777 ).issuer_name(x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), ])).subject_name(x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), ])).public_key( subject_private_key.public_key() ).add_extension( -- cgit v1.2.3 From b4a155d4e343a68ae0e53b728ae148dfab6a27d5 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 1 Aug 2015 23:07:19 -0500 Subject: Add some extra test coverage --- tests/test_x509.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_x509.py b/tests/test_x509.py index 7274fd7e..088e617d 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -881,6 +881,15 @@ class TestCertificateBuilder(object): with pytest.raises(TypeError): x509.CertificateBuilder().serial_number(10.0) + def test_serial_number_must_be_non_negative(self): + with pytest.raises(ValueError): + x509.CertificateBuilder().serial_number(-10) + + def test_serial_number_must_be_less_than_160_bits_long(self): + with pytest.raises(ValueError): + # 2 raised to the 160th power is actually 161 bits + x509.CertificateBuilder().serial_number(2 ** 160) + def test_serial_number_may_only_be_set_once(self): builder = x509.CertificateBuilder().serial_number(10) @@ -894,6 +903,11 @@ class TestCertificateBuilder(object): with pytest.raises(TypeError): x509.CertificateBuilder().not_valid_after(datetime.time()) + with pytest.raises(ValueError): + x509.CertificateBuilder().not_valid_after( + datetime.datetime(1960, 8, 10) + ) + def test_not_valid_after_may_only_be_set_once(self): builder = x509.CertificateBuilder().not_valid_after( datetime.datetime.now() @@ -911,6 +925,11 @@ class TestCertificateBuilder(object): with pytest.raises(TypeError): x509.CertificateBuilder().not_valid_before(datetime.time()) + with pytest.raises(ValueError): + x509.CertificateBuilder().not_valid_before( + datetime.datetime(1960, 8, 10) + ) + def test_not_valid_before_may_only_be_set_once(self): builder = x509.CertificateBuilder().not_valid_before( datetime.datetime.now() -- cgit v1.2.3 From 8f57142489a0d0d832b0ba7fa56eec84e1f00cc4 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 2 Aug 2015 11:31:08 -0500 Subject: Update the docs to be more accurate --- docs/x509/reference.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 2029c08f..ac07eade 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -411,7 +411,10 @@ X.509 Certificate Builder Sets the subject's public key. - :param public_key: The subject's public key. + :param public_key: The subject's public key. This can be one of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` .. method:: serial_number(serial_number) @@ -448,8 +451,9 @@ X.509 Certificate Builder Adds an X.509 extension to the certificate. - :param extension: The :class:`~cryptography.x509.Extension` to add to - the certificate. + :param extension: The extension to add to the certificate. Can be one + of :class:`~cryptography.x509.BasicConstraints` or + :class:`~cryptography.x509.SubjectAlternativeName`. .. method:: sign(backend, private_key, algorithm) -- cgit v1.2.3 From 47e9408311768cfdae8199bb2572ad0bcacbbb2b Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 2 Aug 2015 11:34:47 -0500 Subject: Check for subject alternative name in test Slim RSA key too small test --- tests/test_x509.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/test_x509.py b/tests/test_x509.py index 088e617d..fb583965 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -819,6 +819,12 @@ class TestRSACertificateRequest(object): ) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + x509.OID_SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] class TestCertificateBuilder(object): @@ -1031,6 +1037,12 @@ class TestCertificateBuilder(object): ) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + x509.OID_SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) @pytest.mark.requires_backend_interface(interface=X509Backend) @@ -1074,6 +1086,12 @@ class TestCertificateBuilder(object): ) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + x509.OID_SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) @@ -1092,11 +1110,6 @@ class TestCertificateBuilder(object): x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), ])).public_key( subject_private_key.public_key() - ).add_extension( - x509.BasicConstraints(ca=False, path_length=None), True, - ).add_extension( - x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), - critical=False, ).not_valid_before( not_valid_before ).not_valid_after( -- cgit v1.2.3 From 17c8900f0b38052d16864de493bd1d409cc94180 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 2 Aug 2015 21:13:59 -0500 Subject: Add note to serial_number parameter about entropy - Add reference to random-numbers.rst for easy intra-linking - Document critical parameter of CertificateBuilder.add_extension - Support InhibitAnyPolicy in the CertificateBuilder frontend but not in the backend - Slim down more tests - Fix up test that asserts the backend does not allow for unsupported extensions --- docs/random-numbers.rst | 2 ++ docs/x509/reference.rst | 14 ++++++++++---- src/cryptography/x509.py | 2 ++ tests/hazmat/backends/test_openssl.py | 14 +++----------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/random-numbers.rst b/docs/random-numbers.rst index 8b119a3e..81e5efbb 100644 --- a/docs/random-numbers.rst +++ b/docs/random-numbers.rst @@ -1,3 +1,5 @@ +.. _secure_random_number_generation: + Random number generation ======================== diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index ac07eade..26ac295b 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -425,7 +425,10 @@ X.509 Certificate Builder :param serial_number: Integer number that will be used by the CA to identify this certificate (most notably during certificate - revocation checking). + revocation checking). Users are encouraged to use a method of + generating 20 bytes of entropy, e.g., UUID4. For more information + on secure random number generation, see + :ref:`secure_random_number_generation`. .. method:: not_valid_before(time) @@ -433,7 +436,7 @@ X.509 Certificate Builder clients can start trusting the certificate. It may be different from the time at which the certificate was created. - :param time: The `datetime.datetime` object (in UTC) that marks the + :param time: The :class:`datetime.datetime` object (in UTC) that marks the activation time for the certificate. The certificate may not be trusted clients if it is used before this time. @@ -443,11 +446,11 @@ X.509 Certificate Builder clients should no longer trust the certificate. The CA's policy will determine how long the certificate should remain in use. - :param time: The `datetime.datetime` object (in UTC) that marks the + :param time: The :class:`datetime.datetime` object (in UTC) that marks the expiration time for the certificate. The certificate may not be trusted clients if it is used after this time. - .. method:: add_extension(extension) + .. method:: add_extension(extension, critical) Adds an X.509 extension to the certificate. @@ -455,6 +458,9 @@ X.509 Certificate Builder of :class:`~cryptography.x509.BasicConstraints` or :class:`~cryptography.x509.SubjectAlternativeName`. + :param critical: Set to ``True`` if the extension must be understood and + handled by whoever reads the certificate. + .. method:: sign(backend, private_key, algorithm) Sign the certificate using the CA's private key. diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 5760aae7..9f6cda13 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1720,6 +1720,8 @@ class CertificateBuilder(object): extension = Extension( OID_SUBJECT_ALTERNATIVE_NAME, critical, extension ) + elif isinstance(extension, InhibitAnyPolicy): + extension = Extension(OID_INHIBIT_ANY_POLICY, critical, extension) else: raise NotImplementedError('Unsupported X.509 extension.') diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index daa37874..5b611cd0 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -491,10 +491,6 @@ class TestOpenSSLSignX509Certificate(object): private_key = RSA_KEY_2048.private_key(backend) builder = x509.CertificateBuilder().subject_name(x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), - x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), - x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), ])).public_key( private_key.public_key() ).serial_number( @@ -503,16 +499,12 @@ class TestOpenSSLSignX509Certificate(object): datetime.datetime(1999, 1, 1) ).not_valid_after( datetime.datetime(2020, 1, 1) + ).add_extension( + x509.InhibitAnyPolicy(0), False ) - builder._extensions.append(x509.Extension( - oid=x509.OID_COUNTRY_NAME, - critical=False, - value=object() - )) - with pytest.raises(NotImplementedError): - backend.sign_x509_certificate(builder, private_key, hashes.SHA1()) + builder.sign(backend, private_key, hashes.SHA1()) class TestOpenSSLSerialisationWithOpenSSL(object): -- cgit v1.2.3 From 1517a4bb9f349747bb8d13f7724864c3927e47f4 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 2 Aug 2015 22:11:19 -0500 Subject: Add sign_x509_certificate to MultiBackend Add example of CertificateBuilder to the reference documentation --- docs/x509/reference.rst | 42 ++++++++++++++++++++++++ src/cryptography/hazmat/backends/multibackend.py | 9 +++++ 2 files changed, 51 insertions(+) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 26ac295b..1dd466e8 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -393,6 +393,48 @@ X.509 Certificate Builder .. class:: CertificateBuilder + .. versionadded:: 1.0 + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import rsa + >>> import datetime + >>> import uuid + >>> one_day = datetime.timedelta(1, 0, 0) + >>> private_key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ) + >>> public_key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ).public_key() + >>> builder = x509.CertificateBuilder() + >>> builder = builder.subject_name(x509.Name([ + ... x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ... ])) + >>> builder = builder.issuer_name(x509.Name([ + ... x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ... ])) + >>> builder = builder.not_valid_before(datetime.datetime.today() - one_day) + >>> builder = builder.not_valid_after(datetime.datetime(2018, 8, 2)) + >>> builder = builder.serial_number(int(uuid.uuid4())) + >>> builder = builder.public_key(public_key) + >>> builder = builder.add_extension( + ... x509.BasicConstraints(ca=False, path_length=None), critical=True, + ... ) + >>> certificate = builder.sign( + ... private_key=private_key, algorithm=hashes.SHA256(), + ... backend=default_backend() + ... ) + >>> isinstance(certificate, x509.Certificate) + True + .. method:: issuer_name(name) Sets the issuer's distinguished name. diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index 6e911fd5..8008989e 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -351,3 +351,12 @@ class MultiBackend(object): "This backend does not support X.509.", _Reasons.UNSUPPORTED_X509 ) + + def sign_x509_certificate(self, builder, private_key, algorithm): + for b in self._filtered_backends(X509Backend): + return b.sign_x509_certificate(builder, private_key, algorithm) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) -- cgit v1.2.3 From b7530a42518b30dba3950375cee09e4da419feae Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 2 Aug 2015 22:47:06 -0500 Subject: Use :doc: instead of :ref: for random-numbers --- docs/random-numbers.rst | 2 -- docs/x509/reference.rst | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/random-numbers.rst b/docs/random-numbers.rst index 81e5efbb..8b119a3e 100644 --- a/docs/random-numbers.rst +++ b/docs/random-numbers.rst @@ -1,5 +1,3 @@ -.. _secure_random_number_generation: - Random number generation ======================== diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 1dd466e8..5e58886f 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -469,8 +469,7 @@ X.509 Certificate Builder identify this certificate (most notably during certificate revocation checking). Users are encouraged to use a method of generating 20 bytes of entropy, e.g., UUID4. For more information - on secure random number generation, see - :ref:`secure_random_number_generation`. + on secure random number generation, see :doc:`/random-numbers`. .. method:: not_valid_before(time) -- cgit v1.2.3 From 36a1238703a1aa7aff44654e2e551f2a022c9c1a Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 2 Aug 2015 23:10:22 -0500 Subject: Add test coverage for MultiBackend.sign_x509_certificate --- tests/hazmat/backends/test_multibackend.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py index 3c05cdfa..d516af16 100644 --- a/tests/hazmat/backends/test_multibackend.py +++ b/tests/hazmat/backends/test_multibackend.py @@ -206,6 +206,9 @@ class DummyX509Backend(object): def create_x509_csr(self, builder, private_key, algorithm): pass + def sign_x509_certificate(self, builder, private_key, algorithm): + pass + class TestMultiBackend(object): def test_ciphers(self): @@ -484,6 +487,7 @@ class TestMultiBackend(object): backend.load_pem_x509_csr(b"reqdata") backend.load_der_x509_csr(b"reqdata") backend.create_x509_csr(object(), b"privatekey", hashes.SHA1()) + backend.sign_x509_certificate(object(), b"privatekey", hashes.SHA1()) backend = MultiBackend([]) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): @@ -496,3 +500,7 @@ class TestMultiBackend(object): backend.load_der_x509_csr(b"reqdata") with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): backend.create_x509_csr(object(), b"privatekey", hashes.SHA1()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.sign_x509_certificate( + object(), b"privatekey", hashes.SHA1() + ) -- cgit v1.2.3 From 46479d0d21755b47e14659ecd73bb0caf4074268 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 3 Aug 2015 08:30:20 -0500 Subject: Add sign_x509_certificate to X509Backend interface Add note about CertificateBuilder to the changelog --- CHANGELOG.rst | 7 +++++++ src/cryptography/hazmat/backends/interfaces.py | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4506a466..f07a9087 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -45,6 +45,13 @@ Changelog * :class:`~cryptography.x509.KeyUsage` * :class:`~cryptography.x509.SubjectAlternativeName` +* Add support for creating signed certificates with + :class:`~cryptography.x509.CertificateBuilder`. This includes support for + the following extensions + + * :class:`~cryptography.x509.BasicConstraints` + * :class:`~cryptography.x509.SubjectAlternativeName` + 0.9.3 - 2015-07-09 ~~~~~~~~~~~~~~~~~~ diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index 4d378e6b..49ccda18 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -280,6 +280,12 @@ class X509Backend(object): Create and sign an X.509 CSR from a CSR builder object. """ + @abc.abstractmethod + def sign_x509_certificate(self, builder, private_key, algorithm): + """ + Sign an X.509 Certificate from a CertificateBuilder object. + """ + @six.add_metaclass(abc.ABCMeta) class DHBackend(object): -- cgit v1.2.3