Skip to main content

SCEP Certificate Enrollment

SCEP (Simple Certificate Enrollment Protocol, RFC 8894) is a widely supported protocol that allows devices to automatically obtain and renew certificates from a certificate authority. Unlike manually provisioned certificates, SCEP lets each device enroll itself using a pre-shared challenge password, which makes it practical to deploy certificates to large fleets of devices without touching each one individually.

A common use case is operational technology (OT) and IoT deployments, where sites host many devices that need to authenticate to local services or encrypt traffic between each other. For example, a utility operator may have energy meters distributed across substations; with SCEP each meter can enroll a certificate from the local site's CA at first boot and renew it automatically before expiry, without any manual certificate distribution. Similarly, a retail or facilities operator running on-site hardware — shop gates, access control panels, cameras — can use SCEP to give every device a unique identity certificate tied to the site's CA, enabling mutual TLS between devices and management systems running in Avassa-managed containers on the same site.

In both cases, Avassa acts as the SCEP server: the TLS CA lives in Strongbox on the Control Tower, and the SCEP endpoint is exposed on each site so that devices can reach it locally even when connectivity to the cloud is intermittent.

When SCEP is enabled on a TLS CA, devices can request certificates over HTTP or HTTPS without requiring manual intervention.

For the background on device enrollment, and where SCEP sits relative to EST, see Device certificate enrollment.

note

