Vetra Test PKI
A three-link certificate chain for development and tests only. Generated 2026-08-14 on the desktop.
Vetra Test Root CA CA:TRUE pathlen:1 5 years keyCertSign, cRLSign
└─ Vetra Test Issuing CA CA:TRUE pathlen:0 3 years keyCertSign, cRLSign
EKU: codeSigning, emailProtection
└─ Vetra Test Signer CA:FALSE 3 years digitalSignature, contentCommitment
EKU: codeSigning, emailProtection
Verified with the chain, not asserted:
$ openssl verify -CAfile root-ca-cert.pem -untrusted issuing-ca-cert.pem signer-cert.pem
signer-cert.pem: OK
Why these attributes
pathlen— the root may issue a CA one level down; the issuing CA may issue leaves only. A test PKI that lets any link mint another CA cannot catch a path-length bug.contentCommitment(nonRepudiation) on the leaf — this is the key-usage bit a detached data signature needs, which is what znippy's CMS provenance path produces.digitalSignaturealone covers code but not the data case.emailProtectionbesidecodeSigning— that is the EKU OID CMS/S-MIME verifiers look for on a detached data signature. Code for the appliance payloads, e-mail protection for the data ones.- The EKUs are on the issuing CA too. A verifier that walks EKU down the chain rejects a leaf whose issuer never carried the purpose it delegates.
What is here
Each artefact ships twice: PEM, and the same bytes base64-encoded (.pem.b64) for anywhere a single line is easier to carry than a file.
root-ca-cert root-ca-key
issuing-ca-cert issuing-ca-key
signer-cert signer-key
chain-signer-to-root leaf, issuing CA, root — in verification order
These keys are public and worthless — and they are the ONLY ones
cargo xtask key-census reads every file git ls-files reports and fails the build on any private key outside this directory. Base64 too: every artefact here ships twice, and the .pem.b64 copies carry the same keys straight past a scan that greps for BEGIN PRIVATE KEY, so the census decodes as well as reads. Three keys, six files, named one by one in xtask/src/key_census.rs rather than excused by directory — a fourth cannot join them by being dropped in beside them.
The repository went public on 2026-09-08, and a published private key trips every scanner ever pointed at a git remote whether or not it is worthless. The argument below is why these three stay; the census is why they stay three.
Every private key in this directory is committed in the clear and is a throwaway minted by the generator below. That is the point: a test fixture nobody has to fetch from a secret store, and a key nobody can mistake for a real one. Nothing here may ever become a production identity. The production signing key's custody is a project decision, its private half never enters a repo, an image or a kernel command line, and no code changes when it is swapped in — the paths are the same.
Regenerating
The certificates are DECLARED, not folklore — generator/ is the program that made them and re-running it reproduces the shape (with fresh keys and dates):
cd generator && cargo run --release -- ../
Pure Rust: rcgen 0.13 on the ring backend, the same crate Skidbladnir, edda's nornir-flight and holger's bench-scenarios already use. No openssl, no shell-out, per the suite's pure-Rust law. The openssl invocations above are for inspection only and are not part of producing anything.
One measured gap, for whoever wires CMS
The leaf carries no Subject Key Identifier. rcgen puts SKI on the CA certificates and not on the leaf, which is RFC 5280's own split — SKI is mandatory for a CA, optional for an end entity.
That matters because a CMS SignerIdentifier is either issuerAndSerialNumber or subjectKeyIdentifier. This chain supports the first out of the box; a verifier configured for the second will not find the leaf. It is also exactly the field znippy-common/src/sign.rs:1168 asks about (Profile::Leaf { include_subject_key_identifier }), so decide it there and here together rather than separately.