diff options
author | Fraser Tweedale <frase@frase.id.au> | 2016-11-12 01:28:56 +1000 |
---|---|---|
committer | Paul Kehrer <paul.l.kehrer@gmail.com> | 2016-11-11 07:28:56 -0800 |
commit | 01ee6f5e391eee76e6cd3062de8fc84851bd06e3 (patch) | |
tree | 3309443a73201bcec03c5bb14df019e49eae798c | |
parent | 44eb89e911db7298a29640c9073c9e2ff4d5f806 (diff) | |
download | cryptography-01ee6f5e391eee76e6cd3062de8fc84851bd06e3.tar.gz cryptography-01ee6f5e391eee76e6cd3062de8fc84851bd06e3.tar.bz2 cryptography-01ee6f5e391eee76e6cd3062de8fc84851bd06e3.zip |
Name: add support for multi-value RDNs (#3202)
Update the Name class to accept and internally store a list of
RelativeDistinguishedName objects. Add the 'rdns' attribute to give
access to the RDNs. Update ASN.1 routines to correctly decode and
encode multi-value RDNs.
Fixes: https://github.com/pyca/cryptography/issues/3199
-rw-r--r-- | AUTHORS.rst | 1 | ||||
-rw-r--r-- | CHANGELOG.rst | 6 | ||||
-rw-r--r-- | docs/x509/reference.rst | 21 | ||||
-rw-r--r-- | src/_cffi_src/openssl/x509name.py | 10 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 6 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/decode_asn1.py | 16 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/encode_asn1.py | 22 | ||||
-rw-r--r-- | src/cryptography/x509/name.py | 27 | ||||
-rw-r--r-- | tests/test_x509.py | 84 |
9 files changed, 144 insertions, 49 deletions
diff --git a/AUTHORS.rst b/AUTHORS.rst index f825c2cd..44dec5b1 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -33,3 +33,4 @@ PGP key fingerprints are enclosed in parentheses. * Maximilian Hils <max@maximilianhils.com> * Simo Sorce <simo@redhat.com> * Thomas Sileo <t@a4.io> +* Fraser Tweedale <ftweedal@redhat.com> diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f241c96b..4a9b88c6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -32,6 +32,12 @@ Changelog :attr:`~cryptography.x509.DistributionPoint.relative_name`. Deprecated use of :class:`~cryptography.x509.Name` as :attr:`~cryptography.x509.DistributionPoint.relative_name`. +* :class:`~cryptography.x509.Name` now accepts an iterable of + :class:`~cryptography.x509.RelativeDistinguishedName`. RDNs can + be accessed via the :attr:`~cryptography.x509.Name.rdns` + attribute. When constructed with an iterable of + :class:`~cryptography.x509.NameAttribute`, each attribute becomes + a single-valued RDN. 1.5.3 - 2016-11-05 ~~~~~~~~~~~~~~~~~~ diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index c5623315..ce479a7c 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -1102,6 +1102,18 @@ X.509 CSR (Certificate Signing Request) Builder Object slash or comma delimited string (e.g. ``/CN=mydomain.com/O=My Org/C=US`` or ``CN=mydomain.com, O=My Org, C=US``). + Technically, a Name is a list of *sets* of attributes, called *Relative + Distinguished Names* or *RDNs*, although multi-valued RDNs are rarely + encountered. The iteration order of values within a multi-valued RDN is + undefined. If you need to handle multi-valued RDNs, the ``rdns`` property + gives access to an ordered list of :class:`RelativeDistinguishedName` + objects. + + A Name can be initialized with an iterable of :class:`NameAttribute` (the + common case where each RDN has a single attribute) or an iterable of + :class:`RelativeDistinguishedName` objects (in the rare case of + multi-valued RDNs). + .. doctest:: >>> len(cert.subject) @@ -1112,6 +1124,12 @@ X.509 CSR (Certificate Signing Request) Builder Object <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')> + .. attribute:: rdns + + .. versionadded:: 1.6 + + :type: list of :class:`RelativeDistinguishedName` + .. method:: get_attributes_for_oid(oid) :param oid: An :class:`ObjectIdentifier` instance. @@ -1142,7 +1160,8 @@ X.509 CSR (Certificate Signing Request) Builder Object .. versionadded:: 0.8 - An X.509 name consists of a list of NameAttribute instances. + An X.509 name consists of a list of :class:`RelativeDistinguishedName` + instances, which consist of a set of :class:`NameAttribute` instances. .. attribute:: oid diff --git a/src/_cffi_src/openssl/x509name.py b/src/_cffi_src/openssl/x509name.py index 30acbdbb..0554a024 100644 --- a/src/_cffi_src/openssl/x509name.py +++ b/src/_cffi_src/openssl/x509name.py @@ -35,6 +35,7 @@ void X509_NAME_ENTRY_free(X509_NAME_ENTRY *); int X509_NAME_get_index_by_NID(X509_NAME *, int, int); int X509_NAME_cmp(const X509_NAME *, const X509_NAME *); X509_NAME *X509_NAME_dup(X509_NAME *); +int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *); """ MACROS = """ @@ -76,4 +77,13 @@ Cryptography_STACK_OF_X509_NAME_ENTRY *sk_X509_NAME_ENTRY_dup( """ CUSTOMIZATIONS = """ +#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER && !defined(LIBRESSL_VERSION_NUMBER) +int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *ne) { + return X509_NAME_ENTRY_set(ne); +} +#else +int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *ne) { + return ne->set; +} +#endif """ diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 7efab2be..41e7e773 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -820,7 +820,7 @@ class Backend(object): # Set the subject's name. res = self._lib.X509_set_subject_name( - x509_cert, _encode_name_gc(self, list(builder._subject_name)) + x509_cert, _encode_name_gc(self, builder._subject_name) ) self.openssl_assert(res == 1) @@ -860,7 +860,7 @@ class Backend(object): # Set the issuer name. res = self._lib.X509_set_issuer_name( - x509_cert, _encode_name_gc(self, list(builder._issuer_name)) + x509_cert, _encode_name_gc(self, builder._issuer_name) ) self.openssl_assert(res == 1) @@ -911,7 +911,7 @@ class Backend(object): # Set the issuer name. res = self._lib.X509_CRL_set_issuer_name( - x509_crl, _encode_name_gc(self, list(builder._issuer_name)) + x509_crl, _encode_name_gc(self, builder._issuer_name) ) self.openssl_assert(res == 1) diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py index f8e8c95c..2cbc349e 100644 --- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py @@ -45,11 +45,19 @@ def _decode_x509_name_entry(backend, x509_name_entry): def _decode_x509_name(backend, x509_name): count = backend._lib.X509_NAME_entry_count(x509_name) attributes = [] + prev_set_id = -1 for x in range(count): entry = backend._lib.X509_NAME_get_entry(x509_name, x) - attributes.append(_decode_x509_name_entry(backend, entry)) + attribute = _decode_x509_name_entry(backend, entry) + set_id = backend._lib.Cryptography_X509_NAME_ENTRY_set(entry) + if set_id != prev_set_id: + attributes.append(set([attribute])) + else: + # is in the same RDN a previous entry + attributes[-1].add(attribute) + prev_set_id = set_id - return x509.Name(attributes) + return x509.Name(x509.RelativeDistinguishedName(rdn) for rdn in attributes) def _decode_general_names(backend, gns): @@ -552,13 +560,13 @@ def _decode_crl_distribution_points(backend, cdps): else: rns = cdp.distpoint.name.relativename rnum = backend._lib.sk_X509_NAME_ENTRY_num(rns) - attributes = [] + attributes = set() for i in range(rnum): rn = backend._lib.sk_X509_NAME_ENTRY_value( rns, i ) backend.openssl_assert(rn != backend._ffi.NULL) - attributes.append( + attributes.add( _decode_x509_name_entry(backend, rn) ) diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py index 284c760c..3b784861 100644 --- a/src/cryptography/hazmat/backends/openssl/encode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/encode_asn1.py @@ -79,19 +79,23 @@ def _encode_inhibit_any_policy(backend, inhibit_any_policy): return _encode_asn1_int_gc(backend, inhibit_any_policy.skip_certs) -def _encode_name(backend, attributes): +def _encode_name(backend, name): """ The X509_NAME created will not be gc'd. Use _encode_name_gc if needed. """ subject = backend._lib.X509_NAME_new() - for attribute in attributes: - name_entry = _encode_name_entry(backend, attribute) - # X509_NAME_add_entry dups the object so we need to gc this copy - name_entry = backend._ffi.gc( - name_entry, backend._lib.X509_NAME_ENTRY_free - ) - res = backend._lib.X509_NAME_add_entry(subject, name_entry, -1, 0) - backend.openssl_assert(res == 1) + for rdn in name.rdns: + set_flag = 0 # indicate whether to add to last RDN or create new RDN + for attribute in rdn: + name_entry = _encode_name_entry(backend, attribute) + # X509_NAME_add_entry dups the object so we need to gc this copy + name_entry = backend._ffi.gc( + name_entry, backend._lib.X509_NAME_ENTRY_free + ) + res = backend._lib.X509_NAME_add_entry( + subject, name_entry, -1, set_flag) + backend.openssl_assert(res == 1) + set_flag = -1 return subject diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py index 7dc80646..fedfd78f 100644 --- a/src/cryptography/x509/name.py +++ b/src/cryptography/x509/name.py @@ -90,14 +90,25 @@ class RelativeDistinguishedName(object): class Name(object): def __init__(self, attributes): attributes = list(attributes) - if not all(isinstance(x, NameAttribute) for x in attributes): - raise TypeError("attributes must be a list of NameAttribute") - - self._attributes = attributes + if all(isinstance(x, NameAttribute) for x in attributes): + self._attributes = [ + RelativeDistinguishedName([x]) for x in attributes + ] + elif all(isinstance(x, RelativeDistinguishedName) for x in attributes): + self._attributes = attributes + else: + raise TypeError( + "attributes must be a list of NameAttribute" + " or a list RelativeDistinguishedName" + ) def get_attributes_for_oid(self, oid): return [i for i in self if i.oid == oid] + @property + def rdns(self): + return self._attributes + def __eq__(self, other): if not isinstance(other, Name): return NotImplemented @@ -113,10 +124,12 @@ class Name(object): return hash(tuple(self._attributes)) def __iter__(self): - return iter(self._attributes) + for rdn in self._attributes: + for ava in rdn: + yield ava def __len__(self): - return len(self._attributes) + return sum(len(rdn) for rdn in self._attributes) def __repr__(self): - return "<Name({0!r})>".format(self._attributes) + return "<Name({0!r})>".format(list(self)) diff --git a/tests/test_x509.py b/tests/test_x509.py index 67df30c0..6a999f41 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -2641,6 +2641,30 @@ class TestCertificateSigningRequestBuilder(object): ] @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_build_ca_request_with_multivalue_rdns(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + subject = x509.Name([ + x509.RelativeDistinguishedName([ + x509.NameAttribute(NameOID.TITLE, u'Test'), + x509.NameAttribute(NameOID.COMMON_NAME, u'Multivalue'), + x509.NameAttribute(NameOID.SURNAME, u'RDNs'), + ]), + x509.RelativeDistinguishedName([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA') + ]), + ]) + + request = x509.CertificateSigningRequestBuilder().subject_name( + subject + ).sign(private_key, hashes.SHA1(), backend) + + loaded_request = x509.load_pem_x509_csr( + request.public_bytes(encoding=serialization.Encoding.PEM), backend + ) + assert isinstance(loaded_request.subject, x509.Name) + assert loaded_request.subject == subject + + @pytest.mark.requires_backend_interface(interface=RSABackend) def test_build_nonca_request_with_rsa(self, backend): private_key = RSA_KEY_2048.private_key(backend) @@ -3724,44 +3748,43 @@ class TestObjectIdentifier(object): class TestName(object): def test_eq(self): - name1 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), - x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), - ]) + ava1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ava2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + name1 = x509.Name([ava1, ava2]) name2 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), - x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), + x509.RelativeDistinguishedName([ava1]), + x509.RelativeDistinguishedName([ava2]), ]) + name3 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) + name4 = x509.Name([x509.RelativeDistinguishedName([ava2, ava1])]) assert name1 == name2 + assert name3 == name4 def test_ne(self): - name1 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), - x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), - ]) - name2 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), - x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), - ]) + ava1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ava2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + name1 = x509.Name([ava1, ava2]) + name2 = x509.Name([ava2, ava1]) + name3 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) assert name1 != name2 + assert name1 != name3 assert name1 != object() def test_hash(self): - name1 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), - x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), - ]) + ava1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ava2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + name1 = x509.Name([ava1, ava2]) name2 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), - x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), + x509.RelativeDistinguishedName([ava1]), + x509.RelativeDistinguishedName([ava2]), ]) - name3 = x509.Name([ - x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), - x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), - ]) - + name3 = x509.Name([ava2, ava1]) + name4 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) + name5 = x509.Name([x509.RelativeDistinguishedName([ava2, ava1])]) assert hash(name1) == hash(name2) assert hash(name1) != hash(name3) + assert hash(name1) != hash(name4) + assert hash(name4) == hash(name5) def test_iter_input(self): attrs = [ @@ -3771,6 +3794,17 @@ class TestName(object): assert list(name) == attrs assert list(name) == attrs + def test_rdns(self): + rdn1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + rdn2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + name1 = x509.Name([rdn1, rdn2]) + assert name1.rdns == [ + x509.RelativeDistinguishedName([rdn1]), + x509.RelativeDistinguishedName([rdn2]), + ] + name2 = x509.Name([x509.RelativeDistinguishedName([rdn1, rdn2])]) + assert name2.rdns == [x509.RelativeDistinguishedName([rdn1, rdn2])] + def test_repr(self): name = x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), |