Skip to content

SCEP (RFC 8894)

SCEP (Simple Certificate Enrollment Protocol, RFC 8894) is the oldest automated enrollment protocol still in wide use. Mobile device management platforms (Microsoft Intune, Jamf, MobileIron), network equipment (routers, VPN concentrators, Wi-Fi controllers), printers and many embedded devices ship a SCEP client. The device generates its key, sends a PKCS#10 request wrapped in a signed and encrypted CMS message, and receives its certificate.

PKIFactor runs a SCEP gateway in front of your certification authorities. Each certificate profile with SCEP enabled gets its own SCEP address; the profile decides what may be issued (authority, algorithms, names, validity, organization), and every certificate lands in the inventory.

Status

In August 2026, an enrollment with the sscep client and a one-time challenge password issued a certificate from Microsoft AD CS through the gateway (sscep itself then failed to verify the signature of the response). The gateway's security was reworked afterwards (verification of the CMS signature, no issuance without a challenge or a trusted renewal signer). On the current version, GetCACaps, GetCACert and the generation of challenge passwords were tested; a full enrollment with a real client was not re-tested.

Not tested: renewal (RenewalReq), GetCertInitial, GetCert, GetCRL, and enrollment by an MDM platform or a network device.

What is supported

Operation Request Status
GetCACaps GET /scep/<path>?operation=GetCACaps Supported
GetCACert GET /scep/<path>?operation=GetCACert Supported
PKIOperation, enrollment (PKCSReq) POST /scep/<path>?operation=PKIOperation, or GET with message=<base64> for older clients Supported
PKIOperation, renewal (RenewalReq) Same Supported, see Renewal
PKIOperation, polling (GetCertInitial) Same Supported
PKIOperation, GetCert and GetCRL Same Supported
GetNextCACert Not supported
Revocation Not part of SCEP: revoke from PKIFactor

Capabilities announced by GetCACaps: POSTPKIOperation, SHA-256, SHA-512, AES, DES3, Renewal, SCEPStandard. A profile can restrict this list through the API field scep_allowed_caps; there is no field for it in the interface.

Cryptography:

  • The request envelope is decrypted with the RA key pair of the profile's PKI connector (RSA). Accepted content encryption: AES-128, AES-192 and AES-256 in CBC mode, and Triple DES for legacy clients. Key transport: RSA PKCS#1 v1.5 or RSA-OAEP.
  • The signature of the outer CMS message is verified (RSA or ECDSA signer).
  • The CSR's own signature must be valid.
  • Responses (CertRep) are signed with the RA key and encrypted with AES-256-CBC to the certificate that signed the request.
  • When the authority cannot issue at once, the gateway answers PENDING and the client polls with GetCertInitial.
  • A PKCSReq signed with a self-signed certificate must be signed with the key of the CSR it carries; otherwise it is refused.
  • Replay protection: each message's transaction identifier and sender nonce can be used only once, across all gateway instances. When the message carries a signing time, it must be within 10 minutes of the gateway's clock. Messages without a signing time are accepted unless SCEP_REQUIRE_SIGNING_TIME=true is set on the SCEP service.
  • The gateway limits enrollment requests per client address (ENROLL_RATE_LIMIT, 30 per minute by default) and the size of a message (ENROLL_MAX_BODY_BYTES, 256 KiB by default).

Addresses

The SCEP gateway has its own DNS name (for example scep.example.com, see Requirements) and is reached through nginx:

http://scep.example.com/scep/<path>
https://scep.example.com/scep/<path>

SCEP protects its messages itself, and many clients only speak plain HTTP: nginx answers on port 80 directly (no redirection) as well as on 443. If none of your clients needs plain HTTP, close port 80 for this name at your firewall (see Network flows).

<path> is the profile's SCEP path. PKIFactor generates it from the profile name when you enable SCEP (scep- followed by the name in lower case, for example scep-mdm-devices) and keeps it afterwards. Disabling SCEP on the profile clears it.

The gateway exists only when the scep service is started and SCEP_ENABLED=true (see Docker Compose).

