mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 23:42:00 +00:00
feat(M51): add SCEP server (RFC 8894) for MDM and network device enrollment
Implements Simple Certificate Enrollment Protocol with single-endpoint operation-based dispatch (GetCACaps, GetCACert, PKIOperation), PKCS#7 SignedData CSR extraction with fallback for raw/base64 CSR, challenge password authentication via CSR attributes, and shared internal/pkcs7 package extracted from EST handler to eliminate code duplication. 24 new tests (11 service + 13 handler) plus 5 shared pkcs7 package tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -673,6 +673,46 @@ type ESTService interface {
|
||||
|
||||
**Audit:** Every EST enrollment is recorded in the audit trail with `protocol: "EST"`, the CN, SANs, issuer ID, serial number, and optional profile ID.
|
||||
|
||||
### SCEP Server (RFC 8894)
|
||||
|
||||
The SCEP (Simple Certificate Enrollment Protocol) server provides certificate enrollment for MDM platforms and network devices. It runs at `/scep` with operation-based dispatch via query parameters per RFC 8894.
|
||||
|
||||
**Architecture:** SCEP follows the exact same layering as EST — a handler-level protocol that delegates certificate issuance to an existing `IssuerConnector`. The `SCEPService` bridges the `SCEPHandler` to whichever issuer connector is configured via `CERTCTL_SCEP_ISSUER_ID`.
|
||||
|
||||
```
|
||||
Client (MDM, network device, SCEP client)
|
||||
│
|
||||
▼
|
||||
SCEPHandler (handler layer)
|
||||
│ PKCS#7 envelope parsing, CSR extraction, challenge password extraction
|
||||
▼
|
||||
SCEPService (service layer)
|
||||
│ Challenge password validation, CSR validation, CN/SAN extraction, audit recording
|
||||
▼
|
||||
IssuerConnector (connector layer via IssuerConnectorAdapter)
|
||||
│ Certificate signing (Local CA, step-ca, etc.)
|
||||
▼
|
||||
Signed certificate returned as PKCS#7 certs-only
|
||||
```
|
||||
|
||||
**Wire format:** SCEP clients wrap CSRs in PKCS#7 SignedData envelopes. The handler parses the outer ASN.1 ContentInfo → SignedData → EncapsulatedContentInfo to extract the CSR bytes. Fallback paths handle base64-encoded PKCS#7 and raw CSR submissions (for simpler clients). Responses use PKCS#7 certs-only via the shared `internal/pkcs7` package (same as EST). Single certs are returned as raw DER for `GetCACert`, chains as PKCS#7.
|
||||
|
||||
**Authentication:** SCEP uses challenge passwords embedded in CSR attributes (OID 1.2.840.113549.1.9.7) rather than TLS client certificates. The server validates the challenge password against `CERTCTL_SCEP_CHALLENGE_PASSWORD`. When no challenge password is configured, any value is accepted.
|
||||
|
||||
**Interface:** The `SCEPHandler` defines an `SCEPService` interface (dependency inversion):
|
||||
|
||||
```go
|
||||
type SCEPService interface {
|
||||
GetCACaps(ctx context.Context) string
|
||||
GetCACert(ctx context.Context) (string, error)
|
||||
PKCSReq(ctx context.Context, csrPEM string, challengePassword string, transactionID string) (*domain.SCEPEnrollResult, error)
|
||||
}
|
||||
```
|
||||
|
||||
**Shared PKCS#7 package:** Both EST and SCEP handlers share a common `internal/pkcs7` package for building PKCS#7 certs-only responses and PEM-to-DER chain conversion, eliminating code duplication between the two enrollment protocols.
|
||||
|
||||
**Audit:** Every SCEP enrollment is recorded in the audit trail with `protocol: "SCEP"`, the CN, SANs, issuer ID, serial number, transaction ID, and optional profile ID.
|
||||
|
||||
## Security Model
|
||||
|
||||
### Private Key Management
|
||||
|
||||
+4
-4
@@ -314,16 +314,16 @@ Each issuer handles revocation differently:
|
||||
- **step-ca**: Calls step-ca's `/revoke` API endpoint. Clients should check step-ca's own CRL/OCSP for authoritative status.
|
||||
- **OpenSSL/Custom CA**: Invokes the configured revoke script (`CERTCTL_OPENSSL_REVOKE_SCRIPT`) with the serial number as an argument.
|
||||
|
||||
### EST Integration (GetCACertPEM)
|
||||
### EST/SCEP Integration (GetCACertPEM)
|
||||
|
||||
The `GetCACertPEM()` method returns the PEM-encoded CA certificate chain, used by the EST server's `/.well-known/est/cacerts` endpoint (RFC 7030) to distribute the CA chain to enrolling devices. Each issuer handles this differently:
|
||||
The `GetCACertPEM()` method returns the PEM-encoded CA certificate chain, used by both the EST server's `/.well-known/est/cacerts` endpoint (RFC 7030) and the SCEP server's `GetCACert` operation (RFC 8894) to distribute the CA chain to enrolling devices. Each issuer handles this differently:
|
||||
|
||||
- **Local CA**: Returns the CA certificate PEM (self-signed or sub-CA cert). This is the primary EST issuer.
|
||||
- **Local CA**: Returns the CA certificate PEM (self-signed or sub-CA cert). This is the primary EST/SCEP issuer.
|
||||
- **ACME**: Returns error — ACME CAs provide chains per-issuance, not statically.
|
||||
- **step-ca**: Returns error — step-ca serves its own `/root` endpoint for CA distribution.
|
||||
- **OpenSSL/Custom CA**: Returns error — custom script-based CAs have no CA cert access through certctl.
|
||||
|
||||
Note: EST (Enrollment over Secure Transport) is not a connector — it's a protocol handler (`internal/api/handler/est.go`) that delegates certificate issuance to whichever issuer connector is configured via `CERTCTL_EST_ISSUER_ID`. See the [Architecture Guide](architecture.md#est-server-rfc-7030) for details.
|
||||
Note: EST and SCEP are not connectors — they are protocol handlers (`internal/api/handler/est.go` and `internal/api/handler/scep.go`) that delegate certificate issuance to whichever issuer connector is configured via `CERTCTL_EST_ISSUER_ID` or `CERTCTL_SCEP_ISSUER_ID`. Both share a common `internal/pkcs7` package for PKCS#7 response encoding. See the [Architecture Guide](architecture.md#est-server-rfc-7030) for details.
|
||||
|
||||
### Built-in: Vault PKI
|
||||
|
||||
|
||||
@@ -444,6 +444,27 @@ Accepts both base64-encoded DER (EST standard) and PEM-encoded PKCS#10 CSR input
|
||||
| `CERTCTL_EST_ISSUER_ID` | `iss-local` | Issuer for EST enrollments |
|
||||
| `CERTCTL_EST_PROFILE_ID` | (none) | Optional profile constraint |
|
||||
|
||||
### SCEP Server (RFC 8894)
|
||||
|
||||
<!-- Source: internal/service/scep.go, internal/api/handler/scep.go -->
|
||||
|
||||
Simple Certificate Enrollment Protocol for MDM platforms and network devices. Single endpoint with operation-based dispatch:
|
||||
|
||||
| Operation | Method | Description |
|
||||
|---|---|---|
|
||||
| `GetCACaps` | GET | Server capabilities (plaintext, one per line) |
|
||||
| `GetCACert` | GET | CA certificate (DER for single cert, PKCS#7 for chain) |
|
||||
| `PKIOperation` | POST | Certificate enrollment (PKCS#7-wrapped or raw CSR) |
|
||||
|
||||
SCEP uses a single URL (`/scep?operation=...`). The handler extracts PKCS#10 CSRs from PKCS#7 SignedData envelopes, with fallback support for base64-encoded and raw CSR submissions. Challenge password authentication via CSR attributes (OID 1.2.840.113549.1.9.7). Responses are PKCS#7 certs-only (same shared `internal/pkcs7` package as EST).
|
||||
|
||||
| Env Var | Default | Description |
|
||||
|---|---|---|
|
||||
| `CERTCTL_SCEP_ENABLED` | `false` | Enable SCEP endpoint |
|
||||
| `CERTCTL_SCEP_ISSUER_ID` | `iss-local` | Issuer for SCEP enrollments |
|
||||
| `CERTCTL_SCEP_PROFILE_ID` | (none) | Optional profile constraint |
|
||||
| `CERTCTL_SCEP_CHALLENGE_PASSWORD` | (none) | Shared secret for enrollment authentication |
|
||||
|
||||
---
|
||||
|
||||
## ACME Renewal Information (RFC 9773)
|
||||
|
||||
Reference in New Issue
Block a user