From f22f61234470bd5c86c80ae409b2698d2a2da1a5 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 5 Aug 2015 12:57:13 +0100 Subject: add SubjectKeyIdentifier.create_from_public_key --- docs/x509/reference.rst | 15 ++++++++++++ src/cryptography/x509.py | 37 +++++++++++++++++++++++++++- 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) -- cgit v1.2.3 From d4a7f062d7dc9330fb701086dd06ac81a5b1e3d5 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 5 Aug 2015 18:32:18 +0100 Subject: rename to classmethod to from_public_key --- docs/x509/reference.rst | 2 +- src/cryptography/x509.py | 2 +- tests/test_x509_ext.py | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 2ccc5272..61f73e9d 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -1119,7 +1119,7 @@ X.509 Extensions The binary value of the identifier. - .. classmethod:: create_from_public_key(public_key) + .. classmethod:: from_public_key(public_key) .. versionadded:: 1.0 diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index b58166f6..6c9cd562 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -682,7 +682,7 @@ class SubjectKeyIdentifier(object): self._digest = digest @classmethod - def create_from_public_key(cls, public_key): + def from_public_key(cls, public_key): # This is a very slow way to do this. serialized = public_key.public_bytes( serialization.Encoding.DER, diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py index 4c7cce54..73cdfc5f 100644 --- a/tests/test_x509_ext.py +++ b/tests/test_x509_ext.py @@ -955,7 +955,7 @@ class TestSubjectKeyIdentifierExtension(object): @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_create_from_rsa_public_key(self, backend): + 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, @@ -964,14 +964,14 @@ class TestSubjectKeyIdentifierExtension(object): ext = cert.extensions.get_extension_for_oid( x509.OID_SUBJECT_KEY_IDENTIFIER ) - ski = x509.SubjectKeyIdentifier.create_from_public_key( + 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_create_from_dsa_public_key(self, backend): + 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, @@ -981,14 +981,14 @@ class TestSubjectKeyIdentifierExtension(object): ext = cert.extensions.get_extension_for_oid( x509.OID_SUBJECT_KEY_IDENTIFIER ) - ski = x509.SubjectKeyIdentifier.create_from_public_key( + 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_create_from_ec_public_key(self, backend): + def test_from_ec_public_key(self, backend): _skip_curve_unsupported(backend, ec.SECP384R1()) cert = _load_cert( os.path.join("x509", "ecdsa_root.pem"), @@ -999,7 +999,7 @@ class TestSubjectKeyIdentifierExtension(object): ext = cert.extensions.get_extension_for_oid( x509.OID_SUBJECT_KEY_IDENTIFIER ) - ski = x509.SubjectKeyIdentifier.create_from_public_key( + ski = x509.SubjectKeyIdentifier.from_public_key( cert.public_key() ) assert ext.value == ski -- cgit v1.2.3 From 73cd392198ed800dcd20f67d838f8a451e2bdf8c Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 7 Aug 2015 14:08:01 -0500 Subject: assert not remaining in SKI classmethod --- src/cryptography/x509.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 6c9cd562..f54eccf4 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -691,6 +691,7 @@ class SubjectKeyIdentifier(object): 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, hex it, then decode the hex. -- cgit v1.2.3 From 679e8f5b825be0d4f6c5ee77bff639efcaeac873 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 8 Aug 2015 09:59:48 -0500 Subject: refactor integer to bytes as utils.int_to_bytes --- src/cryptography/utils.py | 13 +++++++++++++ src/cryptography/x509.py | 6 +----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 24afe612..4ca4a7a2 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,18 @@ else: return result +if hasattr(int, "to_bytes"): + int_to_bytes = int.to_bytes +else: + def int_to_bytes(integer, byteorder, signed=False): + assert byteorder == 'big' + assert not signed + + 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 f54eccf4..3fff218a 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -5,7 +5,6 @@ from __future__ import absolute_import, division, print_function import abc -import binascii import datetime import hashlib import ipaddress @@ -699,10 +698,7 @@ class SubjectKeyIdentifier(object): 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))) + data = utils.int_to_bytes(bits, "big") return cls(hashlib.sha1(data).digest()) digest = utils.read_only_property("_digest") -- cgit v1.2.3 From eb9ec00ff857e2788938baa50beb9c92e2b693db Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 8 Aug 2015 10:03:02 -0500 Subject: add more prose about how the digest is generated --- docs/x509/reference.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 61f73e9d..e83a4ace 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -1125,7 +1125,9 @@ X.509 Extensions 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. + 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` -- cgit v1.2.3 From dbd47de45a7dd40ea1e2d1bc941ee07ddf6b1a59 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 8 Aug 2015 10:29:39 -0500 Subject: update comment --- src/cryptography/x509.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 3fff218a..3254f26f 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -693,7 +693,7 @@ class SubjectKeyIdentifier(object): 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, hex it, then decode the hex. + # build an integer and convert that to bytes. bits = 0 for bit in spki.getComponentByName("subjectPublicKey"): bits = bits << 1 | bit -- cgit v1.2.3 From a39e3d165bfc3e4dd94a80d8ff17af356113d918 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 8 Aug 2015 11:10:32 -0500 Subject: py3 int.to_bytes is dead to me --- src/cryptography/utils.py | 14 ++++---------- src/cryptography/x509.py | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 4ca4a7a2..993571bd 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -47,16 +47,10 @@ else: return result -if hasattr(int, "to_bytes"): - int_to_bytes = int.to_bytes -else: - def int_to_bytes(integer, byteorder, signed=False): - assert byteorder == 'big' - assert not signed - - hex_string = '%x' % integer - n = len(hex_string) - return binascii.unhexlify(hex_string.zfill(n + (n & 1))) +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): diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 3254f26f..82b8bd36 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -698,7 +698,7 @@ class SubjectKeyIdentifier(object): for bit in spki.getComponentByName("subjectPublicKey"): bits = bits << 1 | bit - data = utils.int_to_bytes(bits, "big") + data = utils.int_to_bytes(bits) return cls(hashlib.sha1(data).digest()) digest = utils.read_only_property("_digest") -- cgit v1.2.3