Skip to content

Tighten ECDH derive input checks and derived key state - #233

Merged
Frauschi merged 91 commits into
wolfSSL:masterfrom
aidangarske:fenrir-fixes-10396-1610
Oct 15, 2026
Merged

Frauschi merged 91 commits into
wolfSSL:masterfrom
aidangarske:fenrir-fixes-10396-1610

Conversation

@aidangarske

@aidangarske aidangarske commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

F-10396 - d904877 - C_DeriveKey ECDH now returns CKR_MECHANISM_PARAM_INVALID when shared data is supplied with CKD_NULL.

F-4272 - e879a0b - ECDH derive now rejects public data longer than 2 * MAX_ECC_BYTES + 4 and passes the length as word32.

F-1610 - 64809c7 - The raw point fallback in WP11_EC_Derive now resets its index and skips the DER length re-parse, so an unsupported DER length form is treated as a raw point.

F-5159 - 451c81e - ECDH derive now requires the encoded point length to fit in what remains of the supplied public data, returning BUFFER_E otherwise.

F-10383 - 800b60c - WP11_EC_Derive now clears the key's RNG association before freeing its stack RNG, and token keys take the key write lock for the whole derive.

F-6959 - a1f3613 - C_DeriveKey now calls CheckPrivateLogin after the CKA_DERIVE check, as C_GenerateKey does, so deriving a private key object requires a user login.

F-13818 - 0ff14c3 - C_DeriveKey now sets the initial states before AddObject and frees the object if AddObject fails.

F-13816 - 955c000 - New SetDerivedStates sets ALWAYS_SENSITIVE and NEVER_EXTRACTABLE on a derived key from the base key's history and its own SENSITIVE and EXTRACTABLE values.

F-3403 - 97ed5c1 - A SymmKeyLen failure in C_DeriveKey now returns CKR_FUNCTION_FAILED instead of a raw wolfCrypt error code.

@aidangarske aidangarske self-assigned this Oct 8, 2026
Copilot AI balanced review requested due to automatic review settings October 8, 2026 22:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Several feature-gated tests incorrectly report PASS instead of SKIP, and the description omits substantial behavioral changes.

4 open findings
What changed in this PR

Hardens ECDH derivation and also introduces broader PKCS#11 state, persistence, login, and operation-lifecycle fixes.

Changes:

  • Validates ECDH inputs, derived-key state, locking, and login requirements.
  • Improves token storage, PIN state, operation cancellation, and mechanism metadata.
  • Adds extensive regression tests and CI configurations.
File Description
.github/​workflows/​unit-test.yml Adds TPM-debug and no-environment CI jobs.
src/​crypto.c Hardens derive and operation lifecycle behavior.
src/​internal.c Updates storage, locking, login, and session internals.
src/​slot.c Corrects token metadata, PIN handling, and mechanisms.
src/​wolfpkcs11.c Reports finalization failures and pads metadata.
tests/​ec_derive_test.c Adds ECDH regression coverage.
tests/​include.am Registers new test programs.
tests/​info_mech_table_test.c Tests metadata and mechanism consistency.
tests/​login_pin_state_test.c Tests login, PIN, and concurrency state.
tests/​object_policy_integrity_test.c Tests persisted object-policy integrity.
tests/​object_store_decode_test.c Tests malformed stored-object handling.
tests/​pkcs11mtt.c Updates v3 operation-cancellation expectations.
tests/​pkcs11test.c Updates clock and cancellation expectations.
tests/​session_op_lifecycle_test.c Tests operation cleanup and cancellation.
tests/​token_store_test.c Tests transactional token persistence.
wolfpkcs11/​internal.h Declares new limits and internal helpers.
wolfpkcs11/​pkcs11.h Adds the operation-cancellation error constant.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/ec_derive_test.c Resolved
Comment thread tests/object_policy_integrity_test.c Resolved
Comment thread tests/object_store_decode_test.c Resolved
Comment thread src/wolfpkcs11.c Resolved

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, one test request inline.

Comment thread src/internal.c Resolved
@aidangarske
aidangarske force-pushed the fenrir-fixes-10396-1610 branch from aafebc1 to a653795 Compare October 13, 2026 22:19
@aidangarske
aidangarske requested a review from Frauschi October 13, 2026 22:24
@aidangarske
aidangarske force-pushed the fenrir-fixes-10396-1610 branch 3 times, most recently from 916da34 to 5beb906 Compare October 13, 2026 23:44
Frauschi
Frauschi previously approved these changes Oct 14, 2026

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks good. nss-test is only red because of the #231 issue.

Frauschi
Frauschi previously approved these changes Oct 15, 2026
@aidangarske
aidangarske dismissed Frauschi’s stale review October 15, 2026 20:36

The merge-base changed after approval.

@aidangarske
aidangarske force-pushed the fenrir-fixes-10396-1610 branch from 02f611d to 128a0da Compare October 15, 2026 21:01
@Frauschi
Frauschi merged commit 128a0da into wolfSSL:master Oct 15, 2026
91 checks passed
@aidangarske
aidangarske deleted the fenrir-fixes-10396-1610 branch October 15, 2026 21:41
AlexLaroche added a commit to KerckhoffsLabs/KerckhoffsLabs.Security.Cryptography.Pkcs11 that referenced this pull request Oct 30, 2026
AlgorithmConformanceTestCases.Assert_Ecdh_ParameterOutOfRange_IsRefused,
one row per fault, for CKM_ECDH1_DERIVE with CKD_NULL from the SP 800-56A
P-256 vector, through the raw dispatch (the library's parameter type
refuses the first three):
- CKD_NULL with shared data: PKCS#11 v3.2 §6.3.16, "pSharedData must be
  NULL and ulSharedDataLen must be zero";
