Skip to content

Message protection

Every CMP request and response must carry PKIProtection. Unprotected messages are rejected, and transport encryption does not replace message protection, so an HTTP endpoint is still authenticated.

cmp-issuer supports two protection modes, configured in spec.protection.type:

Mode RFC basis Credential Use it when
PasswordBasedMac RFC 4210 PasswordBasedMac Shared reference and secret The server issued you an enrollment code
Signature RFC 4210 certificate-based protection Bootstrap certificate and private key The device or workload already holds an identity the server trusts

The selected mode protects initial P10CR and compatibility P10CR renewal. Response validation is identical for both modes.

KUR always uses the current workload certificate and private key for request protection as required by the CMP profile. This replaces the configured bootstrap or shared-secret request protection only for the KUR exchange. The configured CMP trust policy still validates every response.

PasswordBasedMac

Secret format

Create a Secret in the issuer's namespace:

apiVersion: v1
kind: Secret
metadata:
  name: cmp-pbm-credentials
type: Opaque
stringData:
  reference: "<server-assigned-reference>"
  secret: "<shared-secret>"

Default keys are reference and secret. Override them with referenceKey and secretKey.

Issuer configuration

spec:
  protection:
    type: PasswordBasedMac
    passwordBasedMac:
      secretRef:
        name: cmp-pbm-credentials
      referenceKey: reference
      secretKey: secret
      algorithm:
        owf: SHA256
        mac: HMACSHA256
        iterationCount: 1024

Algorithm fields

Field Allowed values Default
owf SHA256, SHA384, SHA512 SHA256
mac HMACSHA256, HMACSHA384, HMACSHA512 HMACSHA256
iterationCount 100-1048575 1024

These must match the server's PBM profile. The MAC digest must not be longer than the OWF output. For example, SHA384 accepts HMACSHA256 or HMACSHA384, while SHA512 accepts all three MACs. The selected algorithms protect enrollment, polling and confirmation requests without negotiation or fallback. PBMAC1 is planned rather than implemented.

Response protection

Many servers sign every response regardless of how the request was protected. EJBCA does this on any CMP alias left at its own default of responseprotection signature. For interoperability, spec.protocol.macResponseProtection defaults to AllowSignature and both a MAC-protected and a signed response are accepted. This is not the RFC 9483 profile behavior, which requires MAC-based protection throughout a MAC-protected operation.

Accepting a signature does not weaken the response to an unauthenticated one. The signer must chain to spec.cmpTrust.caSecretRef and the response sender must name spec.protocol.recipient, which is the same authority check a Signature issuer already relies on for every response it receives.

Require MAC-based protection throughout with:

spec:
  protocol:
    macResponseProtection: Strict

Then the shared secret authenticates the response as well as the request, and a signature is rejected however well it verifies. A MAC-protected response must also use at least 100 iterations, the floor that applies to iterationCount on requests, so a server that protects its answers with fewer is rejected as well. Set this where the CMP trust anchor is shared with authorities that must not be able to answer for the recipient, or to conform to a profile that requires one protection type for a whole PKI management operation. Confirm the server protects its answers with the shared secret before turning it on, since a server that signs them will have issued the certificate before the response is rejected.

Setting spec.protocol.validationProfile to RFC9483 enables Strict behavior implicitly. An explicit AllowSignature conflicts with that profile and is rejected by API validation.

Server setup

The reference and secret must exist on the CMP server before enrollment. cmp-issuer does not register credentials over CMP.

EJBCA client mode treats the enrollment code as a one-time credential. After issuance the end entity moves to GENERATED and refuses further requests until an administrator resets it. See Tested PKIs.

Signature protection

Secret format

apiVersion: v1
kind: Secret
metadata:
  name: cmp-signature-credentials
type: kubernetes.io/tls
stringData:
  tls.crt: |
    -----BEGIN CERTIFICATE-----
    ...
    -----END CERTIFICATE-----
  tls.key: |
    -----BEGIN PRIVATE KEY-----
    ...
    -----END PRIVATE KEY-----
  ca.crt: |
    -----BEGIN CERTIFICATE-----
    ... optional intermediate chain ...
    -----END CERTIFICATE-----
Key Required Purpose
tls.crt yes Protection certificate (default certificateKey)
tls.key yes Protection private key (default privateKeyKey)
ca.crt optional Intermediate chain sent in extraCerts (default chainKey)

