Skip to content

Document generating X.509 certs signed by HSM-maintained keys #12108

Closed
@amd-isaac

Description

@amd-isaac

I'm using cryptography to generate X.509 certificates, which works wonderfully when my code can access the private key of the issuing certificate. However, in my environment our private keys are stored on Hardware Security Modules (HSMs), and it does not appear that cryptography supports creating certificates (or at least the to-be-signed portions of certificates) and signing certificates as two separate steps. My current flow looks something like this:

from datetime import datetime, timedelta, timezone
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives.serialization import Encoding

subject_private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)

# Build cert for the given public key
builder = x509.CertificateBuilder()
builder = builder.issuer_name(x509.Name([x509.NameAttribute(x509.NameOID.COMMON_NAME, "issuer")]))
builder = builder.subject_name(x509.Name([x509.NameAttribute(x509.NameOID.COMMON_NAME, "subject")]))
builder = builder.not_valid_before(datetime.now(tz=timezone.utc))
builder = builder.not_valid_after(datetime.now(tz=timezone.utc) + timedelta(days=3))
builder = builder.serial_number(x509.random_serial_number())
builder = builder.public_key(subject_private_key.public_key())

# Create cert by signing using an ephemeral fake key
fake_issuer_private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
cert_with_fake_sig = builder.sign(
    private_key=fake_issuer_private_key,
    algorithm=hashes.SHA256(),
    rsa_padding=padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32),
)

# Get real signature from HSM; using local cryptography-generated key here for illustrative purposes
unavailable_hsm_issuer_private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
signature = unavailable_hsm_issuer_private_key.sign(
    cert_with_fake_sig.tbs_certificate_bytes,
    padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32),
    hashes.SHA256(),
)

# Remove signature bytes from previously generated cert
der_with_no_sig = cert_with_fake_sig.public_bytes(Encoding.DER)[:-256]

# Rebuild cert using previous cert bytes (sans signature) with new signature appended
cert_with_correct_sig = x509.load_der_x509_certificate(der_with_no_sig + signature)

This process works, but feels very "hacky". Is there a recommended process to use an HSM to sign X.509 certificates (or CRLs, etc)? After searching through the documentation I see references to "opaque keys" and removed support for different backends, but nothing that specifically walks through how to use cryptography to integrate with an "offline" signing system, whether using a custom sign function as an input to the certificate builder, or a multi-step "generate partial certificate, then finish generating with a user-provided signature".

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions