Add GeneralName and SubjectAlternativeName classes
diff --git a/docs/x509.rst b/docs/x509.rst
index 19f7c40..b4ff748 100644
--- a/docs/x509.rst
+++ b/docs/x509.rst
@@ -399,6 +399,80 @@
 
         The dotted string value of the OID (e.g. ``"2.5.4.3"``)
 
+.. _general_name_classes:
+
+General Name Classes
+~~~~~~~~~~~~~~~~~~~~
+
+.. class:: GeneralName
+
+    .. versionadded:: 0.9
+
+    This is the generic interface that all the following classes are registered
+    against.
+
+.. class:: RFC822Name
+
+    .. versionadded:: 0.9
+
+    This corresponds to an email address. For example, ``user@example.com``.
+
+    .. attribute:: value
+
+        :type: :term:`text`
+
+.. class:: DNSName
+
+    .. versionadded:: 0.9
+
+    This corresponds to a domain name. For example, ``cryptography.io``.
+
+    .. attribute:: value
+
+        :type: :term:`text`
+
+.. class:: DirectoryName
+
+    .. versionadded:: 0.9
+
+    This corresponds to a directory name.
+
+    .. attribute:: value
+
+        :type: :class:`Name`
+
+.. class:: UniformResourceIdentifier
+
+    .. versionadded:: 0.9
+
+    This corresponds to a uniform resource identifier.  For example,
+    ``https://cryptography.io``.
+
+    .. attribute:: value
+
+        :type: :term:`text`
+
+.. class:: IPAddress
+
+    .. versionadded:: 0.9
+
+    This corresponds to an IP address.
+
+    .. attribute:: value
+
+        :type: :class:`~ipaddress.IPv4Address` or
+            :class:`~ipaddress.IPv6Address`.
+
+.. class:: RegisteredID
+
+    .. versionadded:: 0.9
+
+    This corresponds to a registered ID.
+
+    .. attribute:: value
+
+        :type: :class:`ObjectIdentifier`
+
 X.509 Extensions
 ~~~~~~~~~~~~~~~~
 
@@ -591,6 +665,22 @@
 
         The binary value of the identifier.
 
+.. class:: SubjectAlternativeName
+
+    .. versionadded:: 0.9
+
+    Subject alternative name is an X.509 extension that provides a list of
+    :ref:`general name <general_name_classes>` instances that provide a set
+    of identities for which the certificate is valid. The object is iterable to
+    get every element.
+
+    .. method:: get_values_for_type(type)
+
+        :param type: A :class:`GeneralName` provider. This is one of the
+            :ref:`general name classes <general_name_classes>`.
+
+        :returns: A list of values extracted from the matched general names.
+
 
 Object Identifiers
 ~~~~~~~~~~~~~~~~~~
diff --git a/setup.py b/setup.py
index d8c0790..60e6b78 100644
--- a/setup.py
+++ b/setup.py
@@ -43,6 +43,9 @@
 if sys.version_info < (3, 4):
     requirements.append("enum34")
 
+if sys.version_info < (3, 3):
+    requirements.append("ipaddress")
+
 if platform.python_implementation() != "PyPy":
     requirements.append(CFFI_DEPENDENCY)
 
diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py
index a9b6f8b..9db333c 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -5,6 +5,7 @@
 from __future__ import absolute_import, division, print_function
 
 import abc
+import ipaddress
 from enum import Enum
 
 import six
@@ -387,6 +388,175 @@
         return not self == other
 
 
