django_ca.utils
- utility functions¶
Reusable utility functions used throughout django-ca.
- django_ca.utils.GENERAL_NAME_RE = re.compile('^(email|URI|IP|DNS|RID|dirName|otherName):(.*)', re.IGNORECASE)¶
Regular expression to match general names.
- 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)>)¶
OIDs that can occur multiple times in a certificate
- 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: str, pad: str = '0') str [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:
- valuestr
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: bytes) str [source]¶
Convert a bytes array to hex.
>>> bytes_to_hex(b'test') '74:65:73:74'
- django_ca.utils.check_name(name: 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: str) str [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: str) str [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: GeneralName) str [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: Name | RelativeDistinguishedName) str [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.
Examples:
>>> format_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, 'example.com')])) '/CN=example.com'
- django_ca.utils.generate_private_key(key_size: int | None, key_type: Literal['DSA'], elliptic_curve: EllipticCurve | None) DSAPrivateKey [source]¶
- django_ca.utils.generate_private_key(key_size: int | None, key_type: Literal['RSA'], elliptic_curve: EllipticCurve | None) RSAPrivateKey
- django_ca.utils.generate_private_key(key_size: int | None, key_type: Literal['EC'], elliptic_curve: EllipticCurve | None) EllipticCurvePrivateKey
- django_ca.utils.generate_private_key(key_size: int | None, key_type: Literal['Ed25519'], elliptic_curve: EllipticCurve | None) Ed25519PrivateKey
- django_ca.utils.generate_private_key(key_size: int | None, key_type: Literal['Ed448'], elliptic_curve: EllipticCurve | None) Ed448PrivateKey
Generate a private key.
This function assumes that you called
validate_private_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’, ‘EC’, ‘Ed25519’, ‘Ed448’}
The type of the private key.
- elliptic_curve
EllipticCurve
An elliptic curve to use for EC keys. This parameter is ignored if
key_type
is not"EC"
. Defaults to the CA_DEFAULT_ELLIPTIC_CURVE.
- Returns:
- key
A private key of the appropriate type.
- django_ca.utils.get_cert_builder(expires: datetime, serial: int | None = None) CertificateBuilder [source]¶
Get a basic X.509 certificate builder object.
- Parameters:
- expiresdatetime
When this certificate is supposed to expire, as a timezone-aware datetime object.
- serialint, optional
Serial for the certificate. If not passed, a serial will be randomly generated using
random_serial_number()
.
- django_ca.utils.get_crl_cache_key(serial: str, encoding: Encoding = Encoding.DER, scope: str | None = None) str [source]¶
Get the cache key for a CRL with the given parameters.
- django_ca.utils.hex_to_bytes(value: str) bytes [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: int) str [source]¶
Create a hex-representation of the given serial.
>>> int_to_hex(12345678) 'BC614E'
- django_ca.utils.is_power2(num: int) bool [source]¶
Return True if num is a power of 2.
>>> is_power2(4) True >>> is_power2(3) False
- django_ca.utils.merge_x509_names(base: Name, update: Name) Name [source]¶
Merge two
x509.Name
instances.This function will return a new
x509.Name
based on base, with the attributes from update added. If an attribute type occurs in both names, the one from update take precedence.The resulting name will be sorted based on CA_DEFAULT_NAME_ORDER, regardless of order of base or update.
Example:
>>> base = x509.Name([ ... x509.NameAttribute(NameOID.COUNTRY_NAME, "AT"), ... x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Example Org"), ... x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Example Org Unit"), ... ]) >>> update = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, 'example.com')]) >>> merge_x509_names(base, update) <Name(C=AT,O=Example Org,OU=Example Org Unit,CN=example.com)>
- django_ca.utils.multiline_url_validator(value: str) None [source]¶
Validate that a TextField contains one valid URL per line.
- django_ca.utils.parse_encoding(value: str | Encoding | None = None) Encoding [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: int | datetime | timedelta | None = None) datetime [source]¶
Parse a value specifying an expiry into a concrete datetime.
This function always returns a timezone-aware datetime object with UTC as a timezone.
- django_ca.utils.parse_general_name(name: GeneralName | str) GeneralName [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: Type[SHA224 | SHA256 | SHA384 | SHA512 | SHA3_224 | SHA3_256 | SHA3_384 | SHA3_512] | str | SHA224 | SHA256 | SHA384 | SHA512 | SHA3_224 | SHA3_256 | SHA3_384 | SHA3_512 | None = None) SHA224 | SHA256 | SHA384 | SHA512 | SHA3_224 | SHA3_256 | SHA3_384 | SHA3_512 [source]¶
Parse a hash algorithm value.
Deprecated since version 1.25.0: This function will be removed in
django-ca==1.27.0
. Use standard hash algorithm names instead.The most common use case is to pass a str naming a class in
hashes
. For convenience, passingNone
will return the value of CA_DEFAULT_SIGNATURE_HASH_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
- 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: str) EllipticCurve [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_x509(name: str | Iterable[Tuple[str, str]]) Tuple[NameAttribute, ...] [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, 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: str) bytes [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: str) str [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.serialize_name(name: Name | RelativeDistinguishedName) List[SerializedObjectIdentifier] [source]¶
Serialize a
Name
.The value also accepts a
RelativeDistinguishedName
.The returned value is a list of tuples, each consisting of two strings. If an attribute contains
bytes
, it is converted usingbytes_to_hex()
.Examples:
>>> serialize_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, 'example.com')])) [{'oid': '2.5.4.3', 'value': 'example.com'}] >>> serialize_name(x509.RelativeDistinguishedName([ ... x509.NameAttribute(NameOID.COUNTRY_NAME, 'AT'), ... x509.NameAttribute(NameOID.COMMON_NAME, 'example.com'), ... ])) [{'oid': '2.5.4.6', 'value': 'AT'}, {'oid': '2.5.4.3', 'value': 'example.com'}]
- django_ca.utils.sort_name(name: Name) Name [source]¶
Returns the subject in the correct order for a x509 subject.
- django_ca.utils.split_str(val: str, sep: str) Iterator[str] [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: str) str [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: str, allow_port: bool = False) str [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_private_key_parameters(key_type: Literal['DSA', 'RSA'], key_size: int | None, elliptic_curve: EllipticCurve | None) Tuple[int, None] [source]¶
- django_ca.utils.validate_private_key_parameters(key_type: Literal['EC'], key_size: int | None, elliptic_curve: EllipticCurve | None) Tuple[None, EllipticCurve]
- django_ca.utils.validate_private_key_parameters(key_type: Literal['Ed448', 'Ed25519'], key_size: int | None, elliptic_curve: EllipticCurve | None) Tuple[None, None]
Validate parameters for private key generation.
This function can be used to fail early if invalid parameters are passed, before the private key is generated.
>>> validate_private_key_parameters("RSA", 4096, None) (4096, None) >>> validate_private_key_parameters("Ed448", 4096, None) # Ed448 does not care about the key size Traceback (most recent call last): ... ValueError: Key size is not supported for Ed448 keys. >>> validate_private_key_parameters('RSA', 4000, None) Traceback (most recent call last): ... ValueError: 4000: Key size must be a power of two
- django_ca.utils.validate_public_key_parameters(key_type: Literal['RSA', 'DSA', 'EC', 'Ed25519', 'Ed448'], algorithm: SHA224 | SHA256 | SHA384 | SHA512 | SHA3_224 | SHA3_256 | SHA3_384 | SHA3_512 | None) SHA224 | SHA256 | SHA384 | SHA512 | SHA3_224 | SHA3_256 | SHA3_384 | SHA3_512 | None [source]¶
Validate parameters for signing a certificate.
This function can be used to fail early if invalid parameters are passed.
>>> validate_public_key_parameters("RSA", hashes.SHA256()) <cryptography.hazmat.primitives.hashes.SHA256 object at 0x...> >>> validate_public_key_parameters("Ed448", None) >>> validate_public_key_parameters("Ed448", hashes.SHA256()) Traceback (most recent call last): ... ValueError: Ed448 keys do not allow an algorithm for signing.
- django_ca.utils.x509_name(name: str | Iterable[Tuple[str, str]]) Name [source]¶
Parses a string or iterable of two-tuples into a
x509.Name
.>>> x509_name('/C=AT/CN=example.com') <Name(C=AT,CN=example.com)> >>> x509_name([('C', 'AT'), ('CN', 'example.com')]) <Name(C=AT,CN=example.com)>
- django_ca.utils.x509_relative_name(name: str | Iterable[Tuple[str, str]]) RelativeDistinguishedName [source]¶
Parse a relative name (RDN) into a
RelativeDistinguishedName
.>>> x509_relative_name('/CN=example.com') <RelativeDistinguishedName(CN=example.com)>