openssl_csr: allow to specify CRL distribution endpoints (#167)

* Improve error messages for name decoding (not all names appear in SANs).

* Refactor DN parsing, add relative DN parsing code.

* Allow to specify CRL distribution points.

* Add changelog fragment.

* Fix typo.

* Make sure value argument to x509.NameAttribute is a text.

* Update changelogs/fragments/167-openssl_csr-crl-distribution-points.yml

Co-authored-by: Andrew Klychkov <aaklychkov@mail.ru>

* Add example.

Co-authored-by: Andrew Klychkov <aaklychkov@mail.ru>
This commit is contained in:
Felix Fontein
2021-01-26 09:57:40 +01:00
committed by GitHub
parent a7c06b2ec4
commit d8ccebce60
8 changed files with 306 additions and 43 deletions

View File

@@ -152,6 +152,36 @@ def _parse_hex(bytesstr):
return data
DN_COMPONENT_START_RE = re.compile(r'^ *([a-zA-z0-9]+) *= *')
def _parse_dn_component(name, sep=',', sep_str='\\', decode_remainder=True):
m = DN_COMPONENT_START_RE.match(name)
if not m:
raise OpenSSLObjectError('cannot start part in "{0}"'.format(name))
oid = cryptography_name_to_oid(m.group(1))
idx = len(m.group(0))
decoded_name = []
if decode_remainder:
length = len(name)
while idx < length:
i = idx
while i < length and name[i] not in sep_str:
i += 1
if i > idx:
decoded_name.append(name[idx:i])
idx = i
while idx + 1 < length and name[idx] == '\\':
decoded_name.append(name[idx + 1])
idx += 2
if idx < length and name[idx] == sep:
break
else:
decoded_name.append(name[idx:])
idx = len(name)
return x509.NameAttribute(oid, ''.join(decoded_name)), name[idx:]
def _parse_dn(name):
'''
Parse a Distinguished Name.
@@ -166,29 +196,12 @@ def _parse_dn(name):
name = name[1:]
sep_str = sep + '\\'
result = []
start_re = re.compile(r'^ *([a-zA-z0-9]+) *= *')
while name:
m = start_re.match(name)
if not m:
raise OpenSSLObjectError('Error while parsing distinguished name "{0}": cannot start part in "{1}"'.format(original_name, name))
oid = cryptography_name_to_oid(m.group(1))
idx = len(m.group(0))
decoded_name = []
length = len(name)
while idx < length:
i = idx
while i < length and name[i] not in sep_str:
i += 1
if i > idx:
decoded_name.append(name[idx:i])
idx = i
while idx + 1 < length and name[idx] == '\\':
decoded_name.append(name[idx + 1])
idx += 2
if idx < length and name[idx] == sep:
break
result.append(x509.NameAttribute(oid, ''.join(decoded_name)))
name = name[idx:]
try:
attribute, name = _parse_dn_component(name, sep=sep, sep_str=sep_str)
except OpenSSLObjectError as e:
raise OpenSSLObjectError('Error while parsing distinguished name "{0}": {1}'.format(original_name, e))
result.append(attribute)
if name:
if name[0] != sep or len(name) < 2:
raise OpenSSLObjectError('Error while parsing distinguished name "{0}": unexpected end of string'.format(original_name))
@@ -196,9 +209,19 @@ def _parse_dn(name):
return result
def cryptography_get_name(name):
def cryptography_parse_relative_distinguished_name(rdn):
names = []
for part in rdn:
try:
names.append(_parse_dn_component(to_text(part), decode_remainder=False)[0])
except OpenSSLObjectError as e:
raise OpenSSLObjectError('Error while parsing relative distinguished name "{0}": {1}'.format(part, e))
return cryptography.x509.RelativeDistinguishedName(names)
def cryptography_get_name(name, what='Subject Alternative Name'):
'''
Given a name string, returns a cryptography x509.Name object.
Given a name string, returns a cryptography x509.GeneralName object.
Raises an OpenSSLObjectError if the name is unknown or cannot be parsed.
'''
try:
@@ -216,7 +239,7 @@ def cryptography_get_name(name):
if name.startswith('RID:'):
m = re.match(r'^([0-9]+(?:\.[0-9]+)*)$', to_text(name[4:]))
if not m:
raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}"'.format(name))
raise OpenSSLObjectError('Cannot parse {what} "{name}"'.format(name=name, what=what))
return x509.RegisteredID(x509.oid.ObjectIdentifier(m.group(1)))
if name.startswith('otherName:'):
# otherName can either be a raw ASN.1 hex string or in the format that OpenSSL works with.
@@ -228,9 +251,9 @@ def cryptography_get_name(name):
# defailts on the format expected.
name = to_text(name[10:], errors='surrogate_or_strict')
if ';' not in name:
raise OpenSSLObjectError('Cannot parse Subject Alternative Name otherName "{0}", must be in the '
raise OpenSSLObjectError('Cannot parse {what} otherName "{name}", must be in the '
'format "otherName:<OID>;<ASN.1 OpenSSL Encoded String>" or '
'"otherName:<OID>;<hex string>"'.format(name))
'"otherName:<OID>;<hex string>"'.format(name=name, what=what))
oid, value = name.split(';', 1)
b_value = serialize_asn1_string_as_der(value)
@@ -238,10 +261,10 @@ def cryptography_get_name(name):
if name.startswith('dirName:'):
return x509.DirectoryName(x509.Name(_parse_dn(to_text(name[8:]))))
except Exception as e:
raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}": {1}'.format(name, e))
raise OpenSSLObjectError('Cannot parse {what} "{name}": {error}'.format(name=name, what=what, error=e))
if ':' not in name:
raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}" (forgot "DNS:" prefix?)'.format(name))
raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}" (potentially unsupported by cryptography backend)'.format(name))
raise OpenSSLObjectError('Cannot parse {what} "{name}" (forgot "DNS:" prefix?)'.format(name=name, what=what))
raise OpenSSLObjectError('Cannot parse {what} "{name}" (potentially unsupported by cryptography backend)'.format(name=name, what=what))
def _dn_escape_value(value):
@@ -258,7 +281,7 @@ def _dn_escape_value(value):
def cryptography_decode_name(name):
'''
Given a cryptography x509.Name object, returns a string.
Given a cryptography x509.GeneralName object, returns a string.
Raises an OpenSSLObjectError if the name is not supported.
'''
if isinstance(name, x509.DNSName):