+@six.add_metaclass(abc.ABCMeta)
+class GeneralName(object):
+    @abc.abstractproperty
+    def value(self):
+        """
+        Return the value of the object
+        """
+
+
+@utils.register_interface(GeneralName)
+class RFC822Name(object):
+    def __init__(self, value):
+        if not isinstance(value, six.text_type):
+            raise TypeError("value must be a unicode string")
+
+        self._value = value
+
+    value = utils.read_only_property("_value")
+
+    def __repr__(self):
+        return "<RFC822Name(value={0})>".format(self.value)
+
+    def __eq__(self, other):
+        if not isinstance(other, RFC822Name):
+            return NotImplemented
+
+        return self.value == other.value
+
+    def __ne__(self, other):
+        return not self == other
+
+
+@utils.register_interface(GeneralName)
+class DNSName(object):
+    def __init__(self, value):
+        if not isinstance(value, six.text_type):
+            raise TypeError("value must be a unicode string")
+
+        self._value = value
+
+    value = utils.read_only_property("_value")
+
+    def __repr__(self):
+        return "<DNSName(value={0})>".format(self.value)
+
+    def __eq__(self, other):
+        if not isinstance(other, DNSName):
+            return NotImplemented
+
+        return self.value == other.value
+
+    def __ne__(self, other):
+        return not self == other
+
+
+@utils.register_interface(GeneralName)
+class UniformResourceIdentifier(object):
+    def __init__(self, value):
+        if not isinstance(value, six.text_type):
+            raise TypeError("value must be a unicode string")
+
+        self._value = value
+
+    value = utils.read_only_property("_value")
+
+    def __repr__(self):
+        return "<UniformResourceIdentifier(value={0})>".format(self.value)
+
+    def __eq__(self, other):
+        if not isinstance(other, UniformResourceIdentifier):
+            return NotImplemented
+
+        return self.value == other.value
+
+    def __ne__(self, other):
+        return not self == other
+
+
+@utils.register_interface(GeneralName)
+class DirectoryName(object):
+    def __init__(self, value):
+        if not isinstance(value, Name):
+            raise TypeError("value must be a Name")
+
+        self._value = value
+
+    value = utils.read_only_property("_value")
+
+    def __repr__(self):
+        return "<DirectoryName(value={0})>".format(self.value)
+
+    def __eq__(self, other):
+        if not isinstance(other, DirectoryName):
+            return NotImplemented
+
+        return self.value == other.value
+
+    def __ne__(self, other):
+        return not self == other
+
+
+@utils.register_interface(GeneralName)
+class RegisteredID(object):
+    def __init__(self, value):
+        if not isinstance(value, ObjectIdentifier):
+            raise TypeError("value must be an ObjectIdentifier")
+
+        self._value = value
+
+    value = utils.read_only_property("_value")
+
+    def __repr__(self):
+        return "<RegisteredID(value={0})>".format(self.value)
+
+    def __eq__(self, other):
+        if not isinstance(other, RegisteredID):
+            return NotImplemented
+
+        return self.value == other.value
+
+    def __ne__(self, other):
+        return not self == other
+
+
+@utils.register_interface(GeneralName)
+class IPAddress(object):
+    def __init__(self, value):
+        if not isinstance(
+            value, (ipaddress.IPv4Address, ipaddress.IPv6Address)
+        ):
+            raise TypeError(
+                "value must be an instance of ipaddress.IPv4Address or "
+                "ipaddress.IPv6Address"
+            )
+
+        self._value = value
+
+    value = utils.read_only_property("_value")
+
+    def __repr__(self):
+        return "<IPAddress(value={0})>".format(self.value)
+
+    def __eq__(self, other):
+        if not isinstance(other, IPAddress):
+            return NotImplemented
+
+        return self.value == other.value
+
+    def __ne__(self, other):
+        return not self == other
+
+
+class SubjectAlternativeName(object):
+    def __init__(self, general_names):
+        self._general_names = general_names
+
+    def __iter__(self):
+        return iter(self._general_names)
+
+    def __len__(self):
+        return len(self._general_names)
+
+    def get_values_for_type(self, type):
+        return [i.value for i in self if isinstance(i, type)]
+
+    def __repr__(self):
+        return "<SubjectAlternativeName({0})>".format(self._general_names)
+
+
 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 acfe761..b6bb188 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -5,6 +5,7 @@
 from __future__ import absolute_import, division, print_function
 
 import binascii
+import ipaddress
 import os
 
 import pytest
@@ -523,3 +524,158 @@
         assert ku.key_agreement is False
         assert ku.key_cert_sign is True
         assert ku.crl_sign is True
