Using CMP¶
certrally implements CMP as RFC 9810 defines it, with the Lightweight CMP Profile of RFC 9483. --server is the CMP URL of the CA, such as https://ca.example.com/.well-known/cmp. The CMP commands run without a prefix or under certrally cmp, such as certrally cmp enroll.
| Command | What it does |
|---|---|
enroll |
Requests a certificate with ir, cr, kur or p10cr |
poll |
Collects a certificate that the CA kept waiting |
revoke |
Asks the CA to revoke a certificate |
cacerts |
Gets the CA certificates |
cmp genm |
Asks the CA for a root CA update, a request template or CRLs |
ping |
Checks that a server answers CMP |
mock |
Runs a CMP test CA on your machine, see CMP test CA |
Keys and output files describes key types, encrypted keys and DER output, which work the same for CMP, EST and SCEP.
Enroll a certificate¶
A device that has a shared secret from the CA requests its first certificate with an initialization request (ir). --ref names the secret to the CA:
certrally enroll --server https://ca.example.com/.well-known/cmp \
--ref device-42 --secret-file secret.txt \
--subject CN=device-42,O=Example --san device-42.example.com \
--cert-out device.pem --key-out device.key
--secret VALUE gives the secret on the command line instead. CERTRALLY_SECRET or a prompt also work. Protection lists the order and the risk of --secret.
certrally draws each message as it is sent and received, then describes the certificate:
certrally ca.example.com
│ │
│ ir PBMAC1 506 B │
│ ─────────────────────────────────────────────────────────────▶ │
│ ip accepted PBMAC1 1.5 KiB 9 ms │
│ ◀───────────────────────────────────────────────────────────── │
│ certConf PBMAC1 291 B │
│ ─────────────────────────────────────────────────────────────▶ │
│ pkiconf PBMAC1 637 B 2 ms │
│ ◀───────────────────────────────────────────────────────────── │
✓ Certificate issued
Subject CN=device-42,O=Example
Alt names device-42.example.com
Issuer CN=Example CA
Serial 94:01:1D:E4:EA:C9:F1:25:59:06:0B:13:86:06:94:7D
Valid 2026-10-13 00:20 UTC to 2027-10-13 00:21 UTC
Key ECDSA P-256
Status accepted
Saved device.key, device.pem
The diagram goes to standard error and the certificate summary to standard output. --quiet hides both.
A certificate the device already has signs later requests. A key update (kur) replaces the certificate in --cert with one for a new key:
certrally enroll --server https://ca.example.com/.well-known/cmp --type kur \
--cert device.pem --key device.key \
--cert-out device-new.pem --key-out device-new.key
--type cr requests an additional certificate. --type p10cr sends a PKCS#10 request, either from --csr or built from --subject and --san. Some CAs accept only PasswordBasedMac and need --mac pbm.
The sections below cover the other options: key usages and templates, implicit confirmation, collecting a waiting certificate later, ML-KEM keys, keys that the CA generates, forwarding as a registration authority, CA information requests and ping.
Enroll step by step¶
On a terminal, certrally enroll without flags asks for each setting: the server, the CA to trust, the shared secret or certificate, the names, the key type and the files to write. Flags you give answer their questions. --interactive asks only for the rest:
certrally enroll --interactive --server https://ca.example.com/.well-known/cmp
Before it sends the request, certrally prints the same request as one command for scripts and asks for confirmation. The shared secret is hidden while you type it. It never appears in that command, and neither do the values of --secret and --key-password. The questions cannot be combined with --json. With ACCESSIBLE=1 they are asked line by line, for example for a screen reader.
Revoke a certificate¶
RFC 9483 signs a revocation request with the certificate being revoked:
certrally revoke --server https://ca.example.com/.well-known/cmp \
--cert device.pem --key device.key --reason keyCompromise
--reason takes an RFC 5280 reason name and defaults to unspecified. certificateHold suspends a certificate and removeFromCRL releases it, if the CA supports holds. An authority that revokes for someone else names the certificate with --revoke-cert or with --serial and --issuer as described in Revoke by serial number. On a terminal certrally asks before it sends the request. --yes skips the question.
Revoke by serial number¶
An authority that has no copy of the certificate names it with --serial and --issuer. --serial takes a decimal number, hex with 0x or the colon-separated hex that certrally prints. --invalidity-date tells the CA when the key was compromised or the certificate became invalid, as RFC 3339 or YYYY-MM-DD, for the CRL entry:
certrally revoke --server https://ca.example.com/.well-known/cmp \
--ref ra-1 --serial 0x1A2B --issuer 'CN=Example CA' --reason keyCompromise --invalidity-date 2026-10-01
Protection¶
Shared-secret requests are protected with PBMAC1. The request header names CN=<ref> as the sender and <ref> as the key identifier, as RFC 9483 asks.
The shared secret comes from --secret, --secret-file (- reads standard input), the CERTRALLY_SECRET environment variable or a prompt on a terminal, in that order. Other users of the machine can see --secret in the process list, and the shell may keep it in its history.
--mac pbmselects PasswordBasedMac for CAs that accept only that, such as Nokia NCM, Insta Certifier and theopenssl cmpmock server.--iterationssets the MAC iteration count (default 10000).--hash sha1|sha256|sha384|sha512selects the digest of the signatures certrally makes with an RSA or ECDSA key: the message protection with--cert, the proof of possession and a PKCS#10 request. With--mac pbmit also selects the one-way function and HMAC of the MAC. PBMAC1 always uses SHA-256. The default follows the key type. CAs refuse SHA-1 unless configured to accept it.
--hash, --iterations and --general-info also apply to revoke and the genm commands.
Requests use CMP version 2, the syntax of RFC 4210, so servers built for RFC 4210 understand them. Only messages that need a field added by RFC 9810 use version 3, such as the confirmation of a certificate that the CA signed with ML-DSA or composite ML-DSA.
Certificate contents¶
Without --subject or --san a shared-secret request asks for a certificate for CN=<ref>.
--key-usage and --ext-key-usage ask for the keyUsage and extendedKeyUsage extensions, as comma-separated lists such as --key-usage digitalSignature,keyEncipherment --ext-key-usage serverAuth,clientAuth. Names are not case-sensitive.
--key-usagetakesdigitalSignature,nonRepudiation(orcontentCommitment),keyEncipherment,dataEncipherment,keyAgreement,keyCertSign,cRLSign,encipherOnlyanddecipherOnly. keyUsage is marked critical, as RFC 5280 recommends.--ext-key-usagetakesserverAuth,clientAuth,codeSigning,emailProtection,timeStamping,ocspSigning,anyExtendedKeyUsage,cmcRAor a dotted OID.
The CA decides what the certificate gets. Many CAs take the key usages from their profile instead.
--template-cert device.pem copies the subject, subjectAltName, keyUsage and extendedKeyUsage of an existing certificate, to request one shaped like a certificate the CA already issued. --subject, --san, --key-usage and --ext-key-usage replace the part they set. A kur already repeats the certificate in --cert, so it does not take --template-cert. A p10cr built by certrally carries these extensions in its PKCS#10 request. A request given with --csr cannot be changed.
Header fields¶
--general-info OID:int:N, OID:str:TEXT or OID:hex:DER (repeatable) adds an item to the generalInfo field of the request header, for example a vendor profile identifier that a CA keys its policy on.
--cert-req-id N sets the certReqId of an ir, cr or kur (default 0), which the CA must repeat. RFC 9483 requires 0, so most CAs refuse other values.
Implicit confirmation¶
--implicit-confirm asks the CA for implicit confirmation, as RFC 9483 recommends. A CA that grants it repeats implicitConfirm in its response and certrally skips the certConf exchange, so the run ends with the response. A CA that wants the confirmation leaves it out and certrally confirms as usual. The result says which happened.
Collect a certificate later¶
A CA that needs approval answers with status waiting. enroll then polls until the CA decides or --timeout ends, 5 minutes by default. With --poll-later it stops at that answer instead, saves the new key and records the transaction in the --capture file:
certrally enroll --server https://ca.example.com/.well-known/cmp \
--ref device-42 --secret-file secret.txt --subject CN=device-42 \
--cert-out device.pem --key-out device.key \
--poll-later --capture device.jsonl
✓ Request waiting for the CA
Status waiting
Transaction ID d503c4aad8beeb7b34b820adb1809771
Saved device.key
Note Collect the certificate with: certrally poll --transaction-id d503c4aad8beeb7b34b820adb1809771 --capture device.jsonl
poll collects the certificate later, also from another directory. It takes the server, the protection, the trusted CA certificates and the output files from the capture file and reads the shared secret again. A run that signed with an encrypted key takes --key-password or --key-password-file the same way. It polls until the CA decides, confirms the certificate and saves it:
certrally poll --transaction-id d503c4aad8beeb7b34b820adb1809771 \
--capture device.jsonl --secret-file secret.txt
- A poll that ends at
--timeoutwhile the CA still waits records the new state, so the same command can run again. Anenrollwhose polling ends at--timeoutrecords the transaction the same way when it has--capture. --server,--cert-out,--chain-out,--key-out,--out-format,--ra-certand--ra-keyreplace the values from the capture file.- Once the CA issues the certificate or rejects the request, the transaction ends and
pollrefuses it. After other failures, such as a wrong shared secret, the same command can run again.
ML-KEM keys¶
ML-KEM-768 and ML-KEM-1024 keys cannot sign, so they cannot prove possession of the private key with a signature. For them enroll proves possession by decryption instead (RFC 9810 section 5.2.8.3), with ir, cr and kur:
certrally enroll --server https://ca.example.com/.well-known/cmp \
--ref device-42 --secret-file secret.txt --subject CN=device-42 \
--key-type ml-kem-768 --cert-out device-kem.pem --key-out device-kem.key
--pop encrypted-cert, the default, asks the CA to encrypt the certificate for the key. certrally decrypts it. ItscertConfproves that it could.--pop challengeasks the CA for a challenge encrypted for the key, which certrally answers before the CA issues the certificate.
The CA must offer the method. certrally mock --accept encrypted-cert-pop --accept challenge-pop offers both. --pop with a key that can sign is an error. A p10cr needs a key that signs. The result names the method in the Proof of possession field.
The key is saved in the seed form of RFC 9935, which OpenSSL 3.5 and later read. --new-key takes the forms OpenSSL writes, but not a key saved without its seed. poll needs the key to decrypt a waiting certificate: it reads the file that enroll saved or read. --new-key names the key when it is in another file.
Let the CA generate the key¶
A device that cannot generate a good key pair itself can ask the CA for one (RFC 9483 section 4.1.6). --central-keygen sends a request without a public key. The CA generates the key pair, certifies it and returns the private key with the certificate, signed by a key generation authority and encrypted for the requester. certrally saves it to --key-out:
certrally enroll --server https://ca.example.com/.well-known/cmp \
--ref device-42 --secret-file secret.txt --subject CN=device-42 \
--central-keygen --cert-out device.pem --key-out device.key
Without --key-type the CA chooses the key type. --key-type ecdsa-p384 asks for one. The CA may refuse it with badAlg. An RSA request cannot name the key size, so rsa-3072 and rsa-4096 ask for an RSA key of the size the CA chooses.
- It works with
ir,crandkur. Ap10cris signed with the new key, so it cannot ask for one.--new-keyand--popcannot be combined with--central-keygen. - The CA encrypts the key to the shared secret or to the key in
--key. That key must be RSA or ECDSA, because the other key types cannot decrypt. - certrally saves the key only after it checked that the signature of the key generation authority verifies, that its certificate chains to a trusted CA and may sign keys and that the key belongs to the certificate. Otherwise it rejects the certificate in its
certConf. The result names the authority in theKey generated byfield. - The key is saved as PKCS#8 like other keys, as the CA encoded it, which may include its public key.
- With
--poll-laterthe key arrives with the certificate, soenrollsaves nothing.pollsaves the key to the--key-outthatenrollnamed unless its own--key-outnames another file. It needs the same shared secret or--keyto decrypt the key.
certrally mock generates every key type that --key-type offers, with 2048 bits for RSA.
Forward requests as a registration authority¶
A registration authority (RA) checks requests before the CA sees them and vouches for them with its own signature. With --ra-cert and --ra-key certrally acts as the RA (RFC 9483 section 5.2.2.1). It wraps every request in a nested message that it signs with the RA certificate, including the certConf and each pollReq. The request inside keeps its own protection, here the shared secret of the device:
certrally enroll --server https://ca.example.com/.well-known/cmp \
--ref device-42 --secret-file secret.txt --subject CN=device-42 \
--ra-cert ra.pem --ra-key ra.key --cert-out device.pem --key-out device.key
- The CA must accept the RA, usually by the
cmcRAextended key usage of its certificate (id-kp-cmcRA).--ext-key-usage cmcRAasks for one. --ra-certmay hold the chain after the RA certificate. certrally sends all of them inextraCerts.- The CA answers the request inside, so the diagram shows
nested(ir)and then a plainip. The result names the RA in theForwarded byfield. - certrally sends one request per
nestedmessage. It does not batch requests. pollforwards its requests through the RA thatenrollused unless its own--ra-certand--ra-keyname another one.
certrally mock accepts an RA whose certificate it issued with cmcRA. certrally mock --require-ra refuses requests that no RA forwarded, except a request for an RA certificate.
Trust and connections¶
certrally trusts the CA certificates in the first response from the server and the CA certificates in the --cert file, then checks every response against them. The report lists each certificate trusted from the first response with its SHA-256 fingerprint, so you can compare it with the one the CA published. If a response does not carry the CA certificate that issued the new certificate, pass that certificate with --ca-cert.
With --ca-cert ca.pem (repeatable) certrally trusts nothing the server sends. Signed responses and issued certificates must chain to a CA certificate from --ca-cert or --cert.
--chain-out holds the CA certificates that signed the new certificate and each other, as far as the response carries them. A certificate that only carries the name and key identifier of an issuer is left out, since nothing protects the extraCerts of a response.
Over HTTPS the server is checked against the system roots or against the CA certificates in --tls-ca-cert. --insecure skips that check, for a test server with a self-signed certificate. certrally then warns that anyone who intercepts the connection can pose as the server. The capture file records it. --insecure cannot be combined with --tls-ca-cert. Over plain HTTP nothing authenticates the first response, so certrally warns when it trusts one.
certrally connects through the proxy that the HTTPS_PROXY, HTTP_PROXY and NO_PROXY environment variables name, like other HTTP clients. --proxy http://proxy.example.com:3128 names a proxy on the command line instead, on every command that talks to a server. An https proxy works too. Credentials in the URL are sent to the proxy.
Ask the CA for information¶
cacerts and the cmp genm commands send the RFC 9483 support messages. They are protected like enroll requests:
certrally cacerts --server https://ca.example.com/.well-known/cmp --ref device-42 --out ca-certs.pem
| Command | Answer |
|---|---|
cacerts or cmp genm cacerts |
The CA certificates. --out saves them |
cmp genm rootupdate --old-root root.pem |
The new root CA certificate, if the root has been replaced. It is accepted only when the old root certifies it |
cmp genm template |
The subject, extensions and key types the CA expects. --cert-profile selects a profile |
cmp genm crl |
The current CRL. Its signature is checked against the trusted CA certificates |
cmp genm crlupdate --crl ca.crl |
A CRL newer than ca.crl. --crl-issuer ca.pem asks for any CRL from that CA instead |
cmp genm raw caCerts |
The answer to any info types, given by name or as dotted OIDs |
--out - writes the certificates or the CRL to standard output and moves the summary to standard error. --out-format der writes DER, with several certificates as PKCS#7.
Check a server¶
ping sends an unprotected general message and reports who answers and how fast. It needs no credentials, because any CMP answer counts, including the error a CA sends back for an unprotected request:
certrally ping --server https://ca.example.com/.well-known/cmp --count 3
1 error rejection badMessageCheck ECDSA SHA-256 690 B 12.4 ms
2 error rejection badMessageCheck ECDSA SHA-256 689 B 9.8 ms
3 error rejection badMessageCheck ECDSA SHA-256 688 B 10.1 ms
✓ The server answers CMP
Server https://ca.example.com/.well-known/cmp
Answer error rejection badMessageCheck
CA says "message not protected"
Sender CN=Example CA
Protection ECDSA SHA-256
Round trip min 9.8 ms, avg 10.8 ms, max 12.4 ms
Replies 3 of 3
Note An error is the expected answer, because the request carries no protection.
With --ca-cert the answer must also be signed by that CA. A CA that leaves its errors unprotected fails this check.
Test how a CA copes with faulty messages¶
--fault NAME makes enroll and poll send a deliberately wrong message, such as a certConf with a wrong certHash or a request that reuses a nonce. Use it against a test CA or with the consent of the CA operator. Testing a CA with faulty messages lists the faults, the RFC 9483 rule each one breaks and how certrally mock answers it.