From 341feab2bfa84185568266fdc654657b50e5733f Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Wed, 13 May 2026 15:11:24 +0100 Subject: [PATCH] Fix static code compliance findings - F-3637: pin SecretObject_GetAttr CKA_SENSITIVE guard with a test exercising CKA_SENSITIVE=TRUE + CKA_EXTRACTABLE=TRUE. - F-2776: RsaObject_GetAttr / EcObject_GetAttr / DhObject_GetAttr / MldsaObject_GetAttr / MlKemObject_GetAttr now return CKR_ATTRIBUTE_SENSITIVE alongside the existing CK_UNAVAILABLE_INFORMATION sentinel. Updated test_attributes_rsa / ecc / dh in pkcs11test.c and pkcs11mtt.c to expect the new return. - F-1343: introduced WP11_FLAG_NOT_MODIFIABLE (inverted, mirroring NOT_COPYABLE / NOT_DESTROYABLE) and WP11_Object_IsModifiable; C_SetAttributeValue returns CKR_ACTION_PROHIBITED for CKA_MODIFIABLE=FALSE objects. Gate lives in the public entry point only so C_CopyObject (governed by CKA_COPYABLE) is unaffected. - F-3144: new CheckPrivateLogin helper in crypto.c wired into C_CreateObject, C_GenerateKey, C_GenerateKeyPair (both templates), and C_UnwrapKey. Returns CKR_USER_NOT_LOGGED_IN for CKA_PRIVATE=TRUE on a public session; empty-PIN tokens bypassed to match the find-time filter in WP11_Session_Find. - F-2370: SetAttributeDefaults seeds CKA_PRIVATE=CK_TRUE for CKO_PRIVATE_KEY / CKO_SECRET_KEY and CK_FALSE for CKO_PUBLIC_KEY, gated by WOLFPKCS11_LEGACY_PRIVATE_FALSE_DEFAULT. - F-2774: CKA_WRAP / CKA_UNWRAP default flipped to CK_FALSE in SetAttributeDefaults, gated by WOLFPKCS11_LEGACY_WRAP_TRUE_DEFAULT. pkcs11test.c / pkcs11mtt.c get_aes_128_key, get_generic_key, get_rsa_pub_key, get_rsa_priv_key set CKA_WRAP/CKA_UNWRAP=TRUE explicitly where wrap/unwrap is exercised. - F-3407: wp11_Token_Init no longer eager-marks state INITIALIZED; WP11_Slot_TokenReset sets it on C_InitToken. Redundant state-vs-INITIALIZED checks dropped from WP11_Slot_CheckSOPin / CheckUserPin so the NSS empty-PIN bootstrap probe still passes on a fresh slot. --- src/crypto.c | 172 ++++++- src/internal.c | 103 +++- tests/pkcs11_compliance_test.c | 906 +++++++++++++++++++++++++++++++++ tests/pkcs11mtt.c | 25 +- tests/pkcs11test.c | 43 +- wolfpkcs11/internal.h | 13 +- 6 files changed, 1229 insertions(+), 33 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index c503db84..56493553 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -324,6 +324,82 @@ static CK_RV FindValidAttributeType(CK_ATTRIBUTE* pTemplate, CK_ULONG ulCount, return CKR_OK; } +/* Sentinel for CheckPrivateLogin: callers that legitimately have no implicit + * class (C_CreateObject, C_UnwrapKey - both require CKA_CLASS in template per + * spec) pass this so the helper falls back to template inspection only. */ +#define WP11_NO_IMPLICIT_CLASS ((CK_OBJECT_CLASS)~(CK_OBJECT_CLASS)0) + +/* Per PKCS#11 v3.0 sec 5.1 Table 16: creating, generating, or unwrapping an + * object whose effective CKA_PRIVATE is CK_TRUE on a session that is not + * logged in as the user must return CKR_USER_NOT_LOGGED_IN. Empty-PIN tokens + * treat public sessions as logged-in (matches the find-time filter in + * WP11_Session_Find) so this gate must mirror that. + * + * `implicitClass` lets callers whose object class is implied by the API + * itself (C_GenerateKey always creates a CKO_SECRET_KEY, C_GenerateKeyPair + * produces CKO_PUBLIC_KEY/CKO_PRIVATE_KEY) supply that class even when the + * template omits CKA_CLASS. Without this, the spec-default + * CKA_PRIVATE=CK_TRUE would not be inferred for templates like + * `{CKA_VALUE_LEN, CKA_KEY_TYPE}` and a public session could silently + * generate a private key. An explicit CKA_PRIVATE attribute in the template + * still wins over the class-derived default. + * + * Gated by WOLFPKCS11_LEGACY_PRIVATE_FALSE_DEFAULT so the class-based + * inference matches the legacy default in SetAttributeDefaults. */ +static CK_RV CheckPrivateLogin(WP11_Session* session, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, + CK_OBJECT_CLASS implicitClass) +{ + CK_ATTRIBUTE* attr = NULL; + CK_BBOOL isPrivate = CK_FALSE; + WP11_Slot* slot; + + if (session == NULL) + return CKR_OK; + + if (pTemplate != NULL && ulCount > 0) + FindAttributeType(pTemplate, ulCount, CKA_PRIVATE, &attr); + + if (attr != NULL && attr->pValue != NULL && + attr->ulValueLen == sizeof(CK_BBOOL)) { + isPrivate = *(CK_BBOOL*)attr->pValue; + } + else { +#ifndef WOLFPKCS11_LEGACY_PRIVATE_FALSE_DEFAULT + CK_OBJECT_CLASS objClass = implicitClass; + CK_ATTRIBUTE* classAttr = NULL; + if (pTemplate != NULL && ulCount > 0) + FindAttributeType(pTemplate, ulCount, CKA_CLASS, &classAttr); + if (classAttr != NULL && classAttr->pValue != NULL && + classAttr->ulValueLen == sizeof(CK_OBJECT_CLASS)) { + objClass = *(CK_OBJECT_CLASS*)classAttr->pValue; + } + if (objClass == CKO_PRIVATE_KEY || objClass == CKO_SECRET_KEY) + isPrivate = CK_TRUE; +#endif + } + + if (!isPrivate) + return CKR_OK; + + slot = WP11_Session_GetSlot(session); + if (slot == NULL) + return CKR_OK; + /* Fresh / unprovisioned tokens (no user PIN initialized) have nothing to + * log in as, and the existing find-time private-object filter + * (src/internal.c) likewise bypasses on Has_Empty_Pin. Mirror both: + * an unset user PIN is treated as "no protection required" so callers + * that pre-date C_InitToken / C_InitPIN keep working. After the token + * is provisioned with a non-empty user PIN, the gate engages. */ + if (!WP11_Slot_IsTokenUserPinInitialized(slot)) + return CKR_OK; + if (WP11_Slot_Has_Empty_Pin(slot)) + return CKR_OK; + if (!WP11_Slot_IsLoggedIn(slot)) + return CKR_USER_NOT_LOGGED_IN; + return CKR_OK; +} + /** * Check the value and length are valid for the data type of the attributes in * the template. @@ -468,7 +544,14 @@ static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, CK_BBOOL falseVal = CK_FALSE; CK_BBOOL encrypt = CK_TRUE; CK_BBOOL recover = CK_TRUE; + /* PKCS#11 v2.40 sec 4.4.1: CKA_WRAP and CKA_UNWRAP default to CK_FALSE. + * The pre-fix default (CK_TRUE) silently made every secret/RSA key a + * wrapping key, violating least-privilege (Fenrir 2774). */ +#ifdef WOLFPKCS11_LEGACY_WRAP_TRUE_DEFAULT CK_BBOOL wrap = CK_TRUE; +#else + CK_BBOOL wrap = CK_FALSE; +#endif CK_BBOOL derive = (keyType == CKO_PUBLIC_KEY ? CK_FALSE : CK_TRUE); CK_BBOOL verify = CK_TRUE; CK_BBOOL sign = CK_FALSE; @@ -525,6 +608,15 @@ static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, /* Defaults if not set */ switch (keyType) { case CKO_PUBLIC_KEY: +#ifndef WOLFPKCS11_LEGACY_PRIVATE_FALSE_DEFAULT + /* Spec default: public keys are CKA_PRIVATE=FALSE (this is also + * what the historical implementation produced, but make it + * explicit so the default is stable when the macro toggles + * private/secret keys below). */ + if (ret == CKR_OK) + ret = SetIfNotFound(obj, CKA_PRIVATE, falseVal, pTemplate, + ulCount); +#endif if (ret == CKR_OK) ret = SetIfNotFound(obj, CKA_ENCRYPT, encrypt, pTemplate, ulCount); @@ -546,6 +638,13 @@ static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, #endif break; case CKO_SECRET_KEY: +#ifndef WOLFPKCS11_LEGACY_PRIVATE_FALSE_DEFAULT + /* PKCS#11 v2.40 sec 4.4.1: secret keys default to + * CKA_PRIVATE=CK_TRUE (Fenrir 2370). */ + if (ret == CKR_OK) + ret = SetIfNotFound(obj, CKA_PRIVATE, trueVal, pTemplate, + ulCount); +#endif #ifndef WOLFPKCS11_NSS if (ret == CKR_OK) ret = SetIfNotFound(obj, CKA_SENSITIVE, trueVal, pTemplate, @@ -561,13 +660,22 @@ static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, ret = SetIfNotFound(obj, CKA_DECRYPT, trueVal, pTemplate, ulCount); /* CKA_SIGN / CKA_VERIFY default false */ + /* CKA_WRAP / CKA_UNWRAP defaults follow the macro-gated `wrap' + * (CK_TRUE under the legacy macro, CK_FALSE per spec). */ if (ret == CKR_OK) - ret = SetIfNotFound(obj, CKA_WRAP, trueVal, pTemplate, ulCount); + ret = SetIfNotFound(obj, CKA_WRAP, wrap, pTemplate, ulCount); if (ret == CKR_OK) - ret = SetIfNotFound(obj, CKA_UNWRAP, trueVal, pTemplate, + ret = SetIfNotFound(obj, CKA_UNWRAP, wrap, pTemplate, ulCount); break; case CKO_PRIVATE_KEY: +#ifndef WOLFPKCS11_LEGACY_PRIVATE_FALSE_DEFAULT + /* PKCS#11 v2.40 sec 4.4.1: private keys default to + * CKA_PRIVATE=CK_TRUE (Fenrir 2370). */ + if (ret == CKR_OK) + ret = SetIfNotFound(obj, CKA_PRIVATE, trueVal, pTemplate, + ulCount); +#endif #ifndef WOLFPKCS11_NSS if (ret == CKR_OK) ret = SetIfNotFound(obj, CKA_SENSITIVE, trueVal, pTemplate, @@ -659,6 +767,13 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, if (!WP11_Session_IsRW(session) && WP11_Object_OnToken(obj)) return CKR_SESSION_READ_ONLY; + /* CKA_MODIFIABLE enforcement (Fenrir 1343) lives in C_SetAttributeValue, + * not this shared helper - C_CopyObject also funnels through here with + * newObject=FALSE after the new object has just inherited the source + * object's modifiable flag via WP11_Object_Copy, and copy semantics are + * governed by CKA_COPYABLE (already checked separately), not + * CKA_MODIFIABLE. */ + rv = CheckAttributes(pTemplate, ulCount, 1); if (rv != CKR_OK) return rv; @@ -1257,6 +1372,15 @@ CK_RV C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, } } + /* C_CreateObject requires CKA_CLASS in the template; no API-implied + * class to fall back on. */ + rv = CheckPrivateLogin(session, pTemplate, ulCount, + WP11_NO_IMPLICIT_CLASS); + if (rv != CKR_OK) { + WOLFPKCS11_LEAVE("C_CreateObject", rv); + return rv; + } + rv = CreateObject(session, pTemplate, ulCount, &object); if (rv != CKR_OK) { WOLFPKCS11_LEAVE("C_CreateObject", rv); @@ -1736,6 +1860,16 @@ CK_RV C_SetAttributeValue(CK_SESSION_HANDLE hSession, return rv; } + /* PKCS#11 v2.40 sec 4.4.1: an object with CKA_MODIFIABLE=CK_FALSE must + * reject all attribute changes (Fenrir 1343). Gate only the public + * entry point - C_CopyObject also uses SetAttributeValue with + * newObject=FALSE but is governed by CKA_COPYABLE, not CKA_MODIFIABLE. */ + if (!WP11_Object_IsModifiable(obj)) { + rv = CKR_ACTION_PROHIBITED; + WOLFPKCS11_LEAVE("C_SetAttributeValue", rv); + return rv; + } + rv = SetAttributeValue(session, obj, pTemplate, ulCount, CK_FALSE); WOLFPKCS11_LEAVE("C_SetAttributeValue", rv); return rv; @@ -6889,6 +7023,15 @@ CK_RV C_GenerateKey(CK_SESSION_HANDLE hSession, } } + /* C_GenerateKey always produces a secret key, even when the template + * omits CKA_CLASS - pass the implied class so the spec-default + * CKA_PRIVATE=CK_TRUE still gates login. */ + rv = CheckPrivateLogin(session, pTemplate, ulCount, CKO_SECRET_KEY); + if (rv != CKR_OK) { + WOLFPKCS11_LEAVE("C_GenerateKey", rv); + return rv; + } + switch (pMechanism->mechanism) { #ifndef NO_AES case CKM_AES_KEY_GEN: @@ -7332,6 +7475,22 @@ CK_RV C_GenerateKeyPair(CK_SESSION_HANDLE hSession, } } + /* Each template's API-implied class lets the helper detect the + * spec-default CKA_PRIVATE=CK_TRUE for the private arm even when + * the template omits CKA_CLASS. An explicit CKA_PRIVATE=TRUE in the + * public template is still flagged because the helper consults the + * template attribute first. */ + rv = CheckPrivateLogin(session, pPublicKeyTemplate, + ulPublicKeyAttributeCount, CKO_PUBLIC_KEY); + if (rv == CKR_OK) { + rv = CheckPrivateLogin(session, pPrivateKeyTemplate, + ulPrivateKeyAttributeCount, CKO_PRIVATE_KEY); + } + if (rv != CKR_OK) { + WOLFPKCS11_LEAVE("C_GenerateKeyPair", rv); + return rv; + } + switch (pMechanism->mechanism) { #if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) case CKM_RSA_PKCS_KEY_PAIR_GEN: @@ -7923,6 +8082,15 @@ CK_RV C_UnwrapKey(CK_SESSION_HANDLE hSession, } } + /* C_UnwrapKey requires CKA_CLASS in the unwrapped key template per + * spec; no API-implied class to fall back on. */ + rv = CheckPrivateLogin(session, pTemplate, ulAttributeCount, + WP11_NO_IMPLICIT_CLASS); + if (rv != CKR_OK) { + WOLFPKCS11_LEAVE("C_UnwrapKey", rv); + return rv; + } + *phKey = CK_INVALID_HANDLE; ret = WP11_Object_Find(session, hUnwrappingKey, &unwrappingKey); diff --git a/src/internal.c b/src/internal.c index 5171d41c..6a4c2aca 100644 --- a/src/internal.c +++ b/src/internal.c @@ -5907,7 +5907,14 @@ static int wp11_Token_Init(WP11_Token* token, const char* label) if (ret == 0) ret = Rng_New(&globalRandom, &globalLock, &token->rng); if (ret == 0) { - token->state = WP11_TOKEN_STATE_INITIALIZED; + /* Do not set token->state = WP11_TOKEN_STATE_INITIALIZED here. + * wp11_Slot_Init calls this on every fresh slot, and the state must + * stay UNKNOWN until either C_InitToken provisions the token + * (WP11_Slot_TokenReset) or wp11_Token_Load successfully reads a + * persisted token. Otherwise C_GetTokenInfo would advertise + * CKF_TOKEN_INITIALIZED on a token that was never provisioned, and + * applications using that flag to skip provisioning would proceed + * with an unconfigured token (Fenrir 3407). */ token->loginState = WP11_APP_STATE_RW_PUBLIC; token->nextObjId = 1; XMEMCPY(token->label, label, sizeof(token->label)); @@ -6573,6 +6580,12 @@ static int wp11_Slot_Load(WP11_Slot* slot, int id) */ static int wp11_Slot_Store(WP11_Slot* slot, int id) { + /* Only persist tokens that have been explicitly provisioned. Otherwise + * a C_Initialize / C_Finalize cycle on a never-init'd slot would write + * a placeholder token file, which a subsequent C_Initialize would then + * load and treat as INITIALIZED (Fenrir 3407 regression). */ + if (slot->token.state != WP11_TOKEN_STATE_INITIALIZED) + return 0; return wp11_Token_Store(&slot->token, id); } #endif @@ -6987,6 +7000,10 @@ int WP11_Slot_TokenReset(WP11_Slot* slot, char* pin, int pinLen, char* label) token = &slot->token; wp11_Token_Final(token); wp11_Token_Init(token, label); + /* C_InitToken is the canonical provisioning step. Mark the token as + * initialized here rather than in wp11_Token_Init so a fresh slot stays + * UNKNOWN until either init or load (Fenrir 3407). */ + token->state = WP11_TOKEN_STATE_INITIALIZED; WP11_Lock_UnlockRW(&slot->lock); /* Locking used in setting SO PIN. */ @@ -7015,15 +7032,21 @@ int WP11_Slot_CheckSOPin(WP11_Slot* slot, char* pin, int pinLen) WP11_Lock_LockRO(&slot->lock); token = &slot->token; - if (token->state != WP11_TOKEN_STATE_INITIALIZED) - ret = PIN_NOT_SET_E; /* When the SO PIN has not been set, reject any PIN check; otherwise an * empty PIN would constant-compare equal to the unset zero-length * stored PIN and grant SO authentication. NSS's PK11_InitPin bootstraps * a fresh database by calling C_Login(CKU_SO, "", 0) before any SO PIN * exists and relies on that probe succeeding, so for NSS builds the * empty-PIN path is left intact and only non-empty PINs are rejected. - */ + * + * The redundant state-vs-INITIALIZED check that used to sit above this + * block was harmless when wp11_Token_Init unconditionally pre-marked + * fresh slots INITIALIZED, but now (Fenrir 3407) a fresh slot stays + * UNKNOWN until provisioning - and the NSS bootstrap needs the empty-PIN + * probe to succeed against an UNKNOWN-state token. The SO_PIN_SET flag + * is the authoritative signal here: it is set during the same + * WP11_Slot_TokenReset call that flips state to INITIALIZED, so any + * scenario the old state check would have caught is also caught here. */ #ifdef WOLFPKCS11_NSS if (!(token->tokenFlags & WP11_TOKEN_FLAG_SO_PIN_SET) && pinLen > 0) ret = PIN_NOT_SET_E; @@ -7069,8 +7092,10 @@ int WP11_Slot_CheckUserPin(WP11_Slot* slot, char* pin, int pinLen) WP11_Lock_LockRO(&slot->lock); token = &slot->token; - if (token->state != WP11_TOKEN_STATE_INITIALIZED) - ret = PIN_NOT_SET_E; + /* USER_PIN_SET is the authoritative signal; the redundant + * state-vs-INITIALIZED check that used to sit above was harmless under + * the old eager-INITIALIZED semantics but now (Fenrir 3407) would + * spuriously reject probes on UNKNOWN-state fresh tokens. */ if (!(token->tokenFlags & WP11_TOKEN_FLAG_USER_PIN_SET)) ret = PIN_NOT_SET_E; @@ -8993,6 +9018,20 @@ int WP11_Object_IsDestroyable(WP11_Object* object) return (object->opFlag & WP11_FLAG_NOT_DESTROYABLE) == 0; } +/** + * Check whether the object is modifiable. + * + * PKCS#11 v2.40 sec 4.4.1: CKA_MODIFIABLE defaults to CK_TRUE; clear flag + * means modifiable. + * + * @param object [in] Object object. + * @return 1 when modifiable, 0 when not. + */ +int WP11_Object_IsModifiable(WP11_Object* object) +{ + return (object->opFlag & WP11_FLAG_NOT_MODIFIABLE) == 0; +} + #if !defined(NO_RSA) || defined(HAVE_ECC) /** * Set the multi-precision integer from the data. @@ -10171,38 +10210,50 @@ static int RsaObject_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, ret = GetMPIData(&object->data.rsaKey->n, data, len); break; case CKA_PRIVATE_EXPONENT: - if (noPriv) + if (noPriv) { *len = CK_UNAVAILABLE_INFORMATION; + ret = CKR_ATTRIBUTE_SENSITIVE; + } else ret = GetMPIData(&object->data.rsaKey->d, data, len); break; case CKA_PRIME_1: - if (noPriv) + if (noPriv) { *len = CK_UNAVAILABLE_INFORMATION; + ret = CKR_ATTRIBUTE_SENSITIVE; + } else ret = GetMPIData(&object->data.rsaKey->p, data, len); break; case CKA_PRIME_2: - if (noPriv) + if (noPriv) { *len = CK_UNAVAILABLE_INFORMATION; + ret = CKR_ATTRIBUTE_SENSITIVE; + } else ret = GetMPIData(&object->data.rsaKey->q, data, len); break; case CKA_EXPONENT_1: - if (noPriv) + if (noPriv) { *len = CK_UNAVAILABLE_INFORMATION; + ret = CKR_ATTRIBUTE_SENSITIVE; + } else ret = GetMPIData(&object->data.rsaKey->dP, data, len); break; case CKA_EXPONENT_2: - if (noPriv) + if (noPriv) { *len = CK_UNAVAILABLE_INFORMATION; + ret = CKR_ATTRIBUTE_SENSITIVE; + } else ret = GetMPIData(&object->data.rsaKey->dQ, data, len); break; case CKA_COEFFICIENT: - if (noPriv) + if (noPriv) { *len = CK_UNAVAILABLE_INFORMATION; + ret = CKR_ATTRIBUTE_SENSITIVE; + } else ret = GetMPIData(&object->data.rsaKey->u, data, len); break; @@ -10341,8 +10392,10 @@ static int EcObject_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, ret = GetEcParams(object->data.ecKey, data, len); break; case CKA_VALUE: - if (noPriv) + if (noPriv) { *len = CK_UNAVAILABLE_INFORMATION; + ret = CKR_ATTRIBUTE_SENSITIVE; + } else #if defined(HAVE_FIPS_VERSION) && (HAVE_FIPS_VERSION <= 5) ret = GetMPIData(&object->data.ecKey->k, data, len); @@ -10499,8 +10552,10 @@ static int MldsaObject_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, break; case CKA_VALUE: if (object->objClass == CKO_PRIVATE_KEY) { - if (noPriv) + if (noPriv) { *len = CK_UNAVAILABLE_INFORMATION; + ret = CKR_ATTRIBUTE_SENSITIVE; + } else ret = GetMldsaPrivateKey(object->data.mldsaKey, data, len); } @@ -10555,8 +10610,10 @@ static int DhObject_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, ret = GetData(object->data.dhKey->key, object->data.dhKey->len, data, len); } - else + else { *len = CK_UNAVAILABLE_INFORMATION; + ret = CKR_ATTRIBUTE_SENSITIVE; + } break; case CKA_WRAP_TEMPLATE: case CKA_UNWRAP_TEMPLATE: @@ -10682,8 +10739,10 @@ static int MlKemObject_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, break; case CKA_VALUE: if (object->objClass == CKO_PRIVATE_KEY) { - if (noPriv) + if (noPriv) { *len = CK_UNAVAILABLE_INFORMATION; + ret = CKR_ATTRIBUTE_SENSITIVE; + } else ret = GetMlKemPrivateKey(object->data.mlKemKey, data, len); } @@ -10895,8 +10954,10 @@ int WP11_Object_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, len); break; case CKA_MODIFIABLE: - ret = GetOpFlagBool(object->opFlag, WP11_FLAG_MODIFIABLE, data, - len); + /* PKCS#11 default is CK_TRUE; inverted flag matches the + * NOT_COPYABLE/NOT_DESTROYABLE pattern. */ + ret = GetBool( + !(object->opFlag & WP11_FLAG_NOT_MODIFIABLE), data, len); break; case CKA_ALWAYS_SENSITIVE: ret = GetOpFlagBool(object->opFlag, WP11_FLAG_ALWAYS_SENSITIVE, @@ -11309,8 +11370,10 @@ int WP11_Object_SetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, *(CK_BBOOL*)data); break; case CKA_MODIFIABLE: - WP11_Object_SetOpFlag(object, WP11_FLAG_MODIFIABLE, - *(CK_BBOOL*)data); + /* Inverted: store the NOT_MODIFIABLE bit when caller wants + * CKA_MODIFIABLE=CK_FALSE. */ + WP11_Object_SetOpFlag(object, WP11_FLAG_NOT_MODIFIABLE, + !(*(CK_BBOOL*)data)); break; case CKA_ALWAYS_SENSITIVE: WP11_Object_SetOpFlag(object, WP11_FLAG_ALWAYS_SENSITIVE, diff --git a/tests/pkcs11_compliance_test.c b/tests/pkcs11_compliance_test.c index 90f57ff0..360a066d 100644 --- a/tests/pkcs11_compliance_test.c +++ b/tests/pkcs11_compliance_test.c @@ -428,12 +428,19 @@ static CK_RV make_aes_key(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* key) static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; static CK_KEY_TYPE aesKeyType = CKK_AES; static CK_BBOOL ckTrue = CK_TRUE; + static CK_BBOOL ckFalse = CK_FALSE; CK_ATTRIBUTE tpl[] = { { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, { CKA_VALUE, aes_128_key, 16 }, + /* Tests open public sessions and never C_Login. Explicit + * CKA_PRIVATE=FALSE keeps the gate from finding 3144 from + * rejecting these helper keys; finding 2370 (default flip) + * means CKO_SECRET_KEY would otherwise default to CKA_PRIVATE=TRUE + * and require login. */ + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, }; return funcList->C_CreateObject(session, tpl, sizeof(tpl) / sizeof(tpl[0]), key); @@ -689,6 +696,898 @@ static void test_1342_derive_key_rw_session(void) #endif /* !WOLFPKCS11_NSS */ } +/* Finding 3637: SecretObject_GetAttr must return CKR_ATTRIBUTE_SENSITIVE when + * CKA_VALUE is requested on a CKK_GENERIC_SECRET object that has + * CKA_SENSITIVE=TRUE, even when CKA_EXTRACTABLE=TRUE. The existing + * test_attributes_secret coverage always pairs SENSITIVE=FALSE or pairs + * SENSITIVE=TRUE with EXTRACTABLE=FALSE, so a mutation that drops the + * SENSITIVE arm of the noPriv disjunction would not be caught. */ +static void test_3637_generic_secret_sensitive_attr(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; + CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; + CK_BBOOL ckTrue = CK_TRUE; + CK_BBOOL ckFalse = CK_FALSE; + CK_ATTRIBUTE createTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + }; + CK_BYTE buf[32]; + CK_ATTRIBUTE getTmpl[] = { + { CKA_VALUE, buf, sizeof(buf) }, + }; + + printf("\n--- 3637: generic-secret CKA_VALUE rejects when SENSITIVE ---\n"); + + rv = lib_init(); + CHECK_RV(rv, "C_Initialize", CKR_OK); + if (rv != CKR_OK) return; + + rv = open_session(CKF_SERIAL_SESSION | CKF_RW_SESSION, &session); + CHECK_RV(rv, "open R/W session", CKR_OK); + if (rv != CKR_OK) goto out; + + rv = funcList->C_CreateObject(session, createTmpl, + sizeof(createTmpl) / sizeof(createTmpl[0]), + &key); + CHECK_RV(rv, "C_CreateObject(generic_secret, SENSITIVE=TRUE, " + "EXTRACTABLE=TRUE)", CKR_OK); + if (rv != CKR_OK) goto out; + + rv = funcList->C_GetAttributeValue(session, key, getTmpl, 1); + CHECK_RV(rv, "C_GetAttributeValue(CKA_VALUE) on sensitive generic secret", + CKR_ATTRIBUTE_SENSITIVE); + +out: + if (key != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, key); + if (session != 0) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} + +/* Finding 2776: The four asymmetric *Object_GetAttr functions + * (RsaObject_GetAttr, EcObject_GetAttr, DhObject_GetAttr, + * MldsaObject_GetAttr) historically only set *len = CK_UNAVAILABLE_INFORMATION + * when noPriv was true and returned 0, leaving C_GetAttributeValue to report + * CKR_OK. Per PKCS#11, asking for a sensitive attribute must return + * CKR_ATTRIBUTE_SENSITIVE, matching the symmetric path in + * SecretObject_GetAttr. The fix also closes the mutation gap noted in the + * finding: a mutant that drops the SENSITIVE arm of the noPriv disjunction + * would otherwise survive every existing asymmetric-key test. */ +static void test_2776_asymmetric_sensitive_attr(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = 0; +#ifndef NO_RSA + CK_OBJECT_HANDLE rsaKey = CK_INVALID_HANDLE; +#endif +#ifdef HAVE_ECC + CK_OBJECT_HANDLE ecKey = CK_INVALID_HANDLE; +#endif +#ifndef NO_DH + CK_OBJECT_HANDLE dhKey = CK_INVALID_HANDLE; +#endif + CK_OBJECT_CLASS privKeyClass = CKO_PRIVATE_KEY; + CK_BBOOL ckTrue = CK_TRUE; + CK_BBOOL ckFalse = CK_FALSE; + CK_BYTE buf[512]; + + (void)privKeyClass; + (void)ckTrue; + (void)ckFalse; + (void)buf; + + printf("\n--- 2776: asymmetric CKA_VALUE/PRIVATE_EXPONENT sensitive ---\n"); + + rv = lib_init(); + CHECK_RV(rv, "C_Initialize", CKR_OK); + if (rv != CKR_OK) return; + + rv = open_session(CKF_SERIAL_SESSION | CKF_RW_SESSION, &session); + CHECK_RV(rv, "open R/W session", CKR_OK); + if (rv != CKR_OK) goto out; + +#ifndef NO_RSA + { + CK_KEY_TYPE rsaKeyType = CKK_RSA; + CK_ATTRIBUTE tpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, + { CKA_PRIVATE_EXPONENT, rsa_2048_priv_exp, sizeof(rsa_2048_priv_exp) }, + { CKA_PRIME_1, rsa_2048_p, sizeof(rsa_2048_p) }, + { CKA_PRIME_2, rsa_2048_q, sizeof(rsa_2048_q) }, + { CKA_EXPONENT_1, rsa_2048_dP, sizeof(rsa_2048_dP) }, + { CKA_EXPONENT_2, rsa_2048_dQ, sizeof(rsa_2048_dQ) }, + { CKA_COEFFICIENT, rsa_2048_u, sizeof(rsa_2048_u) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + CK_ATTRIBUTE getTmpl[] = { + { CKA_PRIVATE_EXPONENT, buf, sizeof(buf) }, + }; + rv = funcList->C_CreateObject(session, tpl, + sizeof(tpl) / sizeof(tpl[0]), &rsaKey); + CHECK_RV(rv, "C_CreateObject(RSA priv, SENSITIVE=TRUE)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_GetAttributeValue(session, rsaKey, getTmpl, 1); + CHECK_RV(rv, "C_GetAttributeValue(CKA_PRIVATE_EXPONENT) on " + "sensitive RSA key", CKR_ATTRIBUTE_SENSITIVE); + } + } +#endif + +#ifdef HAVE_ECC + { + CK_KEY_TYPE eccKeyType = CKK_EC; + CK_ATTRIBUTE tpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &eccKeyType, sizeof(eccKeyType) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, + }; + CK_ATTRIBUTE getTmpl[] = { + { CKA_VALUE, buf, sizeof(buf) }, + }; + rv = funcList->C_CreateObject(session, tpl, + sizeof(tpl) / sizeof(tpl[0]), &ecKey); + CHECK_RV(rv, "C_CreateObject(EC priv, SENSITIVE=TRUE)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_GetAttributeValue(session, ecKey, getTmpl, 1); + CHECK_RV(rv, "C_GetAttributeValue(CKA_VALUE) on sensitive EC key", + CKR_ATTRIBUTE_SENSITIVE); + } + } +#endif + +#ifndef NO_DH + { + CK_KEY_TYPE dhKeyType = CKK_DH; + CK_ATTRIBUTE tpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &dhKeyType, sizeof(dhKeyType) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIME, dh_ffdhe2048_p, sizeof(dh_ffdhe2048_p) }, + { CKA_BASE, dh_ffdhe2048_g, sizeof(dh_ffdhe2048_g) }, + { CKA_VALUE, dh_2048_priv, sizeof(dh_2048_priv) }, + }; + CK_ATTRIBUTE getTmpl[] = { + { CKA_VALUE, buf, sizeof(buf) }, + }; + rv = funcList->C_CreateObject(session, tpl, + sizeof(tpl) / sizeof(tpl[0]), &dhKey); + CHECK_RV(rv, "C_CreateObject(DH priv, SENSITIVE=TRUE)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_GetAttributeValue(session, dhKey, getTmpl, 1); + CHECK_RV(rv, "C_GetAttributeValue(CKA_VALUE) on sensitive DH key", + CKR_ATTRIBUTE_SENSITIVE); + } + } +#endif + +out: +#ifndef NO_RSA + if (rsaKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, rsaKey); +#endif +#ifdef HAVE_ECC + if (ecKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, ecKey); +#endif +#ifndef NO_DH + if (dhKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, dhKey); +#endif + if (session != 0) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} + +/* Finding 3407: C_GetTokenInfo must not advertise CKF_TOKEN_INITIALIZED on a + * fresh slot. wp11_Slot_Init used to call wp11_Token_Init which unconditionally + * set token state to INITIALIZED, so an application keying off + * CKF_TOKEN_INITIALIZED to decide whether provisioning was needed would skip + * C_InitToken on a token that had never been provisioned. After C_InitToken + * the flag must appear. */ +static void test_3407_fresh_slot_uninitialized(void) +{ + CK_RV rv; + CK_SLOT_ID slot; + CK_SLOT_ID* slotList = NULL; + CK_ULONG slotCount = 0; + CK_TOKEN_INFO info; + /* PKCS#11 32-byte space-padded label, no NUL terminator. */ + CK_UTF8CHAR label[32]; + CK_BYTE soPin[] = "password123456"; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, "compliance-3407", 15); + + printf("\n--- 3407: fresh slot reports !CKF_TOKEN_INITIALIZED ---\n"); + + /* Wipe any persisted token from previous tests in this session so the + * library starts genuinely fresh. */ + cleanup_store(); + + rv = lib_init(); + CHECK_RV(rv, "C_Initialize", CKR_OK); + if (rv != CKR_OK) return; + + rv = funcList->C_GetSlotList(CK_TRUE, NULL, &slotCount); + CHECK_RV(rv, "C_GetSlotList(NULL) count", CKR_OK); + if (rv != CKR_OK || slotCount == 0) goto out; + + slotList = (CK_SLOT_ID*)XMALLOC(slotCount * sizeof(CK_SLOT_ID), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (slotList == NULL) goto out; + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + CHECK_RV(rv, "C_GetSlotList(buf)", CKR_OK); + if (rv != CKR_OK) goto out; + slot = slotList[0]; + + XMEMSET(&info, 0, sizeof(info)); + rv = funcList->C_GetTokenInfo(slot, &info); + CHECK_RV(rv, "C_GetTokenInfo on fresh slot", CKR_OK); + if (rv == CKR_OK) { + CHECK_COND_MSG(!(info.flags & CKF_TOKEN_INITIALIZED), + "CKF_TOKEN_INITIALIZED clear on fresh slot"); + } + + rv = funcList->C_InitToken(slot, soPin, sizeof(soPin) - 1, label); + CHECK_RV(rv, "C_InitToken", CKR_OK); + if (rv == CKR_OK) { + XMEMSET(&info, 0, sizeof(info)); + rv = funcList->C_GetTokenInfo(slot, &info); + CHECK_RV(rv, "C_GetTokenInfo after C_InitToken", CKR_OK); + if (rv == CKR_OK) { + CHECK_COND_MSG(info.flags & CKF_TOKEN_INITIALIZED, + "CKF_TOKEN_INITIALIZED set after C_InitToken"); + } + } + +out: + if (slotList != NULL) + XFREE(slotList, NULL, DYNAMIC_TYPE_TMP_BUFFER); + funcList->C_Finalize(NULL); + cleanup_store(); +} + +/* Finding 1343: C_SetAttributeValue must consult CKA_MODIFIABLE on the + * target object. If CKA_MODIFIABLE=CK_FALSE, any attempt to change an + * attribute must return CKR_ACTION_PROHIBITED. PKCS#11 default for + * CKA_MODIFIABLE is CK_TRUE; the positive arm of the test confirms a key + * created without the flag is still modifiable. */ +static void test_1343_modifiable_enforced(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE lockedKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE plainKey = CK_INVALID_HANDLE; + CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; + CK_KEY_TYPE aesKeyType = CKK_AES; + CK_BBOOL ckTrue = CK_TRUE; + CK_BBOOL ckFalse = CK_FALSE; + CK_ATTRIBUTE lockedTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, + { CKA_MODIFIABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE plainTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + }; + CK_UTF8CHAR newLabel[] = "1343-relabel"; + CK_ATTRIBUTE setLabelTmpl[] = { + { CKA_LABEL, newLabel, sizeof(newLabel) - 1 }, + }; + CK_BBOOL getMod = CK_FALSE; + CK_ATTRIBUTE getTmpl[] = { + { CKA_MODIFIABLE, &getMod, sizeof(getMod) }, + }; + + printf("\n--- 1343: CKA_MODIFIABLE=FALSE blocks SetAttributeValue ---\n"); + + rv = lib_init(); + CHECK_RV(rv, "C_Initialize", CKR_OK); + if (rv != CKR_OK) return; + + rv = open_session(CKF_SERIAL_SESSION | CKF_RW_SESSION, &session); + CHECK_RV(rv, "open R/W session", CKR_OK); + if (rv != CKR_OK) goto out; + + rv = funcList->C_CreateObject(session, lockedTmpl, + sizeof(lockedTmpl) / sizeof(lockedTmpl[0]), + &lockedKey); + CHECK_RV(rv, "C_CreateObject(MODIFIABLE=FALSE)", CKR_OK); + if (rv != CKR_OK) goto out; + + rv = funcList->C_GetAttributeValue(session, lockedKey, getTmpl, 1); + CHECK_RV(rv, "C_GetAttributeValue(CKA_MODIFIABLE) reads FALSE", CKR_OK); + if (rv == CKR_OK) { + CHECK_COND_MSG(getMod == CK_FALSE, + "CKA_MODIFIABLE stored as FALSE"); + } + + rv = funcList->C_SetAttributeValue(session, lockedKey, setLabelTmpl, 1); + CHECK_RV(rv, "C_SetAttributeValue on MODIFIABLE=FALSE object", + CKR_ACTION_PROHIBITED); + + /* Default CKA_MODIFIABLE must be CK_TRUE so unattributed objects stay + * mutable per spec. */ + rv = funcList->C_CreateObject(session, plainTmpl, + sizeof(plainTmpl) / sizeof(plainTmpl[0]), + &plainKey); + CHECK_RV(rv, "C_CreateObject(no CKA_MODIFIABLE)", CKR_OK); + if (rv == CKR_OK) { + getMod = CK_FALSE; + rv = funcList->C_GetAttributeValue(session, plainKey, getTmpl, 1); + CHECK_RV(rv, "C_GetAttributeValue(CKA_MODIFIABLE) default", CKR_OK); + if (rv == CKR_OK) { + CHECK_COND_MSG(getMod == CK_TRUE, + "CKA_MODIFIABLE default is CK_TRUE"); + } + rv = funcList->C_SetAttributeValue(session, plainKey, + setLabelTmpl, 1); + CHECK_RV(rv, "C_SetAttributeValue on default object", CKR_OK); + } + + /* CKA_MODIFIABLE=FALSE may be set at creation time but must not be + * settable after the fact via C_SetAttributeValue - the gate is already + * the CKA_MODIFIABLE check, regardless of what the new value would + * be. */ + { + CK_ATTRIBUTE relockTmpl[] = { + { CKA_MODIFIABLE, &ckFalse, sizeof(ckFalse) }, + }; + rv = funcList->C_SetAttributeValue(session, lockedKey, relockTmpl, 1); + CHECK_RV(rv, "C_SetAttributeValue rejects re-locking a locked object", + CKR_ACTION_PROHIBITED); + (void)ckTrue; + } + +out: + if (lockedKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, lockedKey); + if (plainKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, plainKey); + if (session != 0) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} + +/* Finding 3144: PKCS#11 v3.0 sec 5.1 Table 16 requires the four object- + * creation entry points (C_CreateObject, C_GenerateKey, C_GenerateKeyPair, + * C_UnwrapKey) to return CKR_USER_NOT_LOGGED_IN when the template requests + * CKA_PRIVATE=TRUE on a session that is not logged in as the user. The token + * is initialised with a non-empty user PIN so the empty-PIN bypass does not + * mask the check. */ +static void test_3144_private_create_requires_login(void) +{ + CK_RV rv; + CK_SLOT_ID slot; + CK_SLOT_ID* slotList = NULL; + CK_ULONG slotCount = 0; + CK_SESSION_HANDLE soSession = 0; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pubKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE privKey = CK_INVALID_HANDLE; + CK_UTF8CHAR label[32]; + CK_BYTE soPin[] = "password123456"; + CK_BYTE userPin[] = "someUserPin"; + CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; + CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; + CK_BBOOL ckTrue = CK_TRUE; + CK_ATTRIBUTE createPrivTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, + }; + CK_ULONG aesKeyLen = 16; + CK_KEY_TYPE aesKeyType = CKK_AES; + CK_ATTRIBUTE genPrivTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE_LEN, &aesKeyLen, sizeof(aesKeyLen) }, + }; + /* aes_keygen_attrs_test style: implicit class (C_GenerateKey always + * creates a secret key), no explicit CKA_CLASS or CKA_PRIVATE. The + * implicit class + spec-default CKA_PRIVATE=CK_TRUE must still trigger + * the login gate. */ + CK_ATTRIBUTE genImplicitTmpl[] = { + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_VALUE_LEN, &aesKeyLen, sizeof(aesKeyLen) }, + }; + CK_MECHANISM aesMech; + CK_MECHANISM rsaKpMech; + CK_ATTRIBUTE rsaPubTmpl[] = { + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + }; + CK_ATTRIBUTE rsaPrivTmpl[] = { + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + }; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, "compliance-3144", 15); + + printf("\n--- 3144: private object creation needs login ---\n"); + + cleanup_store(); + rv = lib_init(); + CHECK_RV(rv, "C_Initialize", CKR_OK); + if (rv != CKR_OK) return; + + rv = funcList->C_GetSlotList(CK_TRUE, NULL, &slotCount); + if (rv != CKR_OK || slotCount == 0) goto out; + slotList = (CK_SLOT_ID*)XMALLOC(slotCount * sizeof(CK_SLOT_ID), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (slotList == NULL) goto out; + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv != CKR_OK) goto out; + slot = slotList[0]; + + /* Provision a real (non-empty) user PIN so the empty-PIN bypass does + * not cover for the missing login check. */ + rv = funcList->C_InitToken(slot, soPin, sizeof(soPin) - 1, label); + CHECK_RV(rv, "C_InitToken", CKR_OK); + if (rv != CKR_OK) goto out; + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &soSession); + CHECK_RV(rv, "C_OpenSession(SO)", CKR_OK); + if (rv != CKR_OK) goto out; + rv = funcList->C_Login(soSession, CKU_SO, soPin, sizeof(soPin) - 1); + CHECK_RV(rv, "C_Login(SO)", CKR_OK); + if (rv != CKR_OK) goto out; + rv = funcList->C_InitPIN(soSession, userPin, sizeof(userPin) - 1); + CHECK_RV(rv, "C_InitPIN", CKR_OK); + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + soSession = 0; + + /* Open a fresh public (not logged-in) session and try each of the four + * entry points with CKA_PRIVATE=TRUE. */ + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + CHECK_RV(rv, "C_OpenSession(public)", CKR_OK); + if (rv != CKR_OK) goto out; + + rv = funcList->C_CreateObject(session, createPrivTmpl, + sizeof(createPrivTmpl) / + sizeof(createPrivTmpl[0]), + &obj); + CHECK_RV(rv, "C_CreateObject(CKA_PRIVATE=TRUE) public session", + CKR_USER_NOT_LOGGED_IN); + if (rv == CKR_OK && obj != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, obj); + + XMEMSET(&aesMech, 0, sizeof(aesMech)); + aesMech.mechanism = CKM_AES_KEY_GEN; + obj = CK_INVALID_HANDLE; + rv = funcList->C_GenerateKey(session, &aesMech, genPrivTmpl, + sizeof(genPrivTmpl) / sizeof(genPrivTmpl[0]), + &obj); + if (rv == CKR_MECHANISM_INVALID) { + printf("SKIP: CKM_AES_KEY_GEN not enabled\n"); + } else { + CHECK_RV(rv, "C_GenerateKey(CKA_PRIVATE=TRUE) public session", + CKR_USER_NOT_LOGGED_IN); + if (rv == CKR_OK && obj != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, obj); + + /* Implicit class via API: template lacks CKA_CLASS *and* + * CKA_PRIVATE. C_GenerateKey always creates a secret key, so the + * spec-default CKA_PRIVATE=CK_TRUE must still gate login. */ + obj = CK_INVALID_HANDLE; + rv = funcList->C_GenerateKey(session, &aesMech, genImplicitTmpl, + sizeof(genImplicitTmpl) / + sizeof(genImplicitTmpl[0]), + &obj); + CHECK_RV(rv, "C_GenerateKey(no CKA_CLASS/CKA_PRIVATE) public session", + CKR_USER_NOT_LOGGED_IN); + if (rv == CKR_OK && obj != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, obj); + } + + XMEMSET(&rsaKpMech, 0, sizeof(rsaKpMech)); + rsaKpMech.mechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; + rv = funcList->C_GenerateKeyPair(session, &rsaKpMech, + rsaPubTmpl, + sizeof(rsaPubTmpl)/sizeof(rsaPubTmpl[0]), + rsaPrivTmpl, + sizeof(rsaPrivTmpl)/sizeof(rsaPrivTmpl[0]), + &pubKey, &privKey); + if (rv == CKR_MECHANISM_INVALID) { + printf("SKIP: CKM_RSA_PKCS_KEY_PAIR_GEN not enabled\n"); + } else { + CHECK_RV(rv, + "C_GenerateKeyPair(private template CKA_PRIVATE=TRUE) " + "public session", CKR_USER_NOT_LOGGED_IN); + if (rv == CKR_OK) { + if (pubKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pubKey); + if (privKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, privKey); + } + } + + /* Sanity sibling: after C_Login as user, the same C_CreateObject must + * succeed. Confirms the gate is the login state, not something else. */ + rv = funcList->C_Login(session, CKU_USER, userPin, sizeof(userPin) - 1); + CHECK_RV(rv, "C_Login(user)", CKR_OK); + if (rv == CKR_OK) { + obj = CK_INVALID_HANDLE; + rv = funcList->C_CreateObject(session, createPrivTmpl, + sizeof(createPrivTmpl) / + sizeof(createPrivTmpl[0]), + &obj); + CHECK_RV(rv, "C_CreateObject(CKA_PRIVATE=TRUE) after login", CKR_OK); + if (rv == CKR_OK && obj != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, obj); + funcList->C_Logout(session); + } + +out: + if (slotList != NULL) + XFREE(slotList, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (session != 0) + funcList->C_CloseSession(session); + if (soSession != 0) + funcList->C_CloseSession(soSession); + funcList->C_Finalize(NULL); + cleanup_store(); +} + +/* Finding 2370: When a template omits CKA_PRIVATE, PKCS#11 v2.40 sec 4.4.1 + * requires the default to be CK_TRUE for private and secret keys, CK_FALSE + * for public keys. wolfPKCS11 used to default unconditionally to CK_FALSE + * because SetAttributeDefaults never set CKA_PRIVATE. The fix gates the + * spec-correct default behind WOLFPKCS11_LEGACY_PRIVATE_FALSE_DEFAULT; + * unless that macro is defined, the test asserts CK_TRUE. */ +static void test_2370_private_default(void) +{ + CK_RV rv; + CK_SLOT_ID slot; + CK_SLOT_ID* slotList = NULL; + CK_ULONG slotCount = 0; + CK_SESSION_HANDLE soSession = 0; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE secretKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pubKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE privKey = CK_INVALID_HANDLE; + CK_BYTE soPin[] = "password123456"; + CK_BYTE userPin[] = "someUserPin"; + CK_UTF8CHAR label[32]; + CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; + CK_OBJECT_CLASS pubKeyClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE aesKeyType = CKK_AES; + CK_ULONG aesKeyLen = 16; + CK_MECHANISM aesMech; + CK_ATTRIBUTE secretTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_VALUE_LEN, &aesKeyLen, sizeof(aesKeyLen) }, + }; + CK_BBOOL gotPrivate = CK_FALSE; + CK_ATTRIBUTE getPrivTmpl[] = { + { CKA_PRIVATE, &gotPrivate, sizeof(gotPrivate) }, + }; + /* CKO_PUBLIC_KEY default is CKA_PRIVATE=CK_FALSE per spec. */ + CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; + CK_BBOOL ckTrue = CK_TRUE; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_CLASS, &pubKeyClass, sizeof(pubKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, + }; + + (void)pubKey; + (void)privKey; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, "compliance-2370", 15); + + printf("\n--- 2370: CKA_PRIVATE default is CK_TRUE for private/secret " + "keys ---\n"); + + cleanup_store(); + rv = lib_init(); + CHECK_RV(rv, "C_Initialize", CKR_OK); + if (rv != CKR_OK) return; + + rv = funcList->C_GetSlotList(CK_TRUE, NULL, &slotCount); + if (rv != CKR_OK || slotCount == 0) goto out; + slotList = (CK_SLOT_ID*)XMALLOC(slotCount * sizeof(CK_SLOT_ID), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (slotList == NULL) goto out; + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv != CKR_OK) goto out; + slot = slotList[0]; + + /* Provision token + user PIN so we can log in as user before creating + * a key that would (now) default to CKA_PRIVATE=TRUE. */ + rv = funcList->C_InitToken(slot, soPin, sizeof(soPin) - 1, label); + CHECK_RV(rv, "C_InitToken", CKR_OK); + if (rv != CKR_OK) goto out; + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &soSession); + CHECK_RV(rv, "C_OpenSession(SO)", CKR_OK); + if (rv != CKR_OK) goto out; + rv = funcList->C_Login(soSession, CKU_SO, soPin, sizeof(soPin) - 1); + CHECK_RV(rv, "C_Login(SO)", CKR_OK); + if (rv != CKR_OK) goto out; + rv = funcList->C_InitPIN(soSession, userPin, sizeof(userPin) - 1); + CHECK_RV(rv, "C_InitPIN", CKR_OK); + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + soSession = 0; + + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + CHECK_RV(rv, "C_OpenSession(user)", CKR_OK); + if (rv != CKR_OK) goto out; + rv = funcList->C_Login(session, CKU_USER, userPin, sizeof(userPin) - 1); + CHECK_RV(rv, "C_Login(USER)", CKR_OK); + if (rv != CKR_OK) goto out; + + /* Secret key without explicit CKA_PRIVATE in template. */ + XMEMSET(&aesMech, 0, sizeof(aesMech)); + aesMech.mechanism = CKM_AES_KEY_GEN; + rv = funcList->C_GenerateKey(session, &aesMech, secretTmpl, + sizeof(secretTmpl) / sizeof(secretTmpl[0]), + &secretKey); + if (rv == CKR_MECHANISM_INVALID) { + printf("SKIP: CKM_AES_KEY_GEN not enabled\n"); + } else { + CHECK_RV(rv, "C_GenerateKey(no CKA_PRIVATE) as user", CKR_OK); + if (rv == CKR_OK) { + gotPrivate = CK_FALSE; + rv = funcList->C_GetAttributeValue(session, secretKey, + getPrivTmpl, 1); + CHECK_RV(rv, "C_GetAttributeValue(CKA_PRIVATE) on secret key", + CKR_OK); + if (rv == CKR_OK) { + CHECK_COND_MSG(gotPrivate == CK_TRUE, + "secret-key default CKA_PRIVATE is CK_TRUE"); + } + } + } + + /* Public-key default must remain CK_FALSE so the public arm of a key + * pair stays accessible from public sessions. Use C_CreateObject so we + * don't need a key-pair-generation mechanism enabled. */ + rv = funcList->C_CreateObject(session, pubTmpl, + sizeof(pubTmpl) / sizeof(pubTmpl[0]), + &pubKey); + CHECK_RV(rv, "C_CreateObject(public, no CKA_PRIVATE)", CKR_OK); + if (rv == CKR_OK) { + gotPrivate = CK_TRUE; + rv = funcList->C_GetAttributeValue(session, pubKey, getPrivTmpl, 1); + CHECK_RV(rv, "C_GetAttributeValue(CKA_PRIVATE) on public key", + CKR_OK); + if (rv == CKR_OK) { + CHECK_COND_MSG(gotPrivate == CK_FALSE, + "public-key default CKA_PRIVATE is CK_FALSE"); + } + } + +out: + if (slotList != NULL) + XFREE(slotList, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (secretKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, secretKey); + if (pubKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pubKey); + if (session != 0) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + if (soSession != 0) + funcList->C_CloseSession(soSession); + funcList->C_Finalize(NULL); + cleanup_store(); +} + +/* Finding 2774: PKCS#11 v2.40 sec 4.4.1: when a template omits CKA_WRAP and + * CKA_UNWRAP, both default to CK_FALSE. wolfPKCS11 used to default both to + * CK_TRUE for AES secret keys and RSA key pairs, silently turning every + * key into a wrapping key. The fix is gated behind + * WOLFPKCS11_LEGACY_WRAP_TRUE_DEFAULT; unless that macro is defined, the + * test asserts CK_FALSE. */ +static void test_2774_wrap_default(void) +{ + CK_RV rv; + CK_SLOT_ID slot; + CK_SLOT_ID* slotList = NULL; + CK_ULONG slotCount = 0; + CK_SESSION_HANDLE soSession = 0; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; +#ifndef NO_RSA + CK_OBJECT_HANDLE rsaPub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE rsaPriv = CK_INVALID_HANDLE; +#endif + CK_BYTE soPin[] = "password123456"; + CK_BYTE userPin[] = "someUserPin"; + CK_UTF8CHAR label[32]; + CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; + CK_KEY_TYPE aesKeyType = CKK_AES; + CK_ULONG aesKeyLen = 16; + CK_MECHANISM aesMech; + CK_ATTRIBUTE aesTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_VALUE_LEN, &aesKeyLen, sizeof(aesKeyLen) }, + }; +#ifndef NO_RSA + CK_MECHANISM rsaKpMech; + CK_ULONG rsaBits = 2048; + CK_ATTRIBUTE rsaPubTmpl[] = { + { CKA_MODULUS_BITS, &rsaBits, sizeof(rsaBits) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + CK_ATTRIBUTE rsaPrivTmpl[] = { + { CKA_TOKEN, NULL, 0 }, + }; +#endif + CK_BBOOL gotBool = CK_TRUE; + CK_ATTRIBUTE getWrapTmpl[] = { + { CKA_WRAP, &gotBool, sizeof(gotBool) }, + }; + CK_ATTRIBUTE getUnwrapTmpl[] = { + { CKA_UNWRAP, &gotBool, sizeof(gotBool) }, + }; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, "compliance-2774", 15); + + printf("\n--- 2774: CKA_WRAP/UNWRAP default is CK_FALSE ---\n"); + + cleanup_store(); + rv = lib_init(); + CHECK_RV(rv, "C_Initialize", CKR_OK); + if (rv != CKR_OK) return; + + rv = funcList->C_GetSlotList(CK_TRUE, NULL, &slotCount); + if (rv != CKR_OK || slotCount == 0) goto out; + slotList = (CK_SLOT_ID*)XMALLOC(slotCount * sizeof(CK_SLOT_ID), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (slotList == NULL) goto out; + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv != CKR_OK) goto out; + slot = slotList[0]; + + /* Init + login (CKA_PRIVATE default is now TRUE for these keys per + * finding 2370). */ + rv = funcList->C_InitToken(slot, soPin, sizeof(soPin) - 1, label); + CHECK_RV(rv, "C_InitToken", CKR_OK); + if (rv != CKR_OK) goto out; + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &soSession); + if (rv != CKR_OK) goto out; + rv = funcList->C_Login(soSession, CKU_SO, soPin, sizeof(soPin) - 1); + if (rv != CKR_OK) goto out; + rv = funcList->C_InitPIN(soSession, userPin, sizeof(userPin) - 1); + CHECK_RV(rv, "C_InitPIN", CKR_OK); + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + soSession = 0; + + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + if (rv != CKR_OK) goto out; + rv = funcList->C_Login(session, CKU_USER, userPin, sizeof(userPin) - 1); + CHECK_RV(rv, "C_Login(USER)", CKR_OK); + if (rv != CKR_OK) goto out; + + /* AES secret key: both CKA_WRAP and CKA_UNWRAP must default to FALSE. */ + XMEMSET(&aesMech, 0, sizeof(aesMech)); + aesMech.mechanism = CKM_AES_KEY_GEN; + rv = funcList->C_GenerateKey(session, &aesMech, aesTmpl, + sizeof(aesTmpl) / sizeof(aesTmpl[0]), + &aesKey); + if (rv == CKR_MECHANISM_INVALID) { + printf("SKIP: CKM_AES_KEY_GEN not enabled\n"); + } else { + CHECK_RV(rv, "C_GenerateKey(AES, no CKA_WRAP)", CKR_OK); + if (rv == CKR_OK) { + gotBool = CK_TRUE; + rv = funcList->C_GetAttributeValue(session, aesKey, getWrapTmpl, + 1); + CHECK_RV(rv, "C_GetAttributeValue(CKA_WRAP) on AES", CKR_OK); + if (rv == CKR_OK) { + CHECK_COND_MSG(gotBool == CK_FALSE, + "AES default CKA_WRAP is CK_FALSE"); + } + gotBool = CK_TRUE; + rv = funcList->C_GetAttributeValue(session, aesKey, + getUnwrapTmpl, 1); + CHECK_RV(rv, "C_GetAttributeValue(CKA_UNWRAP) on AES", CKR_OK); + if (rv == CKR_OK) { + CHECK_COND_MSG(gotBool == CK_FALSE, + "AES default CKA_UNWRAP is CK_FALSE"); + } + } + } + + /* RSA key pair: public CKA_WRAP and private CKA_UNWRAP must default to + * FALSE. */ +#ifndef NO_RSA + XMEMSET(&rsaKpMech, 0, sizeof(rsaKpMech)); + rsaKpMech.mechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; + rv = funcList->C_GenerateKeyPair(session, &rsaKpMech, + rsaPubTmpl, + sizeof(rsaPubTmpl)/sizeof(rsaPubTmpl[0]), + rsaPrivTmpl, 0, + &rsaPub, &rsaPriv); + if (rv == CKR_MECHANISM_INVALID) { + printf("SKIP: CKM_RSA_PKCS_KEY_PAIR_GEN not enabled\n"); + } else { + CHECK_RV(rv, "C_GenerateKeyPair(RSA, no CKA_WRAP)", CKR_OK); + if (rv == CKR_OK) { + gotBool = CK_TRUE; + rv = funcList->C_GetAttributeValue(session, rsaPub, getWrapTmpl, + 1); + CHECK_RV(rv, "C_GetAttributeValue(CKA_WRAP) on RSA pub", CKR_OK); + if (rv == CKR_OK) { + CHECK_COND_MSG(gotBool == CK_FALSE, + "RSA pub default CKA_WRAP is CK_FALSE"); + } + gotBool = CK_TRUE; + rv = funcList->C_GetAttributeValue(session, rsaPriv, + getUnwrapTmpl, 1); + CHECK_RV(rv, "C_GetAttributeValue(CKA_UNWRAP) on RSA priv", + CKR_OK); + if (rv == CKR_OK) { + CHECK_COND_MSG(gotBool == CK_FALSE, + "RSA priv default CKA_UNWRAP is CK_FALSE"); + } + } + } +#endif /* !NO_RSA */ + +out: + if (slotList != NULL) + XFREE(slotList, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (aesKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, aesKey); +#ifndef NO_RSA + if (rsaPub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, rsaPub); + if (rsaPriv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, rsaPriv); +#endif + if (session != 0) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + if (soSession != 0) + funcList->C_CloseSession(soSession); + funcList->C_Finalize(NULL); + cleanup_store(); +} + /* Finding 1336: C_Finalize must reject a non-NULL pReserved with * CKR_ARGUMENTS_BAD. */ static void test_1336_finalize_reserved_arg(void) @@ -738,6 +1637,13 @@ int main(int argc, char* argv[]) test_1340_wait_for_slot_event(); test_3634_gcm_null_iv(); test_3635_ccm_null_iv(); + test_3637_generic_secret_sensitive_attr(); + test_2776_asymmetric_sensitive_attr(); + test_3407_fresh_slot_uninitialized(); + test_1343_modifiable_enforced(); + test_3144_private_create_requires_login(); + test_2370_private_default(); + test_2774_wrap_default(); pkcs11_unload(); diff --git a/tests/pkcs11mtt.c b/tests/pkcs11mtt.c index 47501532..54d62153 100644 --- a/tests/pkcs11mtt.c +++ b/tests/pkcs11mtt.c @@ -806,6 +806,9 @@ static CK_RV get_generic_key(CK_SESSION_HANDLE session, unsigned char* data, { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + /* CKA_WRAP/CKA_UNWRAP default flipped to CK_FALSE per Fenrir 2774. */ + { CKA_WRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_UNWRAP, &ckTrue, sizeof(ckTrue) }, { CKA_VALUE, data, len }, }; int cnt = sizeof(generic_key)/sizeof(*generic_key); @@ -995,6 +998,9 @@ static CK_RV get_aes_128_key(CK_SESSION_HANDLE session, unsigned char* id, #endif { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + /* CKA_WRAP/CKA_UNWRAP default to CK_FALSE post-Fenrir 2774. */ + { CKA_WRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_UNWRAP, &ckTrue, sizeof(ckTrue) }, { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, { CKA_ID, id, idLen }, @@ -2308,7 +2314,10 @@ static CK_RV test_attributes_rsa(void* args) if (ret == CKR_OK) { ret = funcList->C_GetAttributeValue(session, priv, rsaPrivTmpl, rsaPrivTmplCnt); - CHECK_CKR(ret, "Get Attributes RSA private key length"); + /* extractable=FALSE -> noPriv -> CKR_ATTRIBUTE_SENSITIVE per + * Fenrir 2776. */ + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_SENSITIVE, + "Get Attributes RSA private key length"); } if (ret == CKR_OK) { CHECK_COND(rsaPrivTmpl[0].ulValueLen == sizeof(modulus), ret, @@ -3680,7 +3689,10 @@ static CK_RV test_attributes_ecc(void* args) if (ret == CKR_OK) { ret = funcList->C_GetAttributeValue(session, priv, eccPrivTmpl, eccPrivTmplCnt); - CHECK_CKR(ret, "Get Attributes EC Private Key NULL values"); + /* extractable=FALSE -> noPriv -> CKR_ATTRIBUTE_SENSITIVE per + * Fenrir 2776. */ + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_SENSITIVE, + "Get Attributes EC Private Key NULL values"); } if (ret == CKR_OK) { CHECK_COND(eccPrivTmpl[0].ulValueLen == sizeof(ecc_p256_params), ret, @@ -4408,7 +4420,10 @@ static CK_RV test_attributes_dh(void* args) if (ret == CKR_OK) { ret = funcList->C_GetAttributeValue(session, priv, dhPrivTmpl, dhPrivTmplCnt); - CHECK_CKR(ret, "Get Attributes DH Public Key"); + /* extractable=FALSE -> noPriv -> CKR_ATTRIBUTE_SENSITIVE per + * Fenrir 2776. */ + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_SENSITIVE, + "Get Attributes DH Private Key (sensitive)"); } if (ret == CKR_OK) { CHECK_COND(dhPrivTmpl[0].ulValueLen == sizeof(prime), ret, @@ -4428,7 +4443,9 @@ static CK_RV test_attributes_dh(void* args) if (ret == CKR_OK) { ret = funcList->C_GetAttributeValue(session, priv, dhPrivTmpl, dhPrivTmplCnt); - CHECK_CKR(ret, "Get Attributes DH Public Key"); + /* same priv key, still noPriv. */ + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_SENSITIVE, + "Get Attributes DH Private Key (sensitive, populated)"); } funcList->C_DestroyObject(session, priv); if (ret == CKR_OK) { diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index 0b80ab64..06a7ed1c 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -462,7 +462,11 @@ static CK_RV test_no_token_init(void* args) CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; CK_RV ret; CK_TOKEN_INFO tokenInfo; - CK_FLAGS expFlags = CKF_RNG | CKF_CLOCK_ON_TOKEN | CKF_TOKEN_INITIALIZED; + /* Per Fenrir 3407: CKF_TOKEN_INITIALIZED must not be set on a slot + * that has never had C_InitToken called. Pre-fix wp11_Token_Init + * unconditionally marked the token state INITIALIZED, so the old + * test included CKF_TOKEN_INITIALIZED in the expected mask. */ + CK_FLAGS expFlags = CKF_RNG | CKF_CLOCK_ON_TOKEN; int flags = CKF_SERIAL_SESSION | CKF_RW_SESSION; ret = funcList->C_GetTokenInfo(slot, &tokenInfo); @@ -4309,6 +4313,10 @@ static CK_RV get_generic_key(CK_SESSION_HANDLE session, unsigned char* data, { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + /* CKA_WRAP/CKA_UNWRAP defaults flipped to CK_FALSE per Fenrir 2774; + * keep both TRUE so this helper still backs wrap/unwrap call sites. */ + { CKA_WRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_UNWRAP, &ckTrue, sizeof(ckTrue) }, { CKA_VALUE, data, len }, }; int cnt = sizeof(generic_key)/sizeof(*generic_key); @@ -4642,6 +4650,11 @@ static CK_RV get_aes_128_key(CK_SESSION_HANDLE session, unsigned char* id, { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + /* CKA_WRAP/CKA_UNWRAP default to CK_FALSE per spec (Fenrir 2774). + * This helper backs the wrapping-key role in test_aes_wrap_unwrap_*, + * so set both explicitly. */ + { CKA_WRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_UNWRAP, &ckTrue, sizeof(ckTrue) }, #ifndef NO_AES { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, #endif @@ -6689,6 +6702,10 @@ static CK_RV get_rsa_priv_key(CK_SESSION_HANDLE session, unsigned char* privId, { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + /* CKA_UNWRAP defaults to CK_FALSE post-2774; set explicitly so the + * RSA wrap/unwrap path exercised by test_rsa_wrap_unwrap_key still + * works. */ + { CKA_UNWRAP, &ckTrue, sizeof(ckTrue) }, { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, { CKA_PRIVATE_EXPONENT, rsa_2048_priv_exp, sizeof(rsa_2048_priv_exp) }, { CKA_PRIME_1, rsa_2048_p, sizeof(rsa_2048_p) }, @@ -6721,6 +6738,9 @@ static CK_RV get_rsa_pub_key(CK_SESSION_HANDLE session, unsigned char* pubId, { CKA_CLASS, &pubKeyClass, sizeof(pubKeyClass) }, { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + /* CKA_WRAP defaults to CK_FALSE post-2774; set explicitly so the + * RSA wrap path in test_rsa_wrap_unwrap_key still works. */ + { CKA_WRAP, &ckTrue, sizeof(ckTrue) }, { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, @@ -7236,7 +7256,12 @@ static CK_RV test_attributes_rsa(void* args) if (ret == CKR_OK) { ret = funcList->C_GetAttributeValue(session, priv, rsaPrivTmpl, rsaPrivTmplCnt); - CHECK_CKR(ret, "Get Attributes RSA private key length"); + /* get_rsa_priv_key(extractable=FALSE) sets the noPriv flag, so per + * Fenrir 2776 the sensitive components now return + * CKR_ATTRIBUTE_SENSITIVE rather than silently CKR_OK with a + * sentinel length. */ + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_SENSITIVE, + "Get Attributes RSA private key length"); } if (ret == CKR_OK) { CHECK_COND(rsaPrivTmpl[0].ulValueLen == sizeof(modulus), ret, @@ -9044,7 +9069,10 @@ static CK_RV test_attributes_ecc(void* args) if (ret == CKR_OK) { ret = funcList->C_GetAttributeValue(session, priv, eccPrivTmpl, eccPrivTmplCnt); - CHECK_CKR(ret, "Get Attributes EC Private Key NULL values"); + /* extractable=FALSE -> noPriv -> CKR_ATTRIBUTE_SENSITIVE per + * Fenrir 2776. */ + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_SENSITIVE, + "Get Attributes EC Private Key NULL values"); } if (ret == CKR_OK) { CHECK_COND(eccPrivTmpl[0].ulValueLen == sizeof(ecc_p256_params), ret, @@ -10074,7 +10102,10 @@ static CK_RV test_attributes_dh(void* args) if (ret == CKR_OK) { ret = funcList->C_GetAttributeValue(session, priv, dhPrivTmpl, dhPrivTmplCnt); - CHECK_CKR(ret, "Get Attributes DH Public Key"); + /* extractable=FALSE -> noPriv -> CKR_ATTRIBUTE_SENSITIVE per + * Fenrir 2776. */ + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_SENSITIVE, + "Get Attributes DH Private Key (sensitive)"); } if (ret == CKR_OK) { CHECK_COND(dhPrivTmpl[0].ulValueLen == sizeof(prime), ret, @@ -10094,7 +10125,9 @@ static CK_RV test_attributes_dh(void* args) if (ret == CKR_OK) { ret = funcList->C_GetAttributeValue(session, priv, dhPrivTmpl, dhPrivTmplCnt); - CHECK_CKR(ret, "Get Attributes DH Public Key"); + /* same priv key, still noPriv. */ + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_SENSITIVE, + "Get Attributes DH Private Key (sensitive, populated)"); } funcList->C_DestroyObject(session, priv); if (ret == CKR_OK) { diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index f3d39db2..615cb008 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -198,7 +198,14 @@ C_EXTRA_FLAGS="-DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT" #define WP11_FLAG_PRIVATE 0x00000001 #define WP11_FLAG_SENSITIVE 0x00000002 #define WP11_FLAG_EXTRACTABLE 0x00000004 -#define WP11_FLAG_MODIFIABLE 0x00000008 +/* Bit 0x00000008 was the original WP11_FLAG_MODIFIABLE (non-inverted) but + * read incorrectly for unset objects (defaulted to CKA_MODIFIABLE=CK_FALSE + * in violation of the spec default of CK_TRUE). The bit is now reserved and + * unused; CKA_MODIFIABLE is tracked by WP11_FLAG_NOT_MODIFIABLE below using + * a fresh bit so existing on-disk objects (where 0x00000008 may be set or + * clear from older builds) all read as the spec default CKA_MODIFIABLE=TRUE + * under new code rather than silently flipping immutability state. */ +#define WP11_FLAG_RESERVED_MODIFIABLE 0x00000008 #define WP11_FLAG_ALWAYS_SENSITIVE 0x00000010 #define WP11_FLAG_NEVER_EXTRACTABLE 0x00000020 #define WP11_FLAG_ALWAYS_AUTHENTICATE 0x00000040 @@ -218,10 +225,11 @@ C_EXTRA_FLAGS="-DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT" #define WP11_FLAG_DERIVE 0x00040000 #define WP11_FLAG_ENCAPSULATE 0x00080000 #define WP11_FLAG_DECAPSULATE 0x00100000 -/* These two flags invert their attribute's spec default (CK_TRUE). When the +/* These three flags invert their attribute's spec default (CK_TRUE). When the * flag is set, the attribute reads as CK_FALSE; clear means CK_TRUE. */ #define WP11_FLAG_NOT_COPYABLE 0x00200000 #define WP11_FLAG_NOT_DESTROYABLE 0x00400000 +#define WP11_FLAG_NOT_MODIFIABLE 0x00800000 /* Flags for token. */ #define WP11_TOKEN_FLAG_USER_PIN_SET 0x00000001 @@ -466,6 +474,7 @@ WP11_LOCAL int WP11_Object_SetClass(WP11_Object* object, CK_OBJECT_CLASS objClas WP11_LOCAL CK_OBJECT_CLASS WP11_Object_GetClass(WP11_Object* object); WP11_LOCAL int WP11_Object_IsCopyable(WP11_Object* object); WP11_LOCAL int WP11_Object_IsDestroyable(WP11_Object* object); +WP11_LOCAL int WP11_Object_IsModifiable(WP11_Object* object); #ifdef WOLFPKCS11_NSS WP11_LOCAL int WP11_Object_SetTrust(WP11_Object* object, unsigned char** data,