django_ca.utils - utility functions

Central functions to load CA key and cert as PKey/X509 objects.

django_ca.utils.GENERAL_NAME_RE = <_sre.SRE_Pattern object at 0x19fa1b0>

Regular expression to match general names.

class django_ca.utils.LazyEncoder(skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False, indent=None, separators=None, encoding='utf-8', default=None)[source]

Encoder that also encodes strings translated with ugettext_lazy.

django_ca.utils.NAME_RE = <_sre.SRE_Pattern object at 0x19a5a30>

Regular expression to match RDNs out of a full x509 name.

django_ca.utils.add_colons(s)[source]

Add colons after every second digit.

This function is used in functions to prettify serials.

>>> add_colons('teststring')
'te:st:st:ri:ng'
django_ca.utils.format_general_names(names)[source]

Format a list of general names.

>>> import ipaddress
>>> format_general_names([x509.DNSName('example.com')])
'DNS:example.com'
>>> format_general_names([x509.IPAddress(ipaddress.IPv4Address('127.0.0.1'))])
'IP:127.0.0.1'
>>> format_general_names([x509.DirectoryName(
...     x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, 'example.com')]))])
'dirname:/CN=example.com'
>>> format_general_names([x509.DNSName('example.com'), x509.DNSName('example.net')])
'DNS:example.com, DNS:example.net'
django_ca.utils.format_name(subject)[source]

Convert a subject into the canonical form for distinguished names.

This function does not take care of sorting the subject in any meaningful order.

Examples:

>>> format_name([('CN', 'example.com'), ])
'/CN=example.com'
>>> format_name([('CN', 'example.com'), ('O', "My Organization"), ])
'/CN=example.com/O=My Organization'
django_ca.utils.get_cert_builder(expires, now=None)[source]

Get a basic X509 cert object.

expires : datetime
When this certificate will expire.
now : datetime
The functions notion of “now”, used for testing.
django_ca.utils.get_cert_profile_kwargs(name=None)[source]

Get kwargs suitable for get_cert X509 keyword arguments from the given profile.

django_ca.utils.int_to_hex(i)[source]

Create a hex-representation of the given serial.

>>> int_to_hex(123456789)
'75:BC:D1'
django_ca.utils.is_power2(num)[source]

Return True if num is a power of 2.

>>> is_power2(4)
True
>>> is_power2(3)
False
django_ca.utils.multiline_url_validator(value)[source]

Validate that a TextField contains one valid URL per line.

django_ca.utils.parse_general_name(name)[source]

Parse a general name from user input.

This function will do its best to detect the intended type of any value passed to it:

>>> parse_general_name('example.com')
<DNSName(value=example.com)>
>>> parse_general_name('*.example.com')
<DNSName(value=*.example.com)>
>>> parse_general_name('user@example.com')
<RFC822Name(value=user@example.com)>
>>> parse_general_name('https://example.com')
<UniformResourceIdentifier(value=https://example.com)>
>>> parse_general_name('1.2.3.4')
<IPAddress(value=1.2.3.4)>
>>> parse_general_name('/CN=example.com')  
<DirectoryName(value=<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>,
                                           value='example.com')>])>)>

The default fallback is to assume a DNSName. If this doesn’t work, an exception will be raised:

>>> parse_general_name('foo..bar`*123')
Traceback (most recent call last):
    ...
idna.core.IDNAError: The label b'' is not a valid A-label
>>> parse_general_name('foo bar')
Traceback (most recent call last):
    ...
idna.core.IDNAError: The label b'foo bar' is not a valid A-label

If you want to override detection, you can prefix the name to match GENERAL_NAME_RE:

>>> parse_general_name('email:user@example.com')
<RFC822Name(value=user@example.com)>
>>> parse_general_name('URI:https://example.com')
<UniformResourceIdentifier(value=https://example.com)>
>>> parse_general_name('dirname:/CN=example.com')  
<DirectoryName(value=<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>,
                                           value='example.com')>])>)>

Some more exotic values can only be generated by using this prefix:

>>> parse_general_name('rid:2.5.4.3')
<RegisteredID(value=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>)>
>>> parse_general_name('otherName:2.5.4.3,example.com')
<OtherName(type_id=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=b'example.com')>

If you give a prefixed value, this function is less forgiving of any typos and does not catch any exceptions:

>>> parse_general_name('email:foo@')
Traceback (most recent call last):
    ...
idna.core.IDNAError: Empty domain
django_ca.utils.parse_name(name)[source]

Parses a subject string as used in OpenSSLs command line utilities.

The name is expected to be close to the subject format commonly used by OpenSSL, for example /C=AT/L=Vienna/CN=example.com/emailAddress=user@example.com. The function does its best to be lenient on deviations from the format, object identifiers are case-insensitive (e.g. cn is the same as CN, whitespace at the start and end is stripped and the subject does not have to start with a slash (/).

>>> parse_name('/CN=example.com')
OrderedDict([('CN', 'example.com')])
>>> parse_name('c=AT/l= Vienna/o="ex org"/CN=example.com')
OrderedDict([('C', 'AT'), ('L', 'Vienna'), ('O', 'ex org'), ('CN', 'example.com')])

Dictionary keys are normalized to the values of OID_NAME_MAPPINGS and keys will be sorted based on x509 name specifications regardless of the given order:

>>> parse_name('L="Vienna / District"/EMAILaddress=user@example.com')
OrderedDict([('L', 'Vienna / District'), ('emailAddress', 'user@example.com')])
>>> parse_name('/C=AT/CN=example.com') == parse_name('/CN=example.com/C=AT')
True

Due to the magic of NAME_RE, the function even supports quoting strings and including slashes, so strings like /OU="Org / Org Unit"/CN=example.com will work as expected.

>>> parse_name('L="Vienna / District"/CN=example.com')
OrderedDict([('L', 'Vienna / District'), ('CN', 'example.com')])

But note that it’s still easy to trick this function, if you really want to. The following example is not a valid subject, the location is just bogus, and whatever you were expecting as output, it’s certainly different:

>>> parse_name('L="Vienna " District"/CN=example.com')
OrderedDict([('L', 'Vienna'), ('CN', 'example.com')])

Examples of where this string is used are:

# openssl req -new -key priv.key -out csr -utf8 -batch -sha256 -subj '/C=AT/CN=example.com'
# openssl x509 -in cert.pem -noout -subject -nameopt compat
/C=AT/L=Vienna/CN=example.com
django_ca.utils.sort_subject_dict(d)[source]

Returns an itemized dictionary in the correct order for a x509 subject.

django_ca.utils.x509_name(name)[source]

Parses a subject string into a x509.Name.

If name is a string, parse_name() is used to parse it. A list of tuples or a dict (preferrably an OrderedDict) is also supported.

>>> x509_name('/C=AT/CN=example.com')  
<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.6, name=countryName)>, value='AT')>,
       <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value='example.com')>])>
>>> x509_name([('C', 'AT'), ('CN', 'example.com')])  
<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.6, name=countryName)>, value='AT')>,
       <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value='example.com')>])>
>>> x509_name(OrderedDict([('C', 'AT'), ('CN', 'example.com')]))  
<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.6, name=countryName)>, value='AT')>,
       <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value='example.com')>])>
>>> x509_name(OrderedDict([('C', 'AT'), ('CN', 'example.com')]))  
<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.6, name=countryName)>, value='AT')>,
       <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value='example.com')>])>