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 (lower-cased) 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 parsesstr
when added.A
GeneralNameList
is alist
subclass that will always only holdGeneralName
instances, but anystr
passed to it will be passed toparse_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
- 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 inMULTIPLE_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. 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: Use
format_name()
instead. This function will be removed indjango_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_curve
EllipticCurve
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 assumePEM
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 anHashAlgorithm
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.
- valuestr or
- 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)[source]¶
Parse a string an
EllipticCurve
instance.This function is intended to parse user input, so it ignores case.
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('secp384r1') <cryptography.hazmat.primitives.asymmetric.ec.SECP384R1 object at ...>
- Parameters
- valuestr
The name of the curve (case insensitive).
- 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 was renamed to
parse_name_x509()
. The old name will be removed indjango_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 asCN
, 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 indjango_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
.>>> 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)>