diff options
-rw-r--r-- | docs/x509.rst | 15 | ||||
-rw-r--r-- | src/_cffi_src/openssl/asn1.py | 2 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/x509.py | 17 | ||||
-rw-r--r-- | src/cryptography/x509.py | 36 | ||||
-rw-r--r-- | tests/test_x509_ext.py | 72 |
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) |