Skip to content

Commit

Permalink
Name: add support for multi-value RDNs
Browse files Browse the repository at this point in the history
Fixes: pyca#3199
  • Loading branch information
frasertweedale committed Oct 19, 2016
1 parent 21ac453 commit cb0055c
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 25 deletions.
12 changes: 12 additions & 0 deletions docs/x509/reference.rst
Expand Up @@ -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)
Expand All @@ -1112,6 +1118,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 set of :class:`NameAttribute`

.. method:: get_attributes_for_oid(oid)

:param oid: An :class:`ObjectIdentifier` instance.
Expand Down
18 changes: 17 additions & 1 deletion src/_cffi_src/openssl/x509name.py
Expand Up @@ -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;
"""

Expand All @@ -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 = """
Expand Down Expand Up @@ -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
"""
6 changes: 3 additions & 3 deletions src/cryptography/hazmat/backends/openssl/backend.py
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
16 changes: 12 additions & 4 deletions src/cryptography/hazmat/backends/openssl/decode_asn1.py
Expand Up @@ -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)

Expand Down Expand Up @@ -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(
Expand Down
22 changes: 13 additions & 9 deletions src/cryptography/hazmat/backends/openssl/encode_asn1.py
Expand Up @@ -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


Expand Down
33 changes: 25 additions & 8 deletions src/cryptography/x509/name.py
Expand Up @@ -4,6 +4,8 @@

from __future__ import absolute_import, division, print_function

import collections

import six

from cryptography import utils
Expand Down Expand Up @@ -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
Expand All @@ -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 "<Name({0!r})>".format(self._attributes)
return "<Name({0!r})>".format(list(self))

0 comments on commit cb0055c

Please sign in to comment.