Lamassu IoT Docs

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:

AspectEST (RFC 7030)CMP (RFC 4210 / RFC 9483)
EndpointOne per operation (/cacerts, /simpleenroll, …)A single multiplexed endpoint (/.well-known/cmp/p/{id}); the operation is determined by the message content
AuthenticationmTLS at the transport layerProtection of the message itself (PKIMessage) by signature or, in ir/cr, CRMF proof of possession
SettingsGeneral enrollment and re-enrollment blocksGeneral settings plus a per-operation policy block (ir, cr, p10cr, kur, rr, genm, ccr)
ConfirmationThe response is the end of the flowAn explicit confirmation protocol (certConf), negotiable implicit confirmation and stateful transactions
Renewal/simplereenroll with the current certificatekur 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:


Before configuring a DMS for CMP, the following resources must exist on the platform:

  • An available CA: the CA acting as EnrollmentCA must 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:

ValueProtocol
EST_RFC7030EST — see the EST guide
CMP_RFC9483CMP — 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:

FieldDescription
enrollment_caCA that will sign the identity certificates issued by this DMS.
registration_modeJITP (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_profileIcon, color, metadata and tags applied to each device created in JITP mode.
enable_replaceable_enrollmentAllows 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_signatureNo 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.

ModeDescription
CLIENT_CERTIFICATEThe 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_WEBHOOKCombines both controls: valid signature and webhook approval.
EXTERNAL_WEBHOOKThe authorization decision is delegated to an external HTTP service, with the same contract as in EST. The message may arrive unprotected.
NO_AUTHNo 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:

FieldTypeDescription
accept_implicitbooleanAllows 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_timeoutdurationWaiting window for the certConf (for example 30s, 5m, 1h). A zero or negative value uses the default: 5 minutes.
workflowdirect / phaseddirect (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_timeoutdurationOnly 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 cmp included) 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 of ir/cr lives in its per-operation block (proof_of_possession.required), kur and rr are always signature-protected as protocol invariants, and the p10cr CSR 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 the id-regCtrl-authenticator control (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 with incorrectData. Marked as legacy: it stores a plaintext secret on the DMS; new configuration should use the per-operation registration_token/authenticator_control controls, 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 general workflow), direct or phased.
  • policy_overrides.confirmation: inherit, implicit (forces willingness to accept implicit confirmation) or explicit (forces the certConf).
  • policy_overrides.issuance_profile_id: pins a specific issuance profile for that operation. It applies in ir, cr and p10cr; kur always 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.

FieldDescription
enabledEnables the operation. Disabling it rejects any ir with notAuthorized.
proof_of_possession.requiredWhether the POPO is mandatory. Default true: an ir without an acceptable proof of possession is rejected with badPOP.
proof_of_possession.allowed_methodsAccepted 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_tokenRFC 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_controlRFC 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_methodsKey-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.enabledMerges with the general server_key_gen_enabled switch and with cr's into a single KGA gate (see Server-side key generation).
identity_sourceSelects 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_overridesSee 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:

FieldDescription
require_existing_deviceDefault 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_behavioradditional (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_certificatesCap 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_idsAllow-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.

FieldDescription
enabledEnables the operation. Disabled by default.
allowed_profile_idsAllow-list of permitted issuance profiles, same as in cr.
policy_overridesSee 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.

FieldDescription
enabledEnables the operation.
key_policyrequire_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_policyHow 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 / .confirmationSame 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.

FieldDescription
enabledEnables the operation.
authorizationself_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_revivalDisabled by default. Allows reactivating a revoked certificate through the reason removeFromCRL; revival is only possible for certificates whose revocation was a suspension (certificateHold).
allowed_reasonsAllow-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_ekuDefault true: requires the EKU id-kp-cmcRA on the RA signer.
trusted_ra.validation_ca_idsFixes 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.

FieldDescription
access_policypublic_discovery (default) answers without requiring a protected message; require_signed rejects an unsigned genm with notAuthorized.
preferred_symmetric_algorithmSymmetric 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 typeOID (id-it-*)DefaultResponse content
ca_certificatescaCerts (17)EnabledCertificates of the DMS's CAs, with the same trust distribution as EST's /cacerts.
signing_key_typessignKeyPairTypes (2)EnabledSupported signature algorithms: RSA and ECC (static value).
encryption_key_typesencKeyPairTypes (3)EnabledSupported encryption/agreement algorithms: RSA (static value).
preferred_symmetric_algorithmpreferredSymmAlg (4)EnabledThe algorithm configured in preferred_symmetric_algorithm.
supported_languagessupportedLangTags (16)EnabledLanguage negotiation for PKIFreeText; the only supported one is en.
root_ca_updaterootCaCert (20)DisabledUpdate of the current root (newWithNew/newWithOld), compared against the root the device declares.
certificate_request_templatecertReqTemplate (19)DisabledTemplate derived from the DMS's issuance profile: supported key algorithms and sizes, and certificate validity.
current_crlcurrentCRL (6)DisabledCurrent CRL, served through the VA client of the DMS Manager service.
crl_updatecrlStatusList (22)DisabledCRL updates per issuer, only those newer than the one the device declares.
protocol_encryption_certificatecaProtEncCert (1)DisabledAlways 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.

FieldDescription
enabledEnables the operation. Disabled by default.
workflowadministrator_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_modeany (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_idsCAs authorized in restricted mode.
require_ca_certificateDefault true: the signer's protection certificate must be a CA certificate.
require_proof_of_possessionDefault 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_validityMaximum 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_constraintsIdentity 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_idPersisted 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).

FieldTypeDescription
server_key_gen_enabledbooleanEnables 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_caslist of CA IDsCAs 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:

FieldEffect in CMP
additional_validation_casAdditional 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_reenrollmentRevokes 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_renewalAllows using an already-expired certificate as the protection credential of the kur.
reenrollment_deltaNot 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_deltaThresholds 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.

StateMeaning
PENDINGRequest accepted, awaiting issuance: a phased flow waiting for approval, or an ir/cr waiting for the popdecr of a challenge-response POPO.
ISSUEDCertificate issued, awaiting the device's certConf.
CONFIRMEDThe device confirmed receipt with certConf (or implicit confirmation was granted). Terminal state.
ISSUE_FAILEDThe CA rejected issuance or an administrator rejected the transaction. The reason remains available to the device via pollReq. Terminal state.
REVOKEDThe 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.

EndpointDescription
GET /v1/dms/{id}/cmp/transactionsPaginated 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}/approveApproves 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}/rejectRejects 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: ISSUED transactions whose confirmation window expired are revoked at the CA with the reason cessationOfOperation and move to REVOKED. The atomic claim of the record prevents revoking a certificate that a legitimate certConf is confirming at that exact moment.
  • Closing pending approvals: PENDING transactions in phased flow that no administrator resolved within approval_timeout move to ISSUE_FAILED with the reason approval window expired, and are kept for 7 days so a late pollReq can 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/transactions confirms the endpoint responds even without device traffic.
  • With a test request: use openssl cmp with the ir command (if you have a valid signer) or genm against 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:

