GreenPhysics

Binding a free certificate authority to a root in hardware it cannot reach

Seventy minutes, scripted in advance and corrected twice, that placed the root of a post-quantum PKI in an HSM the CA software has no way to talk to. The chain verified anyway.

EJBCA Community Edition can generate and use post-quantum keys, and it cannot bind a key in any hardware security module through the token types it ships. That line, measured earlier in the project and since made policy by the vendor, would seem to end the matter for anyone unwilling to buy the Enterprise edition. It does not, because certificate chains are verified by relying parties, not by the certificate authority, and relying parties speak X.509. The issuing software never needs to reach the module that holds the root. It needs the root's certificate, and a certificate for its own key signed by that root. Both are files.

The ceremony therefore reduces to producing two signatures with a key that lives in a Fortanix DSM and nowhere else. OpenSSL built each to-be-signed structure using a throwaway local ML-DSA-87 key, which gives the structure the right algorithm identifier; a forty-line script lifted the bytes, posted them to the module's signing endpoint, and spliced the returned 4,627-byte signature back in; OpenSSL verified the result. The root key never left the module. The CA created its issuing key as it normally would, with --signedby External, wrote a certificate request, and later imported the signed chain. The root does not appear in the CA's database at all; the issuing key is a row.

Two corrections were needed during execution, and both are kept in the record. The first root failed verification because openssl x509 -new had added an authority key identifier computed from the throwaway key, so the root was not its own issuer; the fix is authorityKeyIdentifier=none, which RFC 5280 permits on a root. The second is a property of the verifier: openssl verify does not check a trust anchor's self-signature unless told to, so a root signed by the wrong key printed OK until -check_ss_sig was added. A negative control, the dummy-signed certificate, then failed with error 7 as it should.

$ openssl verify -check_ss_sig -crl_check_all -CAfile root.pem -CRLfile crls.pem \
    -untrusted issuing.pem leaf.pem
leaf.pem: OK

That line, at 21:01 UTC, closes a chain with no classical algorithm anywhere in it: an ML-DSA-87 root in hardware, an ML-DSA-65 issuing CA in software, an ML-DSA-65 leaf, and two revocation lists signed the same way. Three vendors are in the chain and none of them talked to each other: the module signed, the CA issued, OpenSSL verified. The whole of the "integration" was moving two files.

The same ceremony was repeated two weeks later with the root in AWS KMS instead of Fortanix, from the same issuing request. The leaves validated under both roots. The custody decision is reversible by one ceremony that touches nothing below it, which is what the book means by crypto-agility.


In the book: Chapter 20 carries the full transcript, the figures and the runbook. In the apparatus: the fix is in the repository, pinned to the versions that were measured. In your estate: if this is the problem you are looking at, this is what an engagement does about it.