146 lines
4.5 KiB
Python
146 lines
4.5 KiB
Python
"""
|
|
Utility module to load keys and certificates.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Optional
|
|
|
|
from asn1crypto import keys, pem, x509
|
|
from cryptography.hazmat.primitives import serialization
|
|
|
|
__all__ = [
|
|
'load_cert_from_pemder',
|
|
'load_certs_from_pemder',
|
|
'load_certs_from_pemder_data',
|
|
'load_private_key_from_pemder',
|
|
'load_private_key_from_pemder_data',
|
|
]
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def load_certs_from_pemder(cert_files):
|
|
"""
|
|
A convenience function to load PEM/DER-encoded certificates from files.
|
|
|
|
:param cert_files:
|
|
An iterable of file names.
|
|
:return:
|
|
A generator producing :class:`.asn1crypto.x509.Certificate` objects.
|
|
"""
|
|
for cert_file in cert_files:
|
|
with open(cert_file, 'rb') as f:
|
|
cert_data_bytes = f.read()
|
|
yield from load_certs_from_pemder_data(cert_data_bytes)
|
|
|
|
|
|
def load_certs_from_pemder_data(cert_data_bytes: bytes):
|
|
"""
|
|
A convenience function to load PEM/DER-encoded certificates from
|
|
binary data.
|
|
|
|
:param cert_data_bytes:
|
|
``bytes`` object from which to extract certificates.
|
|
:return:
|
|
A generator producing :class:`.asn1crypto.x509.Certificate` objects.
|
|
"""
|
|
# use the pattern from the asn1crypto docs
|
|
# to distinguish PEM/DER and read multiple certs
|
|
# from one PEM file (if necessary)
|
|
if pem.detect(cert_data_bytes):
|
|
pems = pem.unarmor(cert_data_bytes, multiple=True)
|
|
for type_name, _, der in pems:
|
|
if type_name is None or type_name.lower() == 'certificate':
|
|
yield x509.Certificate.load(der)
|
|
else: # pragma: nocover
|
|
logger.debug(
|
|
f'Skipping PEM block of type {type_name} in '
|
|
f'CA chain file.'
|
|
)
|
|
else:
|
|
# no need to unarmor, just try to load it immediately
|
|
yield x509.Certificate.load(cert_data_bytes)
|
|
|
|
|
|
def load_cert_from_pemder(cert_file):
|
|
"""
|
|
A convenience function to load a single PEM/DER-encoded certificate
|
|
from a file.
|
|
|
|
:param cert_file:
|
|
A file name.
|
|
:return:
|
|
An :class:`.asn1crypto.x509.Certificate` object.
|
|
"""
|
|
certs = list(load_certs_from_pemder([cert_file]))
|
|
if len(certs) != 1:
|
|
raise ValueError(f"Number of certs in {cert_file} should be exactly 1")
|
|
return certs[0]
|
|
|
|
|
|
def load_private_key_from_pemder(
|
|
key_file, passphrase: Optional[bytes]
|
|
) -> keys.PrivateKeyInfo:
|
|
"""
|
|
A convenience function to load PEM/DER-encoded keys from files.
|
|
|
|
:param key_file:
|
|
File to read the key from.
|
|
:param passphrase:
|
|
Key passphrase.
|
|
:return:
|
|
A private key encoded as an unencrypted PKCS#8 PrivateKeyInfo object.
|
|
"""
|
|
with open(key_file, 'rb') as f:
|
|
key_bytes = f.read()
|
|
return load_private_key_from_pemder_data(key_bytes, passphrase=passphrase)
|
|
|
|
|
|
def load_private_key_from_pemder_data(
|
|
key_bytes: bytes, passphrase: Optional[bytes]
|
|
) -> keys.PrivateKeyInfo:
|
|
"""
|
|
A convenience function to load PEM/DER-encoded keys from binary data.
|
|
|
|
:param key_bytes:
|
|
``bytes`` object to read the key from.
|
|
:param passphrase:
|
|
Key passphrase.
|
|
:return:
|
|
A private key encoded as an unencrypted PKCS#8 PrivateKeyInfo object.
|
|
"""
|
|
load_fun = (
|
|
serialization.load_pem_private_key
|
|
if pem.detect(key_bytes)
|
|
else serialization.load_der_private_key
|
|
)
|
|
return _translate_pyca_cryptography_key_to_asn1(
|
|
load_fun(key_bytes, password=passphrase)
|
|
)
|
|
|
|
|
|
def _translate_pyca_cryptography_key_to_asn1(
|
|
private_key,
|
|
) -> keys.PrivateKeyInfo:
|
|
# Store the cert and key as generic ASN.1 structures for more
|
|
# "standardised" introspection. This comes at the cost of some encoding/
|
|
# decoding operations, but those should be fairly insignificant in the
|
|
# grand scheme of things.
|
|
#
|
|
# Note: we're not losing any memory protections here:
|
|
# (https://cryptography.io/en/latest/limitations.html)
|
|
# Arguably, memory safety is nigh impossible to obtain in a Python
|
|
# context anyhow, and people with that kind of Serious (TM) security
|
|
# requirements should be using HSMs to manage keys.
|
|
return keys.PrivateKeyInfo.load(
|
|
private_key.private_bytes(
|
|
serialization.Encoding.DER,
|
|
serialization.PrivateFormat.PKCS8,
|
|
serialization.NoEncryption(),
|
|
)
|
|
)
|
|
|
|
|
|
def _translate_pyca_cryptography_cert_to_asn1(cert) -> x509.Certificate:
|
|
return x509.Certificate.load(cert.public_bytes(serialization.Encoding.DER))
|