aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/x509.rst15
-rw-r--r--src/_cffi_src/openssl/asn1.py2
-rw-r--r--src/cryptography/hazmat/backends/openssl/x509.py17
-rw-r--r--src/cryptography/x509.py36
-rw-r--r--tests/test_x509_ext.py72
5 files changed, 140 insertions, 2 deletions
diff --git a/docs/x509.rst b/docs/x509.rst
index a3cf7e25..bcb6ee66 100644
--- a/docs/x509.rst
+++ b/docs/x509.rst
@@ -699,6 +699,20 @@ General Name Classes
:type: :class:`ObjectIdentifier`
+.. class:: OtherName
+
+ .. versionadded:: 1.0
+
+ This corresponds to an ``otherName.`` An ``otherName`` has a type identifier and a value represented in binary DER format.
+
+ .. attribute:: type_id
+
+ :type: :class:`ObjectIdentifier`
+
+ .. attribute:: value
+
+ :type: `bytes`
+
X.509 Extensions
~~~~~~~~~~~~~~~~
@@ -971,6 +985,7 @@ X.509 Extensions
:ref:`general name classes <general_name_classes>`.
:returns: A list of values extracted from the matched general names.
+ The type of the returned values depends on the :class:`GeneralName`.
.. doctest::
diff --git a/src/_cffi_src/openssl/asn1.py b/src/_cffi_src/openssl/asn1.py
index 5210c7c9..01d6f4c2 100644
--- a/src/_cffi_src/openssl/asn1.py
+++ b/src/_cffi_src/openssl/asn1.py
@@ -155,6 +155,8 @@ int ASN1_UTCTIME_check(ASN1_UTCTIME *);
/* Not a macro, const on openssl 1.0 */
int ASN1_STRING_set_default_mask_asc(char *);
+
+int i2d_ASN1_TYPE(ASN1_TYPE *, unsigned char **);
"""
CUSTOMIZATIONS = """
diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py
index 68104e69..399e6a6e 100644
--- a/src/cryptography/hazmat/backends/openssl/x509.py
+++ b/src/cryptography/hazmat/backends/openssl/x509.py
@@ -55,6 +55,17 @@ def _asn1_string_to_utf8(backend, asn1_string):
return backend._ffi.buffer(buf[0], res)[:].decode('utf8')
+def _asn1_to_der(backend, asn1_type):
+ buf = backend._ffi.new("unsigned char **")
+ res = backend._lib.i2d_ASN1_TYPE(asn1_type, buf)
+ assert res >= 0
+ assert buf[0] != backend._ffi.NULL
+ buf = backend._ffi.gc(
+ buf, lambda buffer: backend._lib.OPENSSL_free(buffer[0])
+ )
+ return backend._ffi.buffer(buf[0], res)[:]
+
+
def _decode_x509_name_entry(backend, x509_name_entry):
obj = backend._lib.X509_NAME_ENTRY_get_object(x509_name_entry)
assert obj != backend._ffi.NULL
@@ -158,8 +169,12 @@ def _decode_general_name(backend, gn):
return x509.RFC822Name(
parts[0] + u"@" + idna.decode(parts[1])
)
+ elif gn.type == backend._lib.GEN_OTHERNAME:
+ type_id = _obj2txt(backend, gn.d.otherName.type_id)
+ value = _asn1_to_der(backend, gn.d.otherName.value)
+ return x509.OtherName(x509.ObjectIdentifier(type_id), value)
else:
- # otherName, x400Address or ediPartyName
+ # x400Address or ediPartyName
raise x509.UnsupportedGeneralNameType(
"{0} is not a supported type".format(
x509._GENERAL_NAMES.get(gn.type, gn.type)
diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py
index f8134958..d9d6db4a 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -1045,6 +1045,34 @@ class IPAddress(object):
return not self == other
+@utils.register_interface(GeneralName)
+class OtherName(object):
+ def __init__(self, type_id, value):
+ if not isinstance(type_id, ObjectIdentifier):
+ raise TypeError("type_id must be an ObjectIdentifier")
+ if not isinstance(value, bytes):
+ raise TypeError("value must be a binary string")
+
+ self._type_id = type_id
+ self._value = value
+
+ type_id = utils.read_only_property("_type_id")
+ value = utils.read_only_property("_value")
+
+ def __repr__(self):
+ return "<OtherName(type_id={0}, value={1!r})>".format(
+ self.type_id, self.value)
+
+ def __eq__(self, other):
+ if not isinstance(other, OtherName):
+ return NotImplemented
+
+ return self.type_id == other.type_id and self.value == other.value
+
+ def __ne__(self, other):
+ return not self == other
+
+
class GeneralNames(object):
def __init__(self, general_names):
if not all(isinstance(x, GeneralName) for x in general_names):
@@ -1062,7 +1090,13 @@ class GeneralNames(object):
return len(self._general_names)
def get_values_for_type(self, type):
- return [i.value for i in self if isinstance(i, type)]
+ # Return the value of each GeneralName, except for OtherName instances
+ # which we return directly because it has two important properties not
+ # just one value.
+ objs = (i for i in self if isinstance(i, type))
+ if type != OtherName:
+ objs = (i.value for i in objs)
+ return list(objs)
def __repr__(self):
return "<GeneralNames({0})>".format(self._general_names)
diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py
index d15d6669..993802b8 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -1147,6 +1147,55 @@ class TestIPAddress(object):
assert gn != object()
+class TestOtherName(object):
+ def test_invalid_args(self):
+ with pytest.raises(TypeError):
+ x509.OtherName(b"notanobjectidentifier", b"derdata")
+
+ with pytest.raises(TypeError):
+ x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), u"notderdata")
+
+ def test_repr(self):
+ gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata")
+ if six.PY3:
+ assert repr(gn) == (
+ "<OtherName(type_id=<ObjectIdentifier(oid=1.2.3.4, "
+ "name=Unknown OID)>, value=b'derdata')>"
+ )
+ else:
+ assert repr(gn) == (
+ "<OtherName(type_id=<ObjectIdentifier(oid=1.2.3.4, "
+ "name=Unknown OID)>, value='derdata')>"
+ )
+
+ gn = x509.OtherName(x509.ObjectIdentifier("2.5.4.65"), b"derdata")
+ if six.PY3:
+ assert repr(gn) == (
+ "<OtherName(type_id=<ObjectIdentifier(oid=2.5.4.65, "
+ "name=pseudonym)>, value=b'derdata')>"
+ )
+ else:
+ assert repr(gn) == (
+ "<OtherName(type_id=<ObjectIdentifier(oid=2.5.4.65, "
+ "name=pseudonym)>, value='derdata')>"
+ )
+
+ def test_eq(self):
+ gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata")
+ gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata")
+ assert gn == gn2
+
+ def test_ne(self):
+ gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata")
+ assert gn != object()
+
+ gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata2")
+ assert gn != gn2
+
+ gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.5"), b"derdata")
+ assert gn != gn2
+
+
class TestGeneralNames(object):
def test_get_values_for_type(self):
gns = x509.GeneralNames(
@@ -1578,6 +1627,29 @@ class TestRSASubjectAlternativeNameExtension(object):
assert 'Invalid rfc822name value' in str(exc.value)
+ def test_other_name(self, backend):
+ cert = _load_cert(
+ os.path.join(
+ "x509", "custom", "san_other_name.pem"
+ ),
+ x509.load_pem_x509_certificate,
+ backend
+ )
+
+ ext = cert.extensions.get_extension_for_oid(
+ x509.OID_SUBJECT_ALTERNATIVE_NAME
+ )
+ assert ext is not None
+ assert ext.critical is False
+
+ expected = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"),
+ b'\x16\x0bHello World')
+ assert len(ext.value) == 1
+ assert list(ext.value)[0] == expected
+
+ othernames = ext.value.get_values_for_type(x509.OtherName)
+ assert othernames == [expected]
+
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)