- a 1 KiB peer value (a raw point padded with zeros), and a DER OCTET
  STRING whose length byte promises 65 bytes and has 40 (§6.3.16: the
  peer's point, raw or DER);
  each refused with CKR_MECHANISM_PARAM_INVALID (§5.1.6);
- CKA_VALUE_LEN 33 for a generic secret, one byte more than the secret
  the mechanism can only truncate (§6.3.17): CKR_ATTRIBUTE_VALUE_INVALID,
  CKR_TEMPLATE_INCONSISTENT (§4.1) or CKR_KEY_SIZE_RANGE (§5.18.5).
None makes a key (found by label), and the same session then derives Z,
checked through an HMAC under it where the module has HMAC-SHA256.

From wolfSSL/wolfPKCS11#233, #234

Pass: Craton HSM (every row). Gated, each confirmed by the run and, for
the keys made, a probe reading them back:
- wolfPKCS11 answers the lying DER length with CKR_FUNCTION_FAILED
  (WolfPkcs11RefusesAMalformedEcdhPeerAsInvalid, tripwire);
- Kryoptic answers it with CKR_DEVICE_ERROR
  (KryopticRefusesAMalformedEcdhPeerAsInvalid);
- SoftHSM2 answers both peer rows with CKR_GENERAL_ERROR
  (SoftHsmRefusesAMalformedEcdhPeerAsInvalid) and CKA_VALUE_LEN 33 with
  CKR_FUNCTION_FAILED (SoftHsmRefusesAnEcdhValueLenAboveTheSecret);
- BouncyHsm ignores the shared data and derives Z
  (BouncyHsmRefusesSharedDataWithCkdNull, tripwire) and makes 00 || Z
  for CKA_VALUE_LEN 33 (BouncyHsmRefusesAnEcdhValueLenAboveTheSecret,
  tripwire); its peer rows pass;
- FreeHSM departs on every row: it ignores the shared data, makes a
  32-byte key for CKA_VALUE_LEN 33 and refuses both peers with
  CKR_ATTRIBUTE_VALUE_INVALID (FreeHsmChecksTheEcdhParameters).
The other rows pass on each. NSS and opencryptoki run in CI only.
AlexLaroche added a commit to KerckhoffsLabs/KerckhoffsLabs.Security.Cryptography.Pkcs11 that referenced this pull request Oct 31, 2026
…very backend (#293)

* test: refuse a private object without a login on every backend

SessionConformanceTestCases.Assert_PrivateObjectCreation_WithoutALogin_IsUserNotLoggedIn,
one row per call: with nobody logged in, C_CreateObject, C_CopyObject,
C_GenerateKey, C_GenerateKeyPair, C_DeriveKey (HKDF), C_UnwrapKey (an
AES key wrap), C_EncapsulateKey and C_DecapsulateKey (ML-KEM-768) with
CKA_PRIVATE = TRUE in the template answer CKR_USER_NOT_LOGGED_IN
(PKCS#11 v3.2 §5.1.6) and create nothing. Every input is a public
session object made while logged in, which the C_Logout leaves in place
(§5.6.10). C_CopyObject may also answer CKR_ATTRIBUTE_READ_ONLY or
CKR_TEMPLATE_INCONSISTENT: §4.1.3 lets a token refuse to change
CKA_PRIVATE in a copy.

From wolfSSL/wolfPKCS11#187, #189, #207, #233, #243

Pass: SoftHSM2, BouncyHsm, FreeHSM, wolfPKCS11 (each row its mechanisms
allow). NSS skips (no login). Two gates:
- Kryoptic checks the login only for token objects, so every call makes
  a private session object with nobody logged in
  (KryopticRefusesPrivateObjectsWithoutALogin).
- Craton HSM refuses without a login only CKO_PRIVATE_KEY and
  CKO_SECRET_KEY in C_CreateObject and never looks at CKA_PRIVATE: a
  private data object, a copy, a generated key or key pair and an
  unwrapped key are all made (CratonHsmRefusesPrivateObjectsWithoutALogin,
  with a tripwire in CratonHsmQuirkTests).
opencryptoki runs in CI only.

* test: invalidate private handles on C_Logout on every backend

SessionConformanceTestCases.Assert_Logout_InvalidatesPrivateHandles:
log in, make a private token AES key, a private and a public session
object, log out (PKCS#11 v3.2 §5.6.10). With nobody logged in,
C_GetAttributeValue on the token key and on the private session object
answers CKR_OBJECT_HANDLE_INVALID, C_EncryptInit with the token key is
refused (CKR_KEY_HANDLE_INVALID or CKR_USER_NOT_LOGGED_IN, which §5.8.1
lists, or CKR_OBJECT_HANDLE_INVALID, which SoftHSM2 and wolfPKCS11
answer), and the public session object keeps its handle. After a new
login the private session object's handle is still invalid and no
search finds it. The token key's old handle is not checked after the new
login: a module may give it the same number again.

From wolfSSL/wolfPKCS11#179, #196, #227

Pass: SoftHSM2, wolfPKCS11, Kryoptic. Kryoptic answers C_EncryptInit
with CKR_GENERAL_ERROR, recorded through the case's parameter
(KryopticRefusesAPrivateKeyAfterLogout). NSS skips (no login). Gated:
- BouncyHsm and Craton HSM keep private session objects after C_Logout
  (a new login finds them under their old handles), and while nobody is
  logged in answer a private object's handle with CKR_USER_NOT_LOGGED_IN,
  which §5.7.5 does not list for C_GetAttributeValue
  (BouncyHsmDestroysPrivateSessionObjectsOnLogout,
  CratonHsmDestroysPrivateSessionObjectsOnLogout, each with a tripwire).
- FreeHSM keeps private session objects readable after C_Logout
  (C_GetAttributeValue answers CKR_OK with nobody logged in) and starts
  C_EncryptInit with a private token key whose attributes it refuses to
  read (FreeHsmDestroysPrivateSessionObjectsOnLogout).
opencryptoki runs in CI only.

* test: keep the SO and the user apart on every backend

Two SessionConformanceTestCases:
- Assert_SoAndUser_ExcludeEachOther: with the SO logged in, C_Login
  (CKU_USER) from its session or another answers
  CKR_USER_ANOTHER_ALREADY_LOGGED_IN, and with the user logged in so
  does C_Login(CKU_SO) (PKCS#11 v3.2 §5.1.6); the sessions stay in the
  state of whoever is logged in.
- Assert_So_CreatesNoPrivateObject: with the SO logged in, C_CreateObject
  with CKA_PRIVATE = TRUE answers CKR_USER_NOT_LOGGED_IN and makes
  nothing (§5.7.1: only public objects unless the normal user is logged
  in).
Both log the SO in with the right PIN from read/write sessions only and
leave nobody logged in whatever fails. Whether the SO may see the user's
private objects is left out: only [PKCS11-UG] says so, and Kryoptic,
BouncyHsm, Craton HSM and FreeHSM all show them to the SO.

From wolfSSL/wolfPKCS11#194, #212

Pass: SoftHSM2, wolfPKCS11 (both); Kryoptic, Craton HSM, FreeHSM (the
login one). NSS skips (no login). Gated:
- BouncyHsm logs the user in over the SO with CKR_OK
  (BouncyHsmKeepsTheSoAndTheUserApart) and lets the SO make a private
  object (BouncyHsmRefusesPrivateObjectsToTheSo), each with a tripwire.
- FreeHSM lets the SO make a private object
  (FreeHsmRefusesPrivateObjectsToTheSo).
- Kryoptic and Craton HSM let the SO make one too: their existing
  private-object gates now say so.
opencryptoki runs in CI only.

* test: refuse token-object writes in a read-only session on every backend

SessionConformanceTestCases.Assert_ReadOnlySession_RefusesTokenObjectWrites,
one row per call: in a read-only session with the user logged in,
C_GenerateKey, C_GenerateKeyPair (either half on the token), C_DeriveKey
(HKDF, or ECDH where there is no HKDF), C_UnwrapKey, C_CopyObject (of a
token object, or of a session object into one), C_SetAttributeValue and
C_DestroyObject of a token object answer CKR_SESSION_READ_ONLY (PKCS#11
v3.2 §5.7.1-§5.7.3, §5.7.6, §5.18), and the same call on session
objects succeeds. The token object is made in a read/write session; the
inputs are session objects of the read-only session itself. Nothing new
carries CKA_TOKEN = TRUE afterwards and the token object keeps its label.

From wolfSSL/wolfPKCS11#177, #186, #197

Pass: SoftHSM2, wolfPKCS11, BouncyHsm (every row); Kryoptic and FreeHSM
(the rows below aside). NSS skips (no token objects). Gated:
- Kryoptic copies a token object whose template does not name CKA_TOKEN
  into a new token object, and refuses C_DestroyObject of a token object
  with CKR_ACTION_PROHIBITED (KryopticRefusesTokenObjectWritesInReadOnlySessions,
  CopyObject and DestroyObject rows).
- FreeHSM copies a token object the same way
  (FreeHsmRefusesTokenObjectCopiesInReadOnlySessions, CopyObject row).
- Craton HSM refuses a session object in a read-only session too
  (CratonHsmMakesSessionObjectsInReadOnlySessions, with a tripwire); the
  existing ReadOnlySession_RefusesTokenObjects wrapper, which skipped for
  the same reason in its body, now uses the gate.
opencryptoki runs in CI only.

* test: refuse a PIN outside the token's length range on every backend

SessionConformanceTestCases.Assert_PinOutsideTheLengthRange_IsRefused:
the token's [ulMinPinLen, ulMaxPinLen] holds the user PIN it was given
(PKCS#11 v3.2 §3.2); a PIN one byte shorter than the minimum and one
byte longer than the maximum (where these are reported, from 1 to 1024
bytes) are refused by C_Login with CKR_PIN_LEN_RANGE or
CKR_PIN_INCORRECT, the session stays public, and the right PIN logs in
at once; C_SetPIN to either answers CKR_PIN_LEN_RANGE (§5.5.9, §5.1.6)
and the old PIN still logs in. C_Login gets both codes: §5.1.6 says
CKR_PIN_LEN_RANGE applies to the functions that set a PIN, and §5.6.8
lists neither. No wrong PIN of a valid length is presented, and a
C_SetPIN the token takes is undone.

From wolfSSL/wolfPKCS11#178, #194

Pass: SoftHSM2, wolfPKCS11, FreeHSM. NSS skips (no login). Gated:
- Craton HSM does not check a PIN's length at C_Login and counts the
  short PIN as a failed login; the right PIN right after it is refused
  with CKR_PIN_LOCKED for a backoff window, and so are the rest of the
  collection's logins (CratonHsmTakesTheRightPinRightAfterARefusedOne;
  no tripwire, which would lock the collection out).
- Kryoptic reports ulMinPinLen 8 but C_SetPIN sets a 7-byte PIN
  (KryopticEnforcesItsMinimumPinLength).
- BouncyHsm's native client copies MaxPinLen into ulMinPinLen, so it
  reports [512, 512] and takes a 511-byte PIN in C_SetPIN
  (BouncyHsmReportsItsMinimumPinLength, with a tripwire).
opencryptoki runs in CI only.

* test: report the mechanism count on a short list on every backend

SessionConformanceTestCases.Assert_MechanismList_OneShort_IsBufferTooSmall:
C_GetMechanismList into a list one entry short answers
CKR_BUFFER_TOO_SMALL and sets *pulCount to the number of mechanisms
(PKCS#11 v3.2 §5.5.5), and the retry at that count returns the whole
list. The C_GetSlotList half of the group needs a module with two slots
and is left for later.

From wolfSSL/wolfPKCS11#186, #207

Pass: SoftHSM2, Kryoptic, Craton HSM, FreeHSM, wolfPKCS11. Gated:
BouncyHsm answers CKR_BUFFER_TOO_SMALL but leaves *pulCount as the
caller gave it: its server sends no data with the error
(BouncyHsmReportsTheMechanismCountOnAShortList, with a tripwire). NSS
and opencryptoki run in CI only.

* test: hold the token to what it advertises on every backend

Three SessionConformanceTestCases:
- Assert_RsaMechanisms_AdvertiseThe2048BitKeysTheyTake: on a token that
  makes an RSA-2048 key, every §6.1 RSA mechanism it lists (key pair
  generation, PKCS#1 v1.5, X.509, OAEP, PSS and the hash-and-sign ones)
  reports a key size range, in modulus bits, that contains 2048 (PKCS#11
  v3.2 §6.1.4-§6.1.10), where it reports one.
- Assert_AdvertisedWrapMechanism_WrapsAnAesKey: CKM_AES_KEY_WRAP, _PAD,
  _KWP, _PKCS7 and CKM_RSA_PKCS_OAEP (SHA-256), where listed with
  CKF_WRAP, wrap an extractable AES-256 key, and where also listed with
  CKF_UNWRAP unwrap it to a key that decrypts what the original encrypts
  (§5.5.6). The modes that wrap by encrypting, RSA PKCS#1 v1.5 and raw
  RSA are left to the wrap and mechanism cases.
- Assert_ClockOnToken_ReportsAWellFormedUtcTime: with CKF_CLOCK_ON_TOKEN,
  utcTime is YYYYMMDDhhmmss00 and a valid time (§3.2).

From wolfSSL/wolfPKCS11#168, #231

Pass everywhere the token has the mechanism, the flag or the clock:
SoftHSM2, Kryoptic, BouncyHsm, Craton HSM, FreeHSM, wolfPKCS11 (the
clock: BouncyHsm and wolfPKCS11; the others have none). No gate. NSS and
opencryptoki run in CI only.

* test: hide a sensitive private key's secret parts on every backend

ObjectConformanceTestCases.Assert_SensitivePrivateKey_HidesItsSecretParts,
one row per key type (RSA-2048, EC P-256, ML-DSA-44, ML-KEM-768): on a
private key generated with CKA_SENSITIVE = TRUE, C_GetAttributeValue of
each secret part alone (RSA CKA_PRIVATE_EXPONENT and the CRT values, EC
CKA_VALUE, ML-DSA and ML-KEM CKA_VALUE and CKA_SEED), with pValue NULL and
with a buffer large enough, answers CKR_ATTRIBUTE_SENSITIVE, sets
ulValueLen to CK_UNAVAILABLE_INFORMATION and writes nothing into the
buffer (PKCS#11 v3.2 §5.7.5 case 1; footnote 7 of §6.1.3, §6.3.4,
§6.67.3, §6.68.3). The parts the tables do not require (the CRT values,
the seeds) may also answer CKR_ATTRIBUTE_TYPE_INVALID (case 2). One read
of every secret part, an attribute the key type lacks, CKA_LABEL and
CKA_KEY_TYPE answers one of those codes, marks the first ones
unavailable and fills the last two. Through the raw call, so the
module's own code and lengths are seen.

From wolfSSL/wolfPKCS11#187, #197, #237

Pass: SoftHSM2, Kryoptic, wolfPKCS11, Craton HSM (every row the token
has a mechanism for: no ML-KEM on SoftHSM2 or Craton HSM); BouncyHsm
(RSA, EC); FreeHSM (EC, ML-DSA, ML-KEM). Gated:
- BouncyHsm returns the CKA_VALUE of a sensitive ML-DSA private key and
  of a sensitive ML-KEM private key, and the ML-DSA CKA_SEED, with
  CKR_OK: its ML-DSA, ML-KEM and SLH-DSA private key classes do not
  override IsSensitiveAttribute (BouncyHsmHidesSensitivePqcPrivateKeys,
  with a tripwire).
- FreeHSM answers each secret part of a sensitive RSA key with
  CKR_ATTRIBUTE_TYPE_INVALID, the value unrevealed
  (FreeHsmReportsRsaPrivatePartsAsSensitive).
NSS and opencryptoki run in CI only.

* test: refuse a template missing a required attribute on every backend

ObjectConformanceTestCases.Assert_IncompleteTemplate_IsTemplateIncomplete,
one row per template: C_GenerateKeyPair of an RSA key pair without
CKA_MODULUS_BITS and of an EC key pair without CKA_EC_PARAMS (footnote 3,
PKCS#11 v3.2 §6.1.2, §6.3.3), and C_CreateObject of an RSA public or
private key without CKA_MODULUS, an EC public or private key without
CKA_EC_PARAMS and a secret key without CKA_KEY_TYPE (footnote 1, §6.1.2,
§6.1.3, §6.3.3, §6.3.4, §4.8) answer CKR_TEMPLATE_INCOMPLETE (§4.1.1
point 4) and leave nothing with the case's label. Every other value in
the templates is valid.

From wolfSSL/wolfPKCS11#194, #236, #237

Pass: Kryoptic, wolfPKCS11 (every row). Recorded through the case's
leavesAnObjectBehind parameter: SoftHSM2 answers the four CreateObject
key rows with CKR_TEMPLATE_INCOMPLETE but leaves the object behind, as
it does for an AES key with no value. Gated:
- Craton HSM generates an RSA key pair without CKA_MODULUS_BITS and
  creates the RSA public, EC and secret keys; it refuses the RSA private
  key with CKR_TEMPLATE_INCONSISTENT (CratonHsmRequiresTheKeyAttributes).
- FreeHSM generates both key pairs without the sizing attribute and
  refuses the EC private key with CKR_TEMPLATE_INCONSISTENT
  (FreeHsmRequiresTheKeyAttributes; the other rows pass).
- BouncyHsm answers the four CreateObject key rows with
  CKR_ATTRIBUTE_VALUE_INVALID
  (BouncyHsmReportsAMissingKeyAttributeAsIncomplete) and creates the
  secret key without CKA_KEY_TYPE
  (BouncyHsmRefusesASecretKeyWithoutAKeyType), each with a tripwire; the
  two key pair rows pass.
NSS and opencryptoki run in CI only.

* test: refuse a template whose class or key type contradicts on every backend

ObjectConformanceTestCases.Assert_InconsistentTemplate_IsTemplateInconsistent,
one row per template: C_GenerateKey(CKM_AES_KEY_GEN) with CKK_GENERIC_SECRET
or CKO_DATA and C_GenerateKeyPair(CKM_EC_KEY_PAIR_GEN) with CKK_RSA for
the private key or CKO_PRIVATE_KEY for the public one answer
CKR_TEMPLATE_INCONSISTENT (PKCS#11 v3.2 §5.18.1, §5.18.2);
C_CreateObject of a CKO_SECRET_KEY of type CKK_EC or a CKO_PRIVATE_KEY of
type CKK_AES (§4.1.1 point 5) and C_DeriveKey(CKM_HKDF_DERIVE) asking
for CKO_DATA (§6.62.3: the mechanism contributes the class; CKM_HKDF_DATA
is the one that makes data, §6.62.4) answer CKR_TEMPLATE_INCONSISTENT or
CKR_ATTRIBUTE_VALUE_INVALID, since neither function names the code and
§4.1.1 point 2 can be read to cover it. Nothing carries the case's label
afterwards.

From wolfSSL/wolfPKCS11#216, #263

Pass: Kryoptic (every row). Gated:
- wolfPKCS11 v2.2.0 makes a CKO_PRIVATE_KEY of type CKK_AES
  (WolfPkcs11RefusesAPrivateKeyOfASecretKeyType, with a tripwire; #263,
  after v2.2.0, fixes it). The other rows pass; the HKDF one answers
  CKR_ATTRIBUTE_VALUE_INVALID.
- SoftHSM2 answers a class the generation mechanism does not make with
  CKR_ATTRIBUTE_VALUE_INVALID
  (SoftHsmReportsAContradictoryClassAsInconsistent); it has no
  CKM_HKDF_DERIVE.
- BouncyHsm generates an EC key pair with CKK_RSA in the private template
  or CKO_PRIVATE_KEY in the public one
  (BouncyHsmChecksTheKeyPairTemplatesClassAndKeyType, with a tripwire);
  the other rows pass.
- Craton HSM and FreeHSM take every template here with CKR_OK
  (CratonHsmChecksTheTemplatesClassAndKeyType,
  FreeHsmChecksTheTemplatesClassAndKeyType).
NSS and opencryptoki run in CI only.

* test: keep read-only attributes read-only on every backend

Three new ObjectConformanceTestCases and three new rows:
- Assert_UniqueId_IsTheTokensAndReadOnly: two data objects and a copy
  of one have non-empty, distinct CKA_UNIQUE_IDs, and a CKA_UNIQUE_ID in
  C_CreateObject, C_CopyObject or C_SetAttributeValue answers
  CKR_ATTRIBUTE_READ_ONLY, makes nothing and changes nothing (PKCS#11
  v3.2 §4.4.1). Skips a token that does not report it.
- Assert_KeyComponent_IsReadOnly: C_SetAttributeValue of an AES key's
  CKA_VALUE, an RSA public key's CKA_MODULUS or CKA_PUBLIC_EXPONENT, an
  EC public key's CKA_EC_POINT, and C_CopyObject with a new CKA_VALUE,
  answer CKR_ATTRIBUTE_READ_ONLY and the value reads as before (no
  footnote 8 in §6.7, §6.1.2, §6.3.3; §4.1.2, §4.1.3, §4.1.1 point 3).
- Assert_ProvenanceAttributeInATemplate_IsReadOnly: CKA_ALWAYS_SENSITIVE
  or CKA_NEVER_EXTRACTABLE in C_CreateObject or C_GenerateKey, each the
  opposite of what the token would record, answers
  CKR_ATTRIBUTE_READ_ONLY and makes nothing (§4.11 footnotes 2 and 4,
  §4.1.1 points 3 and 6).
- ReadOnlyKeyAttributes gets CKA_TOKEN, CKA_PRIVATE and CKA_MODIFIABLE,
  which change only in a copy (§4.4, §4.1.3); Kryoptic's own list too.

From wolfSSL/wolfPKCS11#213, #235, #236

Pass: wolfPKCS11 (everything); SoftHSM2, Craton HSM and FreeHSM skip the
CKA_UNIQUE_ID case (not reported). SoftHSM2 refuses the provenance
templates with CKR_ATTRIBUTE_READ_ONLY but leaves the object behind,
recorded through the case's leavesAnObjectBehind parameter. Gated:
- BouncyHsm gives a copy the original's CKA_UNIQUE_ID
  (BouncyHsmGivesACopyItsOwnUniqueId), changes key material through
  C_SetAttributeValue and C_CopyObject, every row
  (BouncyHsmFixesKeyMaterial), and changes CKA_PRIVATE and
  CKA_MODIFIABLE (BouncyHsmFixesThePrivateAndModifiableFlags), each with
  a tripwire. It passes the provenance case.
- Kryoptic refuses a caller's CKA_UNIQUE_ID with
  CKR_ATTRIBUTE_TYPE_INVALID (create) and CKR_ACTION_PROHIBITED (set)
  (KryopticRefusesAUniqueIdWithReadOnly), changes an AES key's CKA_VALUE
  through C_SetAttributeValue and C_CopyObject
  (KryopticFixesASecretKeysValue), and takes the provenance attributes
  in C_GenerateKey, refusing them in C_CreateObject with
  CKR_ATTRIBUTE_TYPE_INVALID
  (KryopticRefusesProvenanceAttributesInATemplate).
- FreeHSM answers a change to RSA or EC public key material with
  CKR_ATTRIBUTE_TYPE_INVALID and a new CKA_VALUE in a copy with
  CKR_TEMPLATE_INCONSISTENT (FreeHsmReportsKeyMaterialAsReadOnly), and
  takes the provenance attributes in a template
  (FreeHsmRefusesProvenanceAttributesInATemplate).
- Craton HSM takes the provenance attributes in a template
  (CratonHsmRefusesProvenanceAttributesInATemplate).
NSS and opencryptoki run in CI only.

* test: refuse a malformed attribute value without harm on every backend

ObjectConformanceTestCases.Assert_MalformedAttribute_IsRefusedHarmlessly,
one row per template, through the raw call: C_CreateObject of a data
object with a 4-byte CKA_TOKEN answers CKR_ATTRIBUTE_VALUE_INVALID
(PKCS#11 v3.2 §4.1.1 point 2), and with CKA_TOKEN {NULL, 1} or CKA_VALUE
{NULL, 16} CKR_ATTRIBUTE_VALUE_INVALID or CKR_ARGUMENTS_BAD, creating
nothing; C_FindObjectsInit with CKA_LABEL {NULL, 4} or a 4-byte CKA_TOKEN
refuses it with one of those codes or starts a search that matches
nothing (§5.7.7). The module still answers C_GetSessionInfo and the
session still finds an object afterwards.

From wolfSSL/wolfPKCS11#213, #235

Two backends crash the process (SIGSEGV, exit 139) on C_CreateObject
with CKA_TOKEN {NULL, 1}; the row is skipped there and has no tripwire,
which would take the test host down:
- Kryoptic (KryopticRefusesANullAttributeValue, which also covers its
  CKR_OK and empty value for CKA_VALUE {NULL, 16}).
- BouncyHsm (BouncyHsmSurvivesANullAttributeValue).

Pass: wolfPKCS11 (every row). Recorded through the case's
leavesAnObjectBehind parameter: SoftHSM2 refuses the three CreateObject
rows with the right code but leaves the object behind. Gated:
- BouncyHsm answers the other rows with CKR_GENERAL_ERROR or
  CKR_DEVICE_ERROR (BouncyHsmRefusesMalformedAttributeValues, with a
  tripwire).
- Craton HSM creates the three data objects and its search with a 4-byte
  CKA_TOKEN finds a session object
  (CratonHsmRefusesMalformedAttributeValues).
- FreeHSM's search with a 4-byte CKA_TOKEN finds a session object
  (FreeHsmMatchesAWrongLengthBoolean; the other rows pass).
NSS and opencryptoki run in CI only.

* test: hold a copy to what it keeps on every backend

Four ObjectConformanceTestCases:
- Assert_CopyObject_KeepsCkaToken: with a template that names only
  CKA_LABEL, the copy of a token data object is a token object and the
  copy of a session object a session object (PKCS#11 v3.2 §5.7.2). The
  token objects are destroyed whatever happens.
- Assert_CopyObject_KeepsADataObjectsFields: the original and its copy
  report the CKA_VALUE, CKA_APPLICATION and CKA_OBJECT_ID the original
  was given (Table 20), and only the label changes.
- Assert_UncopyableObject_StaysUncopyable: on an object created with
  CKA_COPYABLE = FALSE, C_SetAttributeValue(CKA_COPYABLE = TRUE) answers
  CKR_ATTRIBUTE_READ_ONLY or leaves it FALSE ("Can't be set to TRUE once
  it is set to FALSE", Table 19), and C_CopyObject still answers
  CKR_ACTION_PROHIBITED (§4.1.3).
- Assert_StorageControls_DefaultToTrue: an object made without
  CKA_MODIFIABLE, CKA_COPYABLE and CKA_DESTROYABLE reads all three TRUE
  (Table 19) and can be copied, changed and destroyed.
Every copy here names a label: SoftHSM2 answers C_CopyObject with a NULL
template CKR_ARGUMENTS_BAD before it looks at the object.

From wolfSSL/wolfPKCS11#185, #198, #215

Pass: SoftHSM2, Kryoptic, wolfPKCS11 (all four); BouncyHsm, Craton HSM,
FreeHSM (the rest). NSS skips the token object case. Gated:
- BouncyHsm sets CKA_COPYABLE back to TRUE (BouncyHsmKeepsCkaCopyableFalse,
  with a tripwire).
- Craton HSM ignores CKA_COPYABLE (CratonHsmKeepsCkaCopyable); the
  existing UncopyableObject_RefusesCopies wrapper, which skipped for the
  same reason in its body, now uses the gate.
- FreeHSM takes a data object's CKA_OBJECT_ID and does not report it, on
  the object or its copy (FreeHsmKeepsCkaObjectId).
NSS and opencryptoki run in CI only.

* test: leave nothing behind a refused key pair generation on every backend

ObjectConformanceTestCases.Assert_RefusedKeyPairGeneration_LeavesNothing,
one row per refusal, through the raw C_GenerateKeyPair (the library
would refuse the curve itself and retries CKR_FUNCTION_FAILED): a
256-bit RSA modulus, below every advertised minimum; an EC curve named
by an OID no curve has (1.2.3.4); and a CKA_UNIQUE_ID in the private
key's template only (§4.4.1), so that a token which makes the public key
first has to undo it. Each call is refused, whatever the code, and no
object carries the label both templates give (PKCS#11 v3.2 §5.18.2:
"it will fail and return without creating any key objects"). The
refused multi-attribute C_SetAttributeValue half of the group is left
for tier 2.

From wolfSSL/wolfPKCS11#177, #235

Pass: wolfPKCS11, BouncyHsm (every row); SoftHSM2, Kryoptic, Craton HSM,
FreeHSM (the RSA and EC rows). Recorded through the case's
leavesAnObjectBehind parameter: SoftHSM2 refuses the CKA_UNIQUE_ID row
with CKR_ATTRIBUTE_TYPE_INVALID (it has no such attribute) after making
the public key, and leaves that key behind. Gated:
- Kryoptic generates the pair with the CKA_UNIQUE_ID in the template;
  its existing KryopticRefusesAUniqueIdWithReadOnly gate now says so.
- Craton HSM and FreeHSM generate the pair too, though they do not
  report CKA_UNIQUE_ID (CratonHsmRefusesAUniqueIdInATemplate,
  FreeHsmRefusesAUniqueIdInATemplate).
NSS and opencryptoki run in CI only.

* test: record a key's provenance through PBKDF2, derives and copies on every backend

Three KeyProvenanceTestCases:
- Assert_CopyOfAKey_KeepsCkaLocal: the copy of a generated AES key is
  CKA_LOCAL and the copy of an imported one is not (PKCS#11 v3.2 §4.8,
  Table 26).
- Assert_Pbkdf2Key_RecordsItsGeneration: a key from
  C_GenerateKey(CKM_PKCS5_PBKD2), made sensitive and unextractable, is
  CKA_LOCAL, has CKA_KEY_GEN_MECHANISM = CKM_PKCS5_PBKD2 and is
  CKA_ALWAYS_SENSITIVE and CKA_NEVER_EXTRACTABLE (§4.8, §4.11), each of
  the last three where the token reports it.
- Assert_DerivedKey_TakesItsHistoryFromTheBase, one row per mechanism
  (HKDF, ECDH): from a generated base and from an imported one, both
  sensitive and unextractable, a key derived sensitive and unextractable
  is CKA_ALWAYS_SENSITIVE only if the base is and CKA_NEVER_EXTRACTABLE
  only if the base is (§6.62.3, §6.3.17), applied to what the token
  reports for each base. ECDH runs with CKD_NULL under the insecure
  policy: the case is about the attributes.

From wolfSSL/wolfPKCS11#178, #233, #237

Pass: wolfPKCS11, BouncyHsm, FreeHSM (every case the token has the
mechanism for: no PBKDF2 on BouncyHsm); SoftHSM2 (no HKDF, no PBKDF2);
Kryoptic (all but the ECDH row). Craton HSM skips: no CKA_LOCAL
(its existing LocalAttributeAvailable gate) and no
CKA_ALWAYS_SENSITIVE. Gated:
- Kryoptic makes a key derived with ECDH from an imported base (whose
  CKA_ALWAYS_SENSITIVE is FALSE) CKA_ALWAYS_SENSITIVE
  (KryopticDerivesAnEcdhKeysHistoryFromTheBase); HKDF follows the rule.
NSS (its existing LocalOnGeneratedKeysAvailable gate for the CKA_LOCAL
cases) and opencryptoki run in CI only.

* test: hold signatures with message recovery to the operation rules on every backend

CKM_RSA_PKCS with C_SignRecover and C_VerifyRecover, where the token
advertises CKF_SIGN_RECOVER or CKF_VERIFY_RECOVER, with a BCL RSA key
imported for the purpose (TestKeys.ImportRsaRecoveryKeyPair), so that a
result can be checked against the BCL's PKCS#1 v1.5 signature:
- OperationStateTestCases.CallsWithoutInit gets C_SignRecover,
  C_VerifyRecover, and C_VerifyRecover while an ECDSA C_SignInit is
  active: CKR_OPERATION_NOT_INITIALIZED (PKCS#11 v3.2 §5.2, §5.13.6,
  §5.15.6), and the signature still completes.
- SecondInits gets SignRecover and VerifyRecover: CKR_OPERATION_ACTIVE,
  and the first operation returns the BCL's result.
- KeysOfTheWrongType gets verify-recover with an AES key or an RSA
  private key and sign-recover with an RSA public key:
  CKR_KEY_TYPE_INCONSISTENT or CKR_KEY_FUNCTION_NOT_PERMITTED (§5.1.6).
  The theory now asks only for the mechanisms its row uses.
- UsesOfADestroyedKey gets C_SignRecoverInit and C_VerifyRecoverInit
  with a destroyed RSA key: CKR_KEY_HANDLE_INVALID or
  CKR_OBJECT_HANDLE_INVALID.
- KeyCapabilityEnforcementTestCases.Assert_Recover_WithRecoverFalse_IsKeyFunctionNotPermitted:
  a key with CKA_SIGN (CKA_VERIFY) but CKA_SIGN_RECOVER
  (CKA_VERIFY_RECOVER) FALSE is refused by the recover Init with
  CKR_KEY_FUNCTION_NOT_PERMITTED (§5.13.5, §5.15.5; the key is of the
  mechanism's type, so CKR_KEY_TYPE_INCONSISTENT does not apply).
- OperationContractTestCases.BufferTooSmallOperations gets RSA
  sign-recover and verify-recover: a 1-byte buffer answers
  CKR_BUFFER_TOO_SMALL with a usable length and the retry returns the
  BCL's result.

From wolfSSL/wolfPKCS11#177, #178, #194, #201

Only BouncyHsm (sign and verify recovery) and wolfPKCS11 (verify
recovery only) advertise either flag here; SoftHSM2, Kryoptic, Craton
HSM and FreeHSM skip every row. Pass: BouncyHsm and wolfPKCS11, every
row they run, but for two gated:
- wolfPKCS11 answers C_VerifyRecover under an active ECDSA sign with
  CKR_MECHANISM_INVALID (WolfPkcs11ReportsAVerifyRecoverNeverStarted,
  with a tripwire).
- BouncyHsm answers an RSA key of the wrong class with
  CKR_KEY_HANDLE_INVALID (BouncyHsmReportsAKeyOfTheWrongClassAsInconsistent,
  with a tripwire); the AES key row passes.
NSS and opencryptoki run in CI only.

* test: refuse a key its attributes do not allow with CKR_KEY_FUNCTION_NOT_PERMITTED on every backend

KeyCapabilityEnforcementTestCases now asks for the one code PKCS#11
v3.2 §5.1.6 gives for a key of the right type whose attributes do not
allow the use, CKR_KEY_FUNCTION_NOT_PERMITTED (CKR_KEY_TYPE_INCONSISTENT,
which outranks it, is for a key of the wrong type); it accepted any
refusal before. The four existing cases (CKA_SIGN, CKA_VERIFY on an HMAC
key; CKA_ENCRYPT, CKA_DECRYPT on an AES key) are tightened, and three
new ones go through the raw calls, so the refusal is the module's:
- Assert_Derive_WithDeriveFalse_IsKeyFunctionNotPermitted, one row per
  mechanism (ECDH with CKD_NULL from an EC private key, HKDF from a
  generic secret): C_DeriveKey from a base key with CKA_DERIVE = FALSE
  (§4.8 Table 26, §5.18.5) is refused and makes no key.
- Assert_Kem_WithCapabilityFalse_IsKeyFunctionNotPermitted, for
  C_EncapsulateKey with CKA_ENCAPSULATE = FALSE and C_DecapsulateKey with
  CKA_DECAPSULATE = FALSE (ML-KEM-768; §5.18.8, §5.18.9: "MUST be
  CK_TRUE"): refused, no key.

From wolfSSL/wolfPKCS11#185, #197

Pass: wolfPKCS11, BouncyHsm, FreeHSM (everything); SoftHSM2 (no HKDF;
its ML-KEM key-size range skips ML-KEM-768); Craton HSM (no HMAC, HKDF
or ML-KEM). Gated:
- Kryoptic refuses an HMAC key without CKA_SIGN or CKA_VERIFY with
  CKR_KEY_TYPE_INCONSISTENT (KryopticRefusesAnUnpermittedMacKeyAsNotPermitted,
  check_and_fetch_key in src/hmac.rs); the AES, derive and ML-KEM cases
  pass.
- NSS does the same for HMAC keys (SupportsFunctionNotPermittedForMacKeys),
  as recorded when the class was first run on it.
NSS and opencryptoki run in CI only.

* test: refuse a key of the wrong type and an object that is no key on every backend

- OperationStateTestCases.KeysOfTheWrongType gets two derive rows:
  C_DeriveKey(CKM_ECDH1_DERIVE) from an AES key and
  C_DeriveKey(CKM_HKDF_DERIVE) from an EC private key, each base key with
  CKA_DERIVE so that only its type is wrong (§6.3.17, §6.62.3), answer
  CKR_KEY_TYPE_INCONSISTENT or CKR_KEY_FUNCTION_NOT_PERMITTED (PKCS#11
  v3.2 §5.1.6) and make no key.
- Assert_NonKeyObject_IsRefusedAsAKey, one row per use: a session data
  object or X.509 certificate given to C_SignInit (HMAC) or
  C_EncryptInit (AES-CBC), as the wrapping key of C_WrapKey or the
  unwrapping key of C_UnwrapKey (AES key wrap), is refused, nothing is
  unwrapped and the session still digests. The spec singles out no code:
  §5.1.6 has CKR_KEY_HANDLE_INVALID cover a handle to an object that is
  not a key, but CKR_KEY_TYPE_INCONSISTENT and
  CKR_KEY_FUNCTION_NOT_PERMITTED fit too, so the Init rows accept the
  three; the wrap rows accept CKR_(UN)WRAPPING_KEY_HANDLE_INVALID,
  CKR_(UN)WRAPPING_KEY_TYPE_INCONSISTENT (§5.18.3, §5.18.4) or
  CKR_KEY_FUNCTION_NOT_PERMITTED.

From wolfSSL/wolfPKCS11#194, #263

Pass: wolfPKCS11, FreeHSM (every row); SoftHSM2 (no HKDF). Gated:
- BouncyHsm answers every non-key row with CKR_OBJECT_HANDLE_INVALID
  (BouncyHsmRefusesANonKeyObjectAsAKey, with a tripwire); the derive
  rows pass.
- Kryoptic answers a non-key wrapping or unwrapping key with
  CKR_GENERAL_ERROR (KryopticRefusesANonKeyObjectAsAWrappingKey) and an
  HKDF base key of the wrong type with CKR_KEY_HANDLE_INVALID
  (KryopticReportsAnHkdfBaseKeyOfTheWrongTypeAsInconsistent).
- Craton HSM derives an ECDH key from an AES key, using its value as the
  private scalar (CratonHsmChecksTheDeriveBaseKeyType); it has no HKDF
  or HMAC, and the other non-key rows pass.
NSS and opencryptoki run in CI only.

* test: keep the session usable when an active operation's key is destroyed on every backend

OperationStateTestCases.Assert_KeyDestroyedMidOperation_SessionStaysUsable,
one row per operation: after C_SignInit (ECDSA), or C_EncryptInit
(AES-GCM) and one C_EncryptUpdate, C_DestroyObject of the key, then the
call that continues the operation (C_Sign; C_EncryptUpdate and
C_EncryptFinal). PKCS#11 v3.2 does not say what happens (§5.7.3 does not
mention active operations), so the case asserts only that every call
returns, that a result returned is right (the signature verifies, the
ciphertext is the BCL's), and that the session stays usable: the final
ends the operation whatever it returns (§5.2), so a new Init with
another key succeeds, and the session still digests. A module that used
the freed key would crash or return a wrong result. Each row was run on
its own first; no backend crashed.

From wolfSSL/wolfPKCS11#201, #230

Pass: SoftHSM2, Kryoptic, FreeHSM, wolfPKCS11 (both rows); Craton HSM
(ECDSA; it runs AES-GCM single-part only). Recorded through the case's
keepsTheOperation parameter: BouncyHsm answers C_Sign with
CKR_GENERAL_ERROR once the key is gone and keeps the signature active,
which C_SessionCancel ends. NSS skips the AES-GCM row (single-part
only). NSS and opencryptoki run in CI only.

* test: hold output buffers to the length conventions, and guard past them, on every backend

PKCS#11 v3.2 §5.2 and §5.7.5 through the raw calls. Every short buffer
below is the start of a larger one filled with a canary, room for the
whole output and more, and the bytes past the declared length must come
back unchanged, so a module that ignores the length writes into memory
the case owns.
- BufferConventionTestCases.MultipartFinals gets AES-CBC-PAD encrypt
  and decrypt: C_EncryptFinal / C_DecryptFinal into a 1-byte buffer
  answers CKR_BUFFER_TOO_SMALL with a usable length and keeps the
  operation, and the retry completes the BCL's result. Every row now
  checks the canary.
- Assert_CbcPadEncrypt_LengthQuery_CoversThePaddingBlock (16, 32, 48
  bytes): the C_Encrypt length query covers the extra padding block and
  a buffer of exactly that size takes the ciphertext.
- Assert_Digest_LengthQuery_KeepsTheOperation: C_Digest with data and a
  NULL output answers CKR_OK with 32 and keeps the operation.
- Assert_GetAttributeValue_ShortBuffer_WritesNothingPastIt: CKA_LABEL,
  CKA_EC_POINT (1 byte, and 65, the raw point's length), CKA_EC_PARAMS
  and CKA_MODULUS into a short buffer answer CKR_BUFFER_TOO_SMALL, write
  nothing past it and then read in full. ulValueLen stays the existing
  ShortBuffer_IsUnavailableInformation case's.
- Assert_GetAttributeValue_LargeBuffer_SetsTheExactLength: CKA_LABEL,
  CKA_EC_PARAMS and an ML-DSA key's CKA_PARAMETER_SET into a larger
  buffer set ulValueLen to the exact length.
- OperationContractTestCases.BufferTooSmallOperations gets SHA-256
  C_Digest, AES-CBC-PAD C_Decrypt and C_DecryptUpdate (which may also
  hold every block back and answer CKR_OK with nothing), and AES-KW and
  AES-KWP C_Decrypt; every row now checks the canary.
- Assert_CbcPad_MultipartEncrypt_MatchesBcl: C_EncryptUpdate in 7-byte
  pieces, then C_EncryptFinal, whose length query covers the last part
  and whose output completes the BCL's ciphertext.
- Assert_AesCtr_LengthQuery_ReportsTheInputLength: the C_Encrypt and
  C_Decrypt length queries for CKM_AES_CTR (a CK_AES_CTR_PARAMS with a
  128-bit counter, §6.11.2) report the input's length, and the result
  matches a CTR built from the BCL's AES-ECB.

From wolfSSL/wolfPKCS11#179, #189, #194, #212, #237, #238, #241

No backend wrote past a buffer. Pass: SoftHSM2 (no AES-KW/KWP for
C_Decrypt), wolfPKCS11 (no AES-CTR or KWP), everything else on every
backend the mechanisms exist for, but for these, gated:
- wolfPKCS11 answers a CBC-PAD C_Decrypt into a buffer too small with
  CKR_BUFFER_TOO_SMALL and the length of all blocks but the last
  (32 for 43 bytes of plaintext)
  (WolfPkcs11ReportsTheWholeCbcPadPlaintextLength, with a tripwire).
- Kryoptic answers a CBC-PAD C_DecryptFinal into a buffer too small with
  CKR_ENCRYPTED_DATA_INVALID and ends the operation
  (KryopticKeepsACbcPadDecryptFinalOnAShortBuffer).
- FreeHSM answers a public key's CKA_MODULUS, CKA_EC_POINT or
  CKA_EC_PARAMS read into a buffer too small with CKR_OK
  (FreeHsmReportsAShortBufferForKeyAttributes); the label row passes.
- BouncyHsm and Craton HSM take a bare 16-byte counter block for
  CKM_AES_CTR, not a CK_AES_CTR_PARAMS (BouncyHsmTakesAesCtrParams, with
  a tripwire; CratonHsmTakesAesCtrParams).
NSS and opencryptoki run in CI only.

* test: refuse a ciphertext or wrapped key invalid by its length, and a tampered key-wrap blob, on every backend

- OperationStateTestCases.Assert_ShortCiphertext_Decrypt_IsRefused, one
  row per ciphertext: AES-GCM of 0 and 15 bytes (shorter than the tag,
  §6.13.2), AES-CBC-PAD of 0 and 15 bytes, AES-KW and AES-KWP of 8 and
  20 bytes (under two semiblocks, or not a whole number of them). The
  length query, then the call into a buffer if the query answered
  CKR_OK, is refused with CKR_ENCRYPTED_DATA_LEN_RANGE or
  CKR_ENCRYPTED_DATA_INVALID, which PKCS#11 v3.2 §5.10.2 leaves to the
  module, and the refusal ends the operation (§5.2), so the next
  C_DecryptInit succeeds.
- AlgorithmConformanceTestCases.Assert_AesKeyWrap_TamperedBlob_IsRefused
  now asks for CKR_WRAPPED_KEY_INVALID for a blob that fails RFC 3394's
  integrity check (it accepted any refusal before), the truncated
  32-byte blob included, and gets an 8-byte and a 20-byte row that ask
  for CKR_WRAPPED_KEY_LEN_RANGE, which outranks it for a blob invalid by
  its length alone (§5.1.6, §5.18.4).
- Assert_RsaOaep_UnwrapOfABlobNotKBytes_IsWrappedKeyLenRange: an OAEP
  blob one byte short of a 2048-bit modulus, or one byte over, is
  refused with CKR_WRAPPED_KEY_LEN_RANGE (RFC 8017 §7.1.2), and the
  k-byte blob then unwraps.

From wolfSSL/wolfPKCS11#169, #189, #197, #243

Pass: FreeHSM (every row it has the mechanisms for); wolfPKCS11 (no
AES-KWP, no OAEP unwrap) but for one row, gated. Recorded through the
short-ciphertext case's keepsTheOperation parameter: BouncyHsm keeps
the decryption active after the refusal, as it does for a partial
block. Gated:
- wolfPKCS11 answers a 20-byte AES-KW unwrap with
  CKR_WRAPPED_KEY_INVALID, the C_Decrypt code it maps
  (WolfPkcs11RefusesAKeyWrapBlobByItsLength, with a tripwire).
- BouncyHsm decrypts an empty CBC-PAD ciphertext to nothing with CKR_OK
  (BouncyHsmRefusesAnEmptyCbcPadCiphertext, with a tripwire) and answers
  an OAEP blob of the wrong length with CKR_GENERAL_ERROR
  (BouncyHsmRefusesAnOaepBlobByItsLength, with a tripwire).
- SoftHSM2 answers a blob that fails the integrity check with
  CKR_GENERAL_ERROR (SoftHsmReportsATamperedKeyWrapBlobAsInvalid; the
  length rows pass) and an OAEP blob of the wrong length the same way
  (SoftHsmRefusesAnOaepBlobByItsLength). It has no AES-KW C_Decrypt.
- Kryoptic answers a tampered or 8-byte AES-KW blob with
  CKR_DEVICE_ERROR and a 20-byte one with CKR_DATA_LEN_RANGE
  (KryopticReportsATamperedKeyWrapBlobAsInvalid), a one-semiblock
  AES-KW or AES-KWP C_Decrypt with CKR_DEVICE_ERROR after a length query
  that answered CKR_OK (KryopticRefusesAOneSemiblockKeyWrapCiphertext),
  and a short OAEP blob with CKR_DEVICE_ERROR
  (KryopticRefusesAnOaepBlobByItsLength).
- Craton HSM answers a tampered AES-KW blob with
  CKR_ENCRYPTED_DATA_INVALID and a short one with CKR_DATA_LEN_RANGE
  (CratonHsmReportsATamperedKeyWrapBlobAsInvalid); it has no AES-KW
  C_Decrypt or OAEP unwrap, and its short-ciphertext rows pass.
NSS and opencryptoki run in CI only.

* test: run AES-GCM in odd pieces, and refuse C_Verify after C_VerifyUpdate, on every backend

- OperationContractTestCases.Assert_AesGcm_MultipartInOddPieces_MatchesBcl,
  one row per piece length (1, 15, 17, 4097): AES-GCM with AAD, fed
  through several C_EncryptUpdate (C_DecryptUpdate) calls of that
  length, none a whole number of blocks, and a shorter last one, then
  the final, gives the BCL's ciphertext and tag (plaintext), whatever
  the module releases at each update (PKCS#11 v3.2 §6.13.2).
- OperationStateMixingTestCases gets the Verify kind the class lacked:
  after C_VerifyInit(HMAC-SHA256) and C_VerifyUpdate, the single-part
  C_Verify "cannot be used to terminate a multi-part operation"
  (§5.15.2). Assert_Verify_SinglePartAfterUpdate_Throws refuses it
  whether the MAC is over both parts or the second only; the
  TreatsAsContinuation and DiscardsTheUpdate variants pin what a backend
  does instead, as for Digest, Sign and Encrypt, and the class remarks'
  matrix gets the Verify row.

From wolfSSL/wolfPKCS11#198, #240

GCM: pass on SoftHSM2, Kryoptic, BouncyHsm and wolfPKCS11; Craton HSM
and FreeHSM run AES-GCM single-part only (SupportsMultipartAesGcm), and
NSS has the existing MultipartClassicAesGcmAvailable gate.
Verify: SoftHSM2 and Kryoptic refuse it (Throws). BouncyHsm and
wolfPKCS11 verify over both parts (TreatsAsContinuation); FreeHSM
verifies the second part only and ends the operation
(DiscardsTheUpdate). Craton HSM has no HMAC and skips. NSS is wired as
a continuation, as its Sign is, unconfirmed. NSS and opencryptoki run
in CI only.

* test: hold raw RSA verification to the data it signs on every backend

Two AlgorithmConformanceTestCases for CKM_RSA_X_509, through the raw
dispatch (raw RSA is outside the library's policy), with a 2048-bit key
pair and a full 256-byte block m whose top byte is 0x01:
- Assert_RsaX509_Verify_RequiresTheLeadingBytes: the signature over m
  verifies m and, with CKR_SIGNATURE_INVALID, not m[1..]. Raw RSA
  compares the zero-padded data with the whole recovered block (PKCS#11
  v3.2 §6.1.12), so only leading zero bytes may be left out.
- Assert_RsaX509_Verify_OfDataLongerThanTheModulus_IsRefused: under a
  token key, C_Verify of 257 bytes (more than k, §6.1.12 Table 45) is
  refused with CKR_DATA_LEN_RANGE, or CKR_SIGNATURE_INVALID from a
  module that checks the signature against the data as given; the
  token key then verifies m and signs it again at once, so the module
  did not leave the key locked on the error.

From wolfSSL/wolfPKCS11#163, #240

Pass: Kryoptic (both); SoftHSM2 and wolfPKCS11 (the leading-bytes
case). BouncyHsm, Craton HSM and FreeHSM do not offer CKM_RSA_X_509 for
signatures and skip. Gated:
- SoftHSM2 ends the test process inside C_Verify of the 257 bytes:
  AsymVerify in src/lib/SoftHSM.cpp zero-pads with
  data.wipe(size - ulDataLen), which wraps around
  (SoftHsmSurvivesAnOverlongRawRsaVerify). Confirmed by running the
  case alone and by a probe around the call.
- wolfPKCS11 answers it with CKR_FUNCTION_FAILED; the token key works
  afterwards (WolfPkcs11RefusesOverlongRawRsaDataByItsLength, with a
  tripwire that also checks the key).
NSS and opencryptoki run in CI only.

* test: refuse C_DigestKey of a private key, and without a digest, on every backend

- AlgorithmConformanceTestCases.Assert_DigestKey_OfAPrivateKey_IsKeyIndigestible:
  after C_DigestInit(SHA-256) and C_DigestUpdate, C_DigestKey of an EC
  private key answers CKR_KEY_INDIGESTIBLE (PKCS#11 v3.2 §5.12.4
  digests "the value of a secret key"; §5.1.6: "perhaps the key isn't a
  secret key"). The digest then either goes on, and C_DigestFinal gives
  the digest of the data fed before, or was ended (CKR_OPERATION_NOT_
  INITIALIZED); either way the next C_DigestInit succeeds.
- OperationStateTestCases.CallsWithoutInit gets "C_DigestKey": with no
  digest active, C_DigestKey of a secret key answers
  CKR_OPERATION_NOT_INITIALIZED (§5.2, §5.12.4).

From wolfSSL/wolfPKCS11#178, #185, #241

Pass: wolfPKCS11, BouncyHsm, Craton HSM, FreeHSM (both); SoftHSM2 and
Kryoptic (the CallsWithoutInit row). Gated:
- SoftHSM2 digests the CKA_VALUE of any object, a sensitive EC private
  key's scalar included, and answers CKR_OK: confirmed with an imported
  P-256 key, whose digest is SHA-256 of its scalar
  (SoftHsmRefusesToDigestAPrivateKey).
- Kryoptic answers CKR_KEY_HANDLE_INVALID for a key that is not a
  secret key (KryopticReportsAPrivateKeyAsIndigestible).
NSS and opencryptoki run in CI only.

* test: refuse the misuses of saving and restoring an operation's state on every backend

AlgorithmConformanceTestCases.Assert_OperationState_Misuse_IsRefused,
next to DigestState_SavedAndRestored, one row per misuse, for a SHA-256
digest's state saved after one part and restored into the session that
saved it:
- C_GetOperationState with no operation active answers
  CKR_OPERATION_NOT_INITIALIZED (PKCS#11 v3.2 §5.6.6);
- C_SetOperationState of random bytes of the state's length, or of the
  state cut by its last byte, answers CKR_SAVED_STATE_INVALID (§5.6.7);
- C_SetOperationState with an encryption key or an authentication key,
  which a digest does not use, answers CKR_KEY_NOT_NEEDED (§5.1.6).
Both functions are optional, so CKR_FUNCTION_NOT_SUPPORTED is taken;
the rows that need a saved state skip where the module cannot save one
(CKR_STATE_UNSAVEABLE), and the random-bytes row then sends 64 bytes.
Whether a refused restore leaves the operation as it was is not stated,
so only the code is asserted.

From wolfSSL/wolfPKCS11#230

Pass: wolfPKCS11 (every row); SoftHSM2, Kryoptic, BouncyHsm and FreeHSM
(no operation, random bytes; they cannot save a digest's state); Craton
HSM but for two rows, gated: it restores the state given a key it does
not need and answers CKR_OK (CratonHsmRefusesAKeyNotNeeded).
NSS and opencryptoki run in CI only.

* test: refuse out-of-range AES-GCM and AES-CCM parameters on every backend

New MechanismParameterTestCases, with a wrapper per backend, for
parameters the library's own types refuse before any native call: each
case lays the CK_*_PARAMS struct out itself (RawParameter.From, with
NativeBuffers for its pointers) and sends it through the raw dispatch.

Assert_AeadParameter_OutOfRange_IsMechanismParamInvalid, one row per
fault: CK_GCM_PARAMS with a NULL pIv behind ulIvLen 12, ulIvLen 0, a
NULL pAAD behind ulAADLen 9, ulTagBits 136; CK_CCM_PARAMS with a NULL
pNonce, a nonce of 6 or 14 bytes, a NULL pAAD, ulMACLen 2, 5 or 18.
PKCS#11 v3.2 §6.13.7 gives the ranges (IV 1..2^32-1 bytes, tag 0..128
bits, nonce 7..13 bytes, MAC 4..16 even) and §6.13.2, §6.13.4 let pAAD
be NULL only for ulAADLen 0. C_EncryptInit and C_DecryptInit answer
CKR_MECHANISM_PARAM_INVALID (§5.1.6), or CKR_ARGUMENTS_BAD for a NULL
pointer; after each refusal the same session runs the Init with good
parameters and gets the BCL's ciphertext (plaintext).

From wolfSSL/wolfPKCS11#186, #194, #238

Pass: wolfPKCS11 (every row); Craton HSM (the GCM rows; no CCM).
Gated:
- SoftHSM2 ends the test process on a NULL pIv or pAAD behind a length:
  SymEncryptInit and SymDecryptInit in src/lib/SoftHSM.cpp memcpy from
  the pointer unchecked (SoftHsmSurvivesANullGcmPointer; each row run
  alone). It takes ulIvLen 0 and refuses ulTagBits 136 with
  CKR_ARGUMENTS_BAD (SoftHsmRefusesAnOutOfRangeGcmParameterAsInvalid).
  It has no CCM.
- Kryoptic takes a NULL pAAD behind a length as no AAD, for GCM and CCM
  (KryopticRefusesANullAadPointer; a probe's ciphertext and tag were
  the BCL's with empty AAD), and refuses a NULL CCM pNonce with
  CKR_DEVICE_ERROR (KryopticRefusesANullCcmNonceAsInvalid).
- BouncyHsm refuses every out-of-range field with CKR_GENERAL_ERROR
  (BouncyHsmRefusesAnOutOfRangeAeadParameterAsInvalid) and takes a NULL
  pAAD as no AAD (BouncyHsmRefusesANullAadPointer), each with a
  tripwire.
- FreeHSM refuses ulTagBits 136 with CKR_ARGUMENTS_BAD
  (FreeHsmRefusesAnOutOfRangeGcmTagAsInvalid); the other rows pass.
NSS (GCM rows behind RequireClassicAesGcm) and opencryptoki run in CI
only.

* test: refuse an AES-CTR counter width out of range, and a counter overflow, on every backend

Two MechanismParameterTestCases for CKM_AES_CTR, through the raw
dispatch:
- Assert_AesCtr_CounterBitsOutOfRange_IsMechanismParamInvalid (0, 129):
  PKCS#11 v3.2 §6.11.2 has ulCounterBits in 0 < n <= 128 and "for any
  values outside this range the mechanism shall return
  CKR_MECHANISM_PARAM_INVALID"; checked at C_EncryptInit and
  C_DecryptInit, each followed in the same session by an Init with a
  128-bit counter whose result is the BCL's.
- Assert_AesCtr_CounterOverflow_IsDataLenRange: §6.11.3's own example.
  With ulCounterBits 2 and the counter bits starting at 1, three blocks
  encrypt to the BCL's CTR and a fourth is refused with
  CKR_DATA_LEN_RANGE ("an attempt ... which will cause an overflow of
  the counter block's counter bits"), rather than wrapping or carrying
  into the nonce; the next C_EncryptInit succeeds.

From wolfSSL/wolfPKCS11#197, #212

Pass: SoftHSM2 (both). wolfPKCS11 has no AES-CTR. BouncyHsm and Craton
HSM take a bare counter block, not CK_AES_CTR_PARAMS, and keep their
AesCtrParamsAvailable gates. Gated (the counter-bits rows pass on both):
- Kryoptic refuses the three-block message with CKR_DATA_LEN_RANGE:
  its final step in src/ossl/aes.rs refuses once blockctr reaches
  maxblocks, so the last counter value is never used
  (KryopticEncryptsTheLastCounterValue).
- FreeHSM encrypts the four blocks with CKR_OK: it checks ulCounterBits
  for 1..128 and runs a 128-bit counter (src/fhsm_pkcs11.c), carrying
  into the nonce (FreeHsmRefusesAnAesCtrCounterOverflow).
NSS and opencryptoki run in CI only.

* test: refuse MAC and signature parameters out of range, and a NULL OAEP label, on every backend

Through the raw dispatch, as the library's parameter types take none of
these:
- MechanismParameterTestCases.Assert_MacGeneral_LengthOutOfRange_IsMechanismParamInvalid:
  C_SignInit with CKM_SHA256_HMAC_GENERAL and a MAC length of 33, or
  CKM_AES_CMAC_GENERAL and 17, is refused with
  CKR_MECHANISM_PARAM_INVALID (PKCS#11 v3.2 §6.22.3: 1-32; §6.14.3,
  Table 121: 1 to the block size; §5.1.6), and so are 2^32 + 16 and
  2^32 + 8 where CK_ULONG is 64 bits, which a module that narrows the
  length takes for 16 and 8. A length in range then gives the start of
  the full MAC (the BCL's HMAC-SHA256, RFC 4493's CMAC).
- AlgorithmConformanceTestCases.Assert_RsaPss_ParameterOutOfRange_IsMechanismParamInvalid:
  CKM_SHA256_RSA_PKCS_PSS with hashAlg CKM_SHA384 (§6.1.9: it "must
  match the hash algorithm indicated by the signature mechanism") or an
  sLen of 2^32 + 32 (§6.1.15: at most k* - 2 - hLen; 64-bit CK_ULONG
  only) is refused by C_SignInit and C_VerifyInit with
  CKR_MECHANISM_PARAM_INVALID, or taken by the Init and refused by
  C_Sign (§5.13.2 names no code there) and not verified; nothing is
  signed. SHA-256 with a 32-byte salt then signs and verifies.
- EncryptRsaOaepLabelTestCases.Assert_OaepNullLabelWithLength_IsRefused:
  a NULL pSourceData behind ulSourceDataLen 8 (§6.1.7: the length "must
  be 0" for the default label) is refused by C_EncryptInit and
  C_DecryptInit with CKR_MECHANISM_PARAM_INVALID or CKR_ARGUMENTS_BAD;
  the empty label then round-trips.

From wolfSSL/wolfPKCS11#238, #240

Pass: wolfPKCS11 (everything it has; no HMAC_GENERAL); FreeHSM and
Craton HSM (OAEP; neither has HMAC_GENERAL or CMAC_GENERAL); SoftHSM2
(OAEP; no general-length MACs); Kryoptic (MACs, PSS hashAlg). Craton
HSM's PSS rows run through the existing ignoresPssParameters: it ignores
hashAlg too, and signs as with SHA-256 and a 32-byte salt. Gated:
- BouncyHsm cuts a CK_ULONG parameter length to 32 bits: HMAC_GENERAL
  with 2^32 + 16 signs 16 bytes, CMAC_GENERAL with 2^32 + 8 is taken,
  PSS with sLen 2^32 + 32 signs with a 32-byte salt
  (BouncyHsmRefusesAParameterLengthAbove32Bits, tripwire), and it takes
  a NULL OAEP label as the empty one (BouncyHsmRefusesANullOaepLabel,
  tripwire). Its 33 and 17 rows and the hashAlg row pass.
- Kryoptic takes a NULL OAEP label as the empty one
  (KryopticRefusesANullOaepLabel) and refuses sLen 2^32 + 32 with
  CKR_GENERAL_ERROR (KryopticRefusesAnOverlongPssSaltAsInvalid).
- SoftHSM2 refuses the PSS hashAlg with CKR_ARGUMENTS_BAD and the sLen
  with CKR_MECHANISM_INVALID
  (SoftHsmRefusesAnOutOfRangePssParameterAsInvalid).
- FreeHSM takes hashAlg CKM_SHA384 and signs with SHA-256
  (FreeHsmRefusesAMismatchedPssHash); its sLen row passes.
Each behaviour was confirmed by a probe of the call. NSS and
opencryptoki run in CI only.

* test: refuse ECDH parameters out of range on every backend

AlgorithmConformanceTestCases.Assert_Ecdh_ParameterOutOfRange_IsRefused,
one row per fault, for CKM_ECDH1_DERIVE with CKD_NULL from the SP 800-56A
P-256 vector, through the raw dispatch (the library's parameter type
refuses the first three):
- CKD_NULL with shared data: PKCS#11 v3.2 §6.3.16, "pSharedData must be
  NULL and ulSharedDataLen must be zero";
- a 1 KiB peer value (a raw point padded with zeros), and a DER OCTET
  STRING whose length byte promises 65 bytes and has 40 (§6.3.16: the
  peer's point, raw or DER);
  each refused with CKR_MECHANISM_PARAM_INVALID (§5.1.6);
- CKA_VALUE_LEN 33 for a generic secret, one byte more than the secret
  the mechanism can only truncate (§6.3.17): CKR_ATTRIBUTE_VALUE_INVALID,
  CKR_TEMPLATE_INCONSISTENT (§4.1) or CKR_KEY_SIZE_RANGE (§5.18.5).
None makes a key (found by label), and the same session then derives Z,
checked through an HMAC under it where the module has HMAC-SHA256.

From wolfSSL/wolfPKCS11#233, #234

Pass: Craton HSM (every row). Gated, each confirmed by the run and, for
the keys made, a probe reading them back:
- wolfPKCS11 answers the lying DER length with CKR_FUNCTION_FAILED
  (WolfPkcs11RefusesAMalformedEcdhPeerAsInvalid, tripwire);
- Kryoptic answers it with CKR_DEVICE_ERROR
  (KryopticRefusesAMalformedEcdhPeerAsInvalid);
- SoftHSM2 answers both peer rows with CKR_GENERAL_ERROR
  (SoftHsmRefusesAMalformedEcdhPeerAsInvalid) and CKA_VALUE_LEN 33 with
  CKR_FUNCTION_FAILED (SoftHsmRefusesAnEcdhValueLenAboveTheSecret);
- BouncyHsm ignores the shared data and derives Z
  (BouncyHsmRefusesSharedDataWithCkdNull, tripwire) and makes 00 || Z
  for CKA_VALUE_LEN 33 (BouncyHsmRefusesAnEcdhValueLenAboveTheSecret,
  tripwire); its peer rows pass;
- FreeHSM departs on every row: it ignores the shared data, makes a
  32-byte key for CKA_VALUE_LEN 33 and refuses both peers with
  CKR_ATTRIBUTE_VALUE_INVALID (FreeHsmChecksTheEcdhParameters).
The other rows pass on each. NSS and opencryptoki run in CI only.

* test: refuse an HKDF output past RFC 5869's limit, and a salt key that is no secret key, on every backend

HkdfTestCases.Assert_Hkdf_ParameterOutOfRange_IsRefused, through the raw
dispatch (the library's HKDF API refuses these before any native call),
from a 32-byte generic secret with extract and expand under SHA-256:
- CKA_VALUE_LEN 8161 (255 x 32 + 1), past what RFC 5869's expand gives
  (PKCS#11 v3.2 §6.62.3), and 2^32 + 1 where CK_ULONG is 64 bits (a
  module that narrows it makes a 1-byte key): CKR_ATTRIBUTE_VALUE_INVALID,
  CKR_TEMPLATE_INCONSISTENT (§4.1) or CKR_KEY_SIZE_RANGE (§5.18.5);
- CKF_HKDF_SALT_KEY with an EC public key as hSaltKey: §6.62.2 names no
  code, so CKR_MECHANISM_PARAM_INVALID, CKR_KEY_HANDLE_INVALID or
  CKR_KEY_TYPE_INCONSISTENT.
None makes a key (found by label); the same session then derives the
BCL's HKDF output, checked through an HMAC under it.

Left out: CKA_VALUE_LEN 0 or missing with bExpand. §6.62.3 says only that
CKA_VALUE_LEN "should be set", and CKA_VALUE_LEN is not required for
C_DeriveKey by Table 13, so no refusal can be asserted.

From wolfSSL/wolfPKCS11#173, #201, #234

Pass: FreeHSM (every row). SoftHSM2, opencryptoki and Craton HSM have no
HKDF (the class has no SoftHSM2 or opencryptoki wrapper). Gated:
- wolfPKCS11 refuses 8161 with CKR_FUNCTION_FAILED
  (WolfPkcs11RefusesAnHkdfOutputAboveTheRfcLimit, tripwire);
- Kryoptic refuses it with CKR_DEVICE_ERROR
  (KryopticRefusesAnHkdfOutputAboveTheRfcLimit);
- BouncyHsm refuses 8161 with CKR_GENERAL_ERROR and makes a 1-byte key
  for 2^32 + 1 (BouncyHsmRefusesAnHkdfOutputAboveTheRfcLimit, tripwire;
  confirmed by reading CKA_VALUE_LEN back), and refuses the salt key
  with CKR_OBJECT_HANDLE_INVALID
  (BouncyHsmReportsAnHkdfSaltKeyOfTheWrongTypeAsInconsistent, tripwire).
NSS runs in CI only.

* test: hold key generation to the sizes, exponent and curve asked on every backend

Through the raw dispatch, as the library's key APIs refuse these
templates before any native call:
- MechanismParameterTestCases.Assert_GenericSecretKeyGen_LengthIsBoundedInBits:
  CKM_GENERIC_SECRET_KEY_GEN's ulMinKeySize and ulMaxKeySize are in
  bits (PKCS#11 v3.2 §6.8.3), so C_GenerateKey makes keys of
  ceil(min / 8) and max / 8 bytes, each reading back that CKA_VALUE_LEN,
  and refuses max / 8 + 1 and, where CK_ULONG is 64 bits, 2^32 + 16
  (CKR_ATTRIBUTE_VALUE_INVALID, CKR_TEMPLATE_INCONSISTENT or
  CKR_KEY_SIZE_RANGE), making no key. Max rows run up to 16 MiB.
- GenerateRsaKeyPairTestCases.Assert_RsaKeyGen_ModulusBitsAboveTheMaximum_IsRefused:
  CKA_MODULUS_BITS ulMaxKeySize + 8 (where the maximum is at most 8192)
  and 2^32 + 2048 are refused with those codes (§6.1.4) and never make a
  key.
- GenerateRsaKeyPairTestCases.Assert_RsaKeyGen_PublicExponent_IsTheOneAsked:
  a pair made with CKA_PUBLIC_EXPONENT 65539 (01 00 03) or 2^64 + 65537
  reads that integer back and its PSS signature verifies under the BCL
  with (n, e); a refusal is CKR_TEMPLATE_INCONSISTENT, as §6.1.4 says
  ("shall be returned if the implementation cannot use the supplied
  exponent value"), and skips for 65539.
- GenerateEcKeyPairTestCases.Assert_EcKeyGen_CurveOidNotACurve_IsRefused:
  CKA_EC_PARAMS of the P-256 OID with an arc added or its last arc
  dropped is refused with CKR_CURVE_NOT_SUPPORTED,
  CKR_DOMAIN_PARAMS_INVALID or CKR_ATTRIBUTE_VALUE_INVALID.
- AlgorithmConformanceTestCases.Assert_Derive_AesKeyOfAnInvalidLength_IsRefused:
  C_DeriveKey(CKM_ECDH1_DERIVE) of a 13-byte CKK_AES key (§6.10.2) is
  refused with CKR_ATTRIBUTE_VALUE_INVALID, CKR_TEMPLATE_INCONSISTENT or
  CKR_KEY_SIZE_RANGE and makes no key; 16 bytes then derives.

From wolfSSL/wolfPKCS11#171, #173, #201, #231, #233, #236, #243

Pass: every row each backend has the mechanism for, but for these,
gated:
- Kryoptic, CKA_VALUE_LEN 2^32 + 16: it sets out to make the 4 GiB key
  and the kernel's OOM killer ends the test host (exit 137; row run
  alone, kernel log) (KryopticBoundsAGenericSecretLength). It reports a
  generic-secret range of [0, 0], so the range rows skip. It refuses the
  OIDs with CKR_GENERAL_ERROR
  (KryopticRefusesAnOidThatIsNoCurveAsNotSupported).
- BouncyHsm cuts CKA_MODULUS_BITS and CKA_VALUE_LEN to 32 bits: 2^32 +
  2048 makes a 2048-bit RSA key, 2^32 + 16 a 16-byte secret
  (BouncyHsmRefusesAKeyLengthAttributeAbove32Bits, tripwire); it makes a
  secret past its ulMaxKeySize (BouncyHsmBoundsAGenericSecretByItsMaximum)
  and refuses the 13-byte AES key with CKR_GENERAL_ERROR
  (BouncyHsmRefusesAnAesKeyOfAnInvalidLengthAsInvalid, tripwire).
- Craton HSM makes a 2048-bit key for 2^32 + 2048
  (CratonHsmRefusesAModulusAbove32Bits) and every key with exponent
  65537 (CratonHsmKeepsThePublicExponentAsked). No generic secret gen.
- FreeHSM makes every key with exponent 65537
  (FreeHsmKeepsThePublicExponentAsked), a 9-byte secret under a reported
  ulMaxKeySize of 64 (FreeHsmBoundsAGenericSecretByItsMaximum) and a
  13-byte AES key (FreeHsmRefusesAnAesKeyOfAnInvalidLength).
- SoftHSM2 makes a secret past its ulMaxKeySize
  (SoftHsmBoundsAGenericSecretByItsMaximum), and answers
  CKR_GENERAL_ERROR for the OIDs
  (SoftHsmRefusesAnOidThatIsNoCurveAsNotSupported) and for
  CKA_MODULUS_BITS 2^32 + 2048 and the nine-byte exponent
  (SoftHsmRefusesAnRsaKeyGenAttributeAsInvalid).
- wolfPKCS11 answers CKR_FUNCTION_FAILED for the nine-byte exponent
  (WolfPkcs11RefusesAnRsaExponentAsInconsistent, tripwire) and for a
  generic secret past its maximum
  (WolfPkcs11RefusesAGenericSecretLengthAsInvalid, tripwire).
NSS and opencryptoki run in CI only.

* test: skip the NULL AES-GCM pointer rows that end the NSS test host

Fixes the review finding (critical) that NSS would crash on
MechanismParameterTestCases' AEAD rows "GCM pIv NULL" and "GCM pAAD
NULL". Run alone against a softoken built from vendor/nss at the pinned
34716626, each row ends the test host with SIGSEGV (exit code 139):
sftk_CryptInit hands both pointers on unchecked and freebl's
gcm_InitCounter copies the IV and hashes the AAD from them.

NssBackendFixture.NssSurvivesANullGcmPointer (false) records it, and the
NSS wrapper skips the two rows, as SoftHsmSurvivesANullGcmPointer does
for SoftHSM2. The other NULL-pointer-with-length rows of the new cases
were run alone on NSS too: CCM pNonce/pAAD NULL skip (no CKM_AES_CCM),
the OAEP NULL label case passes, and the malformed-attribute NULL rows
fail without a crash (gated with their class).

* test: run the over-long raw RSA verify with a session key pair where there are no token objects

Fixes the review finding that
Assert_RsaX509_Verify_OfDataLongerThanTheModulus_IsRefused generated a
token key pair unconditionally: on NSS, whose token is write-protected,
C_GenerateKeyPair failed with CKR_TOKEN_WRITE_PROTECTED before the case
reached its check. It now asks for a token pair only where
backend.SupportsTokenObjects, as its neighbours do. No other case added
since b221bdd1 creates a token object without that check (the others
skip, or are wolfPKCS11 tripwires).

On NSS the case now reaches C_Verify and fails there (CKR_DEVICE_ERROR),
gated with the AlgorithmConformance NSS departures. Unchanged elsewhere:
Kryoptic passes; the backends gated for it before still are.

* test: take a restore of random bytes of a saved state's length as valid

Fixes the review finding on Assert_OperationState_Misuse_IsRefused's
"restore random bytes" row: PKCS#11 v3.2 §5.6.7 asks for
CKR_SAVED_STATE_INVALID only of a state the module "can determine is not
valid", so a module whose saved state carries no integrity check may
take random bytes of the right length. The row now accepts CKR_OK too,
and ends the digest the bytes restored with C_DigestFinal. The truncated
state row and the 64-byte row (where no state can be saved) stay strict.

No gate was added for that row in 21d5e874 (its only gate,
CratonHsmRefusesAKeyNotNeeded, is for the key-not-needed rows), so none
is removed. No local backend takes the random bytes: wolfPKCS11, Craton
HSM and NSS, which save a digest's state, refuse them; SoftHSM2,
Kryoptic, BouncyHsm and FreeHSM take the 64-byte row. All pass.

* test: run the rows the Craton HSM template gates and the NSS CKA_LOCAL gate hid

Fixes the review finding that whole-theory gates hid rows their flag
does not describe:
- ObjectConformanceTests_CratonHsm.IncompleteTemplate_IsTemplateIncomplete
  (CratonHsmRequiresTheKeyAttributes): "EC key pair without
  CKA_EC_PARAMS" passes on Craton HSM (refused with
  CKR_TEMPLATE_INCOMPLETE), so the gate is a body check on the other six
  rows, which still fail as the flag says.
- ObjectConformanceTests_CratonHsm.MalformedAttribute_IsRefusedHarmlessly
  (CratonHsmRefusesMalformedAttributeValues): "C_FindObjectsInit with
  CKA_LABEL NULL and ulValueLen 4" passes, so it runs; the other four
  rows fail as documented.
- KeyProvenanceTests_Nss.Pbkdf2Key_RecordsItsGeneration
  (SupportsLocalOnGeneratedKeys): the case takes checkLocal, and NSS
  runs it without the CKA_LOCAL check; its CKA_KEY_GEN_MECHANISM,
  CKA_ALWAYS_SENSITIVE and CKA_NEVER_EXTRACTABLE checks pass there.
Both Craton HSM flags' doc comments say which row runs.

Pass: Craton HSM (the two rows), NSS (Pbkdf2), and Pbkdf2 where it ran
before (Kryoptic, FreeHSM, wolfPKCS11).

* test: gate the NSS departures MechanismParameterTestCases finds

Fixes the review finding that the new cases had never run on NSS: run
against a softoken built from vendor/nss at the pinned 34716626, these
rows fail, each a departure from PKCS#11 v3.2 recorded on
NssBackendFixture (false while it stands):
- "GCM ulIvLen 0", "GCM ulTagBits 136": CKR_HOST_MEMORY (§6.13.7)
  (NssRefusesAnOutOfRangeGcmParameterAsInvalid);
- AesCtr_CounterBitsOutOfRange 0 and 129: CKR_HOST_MEMORY (§6.11.2)
  (NssRefusesAnOutOfRangeCtrCounterWidthAsInvalid);
- AesCtr_CounterOverflow: three blocks under a 2-bit counter starting at
  1, §6.11.3's own example, are refused with CKR_DATA_LEN_RANGE; freebl
  allows 2^counterBits - 2 blocks (NssRunsTheCtrBlocksItsCounterAllows);
- MacGeneral, every row: CKM_SHA256_HMAC_GENERAL and
  CKM_AES_CMAC_GENERAL refuse every length, the good one too, with
  CKR_MECHANISM_PARAM_INVALID: sftk_doMACInit hands the _GENERAL
  mechanism to sftk_MAC_InitRaw, which knows only the full-length ones
  (NssRunsTheGeneralLengthMacs);
- GenericSecretKeyGen "ulMaxKeySize / 8 + 1": a 5-byte key past the
  ulMaxKeySize 32 it reports (§6.8.3) (NssBoundsAGenericSecretByItsMaximum).

The reviewer's predictions from nss-dev master differ at the pin for
MacGeneral (refused, then the good length refused too) and
GenericSecretKeyGen 2^32 + 16 (refused, passes). The CCM rows skip (no
CKM_AES_CCM). Every other row passes on NSS.

* test: gate the NSS departures AlgorithmConformanceTestCases finds

Fixes the review finding that the new cases had never run on NSS: run
against a softoken built from vendor/nss at the pinned 34716626, these
fail, each recorded on NssBackendFixture (false while it stands):
- DigestKey_OfAPrivateKey: CKR_KEY_TYPE_INCONSISTENT, not
  CKR_KEY_INDIGESTIBLE (§5.1.6) (NssRefusesToDigestAPrivateKeyAsIndigestible);
- Ecdh_ParameterOutOfRange: a 1 KiB peer is CKR_ARGUMENTS_BAD and a lying
  DER length CKR_KEY_SIZE_RANGE (§6.3.16) (NssRefusesAMalformedEcdhPeerAsInvalid);
  CKA_VALUE_LEN 33 derives (§6.3.17) (NssRefusesAnEcdhValueLenAboveTheSecret);
- RsaOaep_UnwrapOfABlobNotKBytes and AesKeyWrap_TamperedBlob "8 bytes",
  "20 bytes": CKR_DATA_LEN_RANGE, not CKR_WRAPPED_KEY_LEN_RANGE (§5.18.4)
  (NssRefusesAShortCiphertextAsEncryptedDataLenRange);
- RsaPss "sLen 2^32 + 32": narrowed, and signs (§6.1.9)
  (NssRefusesAnOutOfRangePssParameterAsInvalid);
- OperationState_Misuse: a state cut by its last byte restores
  (NssChecksTheLengthOfARestoredState), and so does one given an
  encryption or authentication key (NssRefusesAKeyNotNeeded) (§5.6.7);
- RsaX509_Verify_RequiresTheLeadingBytes: m[1..] verifies under a
  signature over m (§6.1.12) (NssVerifiesRawRsaOverTheWholeBlock);
- RsaX509_Verify_OfDataLongerThanTheModulus: CKR_DEVICE_ERROR (§6.1.12)
  (NssRefusesOverlongRawRsaVerifyData).
Every other AlgorithmConformance case passes or skips for a mechanism
NSS does not offer.

* test: gate NSS's attribute copy past a short buffer

Fixes the review finding that the new cases had never run on NSS: run
against a softoken built from vendor/nss at the pinned 34716626,
GetAttributeValue_ShortBuffer_WritesNothingPastIt fails on every row:
C_GetAttributeValue of a session object's attribute into a buffer
shorter than the value copies the whole value past the caller's buffer
and …
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants