Skip to content

Using SCEP

The certrally scep commands talk to a SCEP server, usually over plain HTTP. --server is the SCEP URL of the server, such as http://scep.example.com/scep. A server with several CAs needs the name of the CA in --ca.

Command What it does
scep caps Lists the capabilities of the server and the algorithms certrally picks from them
scep cacert Gets the CA and RA certificates. --fingerprint and --out save them
scep chain Gets the CA certificate and the certificates that issued it
scep nextca Gets the CA certificate that will replace the current one
scep enroll Requests a certificate for a new key, --new-key or --csr
scep renew Renews the certificate in --cert
scep poll Collects a certificate that the CA kept pending
scep getcert, scep getcrl Get a certificate by its serial number or the CRL that covers a certificate
scep mock Runs a SCEP test CA on your machine, see SCEP test CA
certrally scep enroll --server http://scep.example.com/scep --ca 'Issuing CA' --ca-cert ca.pem \
  --challenge-file challenge.txt --subject CN=device-42 \
  --cert-out device.pem --key-out device.key

Over plain HTTP nothing authenticates the first GetCACert answer, so save the CA certificates once with scep cacert --fingerprint and pass them with --ca-cert. The challenge password comes from --challenge, --challenge-file or CERTRALLY_CHALLENGE. When the CA answers PENDING, certrally keeps polling and saves the request in a state file that scep poll continues. The sections below describe trust, the challenge password, algorithms, pending requests, renewal, queries, busy servers and proxies. Key files, encrypted keys and --out-format der work as for CMP, see keys.md.

Server address and CA name

--server is the SCEP URL of the server, such as http://scep.example.com/scep. certrally adds the operation and message query parameters of RFC 8894 section 4.1.

A server that serves several CAs needs the name of the CA in --ca, which certrally sends as the message parameter of GetCACert, GetCACertChain, GetNextCACert and GetCACaps. Some servers answer GetCACert without content when the name is missing or unknown. certrally then exits with status 2 and suggests --ca. A 404 Not Found usually means a wrong path in --server, but some servers also answer it for a CA name they do not serve. Some answer it to GetNextCACert when they have no next CA certificate.

Trusting the CA

SCEP signs and encrypts its messages itself, so it often runs over plain HTTP. Nothing then authenticates the first GetCACert answer. Commands without --ca-cert trust the certificates of that answer and warn about it.

Check the CA once and save it for later commands:

certrally scep cacert --server http://scep.example.com/scep --ca 'Issuing CA'
certrally scep cacert --server http://scep.example.com/scep --ca 'Issuing CA' \
  --fingerprint 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 --out ca.pem

scep cacert shows each certificate with its SHA-256 and SHA-1 fingerprints and its role: the issuing CA, an RA that decrypts requests or an RA that signs answers. --fingerprint takes a SHA-256 or SHA-1 fingerprint in hex, with or without colons. The certificate with that fingerprint is saved together with the certificates it issued, such as RA certificates. The other certificates of the answer are listed and not saved. Later commands trust the saved file with --ca-cert and do not send GetCACert. --fingerprint on the other commands checks the GetCACert answer before the request is sent.

scep chain sends GetCACertChain, which RFC 8894 does not define but some servers offer. It shows the CA certificate followed by the certificates that issued it, in the order of the server. --fingerprint and --out work as for scep cacert, so the fingerprint of the root saves the whole chain.

Over HTTPS the server certificate is checked against the system roots or --tls-ca-cert. --insecure skips that check for a test server.

Challenge password

A PKCSReq carries a challenge password that authorizes the request. It comes from --challenge, --challenge-file (- reads standard input) or the CERTRALLY_CHALLENGE environment variable, in that order. certrally never asks for it, because many servers need none. Other users of the machine can see --challenge in the process list. Some servers also check the challenge password in a RenewalReq, so scep renew accepts the same flags.

Some servers refuse a missing or wrong challenge password with an HTTP error such as 400 Bad Request or 403 Forbidden instead of a FAILURE answer. Some do the same for a subject they do not know. certrally exits with status 3, shows the text of the error page and suggests checking the challenge password, --subject and --ca.

Algorithms

Before a PKIOperation certrally sends GetCACaps and picks the algorithms from the answer:

Choice Default Flag
Signature digest SHA-256, or SHA-512 or SHA-1 when the server lists one of them and not SHA-256 --hash: sha256, sha384, sha512, sha1 or md5
Content cipher AES-128-CBC, or DES-EDE3-CBC when the server lists DES3 and not AES --cipher: aes128, aes192, aes256, des3 or des
HTTP method POST when the server lists POSTPKIOperation, GET otherwise

