diff options
-rw-r--r-- | docs/hazmat/backends/interfaces.rst | 20 | ||||
-rw-r--r-- | docs/x509/reference.rst | 179 | ||||
-rw-r--r-- | src/_cffi_src/openssl/asn1.py | 5 | ||||
-rw-r--r-- | src/_cffi_src/openssl/ec.py | 2 | ||||
-rw-r--r-- | src/_cffi_src/openssl/x509v3.py | 1 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/interfaces.py | 4 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/multibackend.py | 4 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 119 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/x509.py | 6 | ||||
-rw-r--r-- | src/cryptography/utils.py | 7 | ||||
-rw-r--r-- | src/cryptography/x509.py | 112 | ||||
-rw-r--r-- | tests/hazmat/backends/test_multibackend.py | 6 | ||||
-rw-r--r-- | tests/hazmat/backends/test_openssl.py | 2 | ||||
-rw-r--r-- | tests/test_x509.py | 260 | ||||
-rw-r--r-- | tests/test_x509_ext.py | 82 |
15 files changed, 785 insertions, 24 deletions
diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index fb3786c3..442bd0de 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -550,6 +550,26 @@ A specific ``backend`` may provide one or more of these interfaces. :returns: A new object with the :class:`~cryptography.x509.CertificateSigningRequest` interface. + .. method:: create_x509_certificate(builder, private_key, algorithm) + + .. versionadded:: 1.0 + + :param builder: An instance of + :class:`~cryptography.x509.CertificateBuilder`. + + :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 certificate signature. + + :returns: A new object with the + :class:`~cryptography.x509.Certificate` interface. + .. class:: DHBackend diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 61971fed..d86ebbe8 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -909,14 +909,28 @@ X.509 Extensions Returns an instance of the extension type corresponding to the OID. +.. class:: ExtensionType + + .. versionadded:: 1.0 + + This is the interface against which all the following extension types are + registered. + .. class:: KeyUsage .. versionadded:: 0.9 The key usage extension defines the purpose of the key contained in the certificate. The usage restriction might be employed when a key that could - be used for more than one operation is to be restricted. It corresponds to - :data:`OID_KEY_USAGE`. + be used for more than one operation is to be restricted. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_KEY_USAGE`. .. attribute:: digital_signature @@ -1007,8 +1021,15 @@ X.509 Extensions Basic constraints is an X.509 extension type that defines whether a given certificate is allowed to sign additional certificates and what path - length restrictions may exist. It corresponds to - :data:`OID_BASIC_CONSTRAINTS`. + length restrictions may exist. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_BASIC_CONSTRAINTS`. .. attribute:: ca @@ -1038,6 +1059,15 @@ X.509 Extensions purposes indicated in the key usage extension. The object is iterable to obtain the list of :ref:`extended key usage OIDs <eku_oids>`. + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_EXTENDED_KEY_USAGE`. + + .. class:: OCSPNoCheck .. versionadded:: 1.0 @@ -1051,6 +1081,14 @@ X.509 Extensions extension is only relevant when the certificate is an authorized OCSP responder. + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_OCSP_NO_CHECK`. + .. class:: NameConstraints .. versionadded:: 1.0 @@ -1060,6 +1098,14 @@ X.509 Extensions beneath the CA certificate must (or must not) be in. For specific details on the way this extension should be processed see :rfc:`5280`. + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_NAME_CONSTRAINTS`. + .. attribute:: permitted_subtrees :type: list of :class:`GeneralName` objects or None @@ -1087,6 +1133,14 @@ X.509 Extensions certificate chain. For more information about generation and use of this extension see `RFC 5280 section 4.2.1.1`_. + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_AUTHORITY_KEY_IDENTIFIER`. + .. attribute:: key_identifier :type: bytes @@ -1106,6 +1160,37 @@ X.509 Extensions The serial number of the issuer's issuer. + .. classmethod:: from_issuer_public_key(public_key) + + .. versionadded:: 1.0 + + Creates a new AuthorityKeyIdentifier instance using the public key + provided to generate the appropriate digest. This should be the + **issuer's public key**. The resulting object will contain + :attr:`~cryptography.x509.AuthorityKeyIdentifier.key_identifier`, but + :attr:`~cryptography.x509.AuthorityKeyIdentifier.authority_cert_issuer` + and + :attr:`~cryptography.x509.AuthorityKeyIdentifier.authority_cert_serial_number` + will be None. + The generated ``key_identifier`` is the SHA1 hash of the ``subjectPublicKey`` + ASN.1 bit string. This is the first recommendation in :rfc:`5280` + section 4.2.1.2. + + :param public_key: One of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` + , + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` + , or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`. + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> issuer_cert = x509.load_pem_x509_certificate(pem_data, default_backend()) + >>> x509.AuthorityKeyIdentifier.from_issuer_public_key(issuer_cert.public_key()) + <AuthorityKeyIdentifier(key_identifier='X\x01\x84$\x1b\xbc+R\x94J=\xa5\x10r\x14Q\xf5\xaf:\xc9', authority_cert_issuer=None, authority_cert_serial_number=None)> + .. class:: SubjectKeyIdentifier .. versionadded:: 0.9 @@ -1113,12 +1198,45 @@ X.509 Extensions The subject key identifier extension provides a means of identifying certificates that contain a particular public key. + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_SUBJECT_KEY_IDENTIFIER`. + .. attribute:: digest :type: bytes The binary value of the identifier. + .. classmethod:: from_public_key(public_key) + + .. versionadded:: 1.0 + + Creates a new SubjectKeyIdentifier instance using the public key + provided to generate the appropriate digest. This should be the public + key that is in the certificate. The generated digest is the SHA1 hash + of the ``subjectPublicKey`` ASN.1 bit string. This is the first + recommendation in :rfc:`5280` section 4.2.1.2. + + :param public_key: One of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` + , + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` + , or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`. + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> csr = x509.load_pem_x509_csr(pem_req_data, default_backend()) + >>> x509.SubjectKeyIdentifier.from_public_key(csr.public_key()) + <SubjectKeyIdentifier(digest='\xdb\xaa\xf0\x06\x11\xdbD\xfe\xbf\x93\x03\x8av\x88WP7\xa6\x91\xf7')> + .. class:: SubjectAlternativeName .. versionadded:: 0.9 @@ -1128,6 +1246,14 @@ X.509 Extensions of identities for which the certificate is valid. The object is iterable to get every element. + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_SUBJECT_ALTERNATIVE_NAME`. + .. method:: get_values_for_type(type) :param type: A :class:`GeneralName` provider. This is one of the @@ -1158,6 +1284,14 @@ X.509 Extensions of identities for the certificate issuer. The object is iterable to get every element. + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_ISSUER_ALTERNATIVE_NAME`. + .. method:: get_values_for_type(type) :param type: A :class:`GeneralName` provider. This is one of the @@ -1176,6 +1310,14 @@ X.509 Extensions validation services (such as OCSP) and issuer data. It is an iterable, containing one or more :class:`AccessDescription` instances. + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_AUTHORITY_INFORMATION_ACCESS`. + .. class:: AccessDescription @@ -1206,6 +1348,14 @@ X.509 Extensions obtained. It is an iterable, containing one or more :class:`DistributionPoint` instances. + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_CRL_DISTRIBUTION_POINTS`. + .. class:: DistributionPoint .. versionadded:: 0.9 @@ -1304,6 +1454,14 @@ X.509 Extensions certificates issued by the subject of this certificate, but not in additional certificates in the path. + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_INHIBIT_ANY_POLICY`. + .. attribute:: skip_certs :type: int @@ -1315,6 +1473,14 @@ X.509 Extensions The certificate policies extension is an iterable, containing one or more :class:`PolicyInformation` instances. + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :data:`OID_CERTIFICATE_POLICIES`. + Certificate Policies Classes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1668,6 +1834,11 @@ Extension OIDs Corresponds to the dotted string ``"1.3.6.1.5.5.7.1.1"``. The identifier for the :class:`AuthorityInformationAccess` extension type. +.. data:: OID_INHIBIT_ANY_POLICY + + Corresponds to the dotted string ``"2.5.29.54"``. The identifier + for the :class:`InhibitAnyPolicy` extension type. + .. data:: OID_OCSP_NO_CHECK Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1.5"``. The identifier diff --git a/src/_cffi_src/openssl/asn1.py b/src/_cffi_src/openssl/asn1.py index 44e9de17..96084721 100644 --- a/src/_cffi_src/openssl/asn1.py +++ b/src/_cffi_src/openssl/asn1.py @@ -43,6 +43,7 @@ typedef struct asn1_string_st ASN1_IA5STRING; typedef ... ASN1_BIT_STRING; typedef ... ASN1_OBJECT; typedef struct asn1_string_st ASN1_STRING; +typedef struct asn1_string_st ASN1_UTF8STRING; typedef ... ASN1_TYPE; typedef ... ASN1_GENERALIZEDTIME; typedef ... ASN1_ENUMERATED; @@ -125,9 +126,13 @@ int ASN1_BIT_STRING_set_bit(ASN1_BIT_STRING *, int, int); """ MACROS = """ +ASN1_UTF8STRING *ASN1_UTF8STRING_new(void); +void ASN1_UTF8STRING_free(ASN1_UTF8STRING *); + ASN1_BIT_STRING *ASN1_BIT_STRING_new(void); void ASN1_BIT_STRING_free(ASN1_BIT_STRING *); int i2d_ASN1_BIT_STRING(ASN1_BIT_STRING *, unsigned char **); +int i2d_ASN1_OCTET_STRING(ASN1_OCTET_STRING *, unsigned char **); /* This is not a macro, but is const on some versions of OpenSSL */ int ASN1_BIT_STRING_get_bit(ASN1_BIT_STRING *, int); ASN1_TIME *M_ASN1_TIME_dup(void *); diff --git a/src/_cffi_src/openssl/ec.py b/src/_cffi_src/openssl/ec.py index 93ca2754..10c87c33 100644 --- a/src/_cffi_src/openssl/ec.py +++ b/src/_cffi_src/openssl/ec.py @@ -397,7 +397,7 @@ static const long Cryptography_HAS_EC2M = 1; #endif #if defined(OPENSSL_NO_EC) || OPENSSL_VERSION_NUMBER < 0x1000200f || \ - defined(LIBRESSL_VERSION_NUMBER) + defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20020002L static const long Cryptography_HAS_EC_1_0_2 = 0; const char *(*EC_curve_nid2nist)(int) = NULL; #else diff --git a/src/_cffi_src/openssl/x509v3.py b/src/_cffi_src/openssl/x509v3.py index 6e35dacc..84e49640 100644 --- a/src/_cffi_src/openssl/x509v3.py +++ b/src/_cffi_src/openssl/x509v3.py @@ -279,6 +279,7 @@ void sk_ASN1_INTEGER_free(Cryptography_STACK_OF_ASN1_INTEGER *); int sk_ASN1_INTEGER_num(Cryptography_STACK_OF_ASN1_INTEGER *); ASN1_INTEGER *sk_ASN1_INTEGER_value(Cryptography_STACK_OF_ASN1_INTEGER *, int); int sk_ASN1_INTEGER_push(Cryptography_STACK_OF_ASN1_INTEGER *, ASN1_INTEGER *); +Cryptography_STACK_OF_ASN1_INTEGER *sk_ASN1_INTEGER_new_null(void); X509_EXTENSION *X509V3_EXT_i2d(int, int, void *); diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index 49ccda18..a43621a7 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -281,9 +281,9 @@ class X509Backend(object): """ @abc.abstractmethod - def sign_x509_certificate(self, builder, private_key, algorithm): + def create_x509_certificate(self, builder, private_key, algorithm): """ - Sign an X.509 Certificate from a CertificateBuilder object. + Create and sign an X.509 certificate from a CertificateBuilder object. """ diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index 8008989e..9db32aa5 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -352,9 +352,9 @@ class MultiBackend(object): _Reasons.UNSUPPORTED_X509 ) - def sign_x509_certificate(self, builder, private_key, algorithm): + def create_x509_certificate(self, builder, private_key, algorithm): for b in self._filtered_backends(X509Backend): - return b.sign_x509_certificate(builder, private_key, algorithm) + return b.create_x509_certificate(builder, private_key, algorithm) raise UnsupportedAlgorithm( "This backend does not support X.509.", diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index cf294c01..6675f677 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -38,7 +38,8 @@ from cryptography.hazmat.backends.openssl.rsa import ( _RSAPrivateKey, _RSAPublicKey ) from cryptography.hazmat.backends.openssl.x509 import ( - _Certificate, _CertificateSigningRequest + _Certificate, _CertificateSigningRequest, _DISTPOINT_TYPE_FULLNAME, + _DISTPOINT_TYPE_RELATIVENAME ) from cryptography.hazmat.bindings.openssl.binding import Binding from cryptography.hazmat.primitives import hashes, serialization @@ -181,6 +182,36 @@ def _encode_key_usage(backend, key_usage): return pp, r +def _encode_authority_key_identifier(backend, authority_keyid): + akid = backend._lib.AUTHORITY_KEYID_new() + assert akid != backend._ffi.NULL + akid = backend._ffi.gc(akid, backend._lib.AUTHORITY_KEYID_free) + if authority_keyid.key_identifier is not None: + akid.keyid = _encode_asn1_str( + backend, + authority_keyid.key_identifier, + len(authority_keyid.key_identifier) + ) + + if authority_keyid.authority_cert_issuer is not None: + akid.issuer = _encode_general_names( + backend, authority_keyid.authority_cert_issuer + ) + + if authority_keyid.authority_cert_serial_number is not None: + akid.serial = _encode_asn1_int( + backend, authority_keyid.authority_cert_serial_number + ) + + pp = backend._ffi.new('unsigned char **') + r = backend._lib.i2d_AUTHORITY_KEYID(akid, pp) + assert r > 0 + pp = backend._ffi.gc( + pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + ) + return pp, r + + def _encode_basic_constraints(backend, basic_constraints): constraints = backend._lib.BASIC_CONSTRAINTS_new() constraints = backend._ffi.gc( @@ -253,6 +284,17 @@ def _encode_subject_alt_name(backend, san): return pp, r +def _encode_subject_key_identifier(backend, ski): + asn1_str = _encode_asn1_str_gc(backend, ski.digest, len(ski.digest)) + pp = backend._ffi.new("unsigned char **") + r = backend._lib.i2d_ASN1_OCTET_STRING(asn1_str, pp) + assert r > 0 + pp = backend._ffi.gc( + pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + ) + return pp, r + + def _encode_general_name(backend, name): if isinstance(name, x509.DNSName): gn = backend._lib.GENERAL_NAME_new() @@ -359,6 +401,67 @@ def _encode_extended_key_usage(backend, extended_key_usage): return pp, r +_CRLREASONFLAGS = { + x509.ReasonFlags.key_compromise: 1, + x509.ReasonFlags.ca_compromise: 2, + x509.ReasonFlags.affiliation_changed: 3, + x509.ReasonFlags.superseded: 4, + x509.ReasonFlags.cessation_of_operation: 5, + x509.ReasonFlags.certificate_hold: 6, + x509.ReasonFlags.privilege_withdrawn: 7, + x509.ReasonFlags.aa_compromise: 8, +} + + +def _encode_crl_distribution_points(backend, crl_distribution_points): + cdp = backend._lib.sk_DIST_POINT_new_null() + cdp = backend._ffi.gc(cdp, backend._lib.sk_DIST_POINT_free) + for point in crl_distribution_points: + dp = backend._lib.DIST_POINT_new() + assert dp != backend._ffi.NULL + + if point.reasons: + bitmask = backend._lib.ASN1_BIT_STRING_new() + assert bitmask != backend._ffi.NULL + dp.reasons = bitmask + for reason in point.reasons: + res = backend._lib.ASN1_BIT_STRING_set_bit( + bitmask, _CRLREASONFLAGS[reason], 1 + ) + assert res == 1 + + if point.full_name: + dpn = backend._lib.DIST_POINT_NAME_new() + assert dpn != backend._ffi.NULL + dpn.type = _DISTPOINT_TYPE_FULLNAME + dpn.name.fullname = _encode_general_names(backend, point.full_name) + dp.distpoint = dpn + + if point.relative_name: + dpn = backend._lib.DIST_POINT_NAME_new() + assert dpn != backend._ffi.NULL + dpn.type = _DISTPOINT_TYPE_RELATIVENAME + name = _encode_name_gc(backend, point.relative_name) + relativename = backend._lib.sk_X509_NAME_ENTRY_dup(name.entries) + assert relativename != backend._ffi.NULL + dpn.name.relativename = relativename + dp.distpoint = dpn + + if point.crl_issuer: + dp.CRLissuer = _encode_general_names(backend, point.crl_issuer) + + res = backend._lib.sk_DIST_POINT_push(cdp, dp) + assert res >= 1 + + pp = backend._ffi.new('unsigned char **') + r = backend._lib.i2d_CRL_DIST_POINTS(cdp, pp) + assert r > 0 + pp = backend._ffi.gc( + pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + ) + return pp, r + + @utils.register_interface(CipherBackend) @utils.register_interface(CMACBackend) @utils.register_interface(DERSerializationBackend) @@ -1100,7 +1203,7 @@ class Backend(object): return _CertificateSigningRequest(self, x509_req) - def sign_x509_certificate(self, builder, private_key, algorithm): + def create_x509_certificate(self, builder, private_key, algorithm): if not isinstance(builder, x509.CertificateBuilder): raise TypeError('Builder type mismatch.') if not isinstance(algorithm, hashes.HashAlgorithm): @@ -1167,26 +1270,36 @@ 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.AuthorityKeyIdentifier): + pp, r = _encode_authority_key_identifier(self, extension.value) elif isinstance(extension.value, x509.KeyUsage): pp, r = _encode_key_usage(self, extension.value) elif isinstance(extension.value, x509.ExtendedKeyUsage): pp, r = _encode_extended_key_usage(self, extension.value) elif isinstance(extension.value, x509.SubjectAlternativeName): pp, r = _encode_subject_alt_name(self, extension.value) + elif isinstance(extension.value, x509.SubjectKeyIdentifier): + pp, r = _encode_subject_key_identifier(self, extension.value) elif isinstance(extension.value, x509.AuthorityInformationAccess): pp, r = _encode_authority_information_access( self, extension.value ) + elif isinstance(extension.value, x509.CRLDistributionPoints): + pp, r = _encode_crl_distribution_points( + self, extension.value + ) else: raise NotImplementedError('Extension not yet supported.') - obj = _txt2obj(self, extension.oid.dotted_string) + obj = _txt2obj_gc(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) ) + assert extension != self._ffi.NULL + extension = self._ffi.gc(extension, self._lib.X509_EXTENSION_free) res = self._lib.X509_add_ext(x509_cert, extension, i) assert res == 1 diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index ee9a3bbf..564b2680 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -592,6 +592,10 @@ def _decode_extended_key_usage(backend, sk): return x509.ExtendedKeyUsage(ekus) +_DISTPOINT_TYPE_FULLNAME = 0 +_DISTPOINT_TYPE_RELATIVENAME = 1 + + def _decode_crl_distribution_points(backend, cdps): cdps = backend._ffi.cast("Cryptography_STACK_OF_DIST_POINT *", cdps) cdps = backend._ffi.gc(cdps, backend._lib.sk_DIST_POINT_free) @@ -651,7 +655,7 @@ def _decode_crl_distribution_points(backend, cdps): # point so make sure it's not null. if cdp.distpoint != backend._ffi.NULL: # Type 0 is fullName, there is no #define for it in the code. - if cdp.distpoint.type == 0: + if cdp.distpoint.type == _DISTPOINT_TYPE_FULLNAME: full_name = _decode_general_names( backend, cdp.distpoint.name.fullname ) diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 24afe612..993571bd 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function import abc +import binascii import inspect import struct import sys @@ -46,6 +47,12 @@ else: return result +def int_to_bytes(integer): + hex_string = '%x' % integer + n = len(hex_string) + return binascii.unhexlify(hex_string.zfill(n + (n & 1))) + + class InterfaceNotImplemented(Exception): pass diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 0ddff728..397274e8 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -6,21 +6,53 @@ from __future__ import absolute_import, division, print_function import abc import datetime +import hashlib import ipaddress from email.utils import parseaddr from enum import Enum import idna +from pyasn1.codec.der import decoder +from pyasn1.type import namedtype, univ + import six from six.moves import urllib_parse from cryptography import utils -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa +class _SubjectPublicKeyInfo(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('algorithm', univ.Sequence()), + namedtype.NamedType('subjectPublicKey', univ.BitString()) + ) + + +def _key_identifier_from_public_key(public_key): + # This is a very slow way to do this. + serialized = public_key.public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + spki, remaining = decoder.decode( + serialized, asn1Spec=_SubjectPublicKeyInfo() + ) + assert not remaining + # the univ.BitString object is a tuple of bits. We need bytes and + # pyasn1 really doesn't want to give them to us. To get it we'll + # build an integer and convert that to bytes. + bits = 0 + for bit in spki.getComponentByName("subjectPublicKey"): + bits = bits << 1 | bit + + data = utils.int_to_bytes(bits) + return hashlib.sha1(data).digest() + + _OID_NAMES = { "2.5.4.3": "commonName", "2.5.4.6": "countryName", @@ -313,7 +345,19 @@ class Extension(object): return not self == other +@six.add_metaclass(abc.ABCMeta) +class ExtensionType(object): + @abc.abstractproperty + def oid(self): + """ + Returns the oid associated with the given extension type. + """ + + +@utils.register_interface(ExtensionType) class ExtendedKeyUsage(object): + oid = OID_EXTENDED_KEY_USAGE + def __init__(self, usages): if not all(isinstance(x, ObjectIdentifier) for x in usages): raise TypeError( @@ -341,11 +385,15 @@ class ExtendedKeyUsage(object): return not self == other +@utils.register_interface(ExtensionType) class OCSPNoCheck(object): - pass + oid = OID_OCSP_NO_CHECK +@utils.register_interface(ExtensionType) class BasicConstraints(object): + oid = OID_BASIC_CONSTRAINTS + def __init__(self, ca, path_length): if not isinstance(ca, bool): raise TypeError("ca must be a boolean value") @@ -381,7 +429,10 @@ class BasicConstraints(object): return not self == other +@utils.register_interface(ExtensionType) class KeyUsage(object): + oid = OID_KEY_USAGE + def __init__(self, digital_signature, content_commitment, key_encipherment, data_encipherment, key_agreement, key_cert_sign, crl_sign, encipher_only, decipher_only): @@ -464,7 +515,10 @@ class KeyUsage(object): return not self == other +@utils.register_interface(ExtensionType) class AuthorityInformationAccess(object): + oid = OID_AUTHORITY_INFORMATION_ACCESS + def __init__(self, descriptions): if not all(isinstance(x, AccessDescription) for x in descriptions): raise TypeError( @@ -528,7 +582,10 @@ class AccessDescription(object): access_location = utils.read_only_property("_access_location") +@utils.register_interface(ExtensionType) class CertificatePolicies(object): + oid = OID_CERTIFICATE_POLICIES + def __init__(self, policies): if not all(isinstance(x, PolicyInformation) for x in policies): raise TypeError( @@ -665,10 +722,17 @@ class NoticeReference(object): notice_numbers = utils.read_only_property("_notice_numbers") +@utils.register_interface(ExtensionType) class SubjectKeyIdentifier(object): + oid = OID_SUBJECT_KEY_IDENTIFIER + def __init__(self, digest): self._digest = digest + @classmethod + def from_public_key(cls, public_key): + return cls(_key_identifier_from_public_key(public_key)) + digest = utils.read_only_property("_digest") def __repr__(self): @@ -686,7 +750,10 @@ class SubjectKeyIdentifier(object): return not self == other +@utils.register_interface(ExtensionType) class NameConstraints(object): + oid = OID_NAME_CONSTRAINTS + def __init__(self, permitted_subtrees, excluded_subtrees): if permitted_subtrees is not None: if not all( @@ -750,7 +817,10 @@ class NameConstraints(object): excluded_subtrees = utils.read_only_property("_excluded_subtrees") +@utils.register_interface(ExtensionType) class CRLDistributionPoints(object): + oid = OID_CRL_DISTRIBUTION_POINTS + def __init__(self, distribution_points): if not all( isinstance(x, DistributionPoint) for x in distribution_points @@ -785,7 +855,8 @@ class DistributionPoint(object): def __init__(self, full_name, relative_name, reasons, crl_issuer): if full_name and relative_name: raise ValueError( - "At least one of full_name and relative_name must be None" + "You cannot provide both full_name and relative_name, at " + "least one must be None." ) if full_name and not all( @@ -870,7 +941,10 @@ class ReasonFlags(Enum): remove_from_crl = "removeFromCRL" +@utils.register_interface(ExtensionType) class InhibitAnyPolicy(object): + oid = OID_INHIBIT_ANY_POLICY + def __init__(self, skip_certs): if not isinstance(skip_certs, six.integer_types): raise TypeError("skip_certs must be an integer") @@ -1160,7 +1234,10 @@ class GeneralNames(object): return not self == other +@utils.register_interface(ExtensionType) class SubjectAlternativeName(object): + oid = OID_SUBJECT_ALTERNATIVE_NAME + def __init__(self, general_names): self._general_names = GeneralNames(general_names) @@ -1186,7 +1263,10 @@ class SubjectAlternativeName(object): return not self == other +@utils.register_interface(ExtensionType) class IssuerAlternativeName(object): + oid = OID_ISSUER_ALTERNATIVE_NAME + def __init__(self, general_names): self._general_names = GeneralNames(general_names) @@ -1212,7 +1292,10 @@ class IssuerAlternativeName(object): return not self == other +@utils.register_interface(ExtensionType) class AuthorityKeyIdentifier(object): + oid = OID_AUTHORITY_KEY_IDENTIFIER + def __init__(self, key_identifier, authority_cert_issuer, authority_cert_serial_number): if authority_cert_issuer or authority_cert_serial_number: @@ -1239,6 +1322,15 @@ class AuthorityKeyIdentifier(object): self._authority_cert_issuer = authority_cert_issuer self._authority_cert_serial_number = authority_cert_serial_number + @classmethod + def from_issuer_public_key(cls, public_key): + digest = _key_identifier_from_public_key(public_key) + return cls( + key_identifier=digest, + authority_cert_issuer=None, + authority_cert_serial_number=None + ) + def __repr__(self): return ( "<AuthorityKeyIdentifier(key_identifier={0.key_identifier!r}, " @@ -1720,6 +1812,10 @@ class CertificateBuilder(object): """ if isinstance(extension, BasicConstraints): extension = Extension(OID_BASIC_CONSTRAINTS, critical, extension) + elif isinstance(extension, AuthorityKeyIdentifier): + extension = Extension( + OID_AUTHORITY_KEY_IDENTIFIER, critical, extension + ) elif isinstance(extension, KeyUsage): extension = Extension(OID_KEY_USAGE, critical, extension) elif isinstance(extension, ExtendedKeyUsage): @@ -1732,8 +1828,16 @@ class CertificateBuilder(object): extension = Extension( OID_AUTHORITY_INFORMATION_ACCESS, critical, extension ) + elif isinstance(extension, SubjectKeyIdentifier): + extension = Extension( + OID_SUBJECT_KEY_IDENTIFIER, critical, extension + ) elif isinstance(extension, InhibitAnyPolicy): extension = Extension(OID_INHIBIT_ANY_POLICY, critical, extension) + elif isinstance(extension, CRLDistributionPoints): + extension = Extension( + OID_CRL_DISTRIBUTION_POINTS, critical, extension + ) else: raise NotImplementedError('Unsupported X.509 extension.') @@ -1770,4 +1874,4 @@ class CertificateBuilder(object): if self._public_key is None: raise ValueError("A certificate must have a public key") - return backend.sign_x509_certificate(self, private_key, algorithm) + return backend.create_x509_certificate(self, private_key, algorithm) diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py index d516af16..cc59a8d4 100644 --- a/tests/hazmat/backends/test_multibackend.py +++ b/tests/hazmat/backends/test_multibackend.py @@ -206,7 +206,7 @@ class DummyX509Backend(object): def create_x509_csr(self, builder, private_key, algorithm): pass - def sign_x509_certificate(self, builder, private_key, algorithm): + def create_x509_certificate(self, builder, private_key, algorithm): pass @@ -487,7 +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.create_x509_certificate(object(), b"privatekey", hashes.SHA1()) backend = MultiBackend([]) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): @@ -501,6 +501,6 @@ class TestMultiBackend(object): 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( + backend.create_x509_certificate( object(), b"privatekey", hashes.SHA1() ) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index bd9aadb8..051827af 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -512,7 +512,7 @@ class TestOpenSSLSignX509Certificate(object): private_key = RSA_KEY_2048.private_key(backend) with pytest.raises(TypeError): - backend.sign_x509_certificate(object(), private_key, DummyHash()) + backend.create_x509_certificate(object(), private_key, DummyHash()) def test_checks_for_unsupported_extensions(self): private_key = RSA_KEY_2048.private_key(backend) diff --git a/tests/test_x509.py b/tests/test_x509.py index 5e0342cb..9ca8931d 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -1143,6 +1143,154 @@ class TestCertificateBuilder(object): with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA512(), backend) + @pytest.mark.parametrize( + "cdp", + [ + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=None, + relative_name=x509.Name([ + x509.NameAttribute( + x509.OID_COMMON_NAME, + u"indirect CRL for indirectCRL CA3" + ), + ]), + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), + x509.NameAttribute( + x509.OID_ORGANIZATION_NAME, + u"Test Certificates 2011" + ), + x509.NameAttribute( + x509.OID_ORGANIZATIONAL_UNIT_NAME, + u"indirectCRL CA3 cRLIssuer" + ), + ]) + )], + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), + ]) + )], + relative_name=None, + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + x509.OID_ORGANIZATION_NAME, + u"cryptography Testing" + ), + ]) + )], + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl" + ), + x509.UniformResourceIdentifier( + u"http://backup.myhost.com/myca.crl" + ) + ], + relative_name=None, + reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise + ]), + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), + x509.NameAttribute( + x509.OID_COMMON_NAME, u"cryptography CA" + ), + ]) + )], + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://domain.com/some.crl" + )], + relative_name=None, + reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + x509.ReasonFlags.affiliation_changed, + x509.ReasonFlags.superseded, + x509.ReasonFlags.privilege_withdrawn, + x509.ReasonFlags.cessation_of_operation, + x509.ReasonFlags.aa_compromise, + x509.ReasonFlags.certificate_hold, + ]), + crl_issuer=None + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=None, + relative_name=None, + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + x509.OID_COMMON_NAME, u"cryptography CA" + ), + ]) + )], + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://domain.com/some.crl" + )], + relative_name=None, + reasons=frozenset([x509.ReasonFlags.aa_compromise]), + crl_issuer=None + ) + ]) + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_crl_distribution_points(self, backend, cdp): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + builder = x509.CertificateBuilder().serial_number( + 4444444 + ).issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + ])).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + cdp, + critical=False, + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + ext = cert.extensions.get_extension_for_oid( + x509.OID_CRL_DISTRIBUTION_POINTS + ) + assert ext.critical is False + assert ext.value == cdp + @pytest.mark.requires_backend_interface(interface=DSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_build_cert_with_dsa_private_key(self, backend): @@ -1842,6 +1990,118 @@ class TestCertificateSigningRequestBuilder(object): ) assert ext.value == aia + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_ski(self, 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) + + ski = x509.SubjectKeyIdentifier.from_public_key( + subject_private_key.public_key() + ) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + ski, critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + ext = cert.extensions.get_extension_for_oid( + x509.OID_SUBJECT_KEY_IDENTIFIER + ) + assert ext.value == ski + + @pytest.mark.parametrize( + "aki", + [ + x509.AuthorityKeyIdentifier( + b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" + b"\xcbY", + None, + None + ), + x509.AuthorityKeyIdentifier( + b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" + b"\xcbY", + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + x509.OID_ORGANIZATION_NAME, u"PyCA" + ), + x509.NameAttribute( + x509.OID_COMMON_NAME, u"cryptography CA" + ) + ]) + ) + ], + 333 + ), + x509.AuthorityKeyIdentifier( + None, + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + x509.OID_ORGANIZATION_NAME, u"PyCA" + ), + x509.NameAttribute( + x509.OID_COMMON_NAME, u"cryptography CA" + ) + ]) + ) + ], + 333 + ), + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_aki(self, aki, 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) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + aki, critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid( + x509.OID_AUTHORITY_KEY_IDENTIFIER + ) + assert ext.value == aki + @pytest.mark.requires_backend_interface(interface=DSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py index 890709ae..40231b93 100644 --- a/tests/test_x509_ext.py +++ b/tests/test_x509_ext.py @@ -13,8 +13,12 @@ import pytest import six from cryptography import x509 -from cryptography.hazmat.backends.interfaces import RSABackend, X509Backend +from cryptography.hazmat.backends.interfaces import ( + DSABackend, EllipticCurveBackend, RSABackend, X509Backend +) +from cryptography.hazmat.primitives.asymmetric import ec +from .hazmat.primitives.test_ec import _skip_curve_unsupported from .test_x509 import _load_cert @@ -917,9 +921,9 @@ class TestBasicConstraintsExtension(object): assert ext.value.ca is False -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) class TestSubjectKeyIdentifierExtension(object): + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) def test_subject_key_identifier(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), @@ -936,6 +940,8 @@ class TestSubjectKeyIdentifierExtension(object): b"580184241bbc2b52944a3da510721451f5af3ac9" ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) def test_no_subject_key_identifier(self, backend): cert = _load_cert( os.path.join("x509", "custom", "bc_path_length_zero.pem"), @@ -947,6 +953,57 @@ class TestSubjectKeyIdentifierExtension(object): x509.OID_SUBJECT_KEY_IDENTIFIER ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_from_rsa_public_key(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + x509.OID_SUBJECT_KEY_IDENTIFIER + ) + ski = x509.SubjectKeyIdentifier.from_public_key( + cert.public_key() + ) + assert ext.value == ski + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_from_dsa_public_key(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), + x509.load_pem_x509_certificate, + backend + ) + + ext = cert.extensions.get_extension_for_oid( + x509.OID_SUBJECT_KEY_IDENTIFIER + ) + ski = x509.SubjectKeyIdentifier.from_public_key( + cert.public_key() + ) + assert ext.value == ski + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_from_ec_public_key(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + cert = _load_cert( + os.path.join("x509", "ecdsa_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + + ext = cert.extensions.get_extension_for_oid( + x509.OID_SUBJECT_KEY_IDENTIFIER + ) + ski = x509.SubjectKeyIdentifier.from_public_key( + cert.public_key() + ) + assert ext.value == ski + @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) @@ -2055,6 +2112,25 @@ class TestAuthorityKeyIdentifierExtension(object): ] assert ext.value.authority_cert_serial_number == 3 + def test_from_certificate(self, backend): + issuer_cert = _load_cert( + os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + x509.OID_AUTHORITY_KEY_IDENTIFIER + ) + aki = x509.AuthorityKeyIdentifier.from_issuer_public_key( + issuer_cert.public_key() + ) + assert ext.value == aki + class TestNameConstraints(object): def test_ipaddress_wrong_type(self): |