ItemDescription
CMP host URLBase address of the server, for example https://cmp.lamassu.example. Provided by the PKI administrator.
DMS IDIdentifier of the DMS, included in the endpoint path (/.well-known/cmp/p/{id}).
Message signing certificate and keyOnly 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 certificateThe 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/pkixcmp in both the request and the response. A request with a different Content-Type is rejected with 415 Unsupported Media Type.
  • Body: a PKIMessage in 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 the PKIBody tag — 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:

  1. Include its certificate in the PKIMessage's extraCerts field (first position).
  2. Sign the entire PKIMessage with 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:

MessageProtection mandatory?
ir, cr, p10crOnly if auth_mode is CLIENT_CERTIFICATE or CLIENT_CERTIFICATE_AND_EXTERNAL_WEBHOOK
kurAlways (the signature is the renewal's proof of possession)
rrAlways (the signature is what authorizes the revocation)
genmDepending on access_policy: public_discovery admits unprotected messages; require_signed demands a signature
certConf, pollReq, popdecr, nestedFollow 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.

OperationUseRequires prior identity
irInitial enrollment of a new device.No
crAdditional or replacement issuance for an already-identified device.Yes (if require_existing_device)
p10crEnrollment with a PKCS#10 CSR, without CRMF controls or KGA. Must be enabled on the DMS.No
kurRenewal of a certificate; the message is signed with the current certificate.Yes (the certificate being renewed)
rrRevocation of a certificate, always signed.Yes
genm/genpQuery of the DMS's capabilities (CAs, key types, current CRL, etc.). Issues no certificates.No
ccr/ccpCross-certification between CAs. For CA operators only, disabled by default.The signer must be a CA
nestedWrapper 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, with certReqId 0 for ir/cr/kur and -1 for p10cr.
  • The certHash is verified against the issued certificate; without hashAlg, 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 a hashAlg requires pvno cmp2021(3).
  • It must be protected with the credential of the original request, not with the freshly issued certificate.
  • The recipNonce must return the response's senderNonce, and its own senderNonce must be new (reusing the one from the initial request is badSenderNonce).
  • Confirming the same certificate twice answers with the error certConfirmed, useful after losing a pkiConf.

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 inside EnvelopedData(SignedData(AsymmetricKeyPackage)) and the certificate to open the envelope with is in extraCerts[0].
  • The key is never stored on the server: if the response is lost, no pollReq can recover it — repeat the enrollment with a new transactionID.
  • The subsequent certConf works 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_implicit is active on the DMS and the device requests it, the flow is not complete until the device sends certConf. openssl cmp -cmd ir/kur does it automatically; a manual integration must implement this step.
  • Plan the renewal: schedule the kur before the certificate expires. Unlike EST, the reenrollment_delta window does not restrict CMP's kur, so the policy of when to renew is the device's.
  • phased flow: if the DMS uses workflow: phased on the operation, the first response to ir/cr/kur/p10cr is a wait; the device must retry with pollReq until an administrator approves or rejects the transaction.
  • Recovering lost responses: if a response did not arrive, repeat the operation with pollReq (the ISSUED certificate 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.

HTTPWhere the failure isTypical causeIntegrator action
415JSON error bodyContent-Type other than application/pkixcmp.Set the correct header.
400No bodyThe 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 PKIFailureInfounsupportedVersion: pvno other than cmp2000(2)/cmp2021(3).Use a supported version.
200 (error)Body's PKIFailureInfobadDataFormat: missing or under-128-bit transactionID/senderNonce; malformed PKIHeader; error from the EE.Review the CMP client implementation.
200 (error)Body's PKIFailureInfobadTime: messageTime absent or drifting more than 5 minutes.Synchronize the device's clock.
200 (error)Body's PKIFailureInfobadSenderNonce/badRecipientNonce: reused senderNonce, recipNonce present in an initial request or mismatched.Follow the nonce rules of RFC 9483 §3.1.
200 (error)Body's PKIFailureInfobadMessageCheck: 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 PKIFailureInfotransactionIdInUse: transactionID already registered (replay of an rr).Generate a new transactionID.
200 (error)Body's PKIFailureInfonotAuthorized: 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 PKIFailureInfosignerNotTrusted: 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 PKIFailureInfocertRevoked: 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 PKIFailureInfocertConfirmed: duplicate certConf (the confirmation was already received).Do not repeat the confirmation unless the pkiConf was lost.
200 (error)Body's PKIFailureInfobadRequest: 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 PKIFailureInfoIssuance 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 PKIStatusInfoRevocation: 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) and cmp2021(3) are accepted and answered; the response returns the same pvno as the request.
  • messageTime is mandatory in all messages (not only protected ones), with a 5-minute drift tolerance (badTime).
  • transactionID and senderNonce must carry at least 128 bits of entropy, and the senderNonce must be new in every message.
  • The ir, cr and p10cr operations do not admit recipNonce in the initial request (RFC 9483 §3.1); a client that includes it receives badRecipientNonce. The generalInfo rules are enforced equally: implicitConfirm and confirmWaitTime are mutually exclusive, the value of implicitConfirm must be NULL and that of confirmWaitTime a GeneralizedTime.
  • With signature-based protection, the header's sender must match the protection certificate's subject and the senderKID its SubjectKeyIdentifier (badMessageCheck otherwise). genm messages 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-revPassphrase type of the genm is permanently disabled.
  • The internal APPROVING and REVOKING states of a transaction are transient and are not exposed by the listing API; only PENDING, ISSUED, ISSUE_FAILED, CONFIRMED and REVOKED appear.
  • Transaction records in terminal states (CONFIRMED, REVOKED) are kept for audit and are not purged by expiration; a completed transactionID may be reused in a new operation, except in rr, 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.

On this page