SCEP enrollment requires an RSA-keyed CA. ECDSA-keyed CAs are not supported because SCEP uses RSA key transport (PKCS#1 v1.5) to protect the enrollment message. Use cert-key-type: rsa when creating the CA.

Enable SCEP on a TLS CA

Create a TLS CA with SCEP enabled and a challenge password that enrolling devices must present:

supctl create strongbox tls ca <<EOF
name: device-ca
cert-key-type: rsa
ttl: 2y
auto-renew:
renew-threshold: 90d
activate-threshold: 60d
scep:
enabled: true
challenge-password: my-secret-password
ttl: 365d
EOF

The challenge-password is a pre-shared secret that all enrolling devices must include in their PKCS#10 CSR. The ttl under scep controls how long the issued device certificates will be valid; if omitted it defaults to the CA-level TTL.

Find the SCEP enrollment URLs

Show the CA to retrieve the SCEP endpoint URLs:

supctl show strongbox tls ca device-ca

The output includes the scep section with the computed URLs:

name: device-ca
scep:
enabled: true
ttl: 365d
http-url:
- http://control-tower.example.com:4664/scep/a1b2c3d4-.../device-ca
- http://api.internal:4664/scep/a1b2c3d4-.../device-ca
- http://192.168.1.10:4664/scep/a1b2c3d4-.../device-ca
https-url:
- https://api.internal:4646/scep/a1b2c3d4-.../device-ca
...

http-url and https-url are lists that include several variants:

  • FQDN (control-tower.example.com) — for devices that have DNS configured
  • Local IP (192.168.1.10) — for devices without DNS, using the node's actual IP address on the local network
  • api.internal — reachable from containers running on the site

The IP-based URLs reflect the addresses of the node that handles the request. When the command is run against a specific site using --site, the IPs shown are the site's local addresses, which is what devices on that site need.

All URLs embed the tenant UUID and CA name so each tenant and CA combination has its own isolated SCEP endpoint.

Restrict which names devices may request

By default, when allowed-domains is empty, any CN or DNS SAN is accepted. Use allowed-domains together with allow-subdomains to limit what names devices may request:

supctl update strongbox tls ca device-ca <<EOF
scep:
enabled: true
challenge-password: my-secret-password
allowed-domains:
- devices.example.com
allow-subdomains: true
allow-ip-sans: false
ttl: 365d
EOF

With this configuration:

  • A CSR with CN router1.devices.example.com is accepted because it is a subdomain of devices.example.com and allow-subdomains is true.
  • A CSR with CN router1.other.com is rejected.
  • A CSR with an IP address SAN is rejected because allow-ip-sans is false (the default).

To allow any name (for example, during initial testing), set allow-any-name:

supctl update strongbox tls ca device-ca <<EOF
scep:
enabled: true
challenge-password: my-secret-password
allow-any-name: true
ttl: 365d
EOF

SCEP operations

The SCEP endpoint supports the following standard operations:

OperationDescription
GetCACertRetrieve the CA certificate (or certificate chain for intermediate CAs)
PKCSReqEnroll a new certificate by submitting a PKCS#10 CSR
GetCRLRetrieve the current Certificate Revocation List
GetCertNot currently supported; returns a failure response

Enroll a device

Any standard SCEP client works. With sscep, first retrieve the CA certificate:

sscep getca \
-u http://control-tower.example.com:4664/scep/a1b2c3d4-.../device-ca \
-c ca.crt

Generate a key and CSR for the device, then enroll:

openssl genrsa -out device.key 2048
openssl req -new -key device.key -out device.csr \
-subj "/CN=router1.devices.example.com"

sscep enroll \
-u http://control-tower.example.com:4664/scep/a1b2c3d4-.../device-ca \
-c ca.crt \
-k device.key \
-r device.csr \
-p my-secret-password \
-l device.crt

On success, device.crt contains the issued certificate signed by device-ca.

Some clients will not pass the challenge password on the command line and expect it inside the request instead, as the PKCS#9 challengePassword attribute:

cat > req.cnf <<'EOF'
[req]
prompt = no
distinguished_name = dn
attributes = attrs
[dn]
CN = router1.devices.example.com
[attrs]
challengePassword = my-secret-password
EOF
openssl req -new -key device.key -out device.csr -config req.cnf

With strongSwan's pki instead of sscep:

pki --scep --url http://control-tower.example.com:4664/scep/a1b2c3d4-.../device-ca \
--in device.key \
--dn "CN=router1.devices.example.com" \
--password my-secret-password \
--cacert-enc ca.crt --cacert-sig ca.crt \
--outform pem > device.crt

Confirm what was issued:

openssl x509 -in device.crt -noout -subject -issuer -dates

Renew a device certificate

SCEP has no separate renewal operation here: a device renews by enrolling again before its certificate expires. Choose a ttl that leaves comfortable margin for a device that has been unreachable for a while, and schedule re-enrollment well inside it.

If your devices can speak EST instead, prefer it. EST renews against the device's current certificate, so renewal needs no shared secret at all.

Revoke an issued certificate

Certificates issued through SCEP are ordinary certificates of the CA, so they are revoked the same way as any other, and appear in the CA's CRL and OCSP responses. See Certificate revocation for the CA-level operations.

Troubleshooting

The request is refused. Check, in this order:

  1. Challenge password. It must match the CA's challenge-password exactly, and must reach the server as the PKCS#9 challengePassword attribute. Some clients need it in the CSR rather than on the command line, as shown above.
  2. Requested identity. The CN and any DNS SANs must satisfy allowed-domains together with allow-subdomains and allow-bare-domains. A request for router1.devices.example.com is refused if only devices.example.com is listed and allow-subdomains is false.
  3. IP SANs. Refused unless allow-ip-sans is true.
  4. Key strength. Requests with keys weaker than 2048-bit RSA are refused.

The CA will not serve SCEP at all. Confirm the CA has an RSA key (cert-key-type: rsa). An ECDSA-keyed CA cannot serve SCEP enrollment.

The device cannot reach the endpoint. The enrollment port is reachable from the application network and from the configured management addresses. Verify the device is on a network permitted to reach it, and that you picked a URL form the device can resolve.

Disable SCEP

Remove the scep container to disable the SCEP endpoint for a CA:

supctl delete strongbox tls ca device-ca scep

After this, any SCEP request to the endpoint returns HTTP 404.