Enable SCEP on a profile

  1. In the Administration workspace, open Infrastructure > Certificate Profiles, open the profile and click Edit Profile.
  2. Unfold Automatic enrollment (ACME, EST, CMP, SCEP), then in SCEP Protocol tick Enable SCEP enrolment.
  3. Set the challenge options:

    Field Meaning
    Require challenge On (default): every request must carry a valid one-time challenge password. Off: a request without a challenge is accepted only as a renewal signed by a valid certificate of the same identity (see Renewal); a first enrollment still needs a challenge
    Challenge TTL (seconds) Lifetime of each challenge password, from 60 to 86400 seconds. Default 900 (15 minutes)
  4. Save. The profile page then shows the Enrollment path and the Enrollment endpoint.

Rules applied when you save:

Rule Why
Require validation before issuance is turned off and cannot be ticked The challenge replaces manual approval (see Approval)
The profile must belong to one organization Nobody is there to choose an organization behind a gateway. Otherwise the save is refused: Un profil activé pour ACME, EST, SCEP ou CMP doit être rattaché à une seule organisation : une passerelle ne peut pas choisir.
Via the API
curl -s -c jar -X POST https://clm.example.com/api/auth/login \
  -d "email=admin@example.com" --data-urlencode "password=$ADMIN_PASSWORD"
curl -s -b jar -X PUT https://clm.example.com/api/v1/clm/templates/14 \
  -H "Content-Type: application/json" \
  -d '{"scep_enabled": true, "scep_challenge_required": true,
       "scep_challenge_ttl_seconds": 3600, "requires_approval": false}'

The response contains the generated scep_path.

The RA key pair

SCEP clients encrypt their request to an RA (registration authority) certificate and verify responses with it. PKIFactor keeps one RA key pair per PKI connector, shared by all the SCEP profiles that issue from that connector. The private key is stored encrypted.

On the PKI connector's edit page (Administration workspace, Infrastructure > PKI Connectors), the SCEP RA Keypair panel shows the RA certificate (subject, issuer, validity, fingerprint) and offers:

Action Effect
Generate self-signed Creates a self-signed RSA 2048 key pair. Done automatically on the first SCEP request if you do nothing
Rotate Replaces the key pair. Requests in progress with the old key fail; clients must fetch GetCACert again
Import… Installs an RA certificate issued by your CA, with its RSA private key (PEM, unencrypted). The key is checked against the certificate

A self-signed RA certificate is allowed by RFC 8894; clients typically check the CA fingerprint out of band. Import a CA-issued RA certificate if your MDM platform or devices require one.

GetCACert returns the RA certificate followed by the CA chain. The chain comes from the CA Chain (PEM) field of the PKI connector when filled in, otherwise from the authority.

Challenge passwords

A challenge password is valid once, for one profile, until its TTL expires. It is stored hashed and shown once. The device puts it in the challengePassword attribute of its CSR. The gateway first checks that the challenge belongs to this profile; a challenge presented on another profile's path is refused and stays usable. Once that check passes, the challenge is consumed before the certificate is requested, even if the issuance then fails.

  1. On the profile page, in the SCEP card, click Enroll (SCEP).
  2. Click Generate challenge and copy it (challengePassword shown ONCE). The window shows its expiry and a ready-made sscep command; replace its host name with the SCEP name (scep.example.com), since the window builds the address from the page you are on.

Only administrators can generate challenge passwords. Each generation is recorded in the audit trail (SCEP_CHALLENGE_MINTED, with the administrator).

For MDM platforms that fetch a challenge for each device, generate the challenges through the API.

Via the API
curl -s -b jar -X POST https://clm.example.com/api/scep/admin/challenges \
  -H "Content-Type: application/json" -d '{"template_id": 14}'
# {"challenge": "scep_...", "expires_at": "...", "single_use": true,
#  "scep_path": "scep-mdm-devices", "ttl_seconds": 3600}

Renewal

A device renews by sending a request signed with its current certificate. Without a challenge password, the gateway accepts it only when all of these are true:

  • Require challenge is off on the profile;
  • the signing certificate was issued by PKIFactor and is currently valid (not revoked);
  • the common name requested is the same as the signing certificate's.

Otherwise the request needs a fresh challenge password, like a first enrollment. A renewal is processed as a new issuance: the new certificate is not linked to the previous one in the inventory.

Approval and issuance policies

SCEP requests are never sent for manual approval: the challenge, or the trusted renewal signature, stands for it. This exemption applies to requests that arrive through the SCEP gateway only.

