Device enrollment with CMP
CMP protocol guide for Lamassu IoT, with the complete reference of DMS general and per-operation settings, and the operation reference for device integrators.
Lamassu IoT implements the CMP (RFC 4210) protocol following the Lightweight CMP (RFC 9483) profile as its second certificate enrollment mechanism for devices, alongside EST (RFC 7030). Each DMS exposes one of the two protocols — never both at once — and the one that exposes CMP acts as the Registration Authority (RA) facing the client.
Compared with EST, CMP changes three fundamental ideas:
| Aspect | EST (RFC 7030) | CMP (RFC 4210 / RFC 9483) |
|---|---|---|
| Endpoint | One per operation (/cacerts, /simpleenroll, …) | A single multiplexed endpoint (/.well-known/cmp/p/{id}); the operation is determined by the message content |
| Authentication | mTLS at the transport layer | Protection of the message itself (PKIMessage) by signature or, in ir/cr, CRMF proof of possession |
| Settings | General enrollment and re-enrollment blocks | General settings plus a per-operation policy block (ir, cr, p10cr, kur, rr, genm, ccr) |
| Confirmation | The response is the end of the flow | An explicit confirmation protocol (certConf), negotiable implicit confirmation and stateful transactions |
| Renewal | /simplereenroll with the current certificate | kur protected by the certificate being renewed, with per-DMS key and identity policy |
The protocol is selected with the DMS's protocol field (EST_RFC7030 or CMP_RFC9483) and each has its own configuration block (est_settings / cmp_settings). Changing the protocol of an existing DMS replaces the previous protocol's block entirely: settings are not migrated from one to the other.
This guide is split in two parts:
- DMS configuration, for the PKI administrator: every available setting, general and per-operation.
- Device integration, for anyone consuming the CMP endpoint from firmware, standard CMP libraries or provisioning services.
If you only need a quick orientation:
| Profile | Read first | Then |
|---|---|---|
| PKI administrator | Protocol selection and Per-operation settings | CMP transactions and Confirmation monitor |
| Device integrator | The CMP endpoint and Authentication model | Supported operations and Error codes and diagnostics |
Before configuring a DMS for CMP, the following resources must exist on the platform:
- An available CA: the CA acting as
EnrollmentCAmust be created and active. If server-side key generation (KGA) is enabled, the CAs against which the recipient certificate is validated must also exist (see Server-side key generation). - A protection certificate: CMP signs its own responses. The DMS needs an End Entity certificate whose private key lives in the Lamassu KMS (
protection_certificate, see below). - A created DMS: the CMP configuration is applied over an existing DMS, it does not create one.
The CA Distribution settings (which authorities the device receives as anchors of trust) are identical for both protocols and are described in the DMS overview.
When creating or editing a DMS, the protocol field decides which configuration block applies:
| Value | Protocol |
|---|---|
EST_RFC7030 | EST — see the EST guide |
CMP_RFC9483 | CMP — this page |
Any value other than these two makes DMS creation or edition fail with the error DMS enrollment protocol must be EST_RFC7030 or CMP_RFC9483. When the protocol of an existing DMS changes, the outgoing protocol's block is discarded entirely and replaced by the incoming one: each protocol's settings are self-contained and are not copied across.
These fields are common to both protocols and mean the same in each:
| Field | Description |
|---|---|
enrollment_ca | CA that will sign the identity certificates issued by this DMS. |
registration_mode | JITP (Just-In-Time Provisioning) creates the device automatically on the first enrollment, applying the provisioning profile; PRE_REGISTRATION requires the device to exist beforehand and rejects anything else with device not preregistered. |
device_provisioning_profile | Icon, color, metadata and tags applied to each device created in JITP mode. |
enable_replaceable_enrollment | Allows an already-enrolled device to enroll again. When inactive, a second ir/cr attempt against an existing device is rejected with forbiddenNewEnrollment. Whether replacing the identity revokes the previous certificate (with the reason superseded) is decided by revoke_on_reenrollment, or by cr's certificate_behavior — see Re-enrollment configuration. |
verify_csr_signature | No effect in CMP. The server synthesizes the CSR from the CertTemplate (its signature is replaced with a dummy value) and proof of possession is established through other means: the POPO of ir/cr, the PKCS#10 CSR of p10cr (always verified) or the protection of kur. |
auth_mode defines how the DMS validates the requester's identity. CMP recognizes the same four modes as EST, but the mechanism changes: authentication is verified inside the CMP message itself, not at the transport layer.
| Mode | Description |
|---|---|
CLIENT_CERTIFICATE | The message must be protected by signature with the device's certificate (extraCerts[0] of the PKIMessage), validated against the CAs declared in client_certificate_settings.validation_cas. This is not mTLS: it is the signature over the PKIMessage (RFC 9483 §3.2). |
CLIENT_CERTIFICATE_AND_EXTERNAL_WEBHOOK | Combines both controls: valid signature and webhook approval. |
EXTERNAL_WEBHOOK | The authorization decision is delegated to an external HTTP service, with the same contract as in EST. The message may arrive unprotected. |
NO_AUTH | No mandatory authentication. The message may arrive unprotected; if it carries a signature, it is still validated. For laboratories or isolated provisioning networks only. |
A DMS whose auth_mode is empty or undefined is considered misconfigured: every request is rejected with DMS auth mode not supported, instead of being accepted unauthenticated. The value NONE is accepted as a legacy alias of NO_AUTH from the admin console.
When the mode includes CLIENT_CERTIFICATE, the same parameters as in EST are configured: Validation CAs, Chain Validation Level (-1 validates the full chain) and Allow Authenticating Expired Certificates. When it includes EXTERNAL_WEBHOOK, the same webhook parameters as in EST are configured (URL, method, timeout — 10 seconds by default — and the webhook's authentication mode). The webhook service contract is identical in both protocols; see the equivalent section of the EST guide for the detail of each field and the authorization webhook reference for the schema.
Unlike EST, CMP has no separate authentication mode for renewal: kur is always authenticated with the signature protection of the certificate being renewed.
Unlike EST, CMP includes an explicit confirmation protocol (RFC 4210 §5.2.8) and supports a deferred-approval flow:
| Field | Type | Description |
|---|---|---|
accept_implicit | boolean | Allows skipping the certConf step when the device requests implicit confirmation (id-it-implicitConfirm in generalInfo). It only takes effect if the device also requests it; the request alone does not force it. Disabled by default. |
confirmation_timeout | duration | Waiting window for the certConf (for example 30s, 5m, 1h). A zero or negative value uses the default: 5 minutes. |
workflow | direct / phased | direct (default) issues the certificate online, in the same response to ir/cr/kur/p10cr. phased holds the request until an administrator approves it; the device retrieves the result with pollReq. Server-side key generation and cross-certification have their own flow and do not depend on this field. |
approval_timeout | duration | Only applies with workflow: phased. Time a transaction remains PENDING awaiting approval. With no value, the default is 7 days. |
- Protection Certificate (
protection_certificate): serial number of the End Entity certificate, backed by the KMS, with which the DMS protects (signs) its CMP responses. The server does not require it, but without it every response — including error messages — goes out unprotected, and standard CMP clients (openssl cmpincluded) reject unprotected responses by default. In practice it is essential. - Enforce POPO (
enforce_popo): a legacy option that no longer governs any verification. The proof-of-possession policy ofir/crlives in its per-operation block (proof_of_possession.required),kurandrrare always signature-protected as protocol invariants, and thep10crCSR signature is always verified. The field persists and round-trips through the API, but its value does not change behavior. - Expected Authenticator (
expected_authenticator): shared answer compared against theid-regCtrl-authenticatorcontrol (RFC 4211 §6.2) when an issuance request (ir/cr/kur) includes it. Empty by default: a present control is accepted without comparing its value. With a value configured, a control whose content does not match is rejected withincorrectData. Marked as legacy: it stores a plaintext secret on the DMS; new configuration should use the per-operationregistration_token/authenticator_controlcontrols, which store only a mode.
Besides the general fields above, each CMP operation has its own configuration block with an enabled switch and a specific policy. The issuance operations (ir, cr, p10cr, kur) also support policy_overrides:
policy_overrides.workflow:inherit(takes the generalworkflow),directorphased.policy_overrides.confirmation:inherit,implicit(forces willingness to accept implicit confirmation) orexplicit(forces thecertConf).policy_overrides.issuance_profile_id: pins a specific issuance profile for that operation. It applies inir,crandp10cr;kuralways renews with the DMS's general profile.
Effective implicit confirmation always additionally requires the device to request it (id-it-implicitConfirm): the setting only expresses the server's willingness.
Per-operation blocks are defaulted as a whole
Each per-operation block detects whether it has never been configured by observing a key field: identity_source in ir, certificate_behavior in cr, key_policy in kur, authorization in rr, access_policy in genm and workflow in ccr. If that field arrives empty, the block is filled in entirely with its default values on every read of the DMS — including any other field that was sent. To adjust a secondary field (for example, cr's maximum_active_certificates) you must also set the block's key field, or resolution resets it to the default. p10cr has no key field: it stays disabled unless enabled: true is set explicitly.
Initial enrollment of a new device (RFC 9483 §4.1.1), the equivalent of EST's /simpleenroll. Enabled by default.
| Field | Description |
|---|---|
enabled | Enables the operation. Disabling it rejects any ir with notAuthorized. |
proof_of_possession.required | Whether the POPO is mandatory. Default true: an ir without an acceptable proof of possession is rejected with badPOP. |
proof_of_possession.allowed_methods | Accepted POPO methods: signature (POPOSigningKey over the CertRequest), trusted_ra (raVerified from a trusted RA with EKU id-kp-cmcRA), challenge_response (challenge-response popdecc/popdecr) and encrypted_certificate (the certificate is delivered encrypted to the recipient, who proves possession by decrypting it). Default signature + trusted_ra. An empty list admits no method. |
registration_token | RFC 4211 §6.1 control with mode disabled (default), optional or required. The mode governs the presence of the control: disabled rejects an ir that carries it; required demands it; optional processes it when present. regToken values are one-use credentials provisioned outside the DMS (never stored in the configuration): an already-consumed value is rejected with the regToken control was already used (RFC 4211 §6.1), and the token is burned even if issuance later fails. |
authenticator_control | RFC 4211 §6.2 control with the same three modes. With expected_authenticator configured, optional/required validate the value; without it, presence is enough. |
central_key_generation.allowed_recipient_methods | Key-delivery methods this operation accepts for the generated key: rsa_key_transport and/or ecdh_key_agreement. Unset accepts both; an explicitly empty list denies any generated-key delivery. |
central_key_generation.enabled | Merges with the general server_key_gen_enabled switch and with cr's into a single KGA gate (see Server-side key generation). |
identity_source | Selects where the identity is taken from when the subject arrives empty (subject_only / subject_or_san). Persisted but not yet enforced: the current behavior is fixed — with an empty subject, the identity is derived from the SAN (dNSName, email, URI, IP, in that order) and the issued certificate keeps the NULL-DN subject. |
policy_overrides | See the introduction of this section. |
Issuance for a device that already participates in the PKI (RFC 9483 §4.1.2). Enabled by default. Shares with ir the proof_of_possession, central_key_generation and policy_overrides structure, and adds:
| Field | Description |
|---|---|
require_existing_device | Default true: a cr against an unregistered device is rejected with certification request requires a pre-existing device identity. Disabling it lets a cr also register the device (subject to the registration mode and, if the device already exists, to enable_replaceable_enrollment). |
certificate_behavior | additional (default) issues one more certificate without revoking the previous one; replace replaces the active certificate (the previous one is revoked as superseded after the new one is issued and bound). The data model keeps a single active identity slot per device, so additional does not create a second active identity in parallel: it only avoids revoking the previous one. |
maximum_active_certificates | Cap on non-revoked certificates per device. Default 2; 0 disables the cap. The count is revalidated under lock right before signing, to close the race between two concurrent cr requests. |
allowed_profile_ids | Allow-list of issuance profiles permitted for this operation. The resolved profile (pinned by policy_overrides.issuance_profile_id or the DMS's general one) must be on the list; empty does not restrict. |
cr also inherits the general enrollment safeguards: the device must belong to this DMS (a device registered to another DMS is rejected with device already registered to another DMS) and, if it already exists, enable_replaceable_enrollment is required. When a cr with replace replaces the identity, kur's key_policy also applies: with require_new_key, the cr cannot replace the active certificate using the same public key.
Enrollment with a complete PKCS#10 CSR instead of the CRMF structure (RFC 9483 §4.1.4). Shipped disabled: enabled: true must be set on the DMS or every p10cr request is rejected with notAuthorized.
| Field | Description |
|---|---|
enabled | Enables the operation. Disabled by default. |
allowed_profile_ids | Allow-list of permitted issuance profiles, same as in cr. |
policy_overrides | See the introduction of this section. |
Fixed protocol invariants, not configurable: the CSR's own signature is the proof of possession and is always verified; there is no CRMF POPO to configure; server-side key generation is not available (the client supplies its key); and the CRMF registration controls (regToken, authenticator) do not apply. The response is a cp with certReqId -1, the value the subsequent certConf must use (RFC 4210 Errata 8806).
Renewal of a certificate (RFC 9483 §4.1.3), the equivalent of EST's /simplereenroll. Enabled by default. The message must be protected by signature with the certificate being renewed — a protocol invariant, not configurable.
| Field | Description |
|---|---|
enabled | Enables the operation. |
key_policy | require_new_key (default) requires a public key different from the certificate being renewed (key update must present a new key); permit_reuse allows renewing without rotating the key. |
identity_change_policy | How much the identity may change relative to the current certificate: forbid (default; subject and SAN must match), san_only (the subject must match; the SAN may change) or subject_and_san (any change). |
policy_overrides.workflow / .confirmation | Same as in the other issuance operations. |
Fixed invariants: only one key update may be open per certificate (a second kur while the first awaits certConf is rejected with badRequest); the previous identity stays active until confirmation; and a never-confirmed new certificate is revoked when the window expires. The optional id-regCtrl-oldCertID control, if sent, must reference exactly the certificate being renewed (badCertId otherwise). policy_overrides.issuance_profile_id is defined in the schema but not yet enforced in kur.
Revocation over CMP (RFC 9483 §4.2). Enabled by default. The message must always be protected by signature — a protocol invariant, independent of auth_mode: an unsigned rr is never accepted, not even in NO_AUTH mode.
| Field | Description |
|---|---|
enabled | Enables the operation. |
authorization | self_only (default): only the device itself may revoke its certificate — the message signer must be the revoked certificate. self_and_trusted_ra: a trusted PKI management entity (EKU id-kp-cmcRA, validated against the DMS's CAs) may additionally revoke another's certificate. |
allow_revival | Disabled by default. Allows reactivating a revoked certificate through the reason removeFromCRL; revival is only possible for certificates whose revocation was a suspension (certificateHold). |
allowed_reasons | Allow-list of accepted revocation reasons. Default unspecified, key_compromise, cessation_of_operation and superseded. Accepted values: unspecified, key_compromise, ca_compromise, affiliation_changed, superseded, cessation_of_operation, privilege_withdrawn, aa_compromise. The reason certificateHold is not on the list and is not subject to it: it suspends the certificate and is the only reversible revocation; reactivation is governed by allow_revival. |
trusted_ra.require_cmc_ra_eku | Default true: requires the EKU id-kp-cmcRA on the RA signer. |
trusted_ra.validation_ca_ids | Fixes which CAs must validate the RA certificate; an explicit list replaces the general boundary. Empty: the DMS's general trust boundary applies (the EnrollmentCA plus the authentication and re-enrollment validation CAs). |
Operational notes: revoking an already-expired certificate is never permitted (the CA service rejects status transitions on expired certificates); the target certificate must belong to a device managed by this DMS — a trusted RA cannot revoke certificates of other DMSs (notAuthorized); while a certificate has a key update pending confirmation its status cannot change (badRequest); and an rr cannot be replayed with the same transactionID (transactionIdInUse).
Discovery channel (RFC 9483 §4.3): the device queries the DMS's capabilities and the DMS answers with a genp. It issues no certificates. Enabled by default.
| Field | Description |
|---|---|
access_policy | public_discovery (default) answers without requiring a protected message; require_signed rejects an unsigned genm with notAuthorized. |
preferred_symmetric_algorithm | Symmetric algorithm the DMS advertises for content encryption toward the CA: aes128_cbc, aes192_cbc, aes256_cbc (default), aes128_gcm, aes192_gcm or aes256_gcm. |
information_types.* | Independent switches per information type — see the table below. |
Each information_types.* switch corresponds to an RFC 9483 §4.3 information type. The purely descriptive (static) types are enabled by default; the ones that depend on the DMS's concrete configuration — its CAs, its issuance profile or its VA client — stay disabled until the operator enables them:
| Information type | OID (id-it-*) | Default | Response content |
|---|---|---|---|
ca_certificates | caCerts (17) | Enabled | Certificates of the DMS's CAs, with the same trust distribution as EST's /cacerts. |
signing_key_types | signKeyPairTypes (2) | Enabled | Supported signature algorithms: RSA and ECC (static value). |
encryption_key_types | encKeyPairTypes (3) | Enabled | Supported encryption/agreement algorithms: RSA (static value). |
preferred_symmetric_algorithm | preferredSymmAlg (4) | Enabled | The algorithm configured in preferred_symmetric_algorithm. |
supported_languages | supportedLangTags (16) | Enabled | Language negotiation for PKIFreeText; the only supported one is en. |
root_ca_update | rootCaCert (20) | Disabled | Update of the current root (newWithNew/newWithOld), compared against the root the device declares. |
certificate_request_template | certReqTemplate (19) | Disabled | Template derived from the DMS's issuance profile: supported key algorithms and sizes, and certificate validity. |
current_crl | currentCRL (6) | Disabled | Current CRL, served through the VA client of the DMS Manager service. |
crl_update | crlStatusList (22) | Disabled | CRL updates per issuer, only those newer than the one the device declares. |
protocol_encryption_certificate | caProtEncCert (1) | Disabled | Always answered without a value — Lamassu does not provision a dedicated protocol-encryption certificate. |
Exchange rules: a genm requesting a disabled type is rejected entirely with notAuthorized; the types caCerts, certReqTemplate, currentCRL, signKeyPairTypes, encKeyPairTypes and preferredSymmAlg must arrive without infoValue; supportedLangTags must arrive with the list of offered languages (if none is supported, badRequest); crlStatusList must carry at least one CRLStatus entry. An enabled type whose data provider has nothing to return (for example, a DMS without a VA client for the CRL types, or a root with no newer update) answers with the absent infoValue — a valid not available per the RFC, not an error. The type id-it-revPassphrase (RFC 4210bis §5.3.19.9) is permanently disabled: it is not wired into the revocation flow.
Cross-certification between CAs (RFC 4210bis §5.3.11): a CA asks the DMS's enrollment CA for a cross-certificate for its identity. It is a privileged CA-to-CA operation, disabled by default.
| Field | Description |
|---|---|
enabled | Enables the operation. Disabled by default. |
workflow | administrator_approval (default) or direct. It is independent of the general enrollment workflow: the administrative approval of a ccr is not mixed with that of ir/cr. With administrator_approval, the request stays PENDING (type ccr) and the requesting CA retrieves the cross-certificate with pollReq. |
requester_mode | any (default): any signer that passes require_ca_certificate may request. restricted: the signer must chain to a CA in trusted_requester_ca_ids — an empty list in restricted mode authorizes nobody. |
trusted_requester_ca_ids | CAs authorized in restricted mode. |
require_ca_certificate | Default true: the signer's protection certificate must be a CA certificate. |
require_proof_of_possession | Default true: requires a POPOSigningKey POPO over the CertTemplate key. EncryptedKey POPOs (which would disclose the requester's private key) are always rejected, with or without this option. |
maximum_validity | Maximum validity of the cross-certificate. Default 8760 hours (365 days). The requested notBefore is honored as-is (it may be in the past) and the notAfter is truncated to this limit, not rejected. |
subject_constraints | Identity constraints of the cross-certificate: allowed_dn_patterns (substring match of the subject in RFC 2253 format) and allowed_dns_suffixes (suffix match of the dNSName SAN). An empty list on a dimension imposes no constraint on that dimension — unlike the central_key_generation list, which denies everything when empty. |
issuance_profile_id | Persisted but not yet enforced: the cross-certificate is issued with a fixed internal profile (CMP Cross Certification), which produces a CA certificate with keyCertSign, cRLSign and digitalSignature. |
The ccr additionally requires: a complete CertTemplate (version, signature algorithm, issuer, validity, subject and public key; version v3 or v1, not v2), a signing-capable key (agreement-only keys such as X25519/X448 are rejected) and no request for server-side key generation. It is not intended for device enrollment, but for interoperability between PKIs.
CMP allows the DMS itself to generate the device's key pair when the request (ir/cr) arrives with an empty public key in the CertTemplate (RFC 9483 §4.1.6, also called KGA, Key Generation Authority).
| Field | Type | Description |
|---|---|---|
server_key_gen_enabled | boolean | Enables server-side key generation. Disabled by default: a request with an empty public key is rejected with notAuthorized. This switch and the central_key_generation.enabled of ir/cr resolve into a single gate: enabling any of the three is enough, and the resulting value is written back to all three. |
ckg_trusted_encryption_cas | list of CA IDs | CAs the recipient certificate (the certificate the generated key is encrypted to — the requester's own protection certificate) must chain to. Empty by default: in that case the DMS's general trust boundary applies (the EnrollmentCA plus the validation CAs of auth_mode and of reenrollment_settings.additional_validation_cas). An explicit list replaces that boundary: the recipient must chain to one of the listed CAs. Recipient validation is unconditional, also with auth_mode: NO_AUTH: KGA delivers private key material, so it is a trust boundary independent of the authentication mode. |
The server flow: it generates the key with the algorithm implied by the request (RSA-2048 if the CertTemplate declares no algorithm; ECDSA P-256 if it declares ECC), issues the certificate through the normal enrollment path and returns in the same ip/cp response the certificate together with the PKCS#8 private key wrapped in a CMS EnvelopedData(SignedData(AsymmetricKeyPackage)) structure. The recipient is the requester's protection certificate: with an RSA key it uses key transport (rsa_key_transport), which requires keyEncipherment on the recipient; with an ECC key it uses ECDH key agreement (ecdh_key_agreement), which requires keyAgreement. A recipient outside the trust boundary is rejected with signerNotTrusted.
Security notice: KGA uses software cryptography, not the hardware engine
The KGA-generated key is produced with the process's software cryptographic engine (not AWS KMS, PKCS#11 or any hardware Crypto Engine), because the key must be exportable in encrypted form to the device — something fundamentally incompatible with an HSM that never exposes private keys. The key exists in process memory only for the duration of the request and is never persisted: if the device loses the response, it cannot recover it with pollReq — it must repeat the enrollment with a new transactionID. For transport, the DMS also mints ephemeral helper certificates (Lamassu CMP KGA Signer with EKU id-kp-cmKGA, and Lamassu CMP KARI Originator for ECDH agreement), valid for one hour and signed by the enrollment CA. Prefer on-device key generation whenever possible; reserve KGA for devices that genuinely cannot generate their own key.
The reenrollment_settings section of the CMP block governs renewal (kur) with the same fields as EST:
| Field | Effect in CMP |
|---|---|
additional_validation_cas | Additional CAs accepted as issuers of the current certificate during renewal. It allows migrating to a new enrollment CA without interrupting the renewal of already-enrolled devices. |
revoke_on_reenrollment | Revokes the previous certificate with the reason superseded once the new one is issued and confirmed. In initial enrollment (ir/cr/p10cr) it controls whether replacing the identity revokes the previous certificate. |
enable_expired_renewal | Allows using an already-expired certificate as the protection credential of the kur. |
reenrollment_delta | Not applied in CMP. The pre-expiry window that limits EST's /simplereenroll does not restrict CMP's kur; the policy of when to renew is the device's. |
preventive_delta / critical_delta | Thresholds for preventive and critical renewal alerts. They are materialized on the certificate's metadata when the identity is bound, as in EST. |
The kur key and identity policy (what may change and whether the key must rotate) lives in its per-operation block, described in Per-operation settings.
Every ir/cr/kur/p10cr/ccr received by a DMS over CMP is recorded as a CMP transaction, identified by the hexadecimal transactionID of the PKIHeader. The record keeps the state, the issued certificate (without the DER in the API response), the proof-of-possession method used and the auth_mode in effect at enrollment time. When the WFX integration is active, each transaction transition is also reflected as a workflow job with the exchange messages attached — see WFX integration.
| State | Meaning |
|---|---|
PENDING | Request accepted, awaiting issuance: a phased flow waiting for approval, or an ir/cr waiting for the popdecr of a challenge-response POPO. |
ISSUED | Certificate issued, awaiting the device's certConf. |
CONFIRMED | The device confirmed receipt with certConf (or implicit confirmation was granted). Terminal state. |
ISSUE_FAILED | The CA rejected issuance or an administrator rejected the transaction. The reason remains available to the device via pollReq. Terminal state. |
REVOKED | The issued certificate was revoked (by expiry of the confirmation window or by a later rr). Terminal state. |
Internally, the server also uses the transient states APPROVING and REVOKING as lock markers while it resolves an administrative approval or a revocation: they usually last only instants and could only be observed if the service is interrupted mid-operation, in which case the monitoring job finalizes them automatically. The states exposed by the listing API are only the five above.
| Endpoint | Description |
|---|---|
GET /v1/dms/{id}/cmp/transactions | Paginated list of the DMS's transactions (page_size, bookmark, sort_by, sort_mode with values asc/desc, filter). It does not include the certificate DER; retrieve it from the CAs API using certificate_serial_number. |
POST /v1/dms/{id}/cmp/transactions/{txid}/approve | Approves a PENDING transaction of a phased-flow DMS, issuing the certificate. The device retrieves it in its next pollReq. Approval and rejection use an atomic claim of the record, so two concurrent calls do not issue the certificate twice. After approval, the device's confirmation window becomes that of confirmation_timeout (minimum 2 minutes). |
POST /v1/dms/{id}/cmp/transactions/{txid}/reject | Rejects a PENDING transaction; it moves to ISSUE_FAILED with an optional reason ({"reason": "..."}; default transaction rejected by administrator) that the device receives in its next pollReq. |
Both operations require the read permission for the listing and the update permission to approve or reject, within the DMS's actions. Approvals and rejections only affect PENDING transactions: in workflow: direct a transaction is born already in ISSUED.
An ISSUED certificate that the device never confirms within confirmation_timeout is, by RFC 4210 §5.2.8's definition, in a state of indeterminate trust. A periodic background job — enabled and scheduled through the DMS Manager service configuration key cmp_confirmation_monitoring_job (enabled, frequency; every 5 seconds by default when global monitoring is active) — takes care of:
- Revoking unconfirmed certificates:
ISSUEDtransactions whose confirmation window expired are revoked at the CA with the reasoncessationOfOperationand move toREVOKED. The atomic claim of the record prevents revoking a certificate that a legitimatecertConfis confirming at that exact moment. - Closing pending approvals:
PENDINGtransactions inphasedflow that no administrator resolved withinapproval_timeoutmove toISSUE_FAILEDwith the reasonapproval window expired, and are kept for 7 days so a latepollReqcan still see the rejection reason.
Each run processes at most 100 transactions per batch, to avoid a burst of revocations after a prolonged service interruption.
If the service's WFX client is enabled (wfx.enabled in the DMS Manager configuration), every transition of a CMP transaction is reflected as a job in WFX (Siemens' workflow engine), under two predefined templates depending on the DMS's workflow: lamassu.cmp.transaction.direct.v1 for the direct flow and lamassu.cmp.transaction.phased.v1 for the flow with administrative approval (which adds the AwaitingApproval state). The workflow states — Received, Validated, Responded, AwaitingCertConf, Confirmed, LogicallyComplete (implicit confirmation) and Rejected — let you follow the lifecycle of each transaction from the WFX console; the job is identified by the device's CN and attaches the base64-encoded exchange messages. This is purely external observability/orchestration: if WFX is not enabled, or if a call to WFX fails, the CMP operation in Lamassu is unaffected.
- From the console: open the DMS detail and check that the CMP section shows the active protocol, the enrollment CA, the protection certificate and the enabled operations.
- With the transaction listing:
GET /v1/dms/{id}/cmp/transactionsconfirms the endpoint responds even without device traffic. - With a test request: use
openssl cmpwith theircommand (if you have a valid signer) orgenmagainst the DMS's endpoint — see Example with openssl cmp.
This section is aimed at the device integrator.
The PKI administrator must have configured a DMS with the CMP_RFC9483 protocol, an enrollment CA, a protection certificate and an authentication mode. The details of that configuration are in the DMS configuration section of this same page.
Before the first call, make sure you have:
| Item | Description |
|---|---|
| CMP host URL | Base address of the server, for example https://cmp.lamassu.example. Provided by the PKI administrator. |
| DMS ID | Identifier of the DMS, included in the endpoint path (/.well-known/cmp/p/{id}). |
| Message signing certificate and key | Only for the CLIENT_CERTIFICATE and CLIENT_CERTIFICATE_AND_EXTERNAL_WEBHOOK modes (and always for kur/rr). Unlike EST, it is not used as an mTLS credential: it is included as extraCerts[0] and signs the PKIMessage. |
| DMS protection certificate | The certificate with which the DMS signs its responses. Clients that pin the responder's identity (for example openssl cmp -srvcert) need it. |
CMP is served from a single endpoint:
POST /.well-known/cmp/p/{id}where {id} is the DMS identifier. The path follows the well-known URI format of RFC 9480 §3.3, so standard CMP clients recognize it without additional configuration (openssl cmp -server <host> -path /.well-known/cmp/p/<dms-id>).
- Content-Type:
application/pkixcmpin both the request and the response. A request with a differentContent-Typeis rejected with415 Unsupported Media Type. - Body: a
PKIMessagein binary DER (not base64, unlike the EST endpoints). The response is equally binary DER. - Dispatch by content, not by path: the server decodes the
PKIMessage, validates the envelope (protocol version,transactionID,senderNonce,messageTime) and dispatches the operation according to thePKIBodytag —ir,cr,p10cr,kur,rr,certConf,pollReq,genm,ccr,nested, etc. - No transport-layer authentication: the endpoint requires neither mTLS nor authorization headers; all authentication travels inside the message, as described in Authentication model.
- Maximum request size: 1 MiB. A larger request is rejected before decoding is attempted.
A rejection arrives with HTTP 200 in the form corresponding to the operation: as an error PKIMessage for failures before dispatch (operation disabled, invalid header, failed protection), as a rejection body inside the operation's own response (ip/cp/kup with status: rejection) for issuance policy failures, or as an rp body with a rejection status for revocations. In all cases you must inspect the CMP body, not just the HTTP code. The HTTP error codes (400, 415 and, for internal server failures, 500) are reserved for cases where a valid CMP response cannot be delivered — see Error codes and diagnostics.
CMP authenticates the message, not the connection: there is no mTLS handshake equivalent to EST's. When the DMS's auth_mode requires CLIENT_CERTIFICATE, the device must:
- Include its certificate in the
PKIMessage'sextraCertsfield (first position). - Sign the entire
PKIMessagewith the private key corresponding to that certificate (signature-based protection, RFC 9483 §3.2).
On top of the protected message, the server additionally enforces two RFC 9483 bindings: the header's sender field must match the protection certificate's subject, and the senderKID must be its SubjectKeyIdentifier. A protected request that violates either is rejected with badMessageCheck. If a message carries a signature even though the mode does not require one, the signature is still verified and those bindings apply.
The protection requirement per message type:
| Message | Protection mandatory? |
|---|---|
ir, cr, p10cr | Only if auth_mode is CLIENT_CERTIFICATE or CLIENT_CERTIFICATE_AND_EXTERNAL_WEBHOOK |
kur | Always (the signature is the renewal's proof of possession) |
rr | Always (the signature is what authorizes the revocation) |
genm | Depending on access_policy: public_discovery admits unprotected messages; require_signed demands a signature |
certConf, pollReq, popdecr, nested | Follow the DMS's auth_mode rule |
At the service layer, the protection certificate must chain to a CA declared in client_certificate_settings.validation_cas (or, in kur, to the enrollment CA and the additional re-enrollment CAs), with the same chain-depth and expired-certificate tolerance parameters as EST, and its revocation status is checked as in the EST flow. In addition, a certificate that was a device's identity remains subject to the identity's state: while a key update is pending confirmation it cannot start new operations, and once the replacement is confirmed it stops being usable as a credential (certRevoked). A certificate that is no device's identity (for example, a bootstrap signer) passes without those checks.
| Operation | Use | Requires prior identity |
|---|---|---|
ir | Initial enrollment of a new device. | No |
cr | Additional or replacement issuance for an already-identified device. | Yes (if require_existing_device) |
p10cr | Enrollment with a PKCS#10 CSR, without CRMF controls or KGA. Must be enabled on the DMS. | No |
kur | Renewal of a certificate; the message is signed with the current certificate. | Yes (the certificate being renewed) |
rr | Revocation of a certificate, always signed. | Yes |
genm/genp | Query of the DMS's capabilities (CAs, key types, current CRL, etc.). Issues no certificates. | No |
ccr/ccp | Cross-certification between CAs. For CA operators only, disabled by default. | The signer must be a CA |
nested | Wrapper that adds an extra layer of protection (an RA countersigning a device) or groups several messages into a batch. Not an operation in itself. | — |
The protocol steps certConf (confirmation), pollReq (polling), popdecc/popdecr (challenge-response proof of possession) and error are part of the lifecycle of the operations above, not operations the integrator chooses separately.
Both carry a CertReqMessages body with the certificate template (CertTemplate), the CRMF controls and the proof of possession. The subject may arrive empty (NULL-DN) as long as a SubjectAltName extension carries the identity; with neither subject nor SAN, the ir is rejected. Responses are ip for ir and cp for cr.
Enrollment respects the DMS's registration mode: with JITP, the first request creates the device with the provisioning profile; with PRE_REGISTRATION, an unknown device is rejected with device not preregistered. Re-enrolling an already-enrolled device requires enable_replaceable_enrollment on the DMS (forbiddenNewEnrollment otherwise). The template restrictions are the same for both operations: Lamassu never issues CA certificates over CMP (BasicConstraints cA=TRUE → notAuthorized), using keyCertSign without being a CA or a pathLenConstraint in an end-entity template are badCertTemplate, and the accepted RSA range is 2048 to 8192 bits.
If the DMS's issuance profile drops a critical extension requested by the CertTemplate, the response arrives with status grantedWithMods (1) instead of accepted (0): the integrator must compare the issued certificate with what was requested, not assume that acceptance implies an exact match.
The CRMF controls, when the DMS requires them, go in the request: id-regCtrl-regToken (RFC 4211 §6.1) if the DMS's mode requires it — a one-use value consumed even if issuance later fails — and id-regCtrl-authenticator (§6.2) if the DMS configured an expected answer. A control present when the DMS has it disabled, or absent when it is mandatory, is rejected with badRequest.
The message carries the template of the renewed certificate and is protected by signature with the certificate being renewed — that signature is the proof of possession. With key_policy: require_new_key, the public key must differ from the current one; with identity_change_policy: forbid, the subject and the SAN must match the current certificate (san_only allows changing the SAN; subject_and_san allows both changes). The response is kup.
The exchange stays pending until the certConf: until the device confirms, the previous certificate remains the device's active identity and can keep being used; the new one is bound and the previous one is revoked as superseded only upon confirmation (or immediately if implicit confirmation was negotiated). The optional id-regCtrl-oldCertID control must reference the certificate being renewed.
The body is a complete PKCS#10 CSR. The CSR's signature over its own CertificationRequestInfo is the proof of possession and is always verified. The signature algorithm policy follows RFC 9481 §3: RSA, RSA-PSS, ECDSA or Ed25519 with SHA-256/384/512; those signed with SHA-1, SHA-224, MD5 or DSA are rejected with badPOP, and an unknown algorithm is rejected with badAlg. The response is a cp with certReqId -1, and the subsequent certConf must carry that same value.
The body carries RevReqContent with the CertTemplate of the certificate to revoke and the reason. The message signature must be from the revoked certificate itself (self-revocation) or from a trusted RA entity (EKU id-kp-cmcRA, validated against the DMS's CAs according to trusted_ra). The fields of the CertTemplate — issuer, serial number, subject, public key and extensions — must match the presented certificate. The reason certificateHold suspends the certificate (and is not subject to the allowed-reasons list); the reason removeFromCRL does not revoke: it asks to reactivate a previously revoked certificate — only possible if that revocation was a suspension —, an operation the DMS must have enabled with allow_revival.
The response is always an rp — both on success (Certificate revoked / Certificate revived) and on rejection, with the reason in its PKIStatusInfo.
The body is a sequence of InfoTypeAndValue; the response is a genp with one entry per request. See the information types table to know what each DMS advertises. The infrastructure-dependent types (CRL) require the administrator to have configured a VA client on the DMS Manager service.
Reserved for CA operators. The signer must be a CA certificate and the template must be complete. With workflow: administrator_approval (the default), the first response is a waiting ccp and the cross-certificate is retrieved with pollReq once approved. The requested validity is honored up to the DMS's maximum_validity limit.
certConf closes the cycle of an issuance with explicit confirmation. Main rules:
- It must carry exactly one
CertStatus, withcertReqId0forir/cr/kurand-1forp10cr. - The
certHashis verified against the issued certificate; withouthashAlg, the digest dictated by RFC 9481 §3.3 according to the certificate's own signature algorithm is used (SHA-256, except for certificates signed with SHA-384/512 or Ed25519), and declaring ahashAlgrequirespvnocmp2021(3). - It must be protected with the credential of the original request, not with the freshly issued certificate.
- The
recipNoncemust return the response'ssenderNonce, and its ownsenderNoncemust be new (reusing the one from the initial request isbadSenderNonce). - Confirming the same certificate twice answers with the error
certConfirmed, useful after losing apkiConf.
pollReq polls the transaction's state and supports two flows: lost-response recovery (the ISSUED certificate is re-delivered in the original ip/cp/kup, without consuming the transaction) and polling of the phased flow (while the transaction is PENDING, the response is a pollRep with the retry hint, 60 seconds by default). An ISSUE_FAILED transaction returns the failure reason and a REVOKED one indicates the enrollment must be repeated. The response of a transaction with server-side key generation cannot be recovered: the private key is never stored, so a pollReq on it is rejected and the device must repeat the enrollment with a new transactionID.
A nested (RFC 4210 §5.1.3) is an envelope whose body contains one or more PKIMessage. With a single inner message it is added protection (RFC 9483 §5.2.2.1): an RA wraps and countersigns an already-protected device request; the outer header must copy the inner transactionID and senderNonce, the inner protection is verified and the request is processed normally. With two or more messages it is a batch (§5.2.2.2): the outer header carries new transactionID/senderNonce, the protection of each inner message is verified before any is processed and the responses come back wrapped in a nested, in the same order. A ccr inside a nested is not re-expedited, and nesting inside a batch is not supported.
When the DMS has server_key_gen_enabled: true, a device may send an ir/cr with an empty public key field in the CertTemplate to ask the server to generate the key pair. The mechanism and its limits are described in the configuration section; from the integrator's side:
- The message's protection certificate acts as the recipient: RSA →
rsa_key_transport, ECC →ecdh_key_agreement. The certificate's key and key usage must allow the chosen technique. - The response (
ip/cp) carries the issued certificate and the wrapped key in the same body; the PKCS#8 key travels insideEnvelopedData(SignedData(AsymmetricKeyPackage)) and the certificate to open the envelope with is inextraCerts[0]. - The key is never stored on the server: if the response is lost, no
pollReqcan recover it — repeat the enrollment with a newtransactionID. - The subsequent
certConfworks the same as in an ordinary issuance.
The following commands use OpenSSL's cmp client (≥ 3.0) against a DMS configured in CLIENT_CERTIFICATE mode.
Initial enrollment (ir), signing with a bootstrap certificate (signer.crt/signer.key) issued by one of the DMS's validation_cas, and generating a new key for the device:
openssl cmp -cmd ir \
-server "https://<CMP_HOST>" -tls_used \
-path "/.well-known/cmp/p/<DMS_ID>" \
-cert signer.crt -key signer.key \
-extracerts signer.crt \
-csr device.csr -newkey device.key \
-reqout request.der -rspout response.der \
-certout device.crt \
-unprotected_errors \
-batch-tls_used is necessary in OpenSSL 3.0.x when the server URL uses https without pinning the server certificate (-srvcert); versions ≥ 3.2 infer it automatically. -unprotected_errors allows accepting error responses that arrive unprotected (those whose protection algorithm is NULL); while the DMS has no protection_certificate configured, all its responses — including errors — go out unprotected, so this option keeps openssl cmp from discarding them.
Renewal (kur), authenticating with the device's current certificate:
openssl cmp -cmd kur \
-server "https://<CMP_HOST>" -tls_used \
-path "/.well-known/cmp/p/<DMS_ID>" \
-cert device.crt -key device.key \
-srvcert cmp-protection-cert.pem \
-reqout request.der -rspout response.der \
-certout device.new.crt \
-batch-srvcert pins the certificate the DMS is expected to protect its response with (the Protection Certificate configured on the DMS). Without it, OpenSSL validates the response's protection against its own trust anchors; -unprotected_errors only covers unprotected error messages and does not replace -srvcert for successful responses.
- Confirm the certificate: unless
accept_implicitis active on the DMS and the device requests it, the flow is not complete until the device sendscertConf.openssl cmp -cmd ir/kurdoes it automatically; a manual integration must implement this step. - Plan the renewal: schedule the
kurbefore the certificate expires. Unlike EST, thereenrollment_deltawindow does not restrict CMP'skur, so the policy of when to renew is the device's. phasedflow: if the DMS usesworkflow: phasedon the operation, the first response toir/cr/kur/p10cris a wait; the device must retry withpollRequntil an administrator approves or rejects the transaction.- Recovering lost responses: if a response did not arrive, repeat the operation with
pollReq(theISSUEDcertificate is re-delivered), except for transactions with server-side key generation, where the enrollment must be repeated.
Most rejections are CMP messages with HTTP 200
An error PKIMessage returned with HTTP 200 is the normal answer to a request rejected by policy (failed authentication, disabled operation, expired confirmation window, etc.). It is protected — signed with the protection certificate — if the DMS has one configured; otherwise it goes out unprotected, and strict clients like openssl cmp discard it unless -unprotected_errors is used. The HTTP error codes are reserved for failures where a response PKIMessage cannot be built.
| HTTP | Where the failure is | Typical cause | Integrator action |
|---|---|---|---|
| 415 | JSON error body | Content-Type other than application/pkixcmp. | Set the correct header. |
| 400 | No body | The body cannot be decoded as an ASN.1 PKIMessage (no valid header to build a CMP response from). | Check the body is binary DER, not base64 or PEM. |
200 (error) | Body's PKIFailureInfo | unsupportedVersion: pvno other than cmp2000(2)/cmp2021(3). | Use a supported version. |
200 (error) | Body's PKIFailureInfo | badDataFormat: missing or under-128-bit transactionID/senderNonce; malformed PKIHeader; error from the EE. | Review the CMP client implementation. |
200 (error) | Body's PKIFailureInfo | badTime: messageTime absent or drifting more than 5 minutes. | Synchronize the device's clock. |
200 (error) | Body's PKIFailureInfo | badSenderNonce/badRecipientNonce: reused senderNonce, recipNonce present in an initial request or mismatched. | Follow the nonce rules of RFC 9483 §3.1. |
200 (error) | Body's PKIFailureInfo | badMessageCheck: invalid signature, sender/senderKID not matching the protection certificate, or failed protection in a nested message. | Review the certificate used to sign. |
200 (error) | Body's PKIFailureInfo | transactionIdInUse: transactionID already registered (replay of an rr). | Generate a new transactionID. |
200 (error) | Body's PKIFailureInfo | notAuthorized: operation disabled for the DMS, or revocation of another DMS's certificate. | Confirm with the PKI administrator that the operation is enabled. |
200 (error) | Body's PKIFailureInfo | signerNotTrusted: the signer does not chain to the DMS's validation CAs, or the KGA recipient is outside the trust boundary. | Review the signing certificate and the configured CAs. |
200 (error) | Body's PKIFailureInfo | certRevoked: the protection certificate was replaced by a confirmed renewal, or the target certificate is already revoked. | Repeat the enrollment or review the identity's state. |
200 (error) | Body's PKIFailureInfo | certConfirmed: duplicate certConf (the confirmation was already received). | Do not repeat the confirmation unless the pkiConf was lost. |
200 (error) | Body's PKIFailureInfo | badRequest: unknown or expired transaction, no certificate to confirm, second pending renewal, or KGA recipient without the necessary key usage. | Inspect the exact reason (statusString) and fix the flow. |
200 (ip/cp/kup with status: rejection) | Body's PKIFailureInfo | Issuance policy: missing or failed POPO (badPOP), CA certificate request or RSA outside 2048–8192 bits (badCertTemplate), notAuthorized for POPO/KGA recipients not allowed, invalid certReqId. | Inspect the body's PKIFailureInfo and check it against the DMS's per-operation configuration. |
200 (rp with status: rejection) | Body's PKIStatusInfo | Revocation: reason not allowed (badRequest), repeated transactionID, certificate not found (badCertId), already revoked (certRevoked) or not revivable (badCertId), or signer without authority over the certificate (signerNotTrusted/notAuthorized). | Check against the DMS's rr policy. |
To interpret the exact reason of a CMP rejection, inspect the response PKIMessage's PKIFailureInfo/statusString field (for example with openssl cmp ... -rspout response.der and openssl asn1parse -inform DER -in response.der). In the Lamassu server logs, every rejection generates a structured CMP operation rejected event with the fields dms, pkiStatus, pkiFailureInfo, reason and txid; client failures are logged as WARN and internal ones (systemFailure) as ERROR.
Lamassu's implementation follows the Lightweight CMP profile with the following quirks that affect strict CMP clients:
- CMP is served from a single multiplexed endpoint (
/.well-known/cmp/p/{id}); there are no per-operation routes as in EST. - Both versions
cmp2000(2)andcmp2021(3)are accepted and answered; the response returns the samepvnoas the request. messageTimeis mandatory in all messages (not only protected ones), with a 5-minute drift tolerance (badTime).transactionIDandsenderNoncemust carry at least 128 bits of entropy, and thesenderNoncemust be new in every message.- The
ir,crandp10croperations do not admitrecipNoncein the initial request (RFC 9483 §3.1); a client that includes it receivesbadRecipientNonce. ThegeneralInforules are enforced equally:implicitConfirmandconfirmWaitTimeare mutually exclusive, the value ofimplicitConfirmmust beNULLand that ofconfirmWaitTimeaGeneralizedTime. - With signature-based protection, the header's
sendermust match the protection certificate's subject and thesenderKIDitsSubjectKeyIdentifier(badMessageCheckotherwise).genmmessages are exempt from this identity check. - The subject may be NULL-DN only if the SAN carries the identity; for the device's internal identity the SAN is used as a fallback, but the issued certificate keeps the empty subject.
- Lamassu never issues CA certificates over CMP; the only operation that produces CA certificates is
ccr. - The
id-it-revPassphrasetype of thegenmis permanently disabled. - The internal
APPROVINGandREVOKINGstates of a transaction are transient and are not exposed by the listing API; onlyPENDING,ISSUED,ISSUE_FAILED,CONFIRMEDandREVOKEDappear. - Transaction records in terminal states (
CONFIRMED,REVOKED) are kept for audit and are not purged by expiration; a completedtransactionIDmay be reused in a new operation, except inrr, where replay protection also looks at terminal records. - The protection certificate must be renewed manually before it expires: the DMS does not renew it automatically, and while none is configured, all responses go out unprotected.