aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2015-08-08 12:57:48 -0400
committerAlex Gaynor <alex.gaynor@gmail.com>2015-08-08 12:57:48 -0400
commitd5bf17ad99939920aa73e6d00a36818ecaf1c2cc (patch)
treed61e4033c163d5d0539122098d5422b8e568dd04
parentc9ee947f528269ea7a907a592219a788edf1a279 (diff)
parenta39e3d165bfc3e4dd94a80d8ff17af356113d918 (diff)
downloadcryptography-d5bf17ad99939920aa73e6d00a36818ecaf1c2cc.tar.gz
cryptography-d5bf17ad99939920aa73e6d00a36818ecaf1c2cc.tar.bz2
cryptography-d5bf17ad99939920aa73e6d00a36818ecaf1c2cc.zip
Merge pull request #2204 from reaperhulk/ski-classmethod
SubjectKeyIdentifier classmethod
-rw-r--r--docs/x509/reference.rst17
-rw-r--r--src/cryptography/utils.py7
-rw-r--r--src/cryptography/x509.py34
-rw-r--r--tests/test_x509_ext.py63
4 files changed, 117 insertions, 4 deletions
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index baf8b1e5..dfa91fac 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -1181,6 +1181,23 @@ X.509 Extensions
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`.
+
.. class:: SubjectAlternativeName
.. versionadded:: 0.9
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 45a302ba..5ed3c094 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -6,21 +6,32 @@ 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())
+ )
+
+
_OID_NAMES = {
"2.5.4.3": "commonName",
"2.5.4.6": "countryName",
@@ -697,6 +708,27 @@ class SubjectKeyIdentifier(object):
def __init__(self, digest):
self._digest = digest
+ @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())
+
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..73cdfc5f 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)