Merge pull request #1659 from reaperhulk/move-cipher-interfaces

Move cipher and mode interfaces
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 8134753..0f03d0a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -53,6 +53,8 @@
   :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithNumbers`
   were moved from :mod:`~cryptography.hazmat.primitives.interfaces` to
   :mod:`~cryptography.hazmat.primitives.asymmetric.rsa`.
+* Added support for parsing X.509 names. See the
+  :doc:`X.509 documentation</x509>` for more information.
 
 0.7.2 - 2015-01-16
 ~~~~~~~~~~~~~~~~~~
diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index 003e37d..fefd26b 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -29,6 +29,7 @@
 introspectability
 invariants
 iOS
+iterable
 Koblitz
 Lange
 metadata
diff --git a/docs/x509.rst b/docs/x509.rst
index 587fd88..0298d94 100644
--- a/docs/x509.rst
+++ b/docs/x509.rst
@@ -166,6 +166,54 @@
             >>> cert.not_valid_after
             datetime.datetime(2030, 12, 31, 8, 30)
 
+    .. attribute:: issuer
+
+        .. versionadded:: 0.8
+
+        :type: :class:`Name`
+
+        The :class:`Name` of the issuer.
+
+    .. attribute:: subject
+
+        .. versionadded:: 0.8
+
+        :type: :class:`Name`
+
+        The :class:`Name` of the subject.
+
+
+.. class:: Name
+
+    .. versionadded:: 0.8
+
+    An X509 Name is an ordered list of attributes. The object is iterable to
+    get every attribute or you can use :meth:`Name.get_attributes_for_oid` to
+    obtain the specific type you want. Names are sometimes represented as a
+    slash or comma delimited string (e.g. ``/CN=mydomain.com/O=My Org/C=US`` or
+    ``CN=mydomain.com, O=My Org, C=US``).
+
+    .. doctest::
+
+        >>> len(cert.subject)
+        3
+        >>> for attribute in cert.subject:
+        ...     print(attribute)
+        <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.6, name=countryName)>, value=u'US')>
+        <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.10, name=organizationName)>, value=u'Test Certificates 2011')>
+        <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=u'Good CA')>
+
+    .. method:: get_attributes_for_oid(oid)
+
+        :param oid: An :class:`ObjectIdentifier` instance.
+
+        :returns: A list of :class:`NameAttribute` instances that match the
+            OID provided. If nothing matches an empty list will be returned.
+
+        .. doctest::
+
+            >>> cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)
+            [<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=u'Good CA')>]
 
 .. class:: Version
 
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 75d7e32..8441e89 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -225,8 +225,8 @@
         )
 
     def create_symmetric_encryption_ctx(self, cipher, mode):
-        if (isinstance(mode, CTR) and isinstance(cipher, AES)
-                and not self._evp_cipher_supported(cipher, mode)):
+        if (isinstance(mode, CTR) and isinstance(cipher, AES) and
+                not self._evp_cipher_supported(cipher, mode)):
             # This is needed to provide support for AES CTR mode in OpenSSL
             # 0.9.8. It can be removed when we drop 0.9.8 support (RHEL 5
             # extended life ends 2020).
@@ -235,8 +235,8 @@
             return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT)
 
     def create_symmetric_decryption_ctx(self, cipher, mode):
-        if (isinstance(mode, CTR) and isinstance(cipher, AES)
-                and not self._evp_cipher_supported(cipher, mode)):
+        if (isinstance(mode, CTR) and isinstance(cipher, AES) and
+                not self._evp_cipher_supported(cipher, mode)):
             # This is needed to provide support for AES CTR mode in OpenSSL
             # 0.9.8. It can be removed when we drop 0.9.8 support (RHEL 5
             # extended life ends 2020).
@@ -671,9 +671,10 @@
 
     def cmac_algorithm_supported(self, algorithm):
         return (
-            self._lib.Cryptography_HAS_CMAC == 1
-            and self.cipher_supported(algorithm, CBC(
-                b"\x00" * algorithm.block_size))
+            self._lib.Cryptography_HAS_CMAC == 1 and
+            self.cipher_supported(
+                algorithm, CBC(b"\x00" * algorithm.block_size)
+            )
         )
 
     def create_cmac_ctx(self, algorithm):
diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py
index 66c99c9..76dcf32 100644
--- a/src/cryptography/hazmat/backends/openssl/x509.py
+++ b/src/cryptography/hazmat/backends/openssl/x509.py
@@ -91,3 +91,48 @@
             )
         ).decode("ascii")
         return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ")
+
+    @property
+    def issuer(self):
+        issuer = self._backend._lib.X509_get_issuer_name(self._x509)
+        assert issuer != self._backend._ffi.NULL
+        return self._build_x509_name(issuer)
+
+    @property
+    def subject(self):
+        subject = self._backend._lib.X509_get_subject_name(self._x509)
+        assert subject != self._backend._ffi.NULL
+        return self._build_x509_name(subject)
+
+    def _build_x509_name(self, x509_name):
+        count = self._backend._lib.X509_NAME_entry_count(x509_name)
+        attributes = []
+        for x in range(count):
+            entry = self._backend._lib.X509_NAME_get_entry(x509_name, x)
+            obj = self._backend._lib.X509_NAME_ENTRY_get_object(entry)
+            assert obj != self._backend._ffi.NULL
+            data = self._backend._lib.X509_NAME_ENTRY_get_data(entry)
+            assert data != self._backend._ffi.NULL
+            buf = self._backend._ffi.new("unsigned char **")
+            res = self._backend._lib.ASN1_STRING_to_UTF8(buf, data)
+            assert res >= 0
+            assert buf[0] != self._backend._ffi.NULL
+            buf = self._backend._ffi.gc(
+                buf, lambda buf: self._backend._lib.OPENSSL_free(buf[0])
+            )
+            value = self._backend._ffi.buffer(buf[0], res)[:].decode('utf8')
+            # Set to 80 on the recommendation of
+            # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html
+            buf_len = 80
+            buf = self._backend._ffi.new("char[]", buf_len)
+            res = self._backend._lib.OBJ_obj2txt(buf, buf_len, obj, 1)
+            assert res > 0
+            oid = self._backend._ffi.buffer(buf, res)[:].decode()
+
+            attributes.append(
+                x509.NameAttribute(
+                    x509.ObjectIdentifier(oid), value
+                )
+            )
+
+        return x509.Name(attributes)
diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py
index 7106258..8a888d2 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -104,6 +104,29 @@
     dotted_string = utils.read_only_property("_dotted_string")
 
 
+class Name(object):
+    def __init__(self, attributes):
+        self._attributes = attributes
+
+    def get_attributes_for_oid(self, oid):
+        return [i for i in self._attributes if i.oid == oid]
+
+    def __eq__(self, other):
+        if not isinstance(other, Name):
+            return NotImplemented
+
+        return self._attributes == other._attributes
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __iter__(self):
+        return iter(self._attributes)
+
+    def __len__(self):
+        return len(self._attributes)
+
+
 OID_COMMON_NAME = ObjectIdentifier("2.5.4.3")
 OID_COUNTRY_NAME = ObjectIdentifier("2.5.4.6")
 OID_LOCALITY_NAME = ObjectIdentifier("2.5.4.7")
@@ -158,3 +181,15 @@
         """
         Not after time (represented as UTC datetime)
         """
+
+    @abc.abstractproperty
+    def issuer(self):
+        """
+        Returns the issuer name object.
+        """
+
+    @abc.abstractproperty
+    def subject(self):
+        """
+        Returns the subject name object.
+        """
diff --git a/tests/test_x509.py b/tests/test_x509.py
index 0927520..55a9408 100644
--- a/tests/test_x509.py
+++ b/tests/test_x509.py
@@ -10,6 +10,8 @@
 
 import pytest
 
+import six
+
 from cryptography import x509
 from cryptography.hazmat.backends.interfaces import (
     DSABackend, EllipticCurveBackend, RSABackend, X509Backend
@@ -55,6 +57,171 @@
         fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1()))
         assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d"
 
+    def test_issuer(self, backend):
+        cert = _load_cert(
+            os.path.join(
+                "x509", "PKITS_data", "certs",
+                "Validpre2000UTCnotBeforeDateTest3EE.crt"
+            ),
+            x509.load_der_x509_certificate,
+            backend
+        )
+        issuer = cert.issuer
+        assert isinstance(issuer, x509.Name)
+        assert list(issuer) == [
+            x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'),
+            x509.NameAttribute(
+                x509.OID_ORGANIZATION_NAME, 'Test Certificates 2011'
+            ),
+            x509.NameAttribute(x509.OID_COMMON_NAME, 'Good CA')
+        ]
+        assert issuer.get_attributes_for_oid(x509.OID_COMMON_NAME) == [
+            x509.NameAttribute(x509.OID_COMMON_NAME, 'Good CA')
+        ]
+
+    def test_all_issuer_name_types(self, backend):
+        cert = _load_cert(
+            os.path.join(
+                "x509", "custom",
+                "all_supported_names.pem"
+            ),
+            x509.load_pem_x509_certificate,
+            backend
+        )
+        issuer = cert.issuer
+
+        assert isinstance(issuer, x509.Name)
+        assert list(issuer) == [
+            x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'),
+            x509.NameAttribute(x509.OID_COUNTRY_NAME, 'CA'),
+            x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'),
+            x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Illinois'),
+            x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Chicago'),
+            x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'),
+            x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'Zero, LLC'),
+            x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'One, LLC'),
+            x509.NameAttribute(x509.OID_COMMON_NAME, 'common name 0'),
+            x509.NameAttribute(x509.OID_COMMON_NAME, 'common name 1'),
+            x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, 'OU 0'),
+            x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, 'OU 1'),
+            x509.NameAttribute(x509.OID_DN_QUALIFIER, 'dnQualifier0'),
+            x509.NameAttribute(x509.OID_DN_QUALIFIER, 'dnQualifier1'),
+            x509.NameAttribute(x509.OID_SERIAL_NUMBER, '123'),
+            x509.NameAttribute(x509.OID_SERIAL_NUMBER, '456'),
+            x509.NameAttribute(x509.OID_TITLE, 'Title 0'),
+            x509.NameAttribute(x509.OID_TITLE, 'Title 1'),
+            x509.NameAttribute(x509.OID_SURNAME, 'Surname 0'),
+            x509.NameAttribute(x509.OID_SURNAME, 'Surname 1'),
+            x509.NameAttribute(x509.OID_GIVEN_NAME, 'Given Name 0'),
+            x509.NameAttribute(x509.OID_GIVEN_NAME, 'Given Name 1'),
+            x509.NameAttribute(x509.OID_PSEUDONYM, 'Incognito 0'),
+            x509.NameAttribute(x509.OID_PSEUDONYM, 'Incognito 1'),
+            x509.NameAttribute(x509.OID_GENERATION_QUALIFIER, 'Last Gen'),
+            x509.NameAttribute(x509.OID_GENERATION_QUALIFIER, 'Next Gen'),
+            x509.NameAttribute(x509.OID_DOMAIN_COMPONENT, 'dc0'),
+            x509.NameAttribute(x509.OID_DOMAIN_COMPONENT, 'dc1'),
+            x509.NameAttribute(x509.OID_EMAIL_ADDRESS, 'test0@test.local'),
+            x509.NameAttribute(x509.OID_EMAIL_ADDRESS, 'test1@test.local'),
+        ]
+
+    def test_subject(self, backend):
+        cert = _load_cert(
+            os.path.join(
+                "x509", "PKITS_data", "certs",
+                "Validpre2000UTCnotBeforeDateTest3EE.crt"
+            ),
+            x509.load_der_x509_certificate,
+            backend
+        )
+        subject = cert.subject
+        assert isinstance(subject, x509.Name)
+        assert list(subject) == [
+            x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'),
+            x509.NameAttribute(
+                x509.OID_ORGANIZATION_NAME, 'Test Certificates 2011'
+            ),
+            x509.NameAttribute(
+                x509.OID_COMMON_NAME,
+                'Valid pre2000 UTC notBefore Date EE Certificate Test3'
+            )
+        ]
+        assert subject.get_attributes_for_oid(x509.OID_COMMON_NAME) == [
+            x509.NameAttribute(
+                x509.OID_COMMON_NAME,
+                'Valid pre2000 UTC notBefore Date EE Certificate Test3'
+            )
+        ]
+
+    def test_unicode_name(self, backend):
+        cert = _load_cert(
+            os.path.join(
+                "x509", "custom",
+                "utf8_common_name.pem"
+            ),
+            x509.load_pem_x509_certificate,
+            backend
+        )
+        assert cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME) == [
+            x509.NameAttribute(
+                x509.OID_COMMON_NAME,
+                six.u('We heart UTF8!\u2122')
+            )
+        ]
+        assert cert.issuer.get_attributes_for_oid(x509.OID_COMMON_NAME) == [
+            x509.NameAttribute(
+                x509.OID_COMMON_NAME,
+                six.u('We heart UTF8!\u2122')
+            )
+        ]
+
+    def test_all_subject_name_types(self, backend):
+        cert = _load_cert(
+            os.path.join(
+                "x509", "custom",
+                "all_supported_names.pem"
+            ),
+            x509.load_pem_x509_certificate,
+            backend
+        )
+        subject = cert.subject
+        assert isinstance(subject, x509.Name)
+        assert list(subject) == [
+            x509.NameAttribute(x509.OID_COUNTRY_NAME, 'AU'),
+            x509.NameAttribute(x509.OID_COUNTRY_NAME, 'DE'),
+            x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'California'),
+            x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'New York'),
+            x509.NameAttribute(x509.OID_LOCALITY_NAME, 'San Francisco'),
+            x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Ithaca'),
+            x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'Org Zero, LLC'),
+            x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'Org One, LLC'),
+            x509.NameAttribute(x509.OID_COMMON_NAME, 'CN 0'),
+            x509.NameAttribute(x509.OID_COMMON_NAME, 'CN 1'),
+            x509.NameAttribute(
+                x509.OID_ORGANIZATIONAL_UNIT_NAME, 'Engineering 0'
+            ),
+            x509.NameAttribute(
+                x509.OID_ORGANIZATIONAL_UNIT_NAME, 'Engineering 1'
+            ),
+            x509.NameAttribute(x509.OID_DN_QUALIFIER, 'qualified0'),
+            x509.NameAttribute(x509.OID_DN_QUALIFIER, 'qualified1'),
+            x509.NameAttribute(x509.OID_SERIAL_NUMBER, '789'),
+            x509.NameAttribute(x509.OID_SERIAL_NUMBER, '012'),
+            x509.NameAttribute(x509.OID_TITLE, 'Title IX'),
+            x509.NameAttribute(x509.OID_TITLE, 'Title X'),
+            x509.NameAttribute(x509.OID_SURNAME, 'Last 0'),
+            x509.NameAttribute(x509.OID_SURNAME, 'Last 1'),
+            x509.NameAttribute(x509.OID_GIVEN_NAME, 'First 0'),
+            x509.NameAttribute(x509.OID_GIVEN_NAME, 'First 1'),
+            x509.NameAttribute(x509.OID_PSEUDONYM, 'Guy Incognito 0'),
+            x509.NameAttribute(x509.OID_PSEUDONYM, 'Guy Incognito 1'),
+            x509.NameAttribute(x509.OID_GENERATION_QUALIFIER, '32X'),
+            x509.NameAttribute(x509.OID_GENERATION_QUALIFIER, 'Dreamcast'),
+            x509.NameAttribute(x509.OID_DOMAIN_COMPONENT, 'dc2'),
+            x509.NameAttribute(x509.OID_DOMAIN_COMPONENT, 'dc3'),
+            x509.NameAttribute(x509.OID_EMAIL_ADDRESS, 'test2@test.local'),
+            x509.NameAttribute(x509.OID_EMAIL_ADDRESS, 'test3@test.local'),
+        ]
+
     def test_load_good_ca_cert(self, backend):
         cert = _load_cert(
             os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"),
@@ -301,3 +468,28 @@
         assert repr(oid) == "<ObjectIdentifier(oid=2.5.4.3, name=commonName)>"
         oid = x509.ObjectIdentifier("oid1")
         assert repr(oid) == "<ObjectIdentifier(oid=oid1, name=Unknown OID)>"
+
+
+class TestName(object):
+    def test_eq(self):
+        name1 = x509.Name([
+            x509.NameAttribute(x509.ObjectIdentifier('oid'), 'value1'),
+            x509.NameAttribute(x509.ObjectIdentifier('oid2'), 'value2'),
+        ])
+        name2 = x509.Name([
+            x509.NameAttribute(x509.ObjectIdentifier('oid'), 'value1'),
+            x509.NameAttribute(x509.ObjectIdentifier('oid2'), 'value2'),
+        ])
+        assert name1 == name2
+
+    def test_ne(self):
+        name1 = x509.Name([
+            x509.NameAttribute(x509.ObjectIdentifier('oid'), 'value1'),
+            x509.NameAttribute(x509.ObjectIdentifier('oid2'), 'value2'),
+        ])
+        name2 = x509.Name([
+            x509.NameAttribute(x509.ObjectIdentifier('oid2'), 'value2'),
+            x509.NameAttribute(x509.ObjectIdentifier('oid'), 'value1'),
+        ])
+        assert name1 != name2
+        assert name1 != object()
diff --git a/tests/utils.py b/tests/utils.py
index 37efc58..65c99fb 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -83,8 +83,8 @@
         line = line.strip()
 
         # Blank lines, comments, and section headers are ignored
-        if not line or line.startswith("#") or (line.startswith("[")
-                                                and line.endswith("]")):
+        if not line or line.startswith("#") or (line.startswith("[") and
+                                                line.endswith("]")):
             continue
 
         if line.strip() == "FAIL":