Merge pull request #2204 from reaperhulk/ski-classmethod

SubjectKeyIdentifier classmethod
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index baf8b1e..dfa91fa 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -1181,6 +1181,23 @@
 
         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 24afe61..993571b 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 @@
         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 45a302b..5ed3c09 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -6,21 +6,32 @@
 
 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 @@
     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 890709a..73cdfc5 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -13,8 +13,12 @@
 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 @@
         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 @@
             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 @@
                 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)