django_ca.utils - utility functions

Reusable utility functions used throughout django-ca.

django_ca.utils.ELLIPTIC_CURVE_NAMES = {'brainpoolP256r1': <class 'cryptography.hazmat.primitives.asymmetric.ec.BrainpoolP256R1'>, 'brainpoolP384r1': <class 'cryptography.hazmat.primitives.asymmetric.ec.BrainpoolP384R1'>, 'brainpoolP512r1': <class 'cryptography.hazmat.primitives.asymmetric.ec.BrainpoolP512R1'>, 'secp192r1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECP192R1'>, 'secp224r1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECP224R1'>, 'secp256k1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECP256K1'>, 'secp256r1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECP256R1'>, 'secp384r1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECP384R1'>, 'secp521r1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECP521R1'>, 'sect163k1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECT163K1'>, 'sect163r2': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECT163R2'>, 'sect233k1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECT233K1'>, 'sect233r1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECT233R1'>, 'sect283k1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECT283K1'>, 'sect283r1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECT283R1'>, 'sect409k1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECT409K1'>, 'sect409r1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECT409R1'>, 'sect571k1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECT571K1'>, 'sect571r1': <class 'cryptography.hazmat.primitives.asymmetric.ec.SECT571R1'>}

Mapping of canonical elliptic curve names to the implementing classes

django_ca.utils.GENERAL_NAME_RE = re.compile('^(email|URI|IP|DNS|RID|dirName|otherName):(.*)', re.IGNORECASE)

Regular expression to match general names.

class django_ca.utils.GeneralNameList(iterable=None)[source]

List that holds GeneralName instances and parses str when added.

A GeneralNameList is a list subclass that will always only hold GeneralName instances, but any str passed to it will be passed to parse_general_name():

>>> from cryptography import x509
>>> l = GeneralNameList(['example.com'])
>>> l += ['DNS:example.net', x509.DNSName('example.org')]
>>> print(l)
<GeneralNameList: ['DNS:example.com', 'DNS:example.net', 'DNS:example.org']>
>>> 'example.com' in l, 'DNS:example.com' in l, x509.DNSName('example.com') in l
(True, True, True)
>>> l == ['example.com', 'example.net', 'example.org']
True
>>> l == [x509.DNSName('example.com'), 'example.net', 'DNS:example.org']
True
append(value)[source]

Equivalent to list.append().

count(value)[source]

Equivalent to list.count().

extend(iterable)[source]

Equivalent to list.extend().

index(value, start=0, stop=9223372036854775807)[source]

Equivalent to list.index().

insert(index, value)[source]

Equivalent to list.insert().

remove(value)[source]

Equivalent to list.remove().

serialize()[source]

Generate a list of formatted names.

django_ca.utils.HASH_ALGORITHM_NAMES = {'md5': <class 'cryptography.hazmat.primitives.hashes.MD5'>, 'sha1': <class 'cryptography.hazmat.primitives.hashes.SHA1'>, 'sha224': <class 'cryptography.hazmat.primitives.hashes.SHA224'>, 'sha256': <class 'cryptography.hazmat.primitives.hashes.SHA256'>, 'sha3-224': <class 'cryptography.hazmat.primitives.hashes.SHA3_224'>, 'sha3-256': <class 'cryptography.hazmat.primitives.hashes.SHA3_256'>, 'sha3-384': <class 'cryptography.hazmat.primitives.hashes.SHA3_384'>, 'sha3-512': <class 'cryptography.hazmat.primitives.hashes.SHA3_512'>, 'sha384': <class 'cryptography.hazmat.primitives.hashes.SHA384'>, 'sha512': <class 'cryptography.hazmat.primitives.hashes.SHA512'>, 'sha512-224': <class 'cryptography.hazmat.primitives.hashes.SHA512_224'>, 'sha512-256': <class 'cryptography.hazmat.primitives.hashes.SHA512_256'>, 'sm3': <class 'cryptography.hazmat.primitives.hashes.SM3'>}

Mapping of canonical hash algorithm names to the implementing classes

django_ca.utils.MULTIPLE_OIDS = (<ObjectIdentifier(oid=0.9.2342.19200300.100.1.25, name=domainComponent)>, <ObjectIdentifier(oid=2.5.4.11, name=organizationalUnitName)>, <ObjectIdentifier(oid=2.5.4.9, name=streetAddress)>)

List of OIDs that may occur multiple times in a subject.

django_ca.utils.OID_NAME_MAPPINGS = {<ObjectIdentifier(oid=2.5.4.15, name=businessCategory)>: 'businessCategory', <ObjectIdentifier(oid=2.5.4.3, name=commonName)>: 'CN', <ObjectIdentifier(oid=2.5.4.6, name=countryName)>: 'C', <ObjectIdentifier(oid=2.5.4.46, name=dnQualifier)>: 'dnQualifier', <ObjectIdentifier(oid=0.9.2342.19200300.100.1.25, name=domainComponent)>: 'DC', <ObjectIdentifier(oid=1.2.840.113549.1.9.1, name=emailAddress)>: 'emailAddress', <ObjectIdentifier(oid=2.5.4.44, name=generationQualifier)>: 'generationQualifier', <ObjectIdentifier(oid=2.5.4.42, name=givenName)>: 'givenName', <ObjectIdentifier(oid=1.2.643.3.131.1.1, name=INN)>: 'inn', <ObjectIdentifier(oid=1.3.6.1.4.1.311.60.2.1.3, name=jurisdictionCountryName)>: 'jurisdictionCountryName', <ObjectIdentifier(oid=1.3.6.1.4.1.311.60.2.1.1, name=jurisdictionLocalityName)>: 'jurisdictionLocalityName', <ObjectIdentifier(oid=1.3.6.1.4.1.311.60.2.1.2, name=jurisdictionStateOrProvinceName)>: 'jurisdictionStateOrProvinceName', <ObjectIdentifier(oid=2.5.4.7, name=localityName)>: 'L', <ObjectIdentifier(oid=1.2.643.100.1, name=OGRN)>: 'ogrn', <ObjectIdentifier(oid=2.5.4.11, name=organizationalUnitName)>: 'OU', <ObjectIdentifier(oid=2.5.4.10, name=organizationName)>: 'O', <ObjectIdentifier(oid=2.5.4.16, name=postalAddress)>: 'postalAddress', <ObjectIdentifier(oid=2.5.4.17, name=postalCode)>: 'postalCode', <ObjectIdentifier(oid=2.5.4.65, name=pseudonym)>: 'pseudonym', <ObjectIdentifier(oid=2.5.4.5, name=serialNumber)>: 'serialNumber', <ObjectIdentifier(oid=1.2.643.100.3, name=SNILS)>: 'snils', <ObjectIdentifier(oid=2.5.4.8, name=stateOrProvinceName)>: 'ST', <ObjectIdentifier(oid=2.5.4.9, name=streetAddress)>: 'street', <ObjectIdentifier(oid=2.5.4.4, name=surname)>: 'sn', <ObjectIdentifier(oid=2.5.4.12, name=title)>: 'title', <ObjectIdentifier(oid=1.2.840.113549.1.9.2, name=unstructuredName)>: 'unstructuredName', <ObjectIdentifier(oid=0.9.2342.19200300.100.1.1, name=userID)>: 'uid', <ObjectIdentifier(oid=2.5.4.45, name=x500UniqueIdentifier)>: 'x500UniqueIdentifier'}

Map OID objects to IDs used in subject strings

django_ca.utils.SERIAL_RE = re.compile('^([0-9A-F][0-9A-F]:?)+[0-9A-F][0-9A-F]?$')

Regular expression matching certificate serials as hex

django_ca.utils.add_colons(value, pad='0')[source]

Add colons after every second digit.

This function is used in functions to prettify serials.

>>> add_colons('teststring')
'te:st:st:ri:ng'
Parameters
sstr

The string to add colons to

padstr, optional

If not an empty string, pad the string so that the last element always has two characters. The default is "0".

django_ca.utils.bytes_to_hex(value)[source]

Convert a bytes array to hex.

>>> bytes_to_hex(b'test')
'74:65:73:74'
django_ca.utils.check_name(name)[source]

Check if name is a valid x509 Name.

This method raises ValueError if the CommonName contains an empty value or if any attribute not in MULTIPLE_OIDS occurs multiple times.

The method returns the name unchanged for convenience.

django_ca.utils.encode_dns(name)[source]

IDNA encoding for domains.

Examples:

>>> encode_dns('example.com')
'example.com'
>>> encode_dns('exämple.com')
'xn--exmple-cua.com'
>>> encode_dns('.exämple.com')
'.xn--exmple-cua.com'
>>> encode_dns('*.exämple.com')
'*.xn--exmple-cua.com'
django_ca.utils.encode_url(url)[source]

IDNA encoding for domains in URLs.

Examples:

>>> encode_url('https://example.com')
'https://example.com'
>>> encode_url('https://exämple.com/foobar')
'https://xn--exmple-cua.com/foobar'
>>> encode_url('https://exämple.com:8000/foobar')
'https://xn--exmple-cua.com:8000/foobar'
django_ca.utils.format_general_name(name)[source]

Format a single general name.

>>> import ipaddress
>>> format_general_name(x509.DNSName('example.com'))
'DNS:example.com'
>>> format_general_name(x509.IPAddress(ipaddress.IPv4Address('127.0.0.1')))
'IP:127.0.0.1'
django_ca.utils.format_name(subject)[source]

Convert a x509 name or relative name into the canonical form for distinguished names.

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

Deprecated since version 1.20.0: Passing a list of two-tuples is deprecated as of 1.20.0 and the functionality will be removed in django_ca==1.22.

Examples:

>>> format_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, 'example.com')]))
'/CN=example.com'
django_ca.utils.format_relative_name(name)[source]

