diff options
-rw-r--r-- | docs/x509/reference.rst | 39 | ||||
-rw-r--r-- | src/_cffi_src/openssl/asn1.py | 5 | ||||
-rw-r--r-- | src/_cffi_src/openssl/x509v3.py | 1 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 13 | ||||
-rw-r--r-- | src/cryptography/x509.py | 53 | ||||
-rw-r--r-- | tests/test_x509.py | 36 | ||||
-rw-r--r-- | tests/test_x509_ext.py | 19 |
7 files changed, 148 insertions, 18 deletions
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index dfa91fac..d86ebbe8 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -1160,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 @@ -1198,6 +1229,14 @@ X.509 Extensions , 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 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/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/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 2752d98d..fdd38fa3 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -254,6 +254,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() @@ -1235,6 +1246,8 @@ class Backend(object): 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 diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 5ed3c094..da7603c4 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -32,6 +32,27 @@ class _SubjectPublicKeyInfo(univ.Sequence): ) +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", @@ -710,24 +731,7 @@ class SubjectKeyIdentifier(object): @classmethod def from_public_key(cls, 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 cls(hashlib.sha1(data).digest()) + return cls(_key_identifier_from_public_key(public_key)) digest = utils.read_only_property("_digest") @@ -1318,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}, " @@ -1811,6 +1824,10 @@ 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): diff --git a/tests/test_x509.py b/tests/test_x509.py index 4e763e36..1cbff511 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -1990,6 +1990,42 @@ 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.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 73cdfc5f..40231b93 100644 --- a/tests/test_x509_ext.py +++ b/tests/test_x509_ext.py @@ -2112,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): |