diff options
-rw-r--r-- | docs/x509/reference.rst | 15 | ||||
-rw-r--r-- | src/cryptography/x509.py | 37 | ||||
-rw-r--r-- | tests/test_x509_ext.py | 63 |
3 files changed, 111 insertions, 4 deletions
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 61971fed..2ccc5272 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -1119,6 +1119,21 @@ X.509 Extensions The binary value of the identifier. + .. classmethod:: create_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. + + :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`. + .. class:: SubjectAlternativeName .. versionadded:: 0.9 diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 978eb560..b58166f6 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -5,22 +5,34 @@ from __future__ import absolute_import, division, print_function import abc +import binascii 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()) + ) + + _OID_NAMES = { "2.5.4.3": "commonName", "2.5.4.6": "countryName", @@ -669,6 +681,29 @@ class SubjectKeyIdentifier(object): def __init__(self, digest): self._digest = digest + @classmethod + def create_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() + ) + # 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, hex it, then decode the hex. + bits = 0 + for bit in spki.getComponentByName("subjectPublicKey"): + bits = bits << 1 | bit + + # convert the integer to bytes + hex_string = '%x' % bits + n = len(hex_string) + data = binascii.unhexlify(hex_string.zfill(n + (n & 1))) + return cls(hashlib.sha1(data).digest()) + digest = utils.read_only_property("_digest") def __repr__(self): diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py index 890709ae..4c7cce54 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_create_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.create_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_create_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.create_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_create_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.create_from_public_key( + cert.public_key() + ) + assert ext.value == ski + @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) |