Repository navigation
Releases: misiektoja/go-pki-cmp
Release list
v0.8.0
This release adds central key generation (RFC 9483 section 4.1.6): the CA generates the key pair and sends the private key signed and encrypted for the device. A client can also act as a registration authority, use implicit confirmation, resume a waiting enrollment in a later process and revoke a certificate by issuer and serial number. It also limits the work a peer can force on a verifier with large RSA keys or PBMAC1 parameters. Before upgrading, read the entries on the MAC iteration minimum and the per-credential transaction quota.
client
- Central key generation.
SendIRWithCentralKeyGeneration,SendCRWithCentralKeyGenerationandSendKURWithCentralKeyGenerationask the CA to generate the key pair.EnrollResult.GeneratedKeyholds the private key. The client verifies its signature and checks it against the issued certificate. The CA encrypts the key to the MAC shared secret or to an RSA or ECDSA protection key. WithRegistrationAuthoritysends every request of an operation in a nested message signed with the RA credentials (RFC 9483 section 5.2.2.1). The OpenSSL 4.1 mock server does not accept nested messages.WithImplicitConfirmasks the CA for implicit confirmation. When the CA grants it,EnrollResult.ImplicitConfirmis set and no certConf is sent. The client now also honors a grant it did not ask for. It used to send a certConf that the CA refused.WithDeferredPollingreturns at the first waiting answer withEnrollResult.Pendingset.ResumeEnrollmentcontinues from that state later, also in another process. Polling that stops before the CA decides now returns aPendingErrorthat resumes the same way.errors.Isstill finds the context error in it.SendRRBySerialrevokes a certificate by issuer and serial number without a copy of it.WithInvalidityDateadds an invalidityDate to a revocation request.- New request options set the certReqId (
WithCertReqID), extra generalInfo items (WithGeneralInfo), the proof-of-possession digest (WithPOPHash) and a requested validity (WithTemplateValidity), which the CA may ignore. - The default HTTP client times out an exchange after 120 seconds (
DefaultHTTPTimeout).WithHTTPClientreplaces the client and its timeout. - Test aids
WithCertConfStatus,WithMessageMutatorandWithProtectedMessageMutatorsend messages that break RFC 9483 to check how a CA handles them. Do not use them in production.
server
WithCentralKeyGenerationgenerates the key pair with aKeyGeneratoryou provide and signs the private key with a key generation authority. The default format is the one OpenSSL clients read.WithCentralKeyGenerationAsymmetricKeyPackageandWithCentralKeyGenerationAESKeyWrapselect the RFC 9810 and RFC 9481 formats. The CA gets the public key fromGeneratedPublicKeyFromContext.WithUnprotectedErrorsBeforeAuthenticationsends errors raised before the sender is authenticated without protection, as RFC 9483 section 3.6.4 allows. Use it with a slow signer such as SLH-DSA, which otherwise signs an error for every unauthenticated request.- The per-credential quota follows the shared secret. Transactions and
WithMaxTransactionsPerCredentialof MAC-protected clients are keyed by the shared secret instead of the sender name, which a client could change to get a fresh quota. Clients that share a secret share a quota. MAC transactions in a snapshot from an earlier version cannot be resumed. - A late certConf after the confirmWaitTime is refused with badRequest and the
CertificateConfirmerhearsConfirmExpired. It used to be accepted untilCleanupExpiredran. WithStrictProfileValidationrefuses a pollReq or popdecr that repeats the senderNonce of the last request or response in the transaction.WithAnyCertReqIDaccepts a certReqId other than 0.RevocationRequest.InvalidityDatepasses the requested invalidityDate to theRevoker.- The statusString of a refused protection or recipient holds only the reason, such as
message not protected, without the Go error text.
pkicmp
- Central key generation helpers, such as
NewKeyPackage,ParseKeyPackageandCertTemplate.RequestsCentralKeyGeneration. NewRevDetailsBySerialandNewInvalidityDateExtensionbuild revocations by serial number and with an invalidityDate.CertTemplate.Validityreads and writes a requested validity. A malformed validity now fails parsing.SignatureCredentials.WithHashandCertReqMsg.GeneratePOPWithHashchoose the signature digest.- RSA keys above 16384 bits are refused with
ErrRSAKeyTooLargewherever a peer supplies one. DefaultMinMACIterationCountis 100, as RFC 4211 requires, instead of 1. A MAC with fewer iterations is refused unlessWithMACIterationLimitslowers the minimum.NewMACCredentialsneeds the same option to send fewer.- PBMAC1 limits cover the whole key derivation, iterations times PRF output blocks.
DefaultMaxMACIterationCountis 1000000 units of that work, enough for the Siemens CMP test suite. - Trailing bytes inside the
extraCertswrapper now fail parsing with aParseError.
cms
EncryptSignedDataencrypts a SignedData for one recipient.PasswordandPWRIKEKPasswordencrypt for a shared secret with the RFC 9481 or the RFC 3211 key wrap.Decryptopens both.
examples/mockserver
- The mock CA generates key pairs for central key generation and accepts registration authorities. The example command enables both. It also publishes the invalidityDate of a revocation in the CRL entry.
- The mock CA certifies ML-KEM-768 and ML-KEM-1024 keys that prove possession by decryption. It used to fail to sign them.
v0.7.2
This release lets a client keep the CA certificates in caPubs from becoming trust anchors and requires Go 1.27.3, which fixes GO-2026-6809 in crypto/x509.
client
WithoutCAPubsTrustkeeps the CA certificates in caPubs of a MAC-protected response from extendingWithTrustedCAs, so the issued certificate must chain to the configured trusted CAs. Use it when the anchors are known out of band. Without the option caPubs are still trusted, as RFC 9810 section 5.3.2 allows.EnrollResult.CAPubsstill holds them.
Requirements
- Go 1.27.3 or newer is required. Go 1.27.2 has GO-2026-6809 in
crypto/x509, which the client and server reach when they verify certificate chains.
v0.7.1
This release fixes how the server answers faulty requests. A senderKID that names another key is refused, a malformed request gets an error message with badDataFormat and a reused transaction ID gets transactionIdInUse. The first and last fixes matter most to hosts whose certificate lookup ignores senderKID or that restore stored transactions into a server with small limits.
pkicmp
- infoValue in genm, genp and generalInfo items must be one DER element. A value that is not valid ASN.1 or carries trailing data now fails parsing with a
ParseError. Before, it was kept as raw bytes. ParseError.Headerholds the message header when parsing failed after a readable header, so a receiver can still answer the sender.
server
- senderKID check. A signed request whose senderKID does not match the subject key identifier of the certificate that verifies it is refused with badMessageCheck, as RFC 9483 section 3.5 requires. Before, a
CertificateLookupthat ignored senderKID let such a request through. A certificate without a subject key identifier is not compared. - Malformed requests whose header can be read get an error message with badDataFormat over HTTP 200. Before, a malformed infoValue in a genm got a genp. A malformed ir, cr or kur body or malformed template extensions got systemFailure. A malformed protection or extraCerts field or trailing data got HTTP 400. Input without a readable header still gets HTTP 400. That now includes a request whose generalInfo holds a malformed infoValue, which was processed before.
- transactionIdInUse answers a first message that reuses the transaction ID of a transaction the server still holds, also when a transaction limit is full. Before, a full limit answered systemUnavail with "maximum transaction limit reached".
v0.7.0
This release adds SLH-DSA keys (FIPS 205, RFC 9909) for all twelve parameter sets. Devices can enroll SLH-DSA keys with ir, cr, kur and p10cr and protect CMP messages with them. A CA can hold an SLH-DSA key, sign the CMP responses with it and have clients verify its certificates. It works with OpenSSL 3.5 and later in both directions and with EJBCA 9.6.3. The library now depends on github.com/cloudflare/circl for SLH-DSA.
pkicmp
- SLH-DSA keys.
ParsePublicKeyreturns an SLH-DSA key as anslhdsa.PublicKeyvalue fromgithub.com/cloudflare/circl/sign/slhdsa.MarshalPublicKey,PublicKeysEqual,GeneratePOP,VerifyPOP,NewSignatureCredentialsandVerifysupport SLH-DSA keys and signatures. Signatures use the pure mode with an empty context. The pre-hash variant HashSLH-DSA is refused.SLHDSAAlgorithmOIDandSLHDSAAlgorithmFromOIDmap parameter sets to their identifiers. CheckCertificateRequestSignaturechecks the self-signature of a PKCS #10 request, SLH-DSA requests included, which crypto/x509 cannot check.NewCertStatusconfirms a certificate signed by an SLH-DSA CA with SHA-512 and names it inhashAlg, as for ML-DSA.Verifychecks protection certificates issued by an SLH-DSA CA andRootCAKeyUpdate.Verifychecks SLH-DSA root certificates.
client
SendIR,SendCR,SendKURandSendP10CRaccept SLH-DSA keys and requests.NewSignatureCredentialswith an SLH-DSA key protects requests with SLH-DSA signatures.- The client trusts certificates and responses from a CA with an SLH-DSA key when that CA certificate is in the trust pool and travels in the response.
server
- The server checks SLH-DSA proofs of possession, PKCS #10 requests and message protection. The CA receives an SLH-DSA key in the template as
slhdsa.PublicKey. crypto/x509 cannot certify it, so the CA encodes the SubjectPublicKeyInfo withpkicmp.MarshalPublicKeyand signs the certificate itself. Its key usage must not allow encipherment. WithSigneraccepts an SLH-DSA CA key.WithStrictProfileValidationchecksextraCertschains that an SLH-DSA CA signed. Each SLH-DSA signature is slow, so a separate classical or ML-DSA signer keeps the responses fast.
Requirements
- github.com/cloudflare/circl v1.6.5 is now required for SLH-DSA. It brings in golang.org/x/sys v0.49.0.
v0.6.0
This release enrolls keys that cannot sign, such as ML-KEM-768 and ML-KEM-1024, through proof of possession by decryption: the encrypted certificate and challenge-response methods of RFC 9810. It works against EJBCA, with OpenSSL 3.0 and later for RSA keys and with OpenSSL 4.0 for ML-KEM keys. CAs can hold enrollment and revocation requests until someone approves or rejects them. They can also answer grantedWithMods. Some refusal reasons and the handling of waiting requests changed. Code that checks refusal reasons or relies on waiting requests should read the pkicmp and server entries before upgrading.
The new documentation site at misiektoja.github.io/go-pki-cmp has client and server guides, option references, troubleshooting and the list of tested CMP implementations.
pkicmp
- Proof of possession by decryption.
NewKeyAgreementPOPjoinsNewKeyEnciphermentPOP. Both takeSubsequentMessageEncrCertorSubsequentMessageChallengeResp.NewEncryptedCertificatecarries an encrypted certificate in EnvelopedData andNewEncryptedValueCertificatein the EncryptedValue of CMP version 2.CertOrEncCert.EncryptedCertificateandEncryptedValueCertificateread them.NewPopDecCBody,NewPopDecRBody,PKIBody.PopDecC,PKIBody.PopDecR,ChallengeWitness,Challenge.VerifyWitness,RandandParseRandbuild and check the challenge and its answer. - ML-KEM public keys.
ParsePublicKeyreturns ML-KEM-768 and ML-KEM-1024 keys and refuses ML-KEM-512 withErrMLKEM512.MarshalPublicKey,IsKEMKey,PublicKeysEqualand certificate requests support them. - Refusal reasons are more precise.
ReasonUntrustedSignerreports a protection certificate that does not chain to the trusted CAs, which used to fail withReasonSignatureFailed.ReasonSenderKIDMismatchreports a senderKID that names none of the candidate certificates, which used to fail withReasonNoCandidateSigner. That reason now means no certificate was available. A response that carries CA certificates without a senderKID, as Nokia NCM sends them, now reports the failure of the certificate with the sender's name instead ofReasonSenderMismatch, which now means no candidate has that name. VerifyOptions.AllowSenderKIDMismatchtries every candidate when the senderKID names none of them, for CAs that copy the senderKID of the request. Each candidate must still chain to the trusted CAs, have the sender's name and verify the signature.VerifyOptions.CurrentTimechecks the protection certificate and its chain at a chosen time, such as when a recorded message was received. Do not take that time from the message when the result decides whether to act on it, because the sender sets itsmessageTime.
client
SendIRWithIndirectPOP,SendCRWithIndirectPOPandSendKURWithIndirectPOPenroll a key that cannot sign withEncryptedCertificateorChallengeResponse. They accept ML-KEM-768 and ML-KEM-1024, RSA and elliptic curve keys on P-256, P-384 and P-521. They read certificates sent as EnvelopedData or as EncryptedValue and use CMP version 3 for the whole transaction. The CA must support the method. RFC 9483 lets it refuse both for a key that can sign.WithSenderKIDMismatchaccepts signed responses whose senderKID names none of the candidate signer certificates. It is off by default.
server
WithEncryptedCertificatePOPandWithChallengeResponsePOPaccept proof of possession by decryption. Both are off by default.WithStrictProfileValidationallows them only for keys that cannot sign. An encrypted certificate never gets implicit confirmation and a certConf with the wrong hash gets badPOP. Publish the certificate only afterConfirmAcceptedand revoke it onConfirmExpiredorConfirmRejected. A challenge-response request reaches the CA only after the right answer.POPMethodFromContexttells the CA which method a request used. Keys the server cannot encrypt for, such as X25519, are refused with badAlg.- Encrypted certificate format. A CMP version 2 request for an RSA key gets its certificate as EncryptedValue in a version 2 response, because OpenSSL 3.0 and 3.2 read only that format. Other requests get EnvelopedData in a version 3 response.
WithEnvelopedDataForVersion2sends EnvelopedData to version 2 RSA requests too, for clients that read only EnvelopedData, such as Bouncy Castle 1.86. - Snapshots keep the proof-of-possession method and any challenge waiting for its answer. Earlier versions refuse a snapshot that holds such a challenge.
Response.GrantedWithModsanswers grantedWithMods for a CA that changed the request.Response.StatusTextsets the statusString of the issued certificate. Both are off by default.- Waiting requests end the way immediate ones do. A request that
PendingChecker.CheckPendingrejects gets anip,cporkupwith its certReqId instead of an error message. The final response with a certificate grants implicit confirmation only when the request asked for it. Waiting and rejected responses no longer grant it.WaitingResponse.Reasonbecomes the statusString of the waiting response. - Polling follows RFC 9483 section 4.4. An early pollReq gets another pollRep with the time left instead of failing with "polling too frequently". A pollReq must name the waiting request by its certReqId or by -1, otherwise it gets badRequest.
WithOptionalPollReqRecipNonceaccepts a pollReq without recipNonce, which the Insta Certifier 10 cmpclient and the Insta Certifier 7 ssh-cmpclient send. Without it they cannot collect a certificate that waited for a decision. WaitingErrorlets aRevokerorRevocationAuthorizerdelay a revocation untilPendingChecker.CheckPendingdecides. Such a revocation used to fail with systemFailure.WithWaitingLifetimekeeps a waiting request for 24 hours after its last poll by default. Waiting requests used to expire with the confirmation wait of 10 seconds.WaitingExpirerandWithWaitingExpirertell the CA about each waiting request thatCleanupExpiredremoves.
cms
- New package for the EnvelopedData of proof by decryption, with RSA-OAEP, ECDH on P-256, P-384 and P-521 and ML-KEM-768 and ML-KEM-1024 recipients.
Encryptderives ML-KEM keys with HKDF-SHA256.Decryptalso accepts the KDF2, KDF3 and SHAKE256 derivations that Bouncy Castle and EJBCA use. RSA PKCS #1 v1.5 key transport is refused in EnvelopedData. NewEncryptedValueandDecryptEncryptedValuehandle the EncryptedValue of CMP version 2 for RSA keys. Encryption uses AES-256-CBC with PKCS #1 v1.5 key transport, the only combination OpenSSL reads. Decryption also accepts AES-128, AES-192, Triple DES and RSA-OAEP and reports a padding error like any other failure.
examples/mockserver
MockCAcertifies composite ML-DSA keys. Enrolling one used to fail with systemFailure.
Requirements
- golang.org/x/crypto v0.58.0 or newer is now required.
v0.5.0
This release adds the RFC 9483 support messages. Clients can ask a CA for its CA certificates, a root CA update, a certificate request template and newer CRLs. Servers can answer these requests.
pkicmp
- General messages (
genmandgenp) are built withNewGenMBodyandNewGenPBodyand read withPKIBody.GenMandPKIBody.GenP. Typed helpers encode and parse CA certificates, root CA key updates, certificate request templates, CRL status lists, CRLs, the current CRL and unsupported OIDs.RootCAKeyUpdate.Verifychecks that the old root certified the new one. NewCertProfileInfobuilds the certProfile header item.
client
- Support messages:
GetCACertificates,GetRootCAUpdate,GetCertReqTemplate,GetCRLUpdateandGetCurrentCRLask the CA for information.SendGenMsends other info types. A root CA update is returned only if the current root signed the new one. A CRL update is returned only if it is newer than the CRL the caller holds.ErrInfoTypeNotSupportedreports a type the CA lists as unsupported or leaves out of its answer. Nokia NCM leaves all of them out. EJBCA Community Edition rejects general messages withbadRequest. WithCertProfilenames a certificate profile in the request header, for enrollment or forGetCertReqTemplate.
server
- Support messages are answered by the optional
CACertificatesProvider,RootCAUpdateProvider,CertReqTemplateProviderandCRLProviderinterfaces. The server lists every requested type without a provider as unsupported. It refuses a root CA update that lacks a valid newWithOld certificate withsystemFailure. A customHandleranswers throughResponse.GeneralResponse. WithStrictProfileValidationrequires one item per general message and no value where RFC 9483 defines none.
v0.4.0
The library is now go-pki-cmp, with a new module path. This release adds device compatibility options, configurable MAC iteration limits and exact subject preservation. It fixes same-key update confirmation and enrollment with only a subjectAltName. Clients can distinguish failed certificate confirmation from a refused request.
pkicmp
- MAC iteration limits are configurable through
VerifyOptions.MinMACIterationCount,MaxMACIterationCountandWithMACIterationLimits. PasswordBasedMac and PBMAC1 defaults remain 1 to 500000.VerifyResult.ProtectionParamspreserves the accepted range when echoing a peer's parameters. CertTemplate.RawSubjectpreserves the received subject DER. Marshalling usesSubjectand now supports subjects built withNewDirectoryNameFromRawDER.VerifyPOPWithOptionssupports poposkInput proofs. It checks publicKeyMAC againstPOPOptions.SharedSecretor the sender againstPOPOptions.Sender. Malformed inputs fail parsing.AllowPoposkInputVariantsaccepts Insta Certifier 10 cmpclient encodings. These proofs verify possession of the key but do not cover the certificate template.CertRequest.OldCertIDreads a key update's issuer and serial number.CertID.Identifiesmatches them to a certificate.- Directory names use crypto/x509 string types: PrintableString where possible and UTF8String otherwise. Unencodable values now fail marshalling instead of producing an invalid name.
client
WithMACIterationLimitsbounds MAC iterations in responses. Sending a count outside the defaults also requirespkicmp.WithMACIterationCountand widerpkicmp.WithMACIterationLimitson the credentials.SendKURpreserves the old certificate's subject encoding unlessWithTemplateSubjectoverrides it. For an empty subject it also copies subjectAltName unless one is supplied. Signed requests preserve the signer's subject encoding unlessWithSenderoverrides it.- SAN-only requests carry a NULL-DN subject as RFC 9483 requires, instead of omitting the subject field.
ConfirmationErrorcarries the issued certificate when certConf fails. The CA may keep or revoke it, so retrying enrollment may issue another certificate.errors.Asstill exposes the underlying error.
server
WithMACIterationLimitsbounds MAC iterations in requests. Responses echo the accepted count. Out-of-range requests fail before key derivation withbadMessageCheck. Rejected MAC parameters now report "MAC protection parameters not accepted".WithRawSubjectpreserves every requested subject attribute, its order and string type for CRMF and PKCS#10 requests. It is off by default. CAs must check all attributes inSubject.Namesand clearRawSubjectbefore changingSubject.WithOptionalCertConfRecipNonceaccepts certConf without recipNonce. A supplied nonce must still match and certificate hashes are still checked.WithPoposkInputVariantsaccepts Insta Certifier 10 cmpclient proofs forir,crandkur. Signatures and publicKeyMACs are verified, but only message protection binds the requested names to the key holder.WithSignatureProtectionPOPaccepts a missing CRMF proof when the message is signed with the requested key, as in Nokia ssh-cmpclient same-key updates. MAC-protected requests still need a proof.- NULL-DN subjects with subjectAltName are accepted, including under strict validation. The CA template marks subjectAltName critical.
WithOptionalSubjectalso accepts CRMF templates that omit the subject field. Both cases require subjectAltName. - Revocation options accept missing reasonCode (
WithOptionalRevocationReason), missing issuer (WithOptionalRevocationIssuer) or MAC protection (WithMACRevocation). An absent reason becomes unspecified. A missing issuer comes from the signer's certificate when its serial matches. Otherwise the CA must resolve the serial without an issuer and reject ambiguous matches. MAC revocation requiresRevocationAuthorizerapproval. WithStrictProfileValidationoverrides the optional nonce, proof, subject and revocation rules above and rejects all poposkInput proofs. Raw subject preservation and MAC limits still apply. Strict validation now accepts extraCerts without a self-signed root, while checking chain order.CertificateLookupremains responsible for trust.- Rejection status texts now identify unsupported protocol versions and message types.
- Same-key updates complete confirmation when certConf uses the original request's credentials. Confirmation with the newly issued certificate remains refused.
- MAC-protected responses repeat the request's senderKID, including when a server signer is configured.
Requirements
- The module path is
github.com/misiektoja/go-pki-cmp. Update imports and thego.modrequirement fromgithub.com/misiektoja/go-pkicmp-ng. Package names are unchanged. Releases through v0.3.2 use the old path. - go-composite-mldsa v0.2.1 is now required, with fixes for key parsing and certificate signing.
v0.3.2
This version requires Go 1.27.2 and updates composite ML-DSA support to the published v0.2.0 library.
Requirements
- Go 1.27.2 or newer is required. go-composite-mldsa v0.2.0 validates composite key encodings more strictly. Valid keys retain the same encoding and algorithm support.
v0.3.1
The client can revoke certificates at CAs that sign the revocation response without attaching their certificate.
pkicmp
ReasonNoCandidateSignerreports a signature-protected message that carries no certificate to verify it, either none at all or none matching its senderKID. Such a message previously failed withReasonSignatureFailed, so code that checks for that reason no longer sees it in this case.
client
WithServerCertssupplies the CMP server certificates the client knows out of band, like the-srvcertoption ofopenssl cmp. The client tries them after the certificates in the response, so a response without extraCerts verifies. Unlike-srvcert, each must still chain toWithTrustedCAs, match the sender in the response and verify the signature. A CA certificate without the digitalSignature key usage is accepted, as it already is from extraCerts. Without the option such a response fails withReasonNoCandidateSignerand the error namesWithServerCerts.
v0.3.0
Devices can enroll composite ML-DSA keys, which pair an ML-DSA key with a classical key in one certificate, through ir, cr, kur and p10cr, and revoke those certificates with rr.
pkicmp
- Composite ML-DSA proof of possession.
VerifyPOPandVerifyPOPWithOptionscheck a composite signature over the certificate request. The algorithm must match the requested key exactly and its parameters must be absent.CertReqMsg.PublicKeyreturns a composite key as*compositemldsa.PublicKey.
client
SendIR,SendCRandSendKURaccept a composite ML-DSA key and sign the proof of possession with it.SendP10CRaccepts a PKCS#10 request for a composite key made withcompositex509.CreateCertificateRequest. The client checks that the issued certificate carries that key.
server
- Composite ML-DSA certificate requests reach the CA after the server verifies their proof of possession. The template carries the key as
*compositemldsa.PublicKey, which the CA certifies withcompositex509.CreateCertificate. Confirmation, key update and revocation work for these certificates as for classical ones.