diff options
author | Paul Kehrer <paul.l.kehrer@gmail.com> | 2015-08-03 16:04:06 +0100 |
---|---|---|
committer | Paul Kehrer <paul.l.kehrer@gmail.com> | 2015-08-03 16:04:06 +0100 |
commit | b69aa4cdacb6e85845f590e528f7e9a471d7dd36 (patch) | |
tree | 943886277c3549cc62c6f5a40d7a5d7d9a274061 /src | |
parent | 93b5e3efab0337c51c84c27208d9034b607f09e2 (diff) | |
parent | 46479d0d21755b47e14659ecd73bb0caf4074268 (diff) | |
download | cryptography-b69aa4cdacb6e85845f590e528f7e9a471d7dd36.tar.gz cryptography-b69aa4cdacb6e85845f590e528f7e9a471d7dd36.tar.bz2 cryptography-b69aa4cdacb6e85845f590e528f7e9a471d7dd36.zip |
Merge pull request #2098 from sigmavirus24/cert-builder
Adds certificate builder.
Diffstat (limited to 'src')
-rw-r--r-- | src/cryptography/hazmat/backends/interfaces.py | 6 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/multibackend.py | 9 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 107 | ||||
-rw-r--r-- | src/cryptography/x509.py | 147 |
4 files changed, 269 insertions, 0 deletions
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): 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 + ) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 5ab46d44..0176de21 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 @@ -78,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. @@ -1063,6 +1070,106 @@ class Backend(object): return _CertificateSigningRequest(self, x509_req) + def sign_x509_certificate(self, builder, private_key, algorithm): + 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') + ) + 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 = _encode_asn1_int_gc(self, builder._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( + 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): + 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 NotImplementedError('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 + + # 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 + ) + 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) + 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 75552fc1..a831506e 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 @@ -17,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 = { @@ -95,6 +97,8 @@ _GENERAL_NAMES = { 8: "registeredID", } +_UNIX_EPOCH = datetime.datetime(1970, 1, 1) + class Version(Enum): v1 = 0 @@ -1598,3 +1602,146 @@ 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, issuer_name=None, subject_name=None, + public_key=None, serial_number=None, not_valid_before=None, + not_valid_after=None, extensions=[]): + self._version = Version.v3 + 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 issuer_name(self, name): + """ + Sets the CA's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError('Expecting x509.Name object.') + if self._issuer_name is not None: + raise ValueError('The issuer name may only be set once.') + return CertificateBuilder( + name, self._subject_name, self._public_key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) + + def subject_name(self, name): + """ + Sets the requestor's distinguished name. + """ + 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 CertificateBuilder( + self._issuer_name, name, self._public_key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) + + def public_key(self, key): + """ + Sets the requestor's public key (as found in the signing request). + """ + 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._issuer_name, self._subject_name, key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) + + def serial_number(self, number): + """ + Sets the certificate serial number. + """ + if not isinstance(number, six.integer_types): + 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._issuer_name, self._subject_name, + self._public_key, number, self._not_valid_before, + self._not_valid_after, self._extensions + ) + + def not_valid_before(self, time): + """ + Sets the certificate activation time. + """ + if not isinstance(time, datetime.datetime): + 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._issuer_name, self._subject_name, + self._public_key, self._serial_number, time, + self._not_valid_after, self._extensions + ) + + def not_valid_after(self, time): + """ + Sets the certificate expiration time. + """ + if not isinstance(time, datetime.datetime): + 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._issuer_name, self._subject_name, + self._public_key, self._serial_number, self._not_valid_before, + time, self._extensions + ) + + 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 + ) + elif isinstance(extension, InhibitAnyPolicy): + extension = Extension(OID_INHIBIT_ANY_POLICY, critical, extension) + else: + 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.') + + return CertificateBuilder( + 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. + """ + return backend.sign_x509_certificate(self, private_key, algorithm) |