aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/x509.rst90
-rw-r--r--setup.py3
-rw-r--r--src/cryptography/x509.py170
-rw-r--r--tests/test_x509_ext.py156
4 files changed, 419 insertions, 0 deletions
diff --git a/docs/x509.rst b/docs/x509.rst
index 19f7c405..b4ff748a 100644
--- a/docs/x509.rst
+++ b/docs/x509.rst
@@ -399,6 +399,80 @@ X.509 CSR (Certificate Signing Request) Object
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 @@ X.509 Extensions
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 d8c0790f..60e6b78e 100644
--- a/setup.py
+++ b/setup.py
@@ -43,6 +43,9 @@ requirements = [
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 a9b6f8bd..9db333c2 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 @@ class SubjectKeyIdentifier(object):
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 acfe761d..b6bb1881 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 @@ class TestKeyUsageExtension(object):
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)>])>"
+ )