Messages and protection¶
The pkicmp package is the base of the module. It defines the CMP and CRMF types, builds and parses
messages and applies and verifies their protection. The client and server packages build on it.
Use it directly for custom tooling, a registration authority proxy or tests.
Building a message¶
PKIMessage corresponds to the ASN.1 PKIMessage. Create one from a body and header options:
body := pkicmp.NewPKIConfBody()
msg := pkicmp.NewPKIMessage(body, pkicmp.MessageOptions{
Sender: sender,
Recipient: recipient,
RecipNonce: previous.Header.SenderNonce,
})
The transaction ID and senderNonce default to 128 random bits (RFC 9810 section 5.1.1) and
messageTime to the current time. Body constructors such as NewIRBody, NewIPBody and
NewGenMBody wrap each CHOICE variant. PKIBody keeps the raw DER and decodes it only when a typed
getter such as PKIBody.IR is called.
PKIMessage.MarshalBinary and pkicmp.ParsePKIMessage are the only points where messages become
bytes and back.
Protection¶
Credentials applies protection:
creds, err := pkicmp.NewMACCredentials([]byte("shared-secret"))
if err != nil {
return err
}
if err := creds.Protect(msg); err != nil {
return err
}
| Credentials | Protection |
|---|---|
NewMACCredentials |
PBMAC1 by default, the algorithm RFC 9481 section 7 recommends. WithPBM selects PasswordBasedMac and WithPBMAlgorithms its hash functions |
NewSignatureCredentials |
A signature with an X.509 certificate and key. The chain is sent in extraCerts. The digest follows the key type unless WithHash chooses one |
To answer a MAC-protected message with the same algorithm suite, as RFC 9483 section 3.2 requires,
pass VerifyResult.ProtectionParams from the verified request to NewMACCredentials. It reuses the
suite with a fresh salt.
Verification¶
PKIMessage.Verify checks the protection:
result, err := msg.Verify(pkicmp.VerifyOptions{
SharedSecret: []byte("shared-secret"),
TrustPool: trustedCAs,
RequiredProtection: pkicmp.ProtectionSignature,
})
With both a secret and a trust pool, the message decides which is used. Set RequiredProtection to
pin one mechanism, as RFC 9483 section 3.1 asks within one operation. The zero value accepts either.
A signature check also binds the protection certificate to the sender: when the header sender is a directory name, it must equal the certificate subject (RFC 9483 section 3.5). A NULL-DN sender is accepted on the trust chain alone.
| Option | Effect |
|---|---|
ExtraCerts, TrustedCert |
Candidate signer certificates beyond those the message carries |
SenderKID, AllowSenderKIDMismatch |
Select candidates by subject key identifier or try all of them |
CurrentTime |
Validate the certificates at another time, such as when a recorded message arrived |
AllowSHA1Signatures |
Accept SHA-1 signatures, refused by default |
MinMACIterationCount, MaxMACIterationCount |
Bound the MAC iteration count, 100 to 1000000 by default. For PBMAC1 the maximum covers the iterations multiplied by the PRF blocks of the key length |
RequireDigitalSignatureKeyUsage |
Require the digitalSignature bit that RFC 9483 section 3.5 asks for. Deployed CAs often lack it |
Do not take CurrentTime from the message when the result decides whether to act on it, because
the sender sets its messageTime.
Errors¶
Every error is typed and carries an InvalidReason:
| Type | Meaning |
|---|---|
ParseError |
Malformed message, missing field or a parameter outside the accepted range. When the failure comes after a readable header, its Header field holds that header, so a server can answer with an error message |
ProtectionError |
Protection could not be applied |
VerificationError |
Bad MAC or signature, wrong mechanism, sender mismatch or no certificate to verify with |
Common verification reasons:
| Reason | Cause |
|---|---|
ReasonBadMAC |
The MAC does not match the shared secret |
ReasonUnexpectedProtection |
The message used another mechanism than RequiredProtection |
ReasonSenderMismatch |
No candidate certificate has the sender's name |
ReasonUntrustedSigner |
No candidate with the sender's name chains to the trust pool |
ReasonNoCandidateSigner |
No certificate was available to verify the signature |
ReasonSenderKIDMismatch |
The senderKID names none of the candidate certificates |
ReasonUnsupportedAlgorithm |
The algorithm is unknown or disabled, such as SHA-1 |
PKIStatusInfo.AsError turns a peer status into a *pkicmp.PKIStatusError, and
pkicmp.HasFailure(err, bit) tests its failure bits.
Your responsibilities¶
The package verifies protection and parses CMP. It does not implement a client or CA policy. When you use it directly:
- Pin the mechanism you expect on the message you verify, which is not always the one you send with. Holding both kinds of trust material under the default lets a peer pick the weaker one.
- Keep the protection certificate across a transaction.
extraCertsmay appear only on the first response, so passVerifyResult.ProtectionCertificateback throughExtraCertsorTrustedCert. - Bind the issued certificate to the request by comparing its public key with the key you asked for. Subjects may differ under grantedWithMods.
- Trust caPubs only when
VerifyResult.MACVerifiedis set (RFC 9810 section 5.3.2). - Clamp checkAfter yourself.
PollRepContentholds the peer's value as sent. - Sanitize peer text before logging.
PKIStatusInfo.AsErrorjoins the free text without validating UTF-8 or removing control characters.
Certificate confirmation¶
pkicmp.NewCertStatus computes the certificate hash for a manual certConf, including the explicit
hash identifier that ML-DSA and composite ML-DSA certificates need.