Issuance policies still apply: a rule that blocks a request blocks it over SCEP too. SCEP has no field for a text reason, so the device receives a CertRep with status FAILURE and failInfo badRequest; the reason (the message written on the rule) is in the audit trail, in the POLICY_BLOCK event and in scep.handler_fail with the outcome rejected (since version 1.8.50; before, the refusal surfaced as a server error). If the policies cannot be evaluated, the request is refused rather than allowed (POLICY_EVAL_ERROR). A rule that would require approval cannot be honored on an automated channel; the request goes through and the event POLICY_AUTO_ALLOWED is recorded.

Audit and inventory

  • Each certificate appears in the inventory under the profile's organization, requested by the service account scep@pkifactor.com. Its request data keeps the SCEP path and the SCEP transaction identifier.
  • The PKIFactor audit trail (Oversight workspace, Compliance > Audit Trail) records the issuance (CERTIFICATE_ISSUED), policy refusals (POLICY_BLOCK), challenge generation (SCEP_CHALLENGE_MINTED) and refused requests without a challenge or trusted signer (SCEP_UNAUTHENTICATED_BLOCKED). See Audit.
  • The gateway writes one JSON line per step in its container log (scep.recv, scep.parsed, scep.cert_issued, scep.handler_fail...).

Client example

# Capabilities and CA chain
curl -s "http://scep.example.com/scep/scep-mdm-devices?operation=GetCACaps"
curl -s -o ca-ra.p7 "http://scep.example.com/scep/scep-mdm-devices?operation=GetCACert"
openssl pkcs7 -inform DER -in ca-ra.p7 -print_certs -out ca-ra.pem

# CSR carrying the challenge password
cat > device.cnf <<'EOF'
[req]
prompt = no
distinguished_name = dn
attributes = attrs
[dn]
CN = printer-042.example.com
[attrs]
challengePassword = scep_PASTE_THE_CHALLENGE_HERE
EOF
openssl req -new -newkey rsa:2048 -nodes -keyout device.key \
  -config device.cnf -out device.csr

# Enrollment with sscep
sscep getca -u http://scep.example.com/scep/scep-mdm-devices -c ca.crt
sscep enroll -u http://scep.example.com/scep/scep-mdm-devices \
  -c ca.crt -k device.key -r device.csr -l device.crt

When GetCACert returns several certificates, sscep getca writes them to numbered files; the request must be encrypted to the RA certificate (the first certificate returned). Check the options of your sscep version for the CA and encryption certificates. For an MDM platform, give it the SCEP address, the CA chain from GetCACert, and a way to obtain challenge passwords.

Troubleshooting

Symptom Cause and fix
404 on GetCACaps or GetCACert Wrong SCEP path, SCEP disabled on the profile, or profile inactive
404 on GetCACert while GetCACaps answers No CA chain is available for the profile's connector: fill in CA Chain (PEM) on the PKI connector
400 unsupported operation The operation parameter is missing or unknown (GetNextCACert is not supported)
400 PKIOperation replayed (transactionID/senderNonce already seen) The client resent a message already processed: it must build a new message (new nonce) for each attempt
400 PKIOperation signingTime outside acceptance window The device's clock differs from the gateway's by more than 10 minutes: fix the device's time
400 PKIOperation parse: ... The client encrypted to another certificate (fetch GetCACert again, especially after an RA rotation), used an unsupported cipher, or sent a message whose signature does not verify
CertRep with status FAILURE, failInfo badRequest No valid challenge: missing, already used, expired, issued for another profile, or not in the CSR's challengePassword attribute. Also returned when the authority or an issuance policy refuses the request (the POLICY_BLOCK event in the audit trail gives the rule and its message), and when a self-signed request is not signed with the CSR's key: check the audit trail and backend logs
CertRep FAILURE on a renewal without challenge Require challenge is on, or the signing certificate is unknown, revoked, or for another common name
503 SCEP gateway not configured: SCEP_BACKEND_API_KEY is missing The gateway has no service key for the backend: complete the installation secrets
The client cannot verify the response signature Check that it trusts the RA certificate returned by GetCACert. The sscep client was seen failing this check while the certificate was issued: look for it in the inventory
Enroll (SCEP) does nothing Before version 1.8.50, the window opened only when ACME was also enabled on the profile: upgrade, or generate the challenge through the API (see above)