aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS.rst1
-rw-r--r--CHANGELOG.rst4
-rw-r--r--docs/x509/reference.rst26
-rw-r--r--src/cryptography/x509/extensions.py4
-rw-r--r--src/cryptography/x509/name.py70
-rw-r--r--tests/x509/test_x509.py72
-rw-r--r--tests/x509/test_x509_ext.py41
7 files changed, 146 insertions, 72 deletions
diff --git a/AUTHORS.rst b/AUTHORS.rst
index ed9ac84e..8ba7e0ed 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -41,3 +41,4 @@ PGP key fingerprints are enclosed in parentheses.
* Jeremy Lainé <jeremy.laine@m4x.org>
* Denis Gladkikh <denis@gladkikh.email>
* John Pacific <me@johnpacific.com> (2CF6 0381 B5EF 29B7 D48C 2020 7BB9 71A0 E891 44D9)
+* Marti Raudsepp <marti@juffo.org>
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index eb7a4d89..25c7c8c3 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -23,6 +23,10 @@ Changelog
* Added initial support for parsing PKCS12 files with
:func:`~cryptography.hazmat.primitives.serialization.pkcs12.load_key_and_certificates`.
* Added support for :class:`~cryptography.x509.IssuingDistributionPoint`.
+* Added `rfc4514_string()` method to :class:`~cryptography.x509.Name`,
+ :class:`~cryptography.x509.RelativeDistinguishedName` and
+ :class:`~cryptography.x509.NameAttribute` to format the name or component as
+ a RFC 4514 Distinguished Name string.
.. _v2-4-2:
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index 15891059..ac6bbcdc 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -583,7 +583,7 @@ X.509 CRL (Certificate Revocation List) Object
.. doctest::
>>> crl.issuer
- <Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.6, name=countryName)>, value='US')>, <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value='cryptography.io')>])>
+ <Name(C=US, CN=cryptography.io)>
.. attribute:: next_update
@@ -1246,6 +1246,14 @@ X.509 CSR (Certificate Signing Request) Builder Object
:return bytes: The DER encoded name.
+ .. method:: rfc4514_string()
+
+ .. versionadded:: 2.5
+
+ :return str: Format the given name as a `RFC 4514`_ Distinguished Name
+ string, for example ``CN=mydomain.com, O=My Org, C=US``.
+
+
.. class:: Version
.. versionadded:: 0.7
@@ -1279,6 +1287,13 @@ X.509 CSR (Certificate Signing Request) Builder Object
The value of the attribute.
+ .. method:: rfc4514_string()
+
+ .. versionadded:: 2.5
+
+ :return str: Format the given attribute as a `RFC 4514`_ Distinguished
+ Name string.
+
.. class:: RelativeDistinguishedName(attributes)
@@ -1295,6 +1310,13 @@ X.509 CSR (Certificate Signing Request) Builder Object
:returns: A list of :class:`NameAttribute` instances that match the OID
provided. The list should contain zero or one values.
+ .. method:: rfc4514_string()
+
+ .. versionadded:: 2.5
+
+ :return str: Format the given RDN set as a `RFC 4514`_ Distinguished
+ Name string.
+
.. class:: ObjectIdentifier
@@ -1309,6 +1331,8 @@ X.509 CSR (Certificate Signing Request) Builder Object
The dotted string value of the OID (e.g. ``"2.5.4.3"``)
+.. _`RFC 4514`: https://tools.ietf.org/html/rfc4514
+
.. _general_name_classes:
General Name Classes
diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py
index 12071b66..bdd445d9 100644
--- a/src/cryptography/x509/extensions.py
+++ b/src/cryptography/x509/extensions.py
@@ -541,8 +541,8 @@ class DistributionPoint(object):
def __repr__(self):
return (
"<DistributionPoint(full_name={0.full_name}, relative_name={0.rela"
- "tive_name}, reasons={0.reasons}, crl_issuer={0.crl_is"
- "suer})>".format(self)
+ "tive_name}, reasons={0.reasons}, crl_issuer={0.crl_issuer})>"
+ .format(self)
)
def __eq__(self, other):
diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py
index 5548eda8..470862c2 100644
--- a/src/cryptography/x509/name.py
+++ b/src/cryptography/x509/name.py
@@ -36,6 +36,41 @@ _NAMEOID_DEFAULT_TYPE = {
NameOID.DOMAIN_COMPONENT: _ASN1Type.IA5String,
}
+#: Short attribute names from RFC 4514:
+#: https://tools.ietf.org/html/rfc4514#page-7
+_NAMEOID_TO_NAME = {
+ NameOID.COMMON_NAME: 'CN',
+ NameOID.LOCALITY_NAME: 'L',
+ NameOID.STATE_OR_PROVINCE_NAME: 'ST',
+ NameOID.ORGANIZATION_NAME: 'O',
+ NameOID.ORGANIZATIONAL_UNIT_NAME: 'OU',
+ NameOID.COUNTRY_NAME: 'C',
+ NameOID.STREET_ADDRESS: 'STREET',
+ NameOID.DOMAIN_COMPONENT: 'DC',
+ NameOID.USER_ID: 'UID',
+}
+
+
+def _escape_dn_value(val):
+ """Escape special characters in RFC4514 Distinguished Name value."""
+
+ # See https://tools.ietf.org/html/rfc4514#section-2.4
+ val = val.replace('\\', '\\\\')
+ val = val.replace('"', '\\"')
+ val = val.replace('+', '\\+')
+ val = val.replace(',', '\\,')
+ val = val.replace(';', '\\;')
+ val = val.replace('<', '\\<')
+ val = val.replace('>', '\\>')
+ val = val.replace('\0', '\\00')
+
+ if val[0] in ('#', ' '):
+ val = '\\' + val
+ if val[-1] == ' ':
+ val = val[:-1] + '\\ '
+
+ return val
+
class NameAttribute(object):
def __init__(self, oid, value, _type=_SENTINEL):
@@ -80,6 +115,16 @@ class NameAttribute(object):
oid = utils.read_only_property("_oid")
value = utils.read_only_property("_value")
+ def rfc4514_string(self):
+ """
+ Format as RFC4514 Distinguished Name string.
+
+ Use short attribute name if available, otherwise fall back to OID
+ dotted string.
+ """
+ key = _NAMEOID_TO_NAME.get(self.oid, self.oid.dotted_string)
+ return '%s=%s' % (key, _escape_dn_value(self.value))
+
def __eq__(self, other):
if not isinstance(other, NameAttribute):
return NotImplemented
@@ -117,6 +162,15 @@ class RelativeDistinguishedName(object):
def get_attributes_for_oid(self, oid):
return [i for i in self if i.oid == oid]
+ def rfc4514_string(self):
+ """
+ Format as RFC4514 Distinguished Name string.
+
+ Within each RDN, attributes are joined by '+', although that is rarely
+ used in certificates.
+ """
+ return '+'.join(attr.rfc4514_string() for attr in self._attributes)
+
def __eq__(self, other):
if not isinstance(other, RelativeDistinguishedName):
return NotImplemented
@@ -136,7 +190,7 @@ class RelativeDistinguishedName(object):
return len(self._attributes)
def __repr__(self):
- return "<RelativeDistinguishedName({0!r})>".format(list(self))
+ return "<RelativeDistinguishedName({0})>".format(self.rfc4514_string())
class Name(object):
@@ -154,6 +208,18 @@ class Name(object):
" or a list RelativeDistinguishedName"
)
+ def rfc4514_string(self):
+ """
+ Format as RFC4514 Distinguished Name string.
+ For example 'CN=foobar.com,O=Foo Corp,C=US'
+
+ An X.509 name is a two-level structure: a list of sets of attributes.
+ Each list element is separated by ',' and within each list element, set
+ elements are separated by '+'. The latter is almost never used in
+ real world certificates.
+ """
+ return ', '.join(attr.rfc4514_string() for attr in self._attributes)
+
def get_attributes_for_oid(self, oid):
return [i for i in self if i.oid == oid]
@@ -187,4 +253,4 @@ class Name(object):
return sum(len(rdn) for rdn in self._attributes)
def __repr__(self):
- return "<Name({0!r})>".format(list(self))
+ return "<Name({0})>".format(self.rfc4514_string())
diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py
index 15cfe43d..f4520811 100644
--- a/tests/x509/test_x509.py
+++ b/tests/x509/test_x509.py
@@ -1138,30 +1138,11 @@ class TestRSACertificate(object):
x509.load_pem_x509_certificate,
backend
)
- if not six.PY2:
- assert repr(cert) == (
- "<Certificate(subject=<Name([<NameAttribute(oid=<ObjectIdentif"
- "ier(oid=2.5.4.11, name=organizationalUnitName)>, value='GT487"
- "42965')>, <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.11, "
- "name=organizationalUnitName)>, value='See www.rapidssl.com/re"
- "sources/cps (c)14')>, <NameAttribute(oid=<ObjectIdentifier(oi"
- "d=2.5.4.11, name=organizationalUnitName)>, value='Domain Cont"
- "rol Validated - RapidSSL(R)')>, <NameAttribute(oid=<ObjectIde"
- "ntifier(oid=2.5.4.3, name=commonName)>, value='www.cryptograp"
- "hy.io')>])>, ...)>"
- )
- else:
- assert repr(cert) == (
- "<Certificate(subject=<Name([<NameAttribute(oid=<ObjectIdentif"
- "ier(oid=2.5.4.11, name=organizationalUnitName)>, value=u'GT48"
- "742965')>, <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.11,"
- " name=organizationalUnitName)>, value=u'See www.rapidssl.com/"
- "resources/cps (c)14')>, <NameAttribute(oid=<ObjectIdentifier("
- "oid=2.5.4.11, name=organizationalUnitName)>, value=u'Domain C"
- "ontrol Validated - RapidSSL(R)')>, <NameAttribute(oid=<Object"
- "Identifier(oid=2.5.4.3, name=commonName)>, value=u'www.crypto"
- "graphy.io')>])>, ...)>"
- )
+ assert repr(cert) == (
+ "<Certificate(subject=<Name(OU=GT48742965, OU=See www.rapidssl.com"
+ "/resources/cps (c)14, OU=Domain Control Validated - RapidSSL(R), "
+ "CN=www.cryptography.io)>, ...)>"
+ )
def test_parse_tls_feature_extension(self, backend):
cert = _load_cert(
@@ -3933,6 +3914,18 @@ class TestNameAttribute(object):
"nName)>, value=u'value')>"
)
+ def test_distinugished_name(self):
+ # Escaping
+ na = x509.NameAttribute(NameOID.COMMON_NAME, u'James "Jim" Smith, III')
+ assert na.rfc4514_string() == r'CN=James \"Jim\" Smith\, III'
+ na = x509.NameAttribute(NameOID.USER_ID, u'# escape+,;\0this ')
+ assert na.rfc4514_string() == r'UID=\# escape\+\,\;\00this\ '
+
+ # Nonstandard attribute OID
+ na = x509.NameAttribute(NameOID.EMAIL_ADDRESS, u'somebody@example.com')
+ assert (na.rfc4514_string() ==
+ '1.2.840.113549.1.9.1=somebody@example.com')
+
class TestRelativeDistinguishedName(object):
def test_init_empty(self):
@@ -4120,20 +4113,23 @@ class TestName(object):
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'),
])
- if not six.PY2:
- assert repr(name) == (
- "<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name"
- "=commonName)>, value='cryptography.io')>, <NameAttribute(oid="
- "<ObjectIdentifier(oid=2.5.4.10, name=organizationName)>, valu"
- "e='PyCA')>])>"
- )
- else:
- assert repr(name) == (
- "<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name"
- "=commonName)>, value=u'cryptography.io')>, <NameAttribute(oid"
- "=<ObjectIdentifier(oid=2.5.4.10, name=organizationName)>, val"
- "ue=u'PyCA')>])>"
- )
+ assert repr(name) == "<Name(CN=cryptography.io, O=PyCA)>"
+
+ def test_rfc4514_string(self):
+ n = x509.Name([
+ x509.RelativeDistinguishedName([
+ x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'Sales'),
+ x509.NameAttribute(NameOID.COMMON_NAME, u'J. Smith'),
+ ]),
+ x509.RelativeDistinguishedName([
+ x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'example'),
+ ]),
+ x509.RelativeDistinguishedName([
+ x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'net'),
+ ]),
+ ])
+ assert (n.rfc4514_string() ==
+ 'OU=Sales+CN=J. Smith, DC=example, DC=net')
def test_not_nameattribute(self):
with pytest.raises(TypeError):
diff --git a/tests/x509/test_x509_ext.py b/tests/x509/test_x509_ext.py
index 152db964..6de105fa 100644
--- a/tests/x509/test_x509_ext.py
+++ b/tests/x509/test_x509_ext.py
@@ -1135,16 +1135,14 @@ class TestAuthorityKeyIdentifier(object):
if not six.PY2:
assert repr(aki) == (
"<AuthorityKeyIdentifier(key_identifier=b'digest', authority_"
- "cert_issuer=[<DirectoryName(value=<Name([<NameAttribute(oid="
- "<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value='myC"
- "N')>])>)>], authority_cert_serial_number=1234)>"
+ "cert_issuer=[<DirectoryName(value=<Name(CN=myCN)>)>], author"
+ "ity_cert_serial_number=1234)>"
)
else:
assert repr(aki) == (
- "<AuthorityKeyIdentifier(key_identifier='digest', authority_ce"
- "rt_issuer=[<DirectoryName(value=<Name([<NameAttribute(oid=<Ob"
- "jectIdentifier(oid=2.5.4.3, name=commonName)>, value=u'myCN')"
- ">])>)>], authority_cert_serial_number=1234)>"
+ "<AuthorityKeyIdentifier(key_identifier='digest', authority_"
+ "cert_issuer=[<DirectoryName(value=<Name(CN=myCN)>)>], author"
+ "ity_cert_serial_number=1234)>"
)
def test_eq(self):
@@ -1719,16 +1717,7 @@ class TestDirectoryName(object):
def test_repr(self):
name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'value1')])
gn = x509.DirectoryName(name)
- if not six.PY2:
- assert repr(gn) == (
- "<DirectoryName(value=<Name([<NameAttribute(oid=<ObjectIdentif"
- "ier(oid=2.5.4.3, name=commonName)>, value='value1')>])>)>"
- )
- else:
- assert repr(gn) == (
- "<DirectoryName(value=<Name([<NameAttribute(oid=<ObjectIdentif"
- "ier(oid=2.5.4.3, name=commonName)>, value=u'value1')>])>)>"
- )
+ assert repr(gn) == "<DirectoryName(value=<Name(CN=value1)>)>"
def test_eq(self):
name = x509.Name([
@@ -3656,22 +3645,16 @@ class TestDistributionPoint(object):
if not six.PY2:
assert repr(dp) == (
"<DistributionPoint(full_name=None, relative_name=<RelativeDis"
- "tinguishedName([<NameAttribute(oid=<ObjectIdentifier(oid=2.5."
- "4.3, name=commonName)>, value='myCN')>])>, reasons=frozenset("
- "{<ReasonFlags.ca_compromise: 'cACompromise'>}), crl_issuer=[<"
- "DirectoryName(value=<Name([<NameAttribute(oid=<ObjectIdentifi"
- "er(oid=2.5.4.3, name=commonName)>, value='Important CA')>])>)"
- ">])>"
+ "tinguishedName(CN=myCN)>, reasons=frozenset({<ReasonFlags.ca_"
+ "compromise: 'cACompromise'>}), crl_issuer=[<DirectoryName(val"
+ "ue=<Name(CN=Important CA)>)>])>"
)
else:
assert repr(dp) == (
"<DistributionPoint(full_name=None, relative_name=<RelativeDis"
- "tinguishedName([<NameAttribute(oid=<ObjectIdentifier(oid=2.5."
- "4.3, name=commonName)>, value=u'myCN')>])>, reasons=frozenset"
- "([<ReasonFlags.ca_compromise: 'cACompromise'>]), crl_issuer=["
- "<DirectoryName(value=<Name([<NameAttribute(oid=<ObjectIdentif"
- "ier(oid=2.5.4.3, name=commonName)>, value=u'Important CA')>])"
- ">)>])>"
+ "tinguishedName(CN=myCN)>, reasons=frozenset([<ReasonFlags.ca_"
+ "compromise: 'cACompromise'>]), crl_issuer=[<DirectoryName(val"
+ "ue=<Name(CN=Important CA)>)>])>"
)
def test_hash(self):