[5.29.0] - 2026-10-28
🚀 Features
EST & SCEP Enrollment (#871)
- Add EST (RFC 7030):
cacerts,csrattrs,simpleenroll(TLS client certificate or HTTP Basic bootstrap) andsimplereenroll; enable with[est] est_enabled = trueandest_ca_uid;csrattrsanswers in DER withextensionRequestand one key type,id-ecPublicKeywith the firstallowed_ec_curvesentry of the template (orrsaEncryptionwithmin_rsa_key_bitswhen no curve is listed), as a hint only: enrollment still accepts every key the template allows; a CSR carrying achallengePasswordmust hold the RFC 7030 §3.5 tls-unique value of its TLS 1.2 connection, otherwise it is refused with400(including over TLS 1.3, where tls-unique is undefined) (#1257) - Add SCEP (RFC 8894) at
/scep:GetCACaps,GetCACertandPKIOperation(PKCSReq,RenewalReq); enable with[scep] scep_enabled = true,scep_ca_uidandscep_challenge_password; only AES-128/192/256-CBC envelopes with RSA key transport and SHA-256/384/512 signer digests are accepted. FIPS builds accept only RSAES-OAEP key transport and answer RSA signers with RSAES-OAEP: PKCS#1 v1.5 clients, including the native macOS client, need the non-FIPS build - Add per-endpoint certificate templates (
[templates.<name>],est_template/scep_template): RSA size, EC curves, EKUs, validity and anchored regular expressions for the subject CN and DNS/e-mail/UPN SANs; a CSR SAN without a matchingsan_dns_regex/san_email_regex/san_upn_regexis refused (422/badRequest), and iPAddress and URI SANs needallow_any_identity = trueand nosan_*_regex - Issue EST/SCEP certificates as the reserved identity
[enrollment-service], mark every enrolled certificate with acertificatePoliciesentry (1.3.6.1.4.1.16376.9.2.1) that mTLS and the KMIP socket refuse for authentication, and validate CSRs fail-closed (msExtReq, undecodable or repeatedextensionRequest). Setup action:ckms access-rights grant "[enrollment-service]" -i <ca cert uid> certify, the same for the CA private key, andcreatewhencrypto_officer_usersis configured - Protect enrollment: lockout with
429+Retry-After, at most 16 concurrent enrollments, 64 KiB bodies, SCEP replay protection, fixed error messages, masked secrets, refusal of DES/3DES/RC2 envelopes and SHA-1/MD5 signatures, renewal checks that count only certificates owned by the CA owner (a copy carrying a forged_enrolled_serialtag cannot revive a revoked certificate), and every attempt logged and audited (#1257) - Accept dotted OIDs in extended-key-usage entries of X.509 extension files
- Add
mise run test:scep-interop,test:est-interopandtest:scep-appleblack-box tests
Audit Logging
- Add a
PostgreSQLaudit backend as an alternative to the JSONL file (--audit-postgres-url,--audit-instance-id): session advisory lock per instance, append-onlykms_audit_events, hardened deployments withSELECT/INSERTonly, and the audit database must differ from the object database - Always start the
PostgreSQLaudit backend: on content corruption, seal the latest chain generation as evidence and start a new one with anaudit:reanchorevent carrying a SHA-256 evidence digest; connectivity, TLS, schema and lock failures still abort startup - Record a final
audit:size-cap-reachedevent in the file backend and report a row that does not chain to its predecessor asbroken_link - Honor
sslmode=verify-ca/verify-full,sslrootcert,sslcertandsslkeyon the audit connection, and refuse to start when the four append-only triggers are missing in a no-DDL deployment - Read PostgreSQL audit chains with
ckms audit export/verifythrough--audit-postgres-url(optional--audit-instance-id) in bounded pages (#1189) - Fix
ckms auditflags: reject--audit-instance-idwith a file source, failexportandverifyon an instance with no events, verify every chain generation, include instance and generation in PostgreSQL exports, and stop readingKMS_AUDIT_INSTANCE_ID - Make the
PostgreSQLaudit seal-and-roll write atomic and serialize concurrent schema bootstrap across instances - Read the audit client address from the rightmost
X-Forwarded-Forentry that is not a trusted proxy instead of the leftmost one, which the client controls; update log/SIEM correlation rules - Generalize the audit writer/store to an
AuditSinkinterface shared by the file andPostgreSQLbackends - Add a live
PostgreSQLaudit suite (restricted writer role, tamper → restart → seal-and-roll), run in CI - Reorganize the Audit & SIEM docs (shared overview, file and
PostgreSQLbackend pages, audit events reference, audit tables in the database tables reference) and document CEF export and read-only collection accounts; state that--audit-failure-mode rejectonly governs queue admission and that the SHA-256 chain is unkeyed, so detecting a privileged rewrite needs a chain head (id,row_hash) exported outside the database - Upgrading from 5.28.0 needs no audit migration: the
PostgreSQLaudit tables are new and created on first connection, file audit logs keep their hash-chain format, and every new configuration key is optional
OpenPGP
- Add the
ckms pgpsubcommand group (non-FIPS):keys create(Ed25519, RSA 2048/3072/4096),keys revoke,keys destroy,export,import,encrypt,decrypt,signandsign-verify, withpgp-secret-binaryandpgp-public-binaryGnuPG-compatible exports; transferable keys are version 4 or 6 only, and building requires Rust 1.88 (rPGP 0.21.0) (#1240) - Add the Web UI OpenPGP section (create, encrypt, decrypt, sign, verify, armored and binary
.gpgexport) with WASM bindings, FIPS filtering,en/fr/zh-CNtranslations and Playwright coverage - Generate keys on the blocking thread pool, decrypt DEFLATE-compressed messages with the decompressed plaintext capped at 64 MiB, preserve signed encryption-subkey metadata, and use for
EncryptandSignatureVerifyonly subkeys bound to the primary key by a verified binding signature (and back-signature for signing subkeys), not revoked by the primary key and not past their Key Expiration Time (RFC 9580); a certificate that is key-revoked or whose primary key has expired is refused byEncrypt(OpenPGP certificate is revoked/expired) and givesInvalidonSignatureVerify, so import an up-to-date certificate - Run RSA private-key operations on OpenPGP keys (decrypt with implicit rejection, sign, key generation and self-signature) in OpenSSL instead of the
rsacrate (RUSTSEC-2023-0071) - Refuse
Get/Exportof a Sensitive OpenPGP key withSensitiveeven when aKeyWrappingSpecificationis given, and answer a wrapping request withKey_Wrap_Type_Not_Supported: OpenPGP keys are never wrapped on export - Discover RSA keys and certificates tagged
gnupg-card(override withCOSMIAN_PKCS11_GNUPG_KEY_TAG) sognupg-pkcs11-scdcan expose them as a GnuPG smartcard - Add a 24-test GnuPG interoperability matrix,
.mise/scripts/test/test_gnupg.shand agnupgCI job
Web UI SAML Single Sign-On
- Add Web UI SAML single sign-on through the Authentication Verifier: the new
auth_verifier_saml_realmsetting advertisesAUTH_VERIFIER_SAMLinGET /ui/auth_method,GET /ui/login_samlstarts sign-in, the UI shows a Single sign-on (SSO) button and the session user with a Logout button, and the Authentication Verifier_ea_session cookie is accepted on the KMIP, REST crypto and tokenize APIs and inGET /ui/whoami(#1252) - Accept the
_ea_cookie only whenauth_verifier_saml_realmis configured and only for tokens of that realm (an explicit bearer token takes precedence), and authenticate state-changing cookie requests only withContent-Type: application/jsonorapplication/octet-stream - Refuse to start when
auth_verifier_saml_realmis set andkms_public_urldoes not usehttps://, when the realm is empty or the Authentication Verifier admin realm_, or whenauth_verifier_urldoes not usehttps:// - Revoke the Authentication Verifier session and expire the
_ea_cookie onGET /ui/logout, only for a valid session of the SAML realm - Move the
authenticationsubmodule to the Authentication Verifier release that ships SAML single sign-on (0.5.0), and add thetest:ui-samlend-to-end task (Keycloak IdP and nginx in Docker, Authentication Verifier and KMS on the host with completetest_datafixtures, Playwright inui/tests/e2e-saml/), also run bytest:uion non-FIPS with Docker
HSM
- Add Thales Luna Network HSM support (
hsm_model = "luna") with a 25-test PKCS#11 suite, aprepare_luna.shclient installer, and Kubernetes guides (#1275, #1281) - Add Azure Cloud HSM support (
hsm_model = "azure_cloud_hsm", Linux x86_64): the KMS loads/opt/azurecloudhsm/lib64/libazcloudhsm_pkcs11.so, overridable withAZURE_CLOUD_HSM_PKCS11_LIB; the new Azure Cloud HSM page covers client setup, Crypto User creation, slot-id discovery, configuration and limits (#1211) - Delegate RSA-PSS signing, EC key generation (P-224/P-256/P-384/P-521), ECDSA signing, EdDSA (Ed25519/Ed448) signing and RSA
SignatureVerifyto the HSM (#1154, #1157) - Add HSM trait MAC generate/verify with bridging (#1263),
non-fipsgating on every PKCS#11 loader crate, andSessionhelpers for message-based AES-GCM, HKDF, generic secret keys and multi-part AES-CBC - Add the Helm
commandvalue so the FIPS image can start withOPENSSL_CONFunset for HSM libraries that embed OpenSSL (Trustway Proteccio) - Add
ckms bench --hsm/--hsm-slotandmise run bench:hsm --delegated, withbench:pkcs11reports for every signature algorithm - Sign and verify through the PKCS#11 v3.0 message API (
C_MessageSignInit/C_SignMessage,C_MessageVerifyInit/C_VerifyMessage, persistent per-session contexts) when the provider setssupports_message_sign_verifyand the library exports it, falling back to the classic API for each mechanism the HSM rejects (#1340) - Add
key_encryption_key_wrapping_algorithm(aes-gcm,aes-key-wrap,aes-key-wrap-padding,rsa-oaep; envKMS_KEY_ENCRYPTION_KEY_WRAPPING_ALGORITHM) to choose howkey_encryption_keywraps keys at rest; unset keeps AES-GCM for an HSM AES key, RSA-OAEP (SHA-256) for an HSM RSA key and AES-KWP for a software AES key, and a software RSA key requiresrsa-oaep(#1341) - Support AES-KW (NIST SP 800-38F, RFC 3394) and AES-KWP (RFC 5649) with HSM wrapping keys and honour the wrapping mode requested on
Export/Get; wrapped objects record the mode actually used, and objects wrapped by earlier versions still unwrap (#1341) - Check at startup that an HSM AES
key_encryption_keycan wrap with its configured mode, otherwise stop with an actionable error; for AES-KW/KWP, setCKA_WRAP/CKA_UNWRAPonce on a KEK created by an earlier version (only on a slot that offers AES key wrap) and clear them again if that does not fix wrapping. Note: every new HSM AES key is now created withCKA_WRAPandCKA_UNWRAPenabled, not only key-wrapping keys; restricting them is still under review (#1341)
PKCS#11 v3.x Provider
- Implement
C_GetInterfaceList/C_GetInterface(CK_INFO.cryptokiVersion3.1;C_GetFunctionListv2.40 unchanged),C_LoginUser, theCKO_PROFILEconformance profiles,CKA_UNIQUE_ID, the v3.0 function list stubs, and one-shot EdDSA message signing (C_MessageSignInitreturnsCKR_FUNCTION_NOT_SUPPORTEDfor any other mechanism, whatever its parameters); PINs, user names and tokens passed toC_Login/C_LoginUser, and the copy of the HSM slot PIN made for each login, are wiped from memory after use (the configured slot PIN stays in memory for later logins) (#1166) - Implement
C_VerifyInit/C_Verify/C_VerifyUpdate/C_VerifyFinalthrough KMIPSignatureVerify(previouslyCKR_FUNCTION_NOT_SUPPORTED) and nativeCKM_AES_GCM - Add
CKA_MODULUS_BITSon RSA key objects and reportCKF_VERIFYinC_GetMechanismInfo - Probe the v3.0 interface of loaded HSM libraries additively, degrading to v2.40 when unsupported
PKI & Certificates
- Add
POST /certificates/csr,ckms certificates generate-csrand a Certificates > Certs > Generate CSR page to generate a PKCS#10 CSR for a private key held by the KMS, for example to have an offline root CA sign a sub-CA: an HSM key signs through the crypto oracle and can stay non-extractable (--sensitive), the caller needs onlySign, the KMIP algorithm policy applies, keys that are not Active, lackSignin their usage mask or cannot sign (X25519, ML-KEM) are refused with a 4xx, and the user-settablePublicKeyLinkis never followed (#1328) - Create the key pair during CSR generation:
ckms certificates generate-csr --algorithm <nist-p256|rsa4096|…>(thecertify --generate-key-pairalgorithms; add--private-key-id hsm::<slot>::<id> --sensitivefor a non-extractable HSM key), a Generate a new key pair option on the page, andalgorithm/sensitiverequest fields, the response returning the key ids; the KMIP algorithm policy is checked before the key pair is created, so a refused request leaves no key (#1328) - Sign certificates (
Certify,ReCertify), CRLs and OCSP responses with an issuer private key held in an HSM, including a non-extractable one: the HSM signs the TBS bytes and the result is verified with the issuer public key before it is stored; an issuer given only by its sensitive HSM key id is resolved through the certificate'sPrivateKeyLink; Ed25519/Ed448 HSM keys (non-FIPS) work for CSRs, certificates, CRLs and OCSP; EST issues throughCertify, and SCEP answersNotSupportedfor a sensitive HSM CA key.Certifygiven an HSM private key id with noPublicKeyLink(for example the ID Placeholder after an HSMCreateKeyPair, including a non-extractable RSA key) certifies its public key<sk>_pk, with the caller's ownCertifyrights on it. Upgrade action: this applies to every HSM issuer key, including non-sensitive ones that used to be exported for signing: grantCertifyon the HSM CA key to the users who issue certificates or CRLs with it (HSM grants are per operation); HSM CA keys other than RSA, EC, Ed25519 and Ed448 are refused, and EC HSM CAs sign with the digest matching their curve (SHA-256 for P-256, SHA-384 for P-384, SHA-512 for P-521) (#1328) - Tag every certificate created by
Certify/ReCertifyor imported (PKCS#12 included) with the system tag_serial=<UPPERCASE HEX>soLocateby tag finds it by serial number; on the first start after the upgrade, existing certificates get the tag in a one-time database write (SQLite, PostgreSQL, MySQL/MariaDB, Redis-Findex) recorded by a completion marker (#1328)
REST
- Unwrap the CEK of
POST /v1/crypto/decryptwithRSA-OAEP/RSA-OAEP-256through the HSM crypto oracle whenkiddesignates an HSM key pair (a publickidis resolved throughPrivateKeyLinkor the_pksuffix), so the private key never leaves the HSM; SoftHSM2 2.6.1 cannot do RSA-OAEP decryption with explicit parameters, use another HSM such as Kryoptic (#1338) - Import a public-only RSA key with
POST /v1/crypto/keys(nandewithoutd,key_opsamongencrypt,wrapKeyandverify), and accept a caller-chosenkidon every import (#1338) - Accept
cek_kidinPOST /v1/crypto/encrypt(RSA-OAEP/RSA-OAEP-256only): an active, extractable KMS AES key on which the caller hasExport, used as the CEK of a complete JWE; the key that wraps it (the public key ofkid, or the private key when it has no linked public key) must have theWrapKeyusage and the RSA-OAEP hash must pass the KMIP algorithm policy, otherwise HTTP 422:RSA-OAEP(SHA-1) is refused when the configured policy excludes it (theDEFAULTpolicy does; no policy, or a custom allowlist that includes SHA-1, still accepts it), and public keys imported throughPOST /v1/crypto/keysneedwrapKeyinkey_ops(#1338)
Packaging & Deployment
- Publish signed Alpine Linux
.apkpackages forcosmian_kms(OpenRC service, run as the unprivilegedkmssystem user withkms.tomlasroot:kms0640) andckms, for x86_64 and aarch64: dynamically-linked musl for FIPS, fully static musl for non-FIPS; add thetest:musl-crosscheck,test_alpine_musl.shandalpine-native-buildvalidation paths and gatepublish-apkon the Alpine smoke tests (#1237) - Ship the C++ runtime (
libstdc++,libgcc_s) in the Docker image for vendor PKCS#11 libraries
Server Configuration & KMIP
- Report the real generator (DRBG, CTR, AES-256) in
QuerywithQueryRNGs - Decide per attribute and per operation whether an edit is allowed, needs a grant or is server-managed with one policy table (
Tag::edit_policy) - Support
Certifyover KMIP 1.x, with the X.509 extensions in the Custom Attributey-cosmian::x509-extension(#1335)
Crypto & KEM
- Thread an OpenSSL property query and a FIPS-indicator check through ML-KEM, ML-DSA, SLH-DSA and hybrid KEM key generation; a
fips=yesrequest fails closed when the indicator is unavailable, as required by NIST SP 800-227 RS2 readiness (#1253) - Give
RNGSeedzero entropy credit, zeroize the split-key seed and returnServerErroron RNG failures (#1253) - Add the non-FIPS
secp192k1curve:ckms ec keys create --curve secp192k1,ckms ec signandsign-verifyon the software path, and HSM key generation (OID1.3.132.0.31); it signs with OpenSSL's randomised ECDSA
🐛 Bug Fixes
Hybrid KEM & HSM Key Encryption Key
- Fix hybrid KEM keys (
ConfigurableKEM,X25519MLKEM768,X448MLKEM1024) with an HSMkey_encryption_key:ckms pqc keys create -a ml-kem-768-p256no longer fails withUnsupported key format type: ConfigurableKEMSecretKey; wrap, unwrap, export with the storedKeyFormatTypeand TTLV deserialization now work (#1330) - Unwrap with a KMS wrapping key that is itself wrapped (for example by the HSM KEK), checking its stored usage mask
- Keep the requested
CryptographicUsageMaskofConfigurableKEMkeys instead of OR-ingUnrestricted, and inheritCommonAttributesin per-key attributes - Reject non-empty
Datain KEMEncrypt, and decapsulate the encapsulation component inckms bench - Fix
ImportofX25519MLKEM768,X448MLKEM1024andConfigurableKEMkeys failing withUnsupported key format type, andGet/Exportof aConfigurableKEMkey, by skipping the OpenSSL round trip (#1268)
HSM
- Fix every HSM
Encryptfailing on Thales Luna (non-sensitive RSA-OAEP probe key), AES-GCMEncrypton Luna ("Failed to initialize message-based encryption"), the Luna FIPS IV layout (ciphertext || tag || iv), and creation of non-sensitive keys; cache the supported algorithms per slot (AES-GCM on Luna from 288 to about 4,000 req/s) - Create HSM benchmark keys as sensitive and destroy leaked keys; give
ckms benchsetup keys deterministicbench-<purpose>identifiers - Make key-date handling capability-driven (
supports_key_dates, false for Crypt2pay, AWS CloudHSM and SoftHSM2) and refuse RSA-OAEP wrapping where unsupported - Fix delegated signing: pre-digested ECDSA uses
CKM_ECDSA, pre-digested RSA-PSS usesCKM_RSA_PKCS_PSS, pre-digested PKCS#1 v1.5 wraps aDigestInfo,datatogether withdigested_datais rejected, Ed25519/Ed448 reject pre-hashed requests, a mismatchedCryptographicAlgorithmreturns a clear KMIP error, and a malformed DER ECDSA signature is reported as invalid (#1169) - Infer the RSA PKCS#1 v1.5 hash from the input length only for a digest (
digested_data): a raw message with no explicit hash always signs with SHA-256 - Preserve EC-family HSM metadata (Ed25519/Ed448/X25519 key types and
CKA_EC_PARAMSencodings, curve domain parameters,CKA_START_DATE/CKA_END_DATEread failures) and refuse HSM-backed X25519 key-pair creation until aDeriveKeypath exists - Bound async session checkouts per slot (
max_concurrent_sessions, default 128), resolve signing in one session, avoid the secondC_Signcall, cache non-rotated HSM keyset UIDs for 1 hour, and pool only read-write sessions - Issue one
C_EncryptMessage/C_DecryptMessagecall per AES-GCM operation, and pin the decrypt IV to 12 bytes and the tag to 16 - Fix
SignatureVerifyresolvingdataanddigested_datainconsistently on the crypto-oracle path - Refuse
Get/Exportof a sensitive HSM key even with a key-wrapping specification: the KMS cannot read such a key, so it has nothing to wrap (#1338) - Use the matching public key when wrapping or encrypting with an explicit RSA algorithm on an HSM private-key UID, as the default algorithm already did (#1341)
- Generate AES-GCM IVs inside production HSMs in FIPS builds (FIPS 140-3 IG C.H), through the PKCS#11 v3.0 message API or an HSM-generated IV; an HSM offering neither fails with a message pointing to
key_encryption_key_wrapping_algorithm = "aes-key-wrap-padding", only the SoftHSM2 and Kryoptic development tokens keep caller-supplied IVs, andhsm_model = "other"is treated as a FIPS-validated module (#1341)
PKCS#11 Provider
- Fix
C_Login/C_LoginUserrejecting OIDC bearer tokens above 4 KiB whenpkcs11_use_pin_as_access_token = true(PIN cap raised to 64 KiB) - Match
C_GetInterfaceversions exactly and bound thepInterfaceNameread to 8 bytes - Fix the secp256k1 OID (
1.3.132.0.33was secp224r1's; correct is1.3.132.0.10), Ed25519/Ed448 key types (CKK_EC_EDWARDS/CKK_EC_MONTGOMERY), andCKM_RSA_PKCS_PSStreating its input as a message instead of a digest - Sign a
CKM_RSA_PKCSDERDigestInfo(SHA-1/256/384/512) as a pre-computed digest forgnupg-pkcs11-scdand OpenSSH - Stop serializing every Cryptoki operation process-wide: one lock per session instead of one global lock (
sign-eddsathroughput 19 → 82 ops/s at concurrency 8) - Reuse a
C_Signlength-query signature only for the same data - Return
CKR_BUFFER_TOO_SMALLwith the required length inpulDataLenfromC_Decrypton a length query or a too-small buffer, and keep the decrypt operation active so the caller can retry
Crypto
- Fix
ecdsa_verify()/ed_verify()returning a hardCryptoErrorinstead ofValidityIndicator::Invalidfor tampered, malformed or wrong-curve signatures, which affected every KMIPVerifyclient, not only PKCS#11 (#1183) - Sign NIST P-256 ECDSA (non-FIPS builds) with OpenSSL's RFC 6979 deterministic nonce mode instead of the pure-Rust
p256crate, about 20x faster; the randomised OpenSSL signer is used when a pre-digested input is not exactly the hash length. secp256k1 keepsk256with low-S (#1320) - Unwrap AES-GCM-wrapped keys with 128- and 192-bit key encryption keys; unwrapping always assumed AES-256 (#1341)
PKI
- Fix the OCSP responder answering
unauthorizedto every request about a sub-CA: issuer hashes were computed from the CA's issuer name instead of its subject name (#1328) - Fix automatic CRL regeneration (after
Revokeand from the refresh cron) failing silently, and leaving the CRL stale, when the revoking user ordefault_usernamehad no access to the CA key: it now signs as the owner of the issuer certificate (#1328) - Sign certificates, CSRs, CRLs and OCSP responses of EC CAs with the digest matching the CA curve (ecdsa-with-SHA256 for P-256, SHA384 for P-384, SHA512 for P-521, as RFC 5480 §4 recommends) instead of always SHA-256. Upgrade note: existing P-384 and P-521 software CAs now issue certificates, CRLs and OCSP responses signed with SHA-384 or SHA-512; relying parties that only accept ecdsa-with-SHA256 must be updated
Web UI
- Hide PQC, FPE, Anonymize and Covercrypt (and the new OpenPGP section) in FIPS mode, failing closed while
/server-infoloads - Stop offering
CryptographicAlgorithmandCryptographicLengthin the attribute Set, Modify and Delete forms: they are read-only (COSMIAN-2026-044)
KMIP
- Return one
Encryptresponse layout for every KEM public key (ML-KEM,X25519MLKEM768,X448MLKEM1024,ConfigurableKEM):Datais the serialized(shared secret, encapsulation)tuple andIVCounterNonceis empty, instead of returning the encapsulation inIVCounterNonce; ML-KEM and native hybrid KEM clients unpackData(#1330) - Restrict
RNGSeedto Crypto Officers once roles are configured (add seeding callers tocrypto_officer_users), and answerUnauthorizedinstead ofRouteNotFoundfor unimplemented KMIP operations (Archive,Ping, …) in that case - Return Result Reason
Read_Only_Attribute(0x1D) instead ofAttribute_Read_Only(0x22) fromAddAttributeon a read-only attribute, as KMIP 2.1 requires (ModifyAttributeandDeleteAttributekeep 0x22), and refuseAddAttributeofFresh,LastChangeDateandStatethe same way (Stateused to returnInvalid_Request). Clients matching 0x22 onAddAttributemust accept 0x1D - Use the ID Placeholder in a batched
CertifyorReCertifythat has no Unique Identifier, and set the placeholder to the new certificate afterwards (KMIP 2.1 and 1.x batches); afterCreateKeyPairthe placeholder holds the private key, andCertifyfollows its public-key link to certify the created public key
REST
- Require a JSON object for protected headers in
/v1/crypto/decrypt,/v1/crypto/verifyand/v1/crypto/keys/unwrap(arrays and duplicate parameters fail with HTTP 400) - Return error responses of every route as
text/plain; charset=utf-8withX-Content-Type-Options: nosniffinstead oftext/html - Report an Azure EKM wrap/unwrap with a KEK that is not in an accepted lifecycle state (for example
PreActiveorDeactivated) asKeyDisabled(HTTP 403) instead ofInternalError - Derive
useandalgof RSA keys inGET /.well-known/jwks.jsonfrom the public key usage mask instead of always publishinguse=sigwithRS*:Verifyonly publishesuse=sigwithalg=RS256/384/512,Encrypt/WrapKeyonly publishesuse=encwithoutalg, and keys with both (such as HSM RSA public keys) or neither publish neither; keys without a usage mask are unchanged. Clients that assumed every RSA JWK isuse=sig/RS*must select keys bykid(#1338) - Import a public key from
POST /v1/crypto/keyswith RSAnandebut nodinstead of ignoring them and generating a key pair, and reject akidon a key generation request with HTTP 400 instead of ignoring it. Clients must omitkidwhen generating keys and read the server-assignedkidfrom the response (#1338)
Database
- Fix Redis/Findex concurrent
Upsertand stale tag index: every write is one two-phase Lua script with compare-and-swap, lost races retry, stale Findex keywords are removed and discarded on read; Redis Cluster is not supported (#940, #1258) - Invalidate
RotateNameCacheby keyset name and member UID, and never cache empty results - Empty the
crlstable inclear_database(SQLite, PostgreSQL, MySQL)
CLI
- Include the JOSE AES-GCM benchmarks in
ckms bench --algorithm aes-gcm, and drop the secp256k1 and Ed25519 sign/verify modes of theckmsPKCS#11 benchmarks from FIPS builds - Send curve-appropriate
CryptographicParametersfromckms ec sign, and reject--digestedfor Ed25519/Ed448 - Remove
--cryptographic-algorithm/-aand--cryptographic-lengthfromckms attributes set,modifyanddelete: the server rejects edits of these read-only attributes (COSMIAN-2026-044), and scripts passing them now fail at argument parsing - Remove the
--authenticated-additional-dataoption ofckmskey export and the matching Web UI fields: the value has been silently ignored since KMS v5 (#1341)
Hardening
- Fail the KMIP socket TLS handshake when no client certificate is presented, instead of after the first KMIP bytes (#1276, #1278)
- Compare the AWS XKS SigV4 access key ID and
MACVerifytags in constant time (#1339) - Log only lengths, not the plaintext or ciphertext in hexadecimal, from the PKCS#11 provider
C_Encrypt,C_Decryptand KMS encrypt paths at debug level - Drop reserved
_-prefixed system tags from the objects of an imported PKCS#12 file - Count audit events that the backend fails to write (for example while the
PostgreSQLaudit database is down) in the nextaudit:evictionsentinel, now wordedN events dropped (channel full or write failed), instead of losing them silently - Reject
critand nested-JWTctyheaders in every JWKS-keyed authenticator (Google CSE, OIDC, Auth Verifier, UI SSOid_token); Google CSE tokens must carry non-blankalgandkid - Warn at startup when no authentication method, no
key_encryption_keyor norate_limit_per_secondis configured - Draw every key, nonce/IV and secret token from
KmsRng(OpenSSL AES-256 CTR-DRBG, fail-closed) instead ofrand_bytes,CsRngorrand::rng(), includingC_GenerateRandom; ban every other entropy source with a workspaceclippy.toml; nonces, IVs and serials come from the public DRBG so private-DRBG output never leaves the server (#1274) - Check every
ConfigurableKEMkey against the algorithm policy on each use, judged only by the ML-KEM variant and curve read from its key bytes: relabelled or stripped KMIP attributes neither deny nor allow it (#1330) - Make
CKA_EXTRACTABLEfollowsensitiveon HSM-generated AES and RSA keys (sensitive = true⇒ non-extractable) instead of alwaystrue, and setCKA_EXTRACTABLE=falseexplicitly on sensitive AES keys of HSMs that rejectCKA_SENSITIVE(Thales Luna, Azure, AWS and GCP Cloud HSM) instead of relying on the vendor default; existing keys must be regenerated - Refuse the reserved service identity
[aws-xks-service]regardless of ASCII case or trailing spaces - Enforce
objectVersionin the Kubernetes CSI provider: when set, it must equal the lower-case hexadecimal SHA-256 digest of the object's current content, otherwise the mount fails withFailedPrecondition - Audit
/v1/cryptorequests like the KMIP scope, recording the operation as the HTTP method and route (for examplePOST /v1/crypto/encrypt) and the key involved; a request refused before its handler (for example with HTTP 401) is recorded with the operationv1. The API documentation lists which scopes are audited
♻️ Refactor
- KMIP / Server: cut per-request allocations on the TTLV-JSON, binary TTLV, KMIP
ByteStringdecoding, database, audit and middleware paths (4 KiBEncryptdecode: 166 µs → 7 µs;retrieve_object_for_operationon a cached key: 36 → 3 allocations) (#1280) - Workspace: remove every
#[allow(dead_code)]/#[expect(dead_code)], declare crate versions and shared dependencies once in the rootCargo.toml, use safe OpenSSL APIs instead of raw FFI in PQC (#1267, #1260) - HSM: group the
HsmCapabilitiesvendor flags intoaes(supports_sensitive_attribute,key_wrap_pad_is_rfc5649and theaes.gcmsub-structsupports_caller_iv,allows_caller_iv_in_fips,supports_message, which replacessupports_aes_gcm_message),rsa(supports_sensitive_attribute,oaep_requires_source_data_ptr,supports_oaep_key_wrap) andec(supports_sensitive_attribute,enforces_ecdsa_digest_strength) sub-structs, add the top-levelsupports_message_sign_verifyflag, and remove vendor names frombase_hsmsession code; custom HSM providers must adapt (#1340, #1341) - Code consolidation (no behaviour change):
- Server: one auth middleware stack for the
/tokenize,/v1/cryptoand default scopes; shared attribute dispatch arms; shared helpers for the AWS XKS error mapping, Google CSE responses, Azure EKM parameter checks, the JWS signing input, wizard prompt errors and the Crypto OfficerCreategrant/revoke checks. - HSM and PKCS#11: one session-checkout helper in
BaseHsm; shared output-buffer copy andC_EncryptInit/C_DecryptInitkey resolution in the PKCS#11 module. - KMIP and crypto: macro-generated KMIP 1.4 response and key-block conversions; one raw-key path for Ed25519, Ed448, X25519 and X448 key pairs.
- SQL stores: shared helpers for merging granted operations, rotation filtering and CRL numbers.
- CLI: one
RevokeKeyActionand one destroy helper for every key group except PQC, which keeps its ownRevokeKeyAction(different help text). Therevoke_keymodules of the other key groups ofcosmian_kms_cli_actionsandRevokePgpKeyActionare removed; useactions::shared::RevokeKeyAction.
- Server: one auth middleware stack for the
🧪 Testing
- Add SoftHSM2-backed hybrid KEM lifecycle and TTLV-wrapped import tests,
ConfigurableKEMserver tests (usage masks, policy on use, revocation) andpqc_import_tests - Add live
PostgreSQLTLS tests for everysslmode - Add a PKCS#11 v3.x conformance tier (
pkcs11-tool, Kryoptic suite,ckms pkcs11 verify), a Luna test suite and a Crypt2pay simulator, and wire shared HSM tests into every backend - Add
test:k8s:hsm --model proteccio|crypt2pay --variant fips|non-fipsand a Helmcommandrender check - Add the EST/SCEP interoperability suites and Apple SCEP fixtures replayed in CI
- Validate Thales Luna, AWS CloudHSM, GCP Cloud HSM and Azure Cloud HSM manually against real hardware only: no CI runner has these HSMs, so their CI matrix entries are commented out with the reason
- Add
mise run test:jose-hsm(JWKS, RSA-OAEP-256 decryption through a Kryoptic HSM,cek_kidencryption), also run bytest:jose, and run the key-byte export and JOSE decryption cases intest_hsm_all(JOSE decryption skipped on SoftHSM2) (#1338) - Prove on real Crypt2pay hardware that an extractable AES-256 key can be exported (RSA-OAEP-SHA256 wrap, recovery, AES-GCM decryption outside the HSM), run the hardware suite serially and clean up leftover
kms-test-*objects; the Crypt2pay simulator's FIPS suite checks the refusal of HSMs that generate no AES-GCM IV (#1338, #1341) - Add PKCS#11 v3.0 message sign/verify tests against a C stub and a SoftHSM2 v3 adapter (#1340)
- Build in-process server test instances only through
test_kms()/test_kms_with_config(), enforced by theno-direct-kms-instantiatepre-commit hook (#1341) - Keep
postgres-mtlsandmysql-mtls(profilemtls) andjaegerandotel-collector(profileotel-test) out of a baredocker compose up -dof the test backends; start them with--profile <name>or by service name (#1298)
⚙️ Build
- Merge Dependabot bumps (
rcgen0.14,k2560.14,num-bigint-dig0.9,etcetera0.11,redis1.x,num-bigint0.5,quick-xml0.42,vite), adapting the TTLV XML parser and secp256k1 ECDSA, enablinguuidjsfor WASM, allowingBSL-1.0indeny.tomland recordingcargo-vetexemptions (#1289, #1297, #1327) - Upgrade
rustlsand group Dependabot minor/patch and security updates per ecosystem; ignore semver-major bumps - Override
smol-toml(≥ 1.9.0) andsource-map-js(≥ 1.2.2) in the Web UI build to resolve Dependabot security alerts (#1255) - Add Nix builds for the Alpine musl packages (
nix/kms-server-musl.nix,nix/cli-musl.nix) - Use rPGP 0.21.0 without its
bzip2feature and build withoutclap-config-fallback'syamlfeature - Build the FIPS provider from the CMVP-validated OpenSSL 3.1.2 (certificate #4985) instead of the 3.6.2 sources, which hold no certificate; the main library stays on OpenSSL 3.6.2 (#1253)
- Pin the supply chain: add
cargo-vet, hash-pinned Python requirements withpip-audit,govulncheck, SHA-pinned GitHub Actions and base images, pre-commit policy hooks and wider Dependabot coverage (#1273) - Drop the unmaintained
unsafe-libyaml(RUSTSEC-2024-0449): useserde-saphyr, upgradekubeto 4.2,k8s-openapito 0.28 andschemarsto 1; the server no longer accepts.yaml/.ymlconfiguration files (TOML and JSON remain supported) (#1269) - Make
shell.nixadd the glibc include path only on Linux, sonix-shellevaluates on macOS (#1338)
🔒 Security
- Enforce read-only attribute rules for client updates:
SetAttributereturnsRead_Only_Attribute(HTTP 422) forUniqueIdentifier,ObjectType,CryptographicLength,CertificateLength,Digest,InitialDate,LastChangeDate,OriginalCreationDateandFresh, which were previously accepted and persisted (COSMIAN-2026-030, #1004, #1259) - Refuse client edits of every attribute that KMIP 2.1 marks read-only (
CryptographicAlgorithm,CryptographicDomainParameters,KeyFormatType,CertificateType, the X.509 subject/issuer, the lifecycle dates, …) onAddAttribute,SetAttribute,ModifyAttributeandDeleteAttribute, and check stored keys against the[kmip.policy]allowlists from the key material when an attribute is missing (curve read from the key, wrapped keys of unknown curve refused, storedCryptographicParameterschecked on use), so stripping or relabelling attributes no longer escapes the algorithm policy (COSMIAN-2026-044). Upgrade action: under a curve allowlist, imported PKCS#8 EC and Ed25519 keys are now curve-checked (an imported Ed25519 key is refused by theDEFAULTpolicy, like a created one) - Apply the role check and the request-time KMIP algorithm policy to every operation of a batched KMIP
RequestMessage, as on the single-operation path, and the policy to aMACrequest whose root tag is spelledMac: batched requests skipped the policy, so any user could create keys and send parameters that[kmip.policy]forbids, and a user who is not a Crypto Officer could run a batchedCreateSplitKey(JoinSplitKeystays open to every user by design) (COSMIAN-2026-047). Audit action: after the upgrade,Locatethe keys whose algorithm, length or curve the policy forbids, created through aRequestMessagebefore it, and revoke or re-key them - Require HSM admin rights or an explicit
Decryptgrant on an HSM key to unwrap with it, onImportand when reading a stored key wrapped by it, and an explicitEncryptgrant (instead ofGet) to wrap with it onCreate,ImportorExport; authorize reads served from the shared unwrapped-object cache exactly like uncached reads. The 5.13.0 fix of COSMIAN-2025-009 missed these paths, so any user allowed toImportcould decrypt arbitrary data with any HSM key (COSMIAN-2026-041). The configuredkey_encryption_key, in the HSM or in the database, still unwraps keys at rest for every user. Upgrade action: grantDecrypton the HSM key to the owners of keys wrapped by a non-KEK HSM key, andEncryptto the users who create, import or export keys wrapped by it - Stop the server
key_encryption_keyfrom acting as a decryption oracle:ImportandRegisterof a key block already wrapped by the KEK (or its_pkalias, its uid written as an integer, or a public key or certificate whosePrivateKeyLinkpoints at the KEK), and anImportunwrapping with the KEK, are accepted only from users allowed to decrypt with the KEK (HSM admins or explicitDecryptgrantees for an HSM KEK; the existing owner/grant rule for a database KEK); everyone else getsInvalid_Request. Plain imports are still wrapped by the KEK at rest, and the at-rest exemption applies only when the stored key block names the KEK uid or<kek>_pkliterally: a key reached through aPrivateKeyLinkmust be a private key and needs the usual grant (COSMIAN-2026-046). Upgrade action: to import KEK-wrapped key blocks (for example client-side generated keys wrapped with KMIPEncryptunder the KEK, or a KEK-wrapped export), the importing user needs HSM admin rights or aDecryptgrant on the KEK - Check the caller's grant on the selected key when a bare keyset UID (for example
hsm::<slot>::<name>) is used forEncrypt,Sign,MAC, …; callers with no grant getItem_Not_Found(COSMIAN-2026-042) - Count on a CA's CRL and in its OCSP answers only the certificates it issued (issuer name and signature checked against the CA, then, among copies sharing a serial, those owned by the CA owner or by a user allowed to issue with the CA key), with a revoked copy winning over an active one: any user allowed to
Importcould link a forged or copied certificate to a CA and so put another certificate's serial on its CRL or make OCSP answergoodfor a revoked certificate (COSMIAN-2026-048). This release extends CRL and OCSP signing to HSM-backed CAs with the same checks. Upgrade notes: if no copy of a serial is owned by an authorized user (for example after the issuer lost the grant), all copies count again; each CRL generation now reads every certificate linked to the CA once, which can be slow for a CA with a very large number of certificates; check the CRLs published before the upgrade for serials the CA owner did not revoke - Refuse Authentication Verifier tokens (bearer or
_ea_cookie) whose session has ended through logout, admin revocation or idle timeout: after the signature check, the KMS asksGET {auth_verifier_url}/whoami, through the[proxy]settings when configured, and trusts a live answer for 30 s (COSMIAN-2026-043). Upgrade action: the KMS must reachauth_verifier_urlat runtime; requests carrying an Authentication Verifier token are refused while it is unreachable, and CLI bearer tokens now expire with the realm idle timeout - Stop returning Sensitive Opaque Objects and Split Keys in plaintext when the
Get/Exportrequest carries aKeyWrappingSpecification, which the export path never applies to these types: a Sensitive object is refused withSensitive, a non-sensitive one withKey_Wrap_Type_Not_Supported(COSMIAN-2026-045). Clients must export these objects without a wrapping specification - Stop
Get/Exportof a key withFresh = truefrom writing the auto-unwrapped key back to the database whendefault_unwrap_typesis set (COSMIAN-2026-031) - Harden the KMIP socket server: close connections idle for more than 120 s, abort TLS handshakes not completed within 10 s in total (however slowly the client sends), accept at most 1,024 concurrent connections (clients that keep a connection open between requests must reconnect) and reject messages above 64 MiB before allocation (COSMIAN-2026-032); apply the HTTPS mTLS client-certificate CN checks (no empty,
*or reserved CN, whitespace trimmed) (COSMIAN-2026-033); reject a client certificate withCN=[aws-xks-service](COSMIAN-2026-040) - Verify each JWT only against the JWKS of its issuer, for every identity provider, whitelisted Google CSE KACLS and Google CSE token issuer, and refresh the keys of the token's own issuer (Google CSE authorization keys included) after a key rotation, at most once every 60 s (COSMIAN-2026-034)
- Close the Google CSE
rewrapSSRF bypass: no redirects, IPv6 and IPv4-mapped literals checked, host resolved, checked and pinned (COSMIAN-2026-035) - Fail KMIP requests above 10,000 batch items with
Invalid_Message, and rejectDeriveKeyPBKDF2 with fewer than 1 or more than 10,000,000 iterations and RSA key generation above 16,384 bits (COSMIAN-2026-036); returnItem_Not_Foundinstead ofPermission_Deniedfrom crypto operations (Encrypt,Decrypt,Sign,MAC, …) on a key the caller has no grant on, so the existence or state of keys the caller may not use is not revealed (COSMIAN-2026-037) - Mask the AWS XKS SigV4 secret access key in startup and debug configuration logs (COSMIAN-2026-038)
- Apply the KMIP-scope CORS policy (same-origin plus
cors_allowed_origins) to/v1/crypto/*,/tokenize/*and/ui/*(soGET /ui/whoamiis no longer readable from other origins), and requireContent-Type: application/jsononPOST /kmip/2_1(other types, and an invalid UTF-8 body, return HTTP 422), so other origins of the same site cannot reuse a logged-in UI session (COSMIAN-2026-039) - Enforce
sslmodeand certificate verification onPostgreSQLconnections (COSMIAN-2026-029, #1262):require,verify-caandverify-fullfail at startup when TLS cannot be established and unknown values are rejected, so check that thePostgreSQLserver accepts TLS before upgrading;sslrootcerttrusts only the given CA,verify-cano longer checks the hostname and, as in libpq, refuses to start withoutsslrootcert(add it toverify-caURLs, or useverify-full)
⚙️ CI
- Authenticate Docker Hub pulls and collapse the
packages-testandalpine-packages-testmatrix axes into in-job loops (56 → 14 and 12 → 6 legs) - Pin
ubuntu-latesttoubuntu-24.04, keep the SAP ASE test image onubuntu:22.04, pinazure/setup-helmandmedyagh/setup-minikubeby commit SHA, and addcargo-vet,pip-auditandgovulncheckjobs - Fix new Clippy findings (
large_futures,result_large_err, deprecatedcc::Tool::env) - Install a pinned, SHA-256-checked
rustup-initin the Alpine native build instead of pipingsh.rustup.rsintosh, and make theno-curl-pipe-shellhook catch pipes split across lines - Install the pinned mise
v2026.10.8Linux x64/ARM64 release binary directly (the-muslbuild in Alpine containers, where the glibc one cannot relocate even withgcompat), with its SHA-256 verified before execution, instead of the installer and its unpinned release-index lookup (#1338) - Rerun hosted runners lost during a job from
/ci-fixinstead of committing a fix, and route prompts to the matching skill automatically (#1341)
📚 Documentation
- Document PKCS#11 v3.x provider interfaces, Alpine installation with signature verification, Thales Luna and Kubernetes HSM deployments (Luna, Proteccio, Crypt2pay), GnuPG smartcard use, EST/SCEP trusted-proxy semantics, PostgreSQL
sslmode, the Redis single-node requirement and the Azure EKM trust directions (#1323) - Document NIST SP 800-227 requirements RS1–RS5 for KEMs, which of them the KMS meets or does not meet (no ESV/CMVP entropy claims for PQC key generation), in the random-number-generation page (#1253); add ADRs for the HSM delegation and PKCS#11 v3.0 scope decisions, the PQC
KmsRngseeding decision and the randomness exceptions - Add Thales Luna and Proteccio benchmark reports, the Luna quick reference, and HSM algorithm support matrix columns for Thales Luna and GCP Cloud HSM, all listed in the documentation navigation
- List every supported model, including Azure Cloud HSM and GCP Cloud HSM, in the
--hsm-modelhelp andresources/kms.toml, and document the[audit.postgres]urlandinstance_idkeys in the configuration template - Document the SPIRE KMIP integration for the
KeyManagerplugin only: remove theUpstreamAuthorityplugin guide and thetest:spire-kmip-upstream-authoritytask (#1335)