Merge pull request #1823 from reaperhulk/x509-ski

add subjectkeyidentifier support
diff --git a/docs/x509.rst b/docs/x509.rst
index afc9620..d09651f 100644
--- a/docs/x509.rst
+++ b/docs/x509.rst
@@ -275,6 +275,7 @@
 
             >>> for ext in cert.extensions:
             ...     print(ext)
+            <Extension(oid=<ObjectIdentifier(oid=2.5.29.14, name=subjectKeyIdentifier)>, critical=False, value=<SubjectKeyIdentifier(digest='X\x01\x84$\x1b\xbc+R\x94J=\xa5\x10r\x14Q\xf5\xaf:\xc9')>)>
             <Extension(oid=<ObjectIdentifier(oid=2.5.29.19, name=basicConstraints)>, critical=True, value=<BasicConstraints(ca=True, path_length=None)>)>
 
 X.509 CSR (Certificate Signing Request) Object
@@ -576,6 +577,19 @@
     purposes indicated in the key usage extension. The object is
     iterable to obtain the list of :ref:`extended key usage OIDs <eku_oids>`.
 
+.. class:: SubjectKeyIdentifier
+
+    .. versionadded:: 0.9
+
+    The subject key identifier extension provides a means of identifying
+    certificates that contain a particular public key.
+
+    .. attribute:: digest
+
+        :type: bytes
+
+        The binary value of the identifier.
+
 
 Object Identifiers
 ~~~~~~~~~~~~~~~~~~
diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py
index 6a7032b..5d47c5e 100644
--- a/src/cryptography/hazmat/backends/openssl/x509.py
+++ b/src/cryptography/hazmat/backends/openssl/x509.py
@@ -170,6 +170,8 @@
                 )
             elif oid == x509.OID_BASIC_CONSTRAINTS:
                 value = self._build_basic_constraints(ext)
+            elif oid == x509.OID_SUBJECT_KEY_IDENTIFIER:
+                value = self._build_subject_key_identifier(ext)
             elif oid == x509.OID_KEY_USAGE and critical:
                 # TODO: remove this obviously.
                 warnings.warn(
@@ -217,6 +219,19 @@
 
         return x509.BasicConstraints(ca, path_length)
 
+    def _build_subject_key_identifier(self, ext):
+        asn1_string = self._backend._lib.X509V3_EXT_d2i(ext)
+        assert asn1_string != self._backend._ffi.NULL
+        asn1_string = self._backend._ffi.cast(
+            "ASN1_OCTET_STRING *", asn1_string
+        )
+        asn1_string = self._backend._ffi.gc(
+            asn1_string, self._backend._lib.ASN1_OCTET_STRING_free
+        )
+        return x509.SubjectKeyIdentifier(
+            self._backend._ffi.buffer(asn1_string.data, asn1_string.length)[:]
+        )
+
 
 @utils.register_interface(x509.CertificateSigningRequest)
 class _CertificateSigningRequest(object):
diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py
index 697d7d6..28d1685 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -346,6 +346,27 @@
             return self._decipher_only
 
 
+class SubjectKeyIdentifier(object):
+    def __init__(self, digest):
+        self._digest = digest
+
+    digest = utils.read_only_property("_digest")
+
+    def __repr__(self):
+        return "<SubjectKeyIdentifier(digest={0!r})>".format(self.digest)
+
+    def __eq__(self, other):
+        if not isinstance(other, SubjectKeyIdentifier):
+            return NotImplemented
+
+        return (
+            self.digest == other.digest
+        )
+
+    def __ne__(self, other):
+        return not self == other
+
+
 OID_COMMON_NAME = ObjectIdentifier("2.5.4.3")
 OID_COUNTRY_NAME = ObjectIdentifier("2.5.4.6")
 OID_LOCALITY_NAME = ObjectIdentifier("2.5.4.7")
diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py
index c1512d5..194b18c 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -4,10 +4,13 @@
 
 from __future__ import absolute_import, division, print_function
 
+import binascii
 import os
 
 import pytest
 
+import six
+
 from cryptography import x509
 from cryptography.hazmat.backends.interfaces import RSABackend, X509Backend
 
@@ -132,6 +135,52 @@
             ku.decipher_only
 
 
+class TestSubjectKeyIdentifier(object):
+    def test_properties(self):
+        value = binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9")
+        ski = x509.SubjectKeyIdentifier(value)
+        assert ski.digest == value
+
+    def test_repr(self):
+        ski = x509.SubjectKeyIdentifier(
+            binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9")
+        )
+        ext = x509.Extension(x509.OID_SUBJECT_KEY_IDENTIFIER, False, ski)
+        if six.PY3:
+            assert repr(ext) == (
+                "<Extension(oid=<ObjectIdentifier(oid=2.5.29.14, name=subjectK"
+                "eyIdentifier)>, critical=False, value=<SubjectKeyIdentifier(d"
+                "igest=b\'\\t#\\x84\\x93\"0I\\x8b\\xc9\\x80\\xaa\\x80\\x98Eoo"
+                "\\xf7\\xff:\\xc9\')>)>"
+            )
+        else:
+            assert repr(ext) == (
+                "<Extension(oid=<ObjectIdentifier(oid=2.5.29.14, name=subjectK"
+                "eyIdentifier)>, critical=False, value=<SubjectKeyIdentifier(d"
+                "igest=\'\\t#\\x84\\x93\"0I\\x8b\\xc9\\x80\\xaa\\x80\\x98Eoo"
+                "\\xf7\\xff:\\xc9\')>)>"
+            )
+
+    def test_eq(self):
+        ski = x509.SubjectKeyIdentifier(
+            binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9")
+        )
+        ski2 = x509.SubjectKeyIdentifier(
+            binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9")
+        )
+        assert ski == ski2
+
+    def test_ne(self):
+        ski = x509.SubjectKeyIdentifier(
+            binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9")
+        )
+        ski2 = x509.SubjectKeyIdentifier(
+            binascii.unhexlify(b"aa8098456f6ff7ff3ac9092384932230498bc980")
+        )
+        assert ski != ski2
+        assert ski != object()
+
+
 class TestBasicConstraints(object):
     def test_ca_not_boolean(self):
         with pytest.raises(TypeError):
@@ -345,3 +394,34 @@
         assert ext is not None
         assert ext.critical is False
         assert ext.value.ca is False
+
+
+@pytest.mark.requires_backend_interface(interface=RSABackend)
+@pytest.mark.requires_backend_interface(interface=X509Backend)
+class TestSubjectKeyIdentifierExtension(object):
+    def test_subject_key_identifier(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 = ext.value
+        assert ext is not None
+        assert ext.critical is False
+        assert ski.digest == binascii.unhexlify(
+            b"580184241bbc2b52944a3da510721451f5af3ac9"
+        )
+
+    def test_no_subject_key_identifier(self, backend):
+        cert = _load_cert(
+            os.path.join("x509", "custom", "bc_path_length_zero.pem"),
+            x509.load_pem_x509_certificate,
+            backend
+        )
+        with pytest.raises(x509.ExtensionNotFound):
+            cert.extensions.get_extension_for_oid(
+                x509.OID_SUBJECT_KEY_IDENTIFIER
+            )