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. --- .../hazmat/backends/openssl/backend.py | 96 ++++++++++++++++++++++ src/cryptography/x509.py | 90 ++++++++++++++++++++ 2 files changed, 186 insertions(+) (limited to 'src') 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) -- 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 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 92 insertions(+), 27 deletions(-) (limited to 'src') 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) -- 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 --- src/cryptography/hazmat/backends/openssl/backend.py | 18 +----------------- src/cryptography/x509.py | 5 +++-- 2 files changed, 4 insertions(+), 19 deletions(-) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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(+) (limited to 'src') 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 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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 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 --- src/cryptography/x509.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) (limited to 'src') 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) -- 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 --- src/cryptography/hazmat/backends/openssl/backend.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'src') 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( -- 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 --- src/cryptography/x509.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) (limited to 'src') 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 -- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'src') 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: -- 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(-) (limited to 'src') 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 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 --- src/cryptography/x509.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') 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.') -- 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 --- src/cryptography/hazmat/backends/multibackend.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src') 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 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 --- src/cryptography/hazmat/backends/interfaces.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') 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