Custom key names are set with certificateKey, privateKeyKey and chainKey.

The private key may be RSA, ECDSA, Ed25519 or ML-DSA. PKCS #8 works for every type. PKCS #1 RSA keys and SEC 1 ECDSA keys are accepted too. An ML-DSA key must hold only the seed, see Known limitations.

Issuer configuration

spec:
  protection:
    type: Signature
    signature:
      secretRef:
        name: cmp-signature-credentials
      certificateKey: tls.crt
      privateKeyKey: tls.key
      chainKey: ca.crt

Sender name

Each message names the subject of the protection certificate as its sender, encoded exactly as in the certificate, as RFC 9483 requires. A configured spec.protocol.sender replaces it, except in a KUR request, which always names the certificate being updated.

extraCerts layout

cmp-issuer sends the protection end-entity certificate first, followed by the configured intermediates. Some servers resolve the signing certificate from extraCerts using senderKID, and Nokia NCM requires the end-entity certificate to appear there for P10CR.

EJBCA client mode

EJBCA resolves the authenticating certificate from its own database, so the credential needs only tls.crt and tls.key and chainKey may be omitted.

Client mode enrolls only the identity that owns the authenticating certificate, and the requested subject DN must match the registered end entity. A signature-protected issuer therefore issues for its bootstrap identity rather than arbitrary workload names, unless the server profile allows otherwise.

What gets protected

The signer protects every message it sends:

  • P10CR enrollment requests
  • KUR requests with the current workload certificate
  • pollReq messages during asynchronous transactions
  • certConf confirmation messages when explicit confirmation is configured

What gets validated

Before accepting a certificate the signer validates:

  • Response PKIProtection algorithm and integrity
  • Signer trust against spec.cmpTrust
  • That the response sender names the configured recipient
  • Transaction identifier and nonces
  • P10CR certReqId against policy or KUR certReqId fixed at 0
  • Exactly one CertResponse in CP or KUP
  • KUP caPubs according to the selected validation profile
  • That the issued public key matches the CSR
  • A leaf-first chain that validates against CMP trust
  • RFC 9881 key usages for ML-DSA certificates

Signature verification tries certificates from the response extraCerts first, then the certificates configured in spec.cmpTrust.caSecretRef, then the optional spec.cmpTrust.signerCertificatesSecretRef bundle. A response can omit a configured signer certificate from extraCerts. Every candidate must pass chain, sender and signature validation. A signer that is neither configured nor supplied in extraCerts must have been validated earlier in the same transaction.

Both Interoperable and RFC9483 accept an omitted configured signer. This is an interoperability allowance: RFC 9810 section 5.1 makes extraCerts optional, while RFC 9483 section 3.3 requires the protection certificate in the first signed response. The RFC9483 profile enforces its documented P10CR identifier, MAC continuity and KUP caPubs checks without enforcing that presence rule.

Protected error responses are verified the same way. A verification failure fails the CertificateRequest and stores no certificate. When the server has already issued the refused certificate and did not grant implicit confirmation, cmp-issuer reports the refusal to the server in a certConf with status rejection.

The default Interoperable profile accepts certificates from KUP caPubs and extraCerts only as untrusted candidates for building the issued chain. They never become trust anchors. The completed chain must still validate to a CA from spec.cmpTrust. Set kurResponseCaPubs: RequireAbsent for only the RFC 9483 KUP rule or select validationProfile: RFC9483 to enable the bundled checks.

Confirmation signer retention

Some servers omit extraCerts and senderKID from polling and confirmation responses. cmp-issuer stores the validated response signer in CMPTransaction.status.responseSigner, including when the signer is a configured anchor omitted from the response. The retained certificate verifies later responses in that transaction after a controller restart or delayed confirmation. Invalid protection is rejected. See Tested PKIs for Nokia NCM behavior.

Credential rotation

Credentials are reloaded from the Secret on every reconcile. Rotating a Secret during an open transaction invalidates in-flight protection and the transaction fails within maximumDuration. The transaction configuration digest records the UID and resourceVersion of every referenced credential Secret. For KUR it also records both workload Secrets by their UID and a digest of the consumed key material, so a metadata-only write to a workload Secret does not stop the transaction. cmp-issuer stops the unfinished transaction before sending more CMP traffic when any of those values changes. See Known limitations.