A server that lists SCEPStandard gets SHA-256, AES-128-CBC and POST. scep caps shows the capabilities and these choices.

--hash also sets the signature digest of the PKCS#10 request that scep enroll and scep renew create. Without --hash or with md5, which Go does not sign requests with, the request is signed with SHA-256 for an RSA key and with the digest that matches the curve for an ECDSA key. A request from --csr is sent as it is.

certrally signs a PKCSReq with a self-signed certificate for the request key, or for a temporary RSA key when the request key is not RSA, because SCEP encrypts answers to the signer key. The transaction ID is the SHA-1 hash of the public key in uppercase hex, so a request for the same key always has the same ID.

Requests are encrypted with RSA for the RA certificates, or for the CA certificate when the CA has no RA. When one of them has another key type, certrally stops before it sends the request and says that the CA needs an RSA RA certificate.

certrally decrypts answers encrypted with AES or DES-EDE3-CBC and always with the cipher of its request. Some servers encrypt their answers with single DES whatever the request used. certrally then exits with status 4 and suggests --allow-des, which accepts them. Single DES offers no real protection for the certificate, but the certificate is public.

Waiting for the CA

A CA that an operator must approve answers PENDING. scep enroll and scep renew then send CertPoll every --poll-interval (default 30 seconds) until the CA issues or rejects the certificate, within --timeout (default 5 minutes). --poll-later stops at the first PENDING answer. Some servers send a PENDING answer with HTTP 202 Accepted and a FAILURE answer with 400 Bad Request. certrally reads the CertRep whatever the HTTP status.

A request that is still pending is saved in a state file, by default the --cert-out file name with .state appended, or the file in --state. It holds the request, the certificate that signs it, the temporary signer key when there is one, the CA certificates and the output file names. It never holds the challenge password. It is written with mode 0600 and removed once the CA decides. At the end of --timeout, certrally keeps the state file and exits with status 2.

certrally scep poll --state device.pem.state

scep poll continues the request from the state file and saves the certificate where the enrollment would have. --once sends a single CertPoll. --server, --ca and --allow-des replace the values of the state file.

Renewal

scep renew sends a RenewalReq signed with the certificate in --cert and its key in --key, which must be RSA. The request repeats the subject and subjectAltName of the certificate. A new key is generated unless --new-key or --csr supplies one. The server must list Renewal in its capabilities. certrally warns when it does not.

Servers that follow the drafts before RFC 8894 renew through a PKCSReq signed with the current certificate. Some of them list Renewal and still answer a RenewalReq with FAILURE and badCertId. scep renew --pkcsreq sends such a PKCSReq, signed with --cert and --key and with the same subject and subjectAltName. The server need not list Renewal. PENDING answers and the state file work as for a RenewalReq.

certrally scep renew --server http://scep.example.com/scep --pkcsreq \
  --cert device.pem --key device.key --cert-out device-new.pem --key-out device-new.key

Certificates and CRLs from the CA

scep getcert --serial gets a certificate the CA issued, by its serial number in decimal, 0x hex or colon-separated hex. The issuer is the CA certificate unless --issuer names another. scep getcrl gets the CRL for the certificate in --cert or for --serial and checks its signature against the CA certificate.

Both requests are signed with --cert and --key, which servers usually require to be a certificate the CA issued, or else with a temporary self-signed certificate. Some servers answer these message types or RenewalReq without content when they do not support them. certrally then exits with status 2 and says so.

scep nextca sends GetNextCACert and shows the CA and RA certificates that will replace the current ones. The answer must be signed by the current CA or RA.

Many servers have no next CA certificate until a CA key rollover is prepared. RFC 8894 does not say how a server answers then. certrally reports no next CA certificate and exits with status 0 when the server answers without content or answers 404 Not Found while GetCACert works at the same address. Servers built on go-pki-scep answer 404. With --ca-cert certrally sends GetCACert after the 404, so a wrong path or CA name still exits with status 2. Other servers refuse the request with 403 Forbidden or 500 Internal Server Error or lack the operation. certrally exits with status 3 for those, as interop.md lists.

Busy servers

A server under load may answer HTTP 503. certrally sends the request again up to --busy-retries times (default 3, 0 for never), after the time in the Retry-After header or after --busy-wait (default 5 seconds) when the header is missing.

Proxies

--proxy takes an http, https or socks5 URL. Without it, the HTTP_PROXY, HTTPS_PROXY and NO_PROXY environment variables apply as for CMP.