+
+
+@pytest.mark.parametrize(
+    "name", [
+        x509.RFC822Name,
+        x509.DNSName,
+        x509.UniformResourceIdentifier
+    ]
+)
+class TestTextGeneralNames(object):
+    def test_not_text(self, name):
+        with pytest.raises(TypeError):
+            name(b"notaunicodestring")
+
+        with pytest.raises(TypeError):
+            name(1.3)
+
+    def test_repr(self, name):
+        gn = name(six.u("string"))
+        assert repr(gn) == "<{0}(value=string)>".format(name.__name__)
+
+    def test_eq(self, name):
+        gn = name(six.u("string"))
+        gn2 = name(six.u("string"))
+        assert gn == gn2
+
+    def test_ne(self, name):
+        gn = name(six.u("string"))
+        gn2 = name(six.u("string2"))
+        assert gn != gn2
+        assert gn != object()
+
+
+class TestDirectoryName(object):
+    def test_not_name(self):
+        with pytest.raises(TypeError):
+            x509.DirectoryName(b"notaname")
+
+        with pytest.raises(TypeError):
+            x509.DirectoryName(1.3)
+
+    def test_repr(self):
+        name = x509.Name([x509.NameAttribute(x509.OID_COMMON_NAME, 'value1')])
+        gn = x509.DirectoryName(x509.Name([name]))
+        assert repr(gn) == (
+            "<DirectoryName(value=<Name([<Name([<NameAttribute(oid=<ObjectIden"
+            "tifier(oid=2.5.4.3, name=commonName)>, value='value1')>])>])>)>"
+        )
+
+    def test_eq(self):
+        name = x509.Name([
+            x509.NameAttribute(x509.ObjectIdentifier('oid'), 'value1')
+        ])
+        name2 = x509.Name([
+            x509.NameAttribute(x509.ObjectIdentifier('oid'), 'value1')
+        ])
+        gn = x509.DirectoryName(x509.Name([name]))
+        gn2 = x509.DirectoryName(x509.Name([name2]))
+        assert gn == gn2
+
+    def test_ne(self):
+        name = x509.Name([
+            x509.NameAttribute(x509.ObjectIdentifier('oid'), 'value1')
+        ])
+        name2 = x509.Name([
+            x509.NameAttribute(x509.ObjectIdentifier('oid'), 'value2')
+        ])
+        gn = x509.DirectoryName(x509.Name([name]))
+        gn2 = x509.DirectoryName(x509.Name([name2]))
+        assert gn != gn2
+        assert gn != object()
+
+
+class TestRegisteredID(object):
+    def test_not_oid(self):
+        with pytest.raises(TypeError):
+            x509.RegisteredID(b"notanoid")
+
+        with pytest.raises(TypeError):
+            x509.RegisteredID(1.3)
+
+    def test_repr(self):
+        gn = x509.RegisteredID(x509.OID_COMMON_NAME)
+        assert repr(gn) == (
+            "<RegisteredID(value=<ObjectIdentifier(oid=2.5.4.3, name=commonNam"
+            "e)>)>"
+        )
+
+    def test_eq(self):
+        gn = x509.RegisteredID(x509.OID_COMMON_NAME)
+        gn2 = x509.RegisteredID(x509.OID_COMMON_NAME)
+        assert gn == gn2
+
+    def test_ne(self):
+        gn = x509.RegisteredID(x509.OID_COMMON_NAME)
+        gn2 = x509.RegisteredID(x509.OID_BASIC_CONSTRAINTS)
+        assert gn != gn2
+        assert gn != object()
+
+
+class TestIPAddress(object):
+    def test_not_ipaddress(self):
+        with pytest.raises(TypeError):
+            x509.IPAddress(b"notanipaddress")
+
+        with pytest.raises(TypeError):
+            x509.IPAddress(1.3)
+
+    def test_repr(self):
+        gn = x509.IPAddress(ipaddress.IPv4Address(six.u("127.0.0.1")))
+        assert repr(gn) == "<IPAddress(value=127.0.0.1)>"
+
+        gn2 = x509.IPAddress(ipaddress.IPv6Address(six.u("ff::")))
+        assert repr(gn2) == "<IPAddress(value=ff::)>"
+
+    def test_eq(self):
+        gn = x509.IPAddress(ipaddress.IPv4Address(six.u("127.0.0.1")))
+        gn2 = x509.IPAddress(ipaddress.IPv4Address(six.u("127.0.0.1")))
+        assert gn == gn2
+
+    def test_ne(self):
+        gn = x509.IPAddress(ipaddress.IPv4Address(six.u("127.0.0.1")))
+        gn2 = x509.IPAddress(ipaddress.IPv4Address(six.u("127.0.0.2")))
+        assert gn != gn2
+        assert gn != object()
+
+
+class TestSubjectAlternativeName(object):
+    def test_get_values_for_type(self):
+        san = x509.SubjectAlternativeName(
+            [x509.DNSName(six.u("cryptography.io"))]
+        )
+        names = san.get_values_for_type(x509.DNSName)
+        assert names == [six.u("cryptography.io")]
+
+    def test_iter_names(self):
+        san = x509.SubjectAlternativeName([
+            x509.DNSName(six.u("cryptography.io")),
+            x509.DNSName(six.u("crypto.local")),
+        ])
+        assert len(san) == 2
+        assert list(san) == [
+            x509.DNSName(six.u("cryptography.io")),
+            x509.DNSName(six.u("crypto.local")),
+        ]
+
+    def test_repr(self):
+        san = x509.SubjectAlternativeName(
+            [
+                x509.DNSName(six.u("cryptography.io"))
+            ]
+        )
+        assert repr(san) == (
+            "<SubjectAlternativeName([<DNSName(value=cryptography.io)>])>"
+        )