From 0ef595f1d9b5336872dc24d7d67c8cd127b31cea Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Mon, 18 May 2015 13:53:43 -0400 Subject: Adds CSR builder. --- .../hazmat/backends/openssl/backend.py | 145 ++++++++++++++++++++- src/cryptography/x509.py | 43 ++++++ 2 files changed, 187 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 4d469c40..f03ca077 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -10,7 +10,7 @@ from contextlib import contextmanager import six -from cryptography import utils +from cryptography import utils, x509 from cryptography.exceptions import ( InternalError, UnsupportedAlgorithm, _Reasons ) @@ -56,6 +56,88 @@ _OpenSSLError = collections.namedtuple("_OpenSSLError", ["code", "lib", "func", "reason"]) +def _encode_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 _encode_asn1_str(backend, x, n): + 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): + resolve = { + x509.OID_COMMON_NAME: b'CN', + x509.OID_COUNTRY_NAME: b'C', + x509.OID_STATE_OR_PROVINCE_NAME: b'ST', + x509.OID_LOCALITY_NAME: b'L', + x509.OID_ORGANIZATION_NAME: b'O', + x509.OID_ORGANIZATIONAL_UNIT_NAME: b'OU', + } + subject = backend._lib.X509_NAME_new() + subject = backend._ffi.gc(subject, backend._lib.X509_NAME_free) + for attribute in attributes: + value = attribute.value + if isinstance(value, six.text_type): + value = value.encode('ascii') + res = backend._lib.X509_NAME_add_entry_by_txt( + subject, + resolve[attribute.oid], + backend._lib.MBSTRING_ASC, + value, + -1, -1, 0, + ) + assert res == 1 + return subject + + +def _txt2obj(backend, name): + if isinstance(name, six.text_type): + name = name.encode('ascii') + obj = backend._lib.OBJ_txt2obj(name, 1) + assert obj != backend._ffi.NULL + # NOTE: not sure if we should GC... + return obj + + +def _encode_basic_constraints(backend, ca=False, pathlen=0, critical=False): + obj = _txt2obj(backend, x509.OID_BASIC_CONSTRAINTS.dotted_string) + assert obj is not None + constraints = backend._lib.BASIC_CONSTRAINTS_new() + constraints.ca = 255 if ca else 0 + if ca: + constraints.pathlen = _encode_asn1_int(backend, pathlen) + + # Allocate a buffer for encoded payload. + cdata = backend._ffi.new( + 'unsigned char[]', + 2048, # TODO: shrink to fit! + ) + assert cdata != backend._ffi.NULL + + # Fetch the encoded payload. + p = backend._ffi.new('unsigned char*[1]') + assert p != backend._ffi.NULL + p[0] = cdata + r = backend._lib.i2d_BASIC_CONSTRAINTS(constraints, p) + assert r > 0 + + # Wrap that in an X509 extension object. + extension = backend._lib.X509_EXTENSION_create_by_OBJ( + backend._ffi.NULL, + obj, + 1 if critical else 0, + _encode_asn1_str(backend, cdata, r), + ) + assert extension != backend._ffi.NULL + return extension + + @utils.register_interface(CipherBackend) @utils.register_interface(CMACBackend) @utils.register_interface(DERSerializationBackend) @@ -710,6 +792,67 @@ class Backend(object): def create_cmac_ctx(self, algorithm): return _CMACContext(self, algorithm) + def sign_x509_request(self, builder, private_key, algorithm): + # TODO: check type of private key parameter. + 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 request. + x509_req = self._lib.X509_REQ_new() + assert x509_req != self._ffi.NULL + + # Set x509 version. + res = self._lib.X509_REQ_set_version(x509_req, builder._version.value) + assert res == 1 + + # Set subject name. + res = self._lib.X509_REQ_set_subject_name( + x509_req, _encode_name(self, list(builder._subject_name)) + ) + assert res == 1 + + # Set subject public key. + public_key = private_key.public_key() + res = self._lib.X509_REQ_set_pubkey( + x509_req, public_key._evp_pkey + ) + assert res == 1 + + # Add extensions. + extensions = self._lib.sk_X509_EXTENSION_new_null() + extensions = self._ffi.gc( + extensions, + self._lib.sk_X509_EXTENSION_free, + ) + for extension in 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.sk_X509_EXTENSION_push(extensions, extension) + assert res == 1 + res = self._lib.X509_REQ_add_extensions(x509_req, extensions) + assert res == 1 + + # Sign the request using the requester's private key. + res = self._lib.X509_REQ_sign( + x509_req, private_key._evp_pkey, evp_md + ) + assert res > 0 + + return _CertificateSigningRequest(self, x509_req) + 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 4b030ca9..012c13ba 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1442,3 +1442,46 @@ class RevokedCertificate(object): """ Returns an Extensions object containing a list of Revoked extensions. """ + + +class CertificateSigningRequestBuilder(object): + def __init__(self): + """ + Creates an empty X.509 certificate request (v1). + """ + self._version = Version.v1 + self._subject_name = None + self._extensions = [] + + def set_version(self, version): + """ + Sets the X.509 version. + """ + if not isinstance(version, Version): + raise TypeError('Expecting x509.Version object.') + self._version = version + + def set_subject_name(self, name): + """ + Sets the certificate requestor's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError('Expecting x509.Name object.') + self._subject_name = name + + def add_extension(self, extension): + """ + Adds an X.509 extension to the certificate request. + """ + 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 request using the requestor's private key. + """ + return backend.sign_x509_request(self, private_key, algorithm) -- cgit v1.2.3 From ca4c4462dcdbb72f653801f608c85696a4630349 Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Tue, 19 May 2015 08:48:12 -0400 Subject: Fixes memory allocation. --- src/cryptography/hazmat/backends/openssl/backend.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index f03ca077..bf838ead 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -113,18 +113,10 @@ def _encode_basic_constraints(backend, ca=False, pathlen=0, critical=False): if ca: constraints.pathlen = _encode_asn1_int(backend, pathlen) - # Allocate a buffer for encoded payload. - cdata = backend._ffi.new( - 'unsigned char[]', - 2048, # TODO: shrink to fit! - ) - assert cdata != backend._ffi.NULL - # Fetch the encoded payload. - p = backend._ffi.new('unsigned char*[1]') - assert p != backend._ffi.NULL - p[0] = cdata - r = backend._lib.i2d_BASIC_CONSTRAINTS(constraints, p) + pp = backend._ffi.new('unsigned char**') + assert pp != backend._ffi.NULL + r = backend._lib.i2d_BASIC_CONSTRAINTS(constraints, pp) assert r > 0 # Wrap that in an X509 extension object. @@ -132,7 +124,7 @@ def _encode_basic_constraints(backend, ca=False, pathlen=0, critical=False): backend._ffi.NULL, obj, 1 if critical else 0, - _encode_asn1_str(backend, cdata, r), + _encode_asn1_str(backend, pp[0], r), ) assert extension != backend._ffi.NULL return extension -- cgit v1.2.3 From a33ea283d74c85076a21e60e1f09e4998f5c7c48 Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Sun, 31 May 2015 16:32:26 -0400 Subject: Renames sign_509_request to create_x509_csr. --- src/cryptography/hazmat/backends/interfaces.py | 6 ++++++ src/cryptography/hazmat/backends/multibackend.py | 9 +++++++++ src/cryptography/hazmat/backends/openssl/backend.py | 2 +- src/cryptography/x509.py | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index eca7ddf4..512cb6e3 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -274,6 +274,12 @@ class X509Backend(object): Load an X.509 CSR from PEM encoded data. """ + @abc.abstractmethod + def create_x509_csr(self, builder, private_key, algorithm): + """ + Create and sign an X.509 CSR from a CSR buidler object. + """ + @six.add_metaclass(abc.ABCMeta) class DHBackend(object): diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index 784ab84d..6e911fd5 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -342,3 +342,12 @@ class MultiBackend(object): "This backend does not support X.509.", _Reasons.UNSUPPORTED_X509 ) + + def create_x509_csr(self, builder, private_key, algorithm): + for b in self._filtered_backends(X509Backend): + return b.create_x509_csr(builder, private_key, algorithm) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index bf838ead..b8b2ab6b 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -784,7 +784,7 @@ class Backend(object): def create_cmac_ctx(self, algorithm): return _CMACContext(self, algorithm) - def sign_x509_request(self, builder, private_key, algorithm): + def create_x509_csr(self, builder, private_key, algorithm): # TODO: check type of private key parameter. if not isinstance(algorithm, hashes.HashAlgorithm): raise TypeError('Algorithm must be a registered hash algorithm.') diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 012c13ba..2ee1c3ef 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1484,4 +1484,4 @@ class CertificateSigningRequestBuilder(object): """ Signs the request using the requestor's private key. """ - return backend.sign_x509_request(self, private_key, algorithm) + return backend.create_x509_csr(self, private_key, algorithm) -- cgit v1.2.3 From b1103d25a72818d7fd055f17d8008d0ac99f8b95 Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Sun, 31 May 2015 16:58:22 -0400 Subject: Cleans up some GC semantics. --- src/cryptography/hazmat/backends/openssl/backend.py | 19 ++++++++++++++++--- 1 file changed, 16 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 b8b2ab6b..08e56d07 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -57,15 +57,24 @@ _OpenSSLError = collections.namedtuple("_OpenSSLError", def _encode_asn1_int(backend, x): + """ + Converts a python integer to a ASN1_INTEGER. The returned ASN1_INTEGER will + not be garbage collected (to support adding them to structs that take + ownership of the object). Be sure to register it for GC if it will be + discarded after use. + + """ 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 _encode_asn1_str(backend, x, n): + """ + Create an ASN1_OCTET_STRING from a Python byte string. + """ s = backend._lib.ASN1_OCTET_STRING_new() - # s = backend._ffi.gc(s, backend._lib.ASN1_OCTET_STRING_free) + s = backend._ffi.gc(s, backend._lib.ASN1_OCTET_STRING_free) backend._lib.ASN1_OCTET_STRING_set(s, x, n) return s @@ -97,11 +106,15 @@ def _encode_name(backend, attributes): def _txt2obj(backend, name): + """ + Converts a Python string with an ASN.1 object ID in dotted form to a + ASN1_OBJECT. + """ if isinstance(name, six.text_type): name = name.encode('ascii') obj = backend._lib.OBJ_txt2obj(name, 1) assert obj != backend._ffi.NULL - # NOTE: not sure if we should GC... + obj = backend._ffi.gc(obj, backend._lib.ASN1_OBJECT_free) return obj -- cgit v1.2.3 From eebe700861774a640f82391d2a4597d2fe6ff399 Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Sun, 31 May 2015 17:15:36 -0400 Subject: Removes OID to txt to OID conversion. --- src/cryptography/hazmat/backends/openssl/backend.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 08e56d07..c509ddb3 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -80,23 +80,16 @@ def _encode_asn1_str(backend, x, n): def _encode_name(backend, attributes): - resolve = { - x509.OID_COMMON_NAME: b'CN', - x509.OID_COUNTRY_NAME: b'C', - x509.OID_STATE_OR_PROVINCE_NAME: b'ST', - x509.OID_LOCALITY_NAME: b'L', - x509.OID_ORGANIZATION_NAME: b'O', - x509.OID_ORGANIZATIONAL_UNIT_NAME: b'OU', - } subject = backend._lib.X509_NAME_new() subject = backend._ffi.gc(subject, backend._lib.X509_NAME_free) for attribute in attributes: value = attribute.value if isinstance(value, six.text_type): value = value.encode('ascii') - res = backend._lib.X509_NAME_add_entry_by_txt( + obj = _txt2obj(backend, attribute.oid.dotted_string) + res = backend._lib.X509_NAME_add_entry_by_OBJ( subject, - resolve[attribute.oid], + obj, backend._lib.MBSTRING_ASC, value, -1, -1, 0, -- cgit v1.2.3 From fc164c5e4fce2f6617b35887a5799ec10082b906 Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Sun, 31 May 2015 17:36:18 -0400 Subject: Adds method chaining to CSR builder. --- src/cryptography/x509.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 2ee1c3ef..c59de606 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1445,13 +1445,13 @@ class RevokedCertificate(object): class CertificateSigningRequestBuilder(object): - def __init__(self): + def __init__(self, version=Version.v1, subject_name=None, extensions=[]): """ Creates an empty X.509 certificate request (v1). """ self._version = Version.v1 - self._subject_name = None - self._extensions = [] + self._subject_name = subject_name + self._extensions = extensions[:] def set_version(self, version): """ @@ -1459,7 +1459,9 @@ class CertificateSigningRequestBuilder(object): """ if not isinstance(version, Version): raise TypeError('Expecting x509.Version object.') - self._version = version + return CertificateSigningRequestBuilder( + version, self._subject_name, self._extensions + ) def set_subject_name(self, name): """ @@ -1467,7 +1469,9 @@ class CertificateSigningRequestBuilder(object): """ if not isinstance(name, Name): raise TypeError('Expecting x509.Name object.') - self._subject_name = name + return CertificateSigningRequestBuilder( + self._version, name, self._extensions + ) def add_extension(self, extension): """ @@ -1478,7 +1482,9 @@ class CertificateSigningRequestBuilder(object): for e in self._extensions: if e.oid == extension.oid: raise ValueError('This extension has already been set.') - self._extensions.append(extension) + return CertificateSigningRequestBuilder( + self._version, self._subject_name, self._extensions + [extension] + ) def sign(self, backend, private_key, algorithm): """ -- cgit v1.2.3 From dafd5c28b9d512547be1018db08b2d3a815a76b4 Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Sun, 31 May 2015 23:06:26 -0400 Subject: Fixes docstring typo. --- src/cryptography/hazmat/backends/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index 512cb6e3..4d378e6b 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -277,7 +277,7 @@ class X509Backend(object): @abc.abstractmethod def create_x509_csr(self, builder, private_key, algorithm): """ - Create and sign an X.509 CSR from a CSR buidler object. + Create and sign an X.509 CSR from a CSR builder object. """ -- cgit v1.2.3 From ce02de706e4ec0a3945240096d4fd4d92478effe Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Sun, 31 May 2015 23:06:53 -0400 Subject: Extends supported range for integer conversion, --- src/cryptography/hazmat/backends/openssl/backend.py | 11 +++++++++-- 1 file changed, 9 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 c509ddb3..70ed25d6 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -64,8 +64,15 @@ def _encode_asn1_int(backend, x): discarded after use. """ - i = backend._lib.ASN1_INTEGER_new() - backend._lib.ASN1_INTEGER_set(i, x) + # Convert Python integer to OpenSSL "bignum" in case value exceeds + # machine's native integer limits (note: `int_to_bn` doesn't automatically + # GC). + i = backend._int_to_bn(x) + i = backend._ffi.gc(i, backend._lib.BN_free) + + # Wrap in a ASN.1 integer. Don't GC -- as documented. + i = backend._lib.BN_to_ASN1_INTEGER(i, backend._ffi.NULL) + assert i != backend._ffi.NULL return i -- cgit v1.2.3 From 0fdf009ea0dc96a1d70ab0cded11d5846b03d4e2 Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Sun, 31 May 2015 23:07:06 -0400 Subject: Fixes memory leak, --- src/cryptography/hazmat/backends/openssl/backend.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 70ed25d6..a6acb076 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -140,6 +140,12 @@ def _encode_basic_constraints(backend, ca=False, pathlen=0, critical=False): _encode_asn1_str(backend, pp[0], r), ) assert extension != backend._ffi.NULL + + # Release acquired memory. + backend._lib.OPENSSL_free(pp[0]) + pp[0] = backend._ffi.NULL + + # Return the wrapped extension. return extension -- cgit v1.2.3 From 99d0f90ff256b540acb007458bbb07c467642368 Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Mon, 1 Jun 2015 08:36:59 -0400 Subject: Removes CSR builder version setter. --- src/cryptography/hazmat/backends/openssl/backend.py | 2 +- src/cryptography/x509.py | 19 +++---------------- 2 files changed, 4 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index a6acb076..c32b5270 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -819,7 +819,7 @@ class Backend(object): assert x509_req != self._ffi.NULL # Set x509 version. - res = self._lib.X509_REQ_set_version(x509_req, builder._version.value) + res = self._lib.X509_REQ_set_version(x509_req, x509.Version.v1.value) assert res == 1 # Set subject name. diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index c59de606..b1aa0679 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1445,33 +1445,20 @@ class RevokedCertificate(object): class CertificateSigningRequestBuilder(object): - def __init__(self, version=Version.v1, subject_name=None, extensions=[]): + def __init__(self, subject_name=None, extensions=[]): """ Creates an empty X.509 certificate request (v1). """ - self._version = Version.v1 self._subject_name = subject_name self._extensions = extensions[:] - def set_version(self, version): - """ - Sets the X.509 version. - """ - if not isinstance(version, Version): - raise TypeError('Expecting x509.Version object.') - return CertificateSigningRequestBuilder( - version, self._subject_name, self._extensions - ) - def set_subject_name(self, name): """ Sets the certificate requestor's distinguished name. """ if not isinstance(name, Name): raise TypeError('Expecting x509.Name object.') - return CertificateSigningRequestBuilder( - self._version, name, self._extensions - ) + return CertificateSigningRequestBuilder(name, self._extensions) def add_extension(self, extension): """ @@ -1483,7 +1470,7 @@ class CertificateSigningRequestBuilder(object): if e.oid == extension.oid: raise ValueError('This extension has already been set.') return CertificateSigningRequestBuilder( - self._version, self._subject_name, self._extensions + [extension] + self._subject_name, self._extensions + [extension] ) def sign(self, backend, private_key, algorithm): -- cgit v1.2.3 From 472fd6991e05735e00fdca7fbe2573a44fdabd17 Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Sat, 6 Jun 2015 20:04:44 -0400 Subject: Changes builder extension API. --- src/cryptography/x509.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index b1aa0679..f518b68e 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1460,12 +1460,14 @@ class CertificateSigningRequestBuilder(object): raise TypeError('Expecting x509.Name object.') return CertificateSigningRequestBuilder(name, self._extensions) - def add_extension(self, extension): + def add_extension(self, extension, critical=False): """ Adds an X.509 extension to the certificate request. """ - if not isinstance(extension, Extension): - raise TypeError('Expecting x509.Extension object.') + if isinstance(extension, BasicConstraints): + extension = Extension(OID_BASIC_CONSTRAINTS, critical, extension) + else: + raise ValueError('Unsupported X.509 extension.') for e in self._extensions: if e.oid == extension.oid: raise ValueError('This extension has already been set.') -- cgit v1.2.3 From a9a5117f9aae4f0aa3e2e1bd3dcd6a93867c67a4 Mon Sep 17 00:00:00 2001 From: Andre Caron Date: Sat, 6 Jun 2015 20:18:44 -0400 Subject: Removes set_ prefix on CSR builder method. --- src/cryptography/x509.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index f518b68e..ab4f3c7e 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1452,7 +1452,7 @@ class CertificateSigningRequestBuilder(object): self._subject_name = subject_name self._extensions = extensions[:] - def set_subject_name(self, name): + def subject_name(self, name): """ Sets the certificate requestor's distinguished name. """ -- cgit v1.2.3 From 0112b0242717e394ec35aad8d0c8311a47dfa577 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 16 Jun 2015 17:51:18 -0500 Subject: Address code review regarding style and gc - Use keyword arguments for x509.BasicConstraints in several places - Use SHA256 instead of SHA1 in documented examples - Give function variables meaningful names in _encode_asn1_str - Accept a x509.BasicConstraints object in _encode_basic_constraints - Properly garbage-collect some things - Raise a NotImplementedError instead of a ValueError --- .../hazmat/backends/openssl/backend.py | 25 +++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index c32b5270..1861d182 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -76,13 +76,13 @@ def _encode_asn1_int(backend, x): return i -def _encode_asn1_str(backend, x, n): +def _encode_asn1_str(backend, data, length): """ Create an ASN1_OCTET_STRING from a Python byte string. """ 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) + backend._lib.ASN1_OCTET_STRING_set(s, data, length) return s @@ -118,17 +118,18 @@ def _txt2obj(backend, name): return obj -def _encode_basic_constraints(backend, ca=False, pathlen=0, critical=False): +def _encode_basic_constraints(backend, basic_constraints, critical): obj = _txt2obj(backend, x509.OID_BASIC_CONSTRAINTS.dotted_string) assert obj is not None constraints = backend._lib.BASIC_CONSTRAINTS_new() - constraints.ca = 255 if ca else 0 - if ca: - constraints.pathlen = _encode_asn1_int(backend, pathlen) + constraints.ca = 255 if basic_constraints.ca else 0 + if basic_constraints.ca: + constraints.pathlen = _encode_asn1_int( + backend, basic_constraints.path_length + ) # Fetch the encoded payload. - pp = backend._ffi.new('unsigned char**') - assert pp != backend._ffi.NULL + pp = backend._ffi.new('unsigned char **') r = backend._lib.i2d_BASIC_CONSTRAINTS(constraints, pp) assert r > 0 @@ -141,8 +142,8 @@ def _encode_basic_constraints(backend, ca=False, pathlen=0, critical=False): ) assert extension != backend._ffi.NULL + pp[0] = backend._ffi.gc(pp[0], backend._lib.OPENSSL_free) # Release acquired memory. - backend._lib.OPENSSL_free(pp[0]) pp[0] = backend._ffi.NULL # Return the wrapped extension. @@ -816,6 +817,7 @@ class Backend(object): # Create an empty request. x509_req = self._lib.X509_REQ_new() + x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free) assert x509_req != self._ffi.NULL # Set x509 version. @@ -845,12 +847,11 @@ class Backend(object): if isinstance(extension.value, x509.BasicConstraints): extension = _encode_basic_constraints( self, - extension.value.ca, - extension.value.path_length, + extension.value, extension.critical ) else: - raise ValueError('Extension not yet supported.') + raise NotImplementedError('Extension not yet supported.') res = self._lib.sk_X509_EXTENSION_push(extensions, extension) assert res == 1 res = self._lib.X509_REQ_add_extensions(x509_req, extensions) -- cgit v1.2.3 From 41f51ce4690472ae930ccffd1a0b9e198945aa84 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 17 Jun 2015 11:49:11 -0500 Subject: Update CSR tests and implementation - Use keyword arguments for x509.BasicConstraints in tests (missed in b790edbdc8fb9a026353d6fb99994326197705c7). - Place X509_request garbage collection under assertion. - Assert that X509 extensions created are not null. - Don't copy the extensions list in CertificateSigningBuilder. They're never appended to, so copying isn't necessary. - Use RSA key fixtures instead of generating new ones on each test run --- src/cryptography/hazmat/backends/openssl/backend.py | 3 ++- src/cryptography/x509.py | 2 +- 2 files changed, 3 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 1861d182..406117b9 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -817,8 +817,8 @@ class Backend(object): # Create an empty request. x509_req = self._lib.X509_REQ_new() - x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free) assert x509_req != self._ffi.NULL + x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free) # Set x509 version. res = self._lib.X509_REQ_set_version(x509_req, x509.Version.v1.value) @@ -839,6 +839,7 @@ class Backend(object): # Add extensions. extensions = self._lib.sk_X509_EXTENSION_new_null() + assert extensions != self._ffi.NULL extensions = self._ffi.gc( extensions, self._lib.sk_X509_EXTENSION_free, diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index ab4f3c7e..7e1e34e2 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1450,7 +1450,7 @@ class CertificateSigningRequestBuilder(object): Creates an empty X.509 certificate request (v1). """ self._subject_name = subject_name - self._extensions = extensions[:] + self._extensions = extensions def subject_name(self, name): """ -- cgit v1.2.3 From f0388d068fd07209068de6b3ca0f8372e01ac086 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 17 Jun 2015 11:53:58 -0500 Subject: Update registering pp with ffi.gc This makes it more in-line with existing functions, e.g., L40-L47 of src/cryptography/hazmat/backends/openssl/x509.py @ b0e8ffa --- src/cryptography/hazmat/backends/openssl/backend.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 406117b9..f4cfa940 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -132,6 +132,9 @@ def _encode_basic_constraints(backend, basic_constraints, critical): pp = backend._ffi.new('unsigned char **') r = backend._lib.i2d_BASIC_CONSTRAINTS(constraints, pp) assert r > 0 + pp = backend._ffi.gc( + pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + ) # Wrap that in an X509 extension object. extension = backend._lib.X509_EXTENSION_create_by_OBJ( @@ -142,10 +145,6 @@ def _encode_basic_constraints(backend, basic_constraints, critical): ) assert extension != backend._ffi.NULL - pp[0] = backend._ffi.gc(pp[0], backend._lib.OPENSSL_free) - # Release acquired memory. - pp[0] = backend._ffi.NULL - # Return the wrapped extension. return extension -- cgit v1.2.3 From b2b4b6eb91e5330918a622a1a69e52dcf239b1bc Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 17 Jun 2015 12:08:44 -0500 Subject: Unconditionally encode values to ascii --- src/cryptography/hazmat/backends/openssl/backend.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index f4cfa940..a6dc0d4e 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -90,9 +90,7 @@ def _encode_name(backend, attributes): subject = backend._lib.X509_NAME_new() subject = backend._ffi.gc(subject, backend._lib.X509_NAME_free) for attribute in attributes: - value = attribute.value - if isinstance(value, six.text_type): - value = value.encode('ascii') + value = attribute.value.encode('ascii') obj = _txt2obj(backend, attribute.oid.dotted_string) res = backend._lib.X509_NAME_add_entry_by_OBJ( subject, @@ -110,8 +108,7 @@ def _txt2obj(backend, name): Converts a Python string with an ASN.1 object ID in dotted form to a ASN1_OBJECT. """ - if isinstance(name, six.text_type): - name = name.encode('ascii') + name = name.encode('ascii') obj = backend._lib.OBJ_txt2obj(name, 1) assert obj != backend._ffi.NULL obj = backend._ffi.gc(obj, backend._lib.ASN1_OBJECT_free) -- cgit v1.2.3 From d09ec37cb4c2995b1e2e2f88d187f4fbaef68ad8 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 17 Jun 2015 21:37:51 -0500 Subject: Only allow subject_name to be set once on a Builder --- 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 7e1e34e2..f59ea78a 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1458,6 +1458,8 @@ class CertificateSigningRequestBuilder(object): """ if not isinstance(name, Name): raise TypeError('Expecting x509.Name object.') + if self._subject_name is not None: + raise ValueError('The subject name may only be set once.') return CertificateSigningRequestBuilder(name, self._extensions) def add_extension(self, extension, critical=False): -- cgit v1.2.3 From f06b6be82300d9339bcfb062aedd7d7a3865aec9 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 21 Jun 2015 10:09:18 -0500 Subject: Address review comments around add_extension method - Fix typo in the docs (s/buidlder/builder/) - Remove default from the method declaration and docs - Replace ValueError with NotImpelementedError for unsupported X.509 extensions - Add TODO comment as requested by Alex - Fix test to pass critical=False since it no longer is a default value --- src/cryptography/x509.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index f59ea78a..21e18ddd 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1462,14 +1462,15 @@ class CertificateSigningRequestBuilder(object): raise ValueError('The subject name may only be set once.') return CertificateSigningRequestBuilder(name, self._extensions) - def add_extension(self, extension, critical=False): + def add_extension(self, extension, critical): """ Adds an X.509 extension to the certificate request. """ if isinstance(extension, BasicConstraints): extension = Extension(OID_BASIC_CONSTRAINTS, critical, extension) else: - raise ValueError('Unsupported X.509 extension.') + raise NotImplementedError('Unsupported X.509 extension.') + # 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.') -- cgit v1.2.3 From 8ed8edce1764ea17800ef83f422c7a73bfdfa74b Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 22 Jun 2015 20:11:17 -0500 Subject: Add tests to the CSR Builder for EC and DSA keys This skips certain tests on certain versions of differences in how X509_REQ_sign works on those versions. A separate pull request will address those differences. --- src/cryptography/hazmat/backends/openssl/backend.py | 13 ++++++++++++- 1 file changed, 12 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 a6dc0d4e..7963b5d3 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -801,10 +801,21 @@ class Backend(object): return _CMACContext(self, algorithm) def create_x509_csr(self, builder, private_key, algorithm): - # TODO: check type of private key parameter. 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 signing requests aren't implemented for DSA" + " keys on OpenSSL versions less than 1.0.1." + ) + if isinstance(private_key, _EllipticCurvePrivateKey): + raise NotImplementedError( + "Certificate signing requests 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') -- cgit v1.2.3 From 87d61f8a6efdf1f9b0a0d6e8c59e323e6004ec72 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 24 Jun 2015 19:31:26 -0500 Subject: Use utf8 to encode attribute values instead of ascii --- src/cryptography/hazmat/backends/openssl/backend.py | 4 ++-- 1 file changed, 2 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 7963b5d3..78de79d1 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -90,12 +90,12 @@ def _encode_name(backend, attributes): subject = backend._lib.X509_NAME_new() subject = backend._ffi.gc(subject, backend._lib.X509_NAME_free) for attribute in attributes: - value = attribute.value.encode('ascii') + value = attribute.value.encode('utf8') obj = _txt2obj(backend, attribute.oid.dotted_string) res = backend._lib.X509_NAME_add_entry_by_OBJ( subject, obj, - backend._lib.MBSTRING_ASC, + backend._lib.MBSTRING_UTF8, value, -1, -1, 0, ) -- cgit v1.2.3