Convert a relative name (RDN) into a canonical form.

Deprecated since version 1.20.0: This function is deprecated in favor of format_name(), which provides identical functionality. This function will be removed in django_ca==1.22.

django_ca.utils.generate_private_key(key_size, key_type, ecc_curve)[source]

Generate a private key.

This function assumes that you called validate_key_parameters() on the input values and does not do any sanity checks on its own.

Parameters
key_sizeint

The size of the private key. The value is ignored if key_type is not "DSA" or "RSA".

key_type{‘RSA’, ‘DSA’, ‘ECC’, ‘EdDSA’, ‘Ed448’}

The type of the private key.

ecc_curveEllipticCurve

An elliptic curve to use for ECC keys. This parameter is ignored if key_type is not "ECC". Defaults to the CA_DEFAULT_ECC_CURVE.

Returns
key

A private key of the appropriate type.

django_ca.utils.get_cert_builder(expires, serial=None)[source]

Get a basic X.509 certificate builder object.

Parameters
expiresdatetime

Serial number to set for this certificate. Use random_serial_number() to generate such a value. By default, a value will be generated.

django_ca.utils.get_crl_cache_key(serial, algorithm=<cryptography.hazmat.primitives.hashes.SHA512 object>, encoding=Encoding.DER, scope=None)[source]

