diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index bee7c1763ae7..78b41cf21924 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -1102,6 +1102,12 @@ 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 the ordered list of RDNs. + .. doctest:: >>> len(cert.subject) @@ -1112,6 +1118,12 @@ X.509 CSR (Certificate Signing Request) Builder Object , value=u'Test Certificates 2011')> , value=u'Good CA')> + .. attribute:: rdns + + .. versionadded:: 1.6 + + :type: list of set of :class:`NameAttribute` + .. method:: get_attributes_for_oid(oid) :param oid: An :class:`ObjectIdentifier` instance. diff --git a/src/_cffi_src/openssl/x509name.py b/src/_cffi_src/openssl/x509name.py index 30acbdbbb03e..dded2a684810 100644 --- a/src/_cffi_src/openssl/x509name.py +++ b/src/_cffi_src/openssl/x509name.py @@ -17,7 +17,7 @@ TYPES = """ typedef ... Cryptography_STACK_OF_X509_NAME_ENTRY; typedef ... X509_NAME; -typedef ... X509_NAME_ENTRY; +typedef struct X509_name_entry_st X509_NAME_ENTRY; typedef ... Cryptography_STACK_OF_X509_NAME; """ @@ -35,6 +35,7 @@ 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,19 @@ """ CUSTOMIZATIONS = """ +#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER +int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *ne) { + return X509_NAME_ENTRY_set(ne); +} +#else +struct x509_name_entry_st { + ASN1_OBJECT *object; + ASN1_STRING *value; + int set; + int size; +}; +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 7efab2bee4f2..41e7e773d7fb 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -820,7 +820,7 @@ def create_x509_certificate(self, builder, private_key, algorithm): # 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 @@ def create_x509_certificate(self, builder, private_key, algorithm): # 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 @@ def create_x509_crl(self, builder, private_key, algorithm): # 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 af9d3920dee5..693c6102d34e 100644 --- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py @@ -45,9 +45,17 @@ 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) @@ -548,17 +556,17 @@ 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) ) - relative_name = x509.Name(attributes) + relative_name = x509.Name([attributes]) dist_points.append( x509.DistributionPoint( diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py index 284c760cc532..3b784861f179 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 7e55f6e31201..906bcae7c5ee 100644 --- a/src/cryptography/x509/name.py +++ b/src/cryptography/x509/name.py @@ -4,6 +4,8 @@ from __future__ import absolute_import, division, print_function +import collections + import six from cryptography import utils @@ -55,14 +57,27 @@ def __repr__(self): 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 = [set([x]) for x in attributes] + elif all( + isinstance(rdn, collections.Set) + and all(isinstance(ava, NameAttribute) for ava in rdn) + for rdn in attributes + ): + self._attributes = attributes + else: + raise TypeError( + "attributes must be a list of NameAttribute" + " or a list of set of NameAttribute" + ) 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 @@ -75,13 +90,15 @@ def __ne__(self, other): def __hash__(self): # TODO: this is relatively expensive, if this looks like a bottleneck # for you, consider optimizing! - return hash(tuple(self._attributes)) + return hash(tuple(frozenset(rdn) for rdn in 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 "".format(self._attributes) + return "".format(list(self))