aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS.rst1
-rw-r--r--CHANGELOG.rst6
-rw-r--r--docs/x509/reference.rst21
-rw-r--r--src/_cffi_src/openssl/x509name.py10
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py6
-rw-r--r--src/cryptography/hazmat/backends/openssl/decode_asn1.py16
-rw-r--r--src/cryptography/hazmat/backends/openssl/encode_asn1.py22
-rw-r--r--src/cryptography/x509/name.py27
-rw-r--r--tests/test_x509.py84
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'),