Get the cache key for a CRL with the given parameters.

django_ca.utils.hex_to_bytes(value)[source]

Convert a hex number to bytes.

This should be the inverse of bytes_to_hex().

>>> hex_to_bytes('74:65:73:74')
b'test'
django_ca.utils.int_to_hex(i)[source]

Create a hex-representation of the given serial.

>>> int_to_hex(12345678)
'BC614E'
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.make_naive(timestamp)[source]

Like make_naive(), but does not return an error if already naive.

django_ca.utils.multiline_url_validator(value)[source]

Validate that a TextField contains one valid URL per line.

django_ca.utils.parse_encoding(value=None)[source]

Parse a value to a valid encoding.

This function accepts either a member of Encoding or a string describing a member. If no value is passed, it will assume PEM as a default value. Note that "ASN1" is treated as an alias for "DER".

>>> parse_encoding()
<Encoding.PEM: 'PEM'>
>>> parse_encoding('DER')
<Encoding.DER: 'DER'>
>>> parse_encoding(Encoding.PEM)
<Encoding.PEM: 'PEM'>
django_ca.utils.parse_expires(expires=None)[source]

Parse a value specifying an expiry into a concrete datetime.

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('.example.com')  # Syntax used e.g. for NameConstraints: All levels of subdomains
<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('fd00::1')
<IPAddress(value=fd00::1)>
>>> parse_general_name('/CN=example.com')
<DirectoryName(value=<Name(CN=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):
    ...
ValueError: Could not parse name: foo..bar`*123

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(CN=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;UTF8:example.com')
<OtherName(type_id=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=b'\x0c\x0bexample.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@bar com')
Traceback (most recent call last):
    ...
ValueError: Invalid domain: bar com
django_ca.utils.parse_hash_algorithm(value=None)[source]

Parse a hash algorithm value.

The most common use case is to pass a str naming a class in hashes.

For convenience, passing None will return the value of CA_DIGEST_ALGORITHM, and passing an HashAlgorithm will return that instance unchanged.

Example usage:

>>> parse_hash_algorithm()  
<cryptography.hazmat.primitives.hashes.SHA512 object at ...>
>>> parse_hash_algorithm('SHA512')  
<cryptography.hazmat.primitives.hashes.SHA512 object at ...>
>>> parse_hash_algorithm(' SHA512 ')  
<cryptography.hazmat.primitives.hashes.SHA512 object at ...>
>>> parse_hash_algorithm(hashes.SHA512)  
<cryptography.hazmat.primitives.hashes.SHA512 object at ...>
>>> parse_hash_algorithm(hashes.SHA512())  
<cryptography.hazmat.primitives.hashes.SHA512 object at ...>
>>> parse_hash_algorithm('Wrong')  
Traceback (most recent call last):
    ...
ValueError: Unknown hash algorithm: Wrong
>>> parse_hash_algorithm(object())  
Traceback (most recent call last):
    ...
ValueError: Unknown type passed: object
Parameters
valuestr or HashAlgorithm, optional

The value to parse, the function description on how possible values are used.

Returns
algorithm

A HashAlgorithm instance.

Raises
ValueError

If an unknown object is passed or if value does not name a known algorithm.

django_ca.utils.parse_key_curve(value=None)[source]

Parse an elliptic curve value.

This function uses a value identifying an elliptic curve to return an EllipticCurve instance. The name must match a class name of one of the classes named under “Elliptic Curves” in Elliptic curve cryptography.

For convenience, passing None will return the value of CA_DEFAULT_ECC_CURVE, and passing an EllipticCurve will return that instance unchanged.

Example usage:

>>> parse_key_curve('SECP256R1')  
<cryptography.hazmat.primitives.asymmetric.ec.SECP256R1 object at ...>
>>> parse_key_curve('SECP384R1')  
<cryptography.hazmat.primitives.asymmetric.ec.SECP384R1 object at ...>
>>> parse_key_curve(ec.SECP256R1())  
<cryptography.hazmat.primitives.asymmetric.ec.SECP256R1 object at ...>
>>> parse_key_curve()  
<cryptography.hazmat.primitives.asymmetric.ec.SECP256R1 object at ...>
Parameters
valuestr, otional

The name of the curve or None to return the default curve.

Returns
curve

An EllipticCurve instance.

Raises
ValueError

If the named curve is not supported.

django_ca.utils.parse_name(name)[source]

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

Deprecated since version 1.20.0: This function has been renamed to parse_name_x509(). The old name will be removed in django_ca==1.22.

django_ca.utils.parse_name_x509(name)[source]

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

Changed in version 1.20.0: This function no longer returns the subject in pseudo-sorted order.

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_x509('/CN=example.com')
[<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value='example.com')>]
>>> parse_name_x509('c=AT/l= Vienna/o="quoting/works"/CN=example.com')  
[<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.6, name=countryName)>, value='AT')>,
 <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.7, name=localityName)>, value='Vienna')>,
 <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.10, name=organizationName)>, value='quoting/works')>,
 <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value='example.com')>]

The function also handles whitespace, quoting and slashes correctly:

>>> parse_name_x509('L="Vienna / District"/CN=example.com')  
[<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.7, name=localityName)>, value='Vienna / District')>,
 <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value='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.read_file(path)[source]

Read the file from the given path.

If path is an absolute path, reads a file from the local file system. For relative paths, read the file using the storage backend configured using CA_FILE_STORAGE.

django_ca.utils.sanitize_serial(value)[source]

Sanitize a serial provided by user/untrusted input.

This function is intended to be used to get a serial as used internally by django-ca from untrusted user input. Internally, serials are stored in upper case and without : and leading zeros, but user output adds at least :.

Examples:

>>> sanitize_serial('01:aB')
'1AB'
django_ca.utils.shlex_split(val, sep)[source]

Split a character on the given set of characters.

Deprecated since version 1.20.0: This function has been renamed to split_str(). The old name will be removed in django_ca==1.22.

django_ca.utils.sort_name(name)[source]

Returns the subject in the correct order for a x509 subject.

django_ca.utils.split_str(val, sep)[source]

Split a character on the given set of characters.

Example:

>>> list(split_str('foo,bar', ', '))
['foo', 'bar']
>>> list(split_str('foo\\,bar1', ','))  # escape a separator
['foo,bar1']
>>> list(split_str('foo,"bar,bla"', ','))  # do not split on quoted separator
['foo', 'bar,bla']

Note that sep gives one or more separator characters, not a single separator string:

>>> list(split_str("foo,bar bla", ", "))
['foo', 'bar', 'bla']

Unlike str.split(), separators at the start/end of a string are simply ignored, as are multiple subsequent separators:

>>> list(split_str("/C=AT//ST=Vienna///OU=something//CN=example.com/", "/"))
['C=AT', 'ST=Vienna', 'OU=something', 'CN=example.com']
Parameters
valstr

The string to split.

sep: str

String of characters that are considered separators.

django_ca.utils.validate_email(addr)[source]

Validate an email address.

This function raises ValueError if the email address is not valid.

>>> validate_email('foo@bar.com')
'foo@bar.com'
>>> validate_email('foo@bar com')
Traceback (most recent call last):
    ...
ValueError: Invalid domain: bar com
django_ca.utils.validate_hostname(hostname, allow_port=False)[source]

Validate a hostname, optionally with a given port.

>>> validate_hostname('example.com')
'example.com'
>>> validate_hostname('example.com:8000', allow_port=True)
'example.com:8000'
Parameters
hostnamestr

The hostname to validate.

allow_portbool, optional

If True, the hostname can also contain an optional port number, e.g. “example.com:8000”.

Raises
ValueError

If hostname or port are not valid.

django_ca.utils.validate_key_parameters(key_size=None, key_type='RSA', ecc_curve=None)[source]

Validate parameters for private key generation and return sanitized values.

This function can be used to fail early if invalid parameters are passed, before the private key is generated.

>>> validate_key_parameters(4096, "RSA", None)
>>> validate_key_parameters(4096, "Ed448", None)  # Ed448 does not care about the key size
>>> validate_key_parameters(4000, 'RSA', None)
Traceback (most recent call last):
    ...
ValueError: 4000: Key size must be a power of two
django_ca.utils.x509_name(name)[source]

Parses a string into a x509.Name.

Deprecated since version 1.20.0: Passing a list of two-tuples is deprecated as of 1.20.0 and the functionality will be removed in django_ca==1.22.

>>> x509_name('/C=AT/CN=example.com')
<Name(C=AT,CN=example.com)>
django_ca.utils.x509_relative_name(name)[source]

Parse a relative name (RDN) into a RelativeDistinguishedName.

>>> x509_relative_name('/CN=example.com')
<RelativeDistinguishedName(CN=example.com)>