From e105b128c4355fae797edb714ba100634e13dd5b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 08:22:46 -0700 Subject: [PATCH 001/204] F-8673 - Lock the cached empty user PIN state --- src/internal.c | 62 +++++--- tests/include.am | 7 + tests/login_pin_state_test.c | 277 +++++++++++++++++++++++++++++++++++ 3 files changed, 322 insertions(+), 24 deletions(-) create mode 100644 tests/login_pin_state_test.c diff --git a/src/internal.c b/src/internal.c index 80f4afa9..f16bf98d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -865,33 +865,38 @@ static int wp11_Session_New(WP11_Slot* slot, CK_OBJECT_HANDLE handle, */ int WP11_Slot_Has_Empty_Pin(WP11_Slot* slot) { + int ret = 0; + int pinSet; + int cached; + byte seed[PIN_SEED_SZ]; + if (slot == NULL) return 0; - if (slot->token.tokenFlags & WP11_TOKEN_FLAG_USER_PIN_SET) { - switch (slot->token.userPinEmpty) { - case 1: - /* Empty user PIN */ - return 1; - case 2: - /* Non-empty user PIN */ - return 0; - default: - /* Cache result as WP11_Slot_CheckUserPin is very expensive */ - if (WP11_Slot_CheckUserPin(slot, (char*)"", 0) == 0) { - /* Empty user PIN */ - slot->token.userPinEmpty = 1; - return 1; - } - else { - /* Non-empty user PIN */ - slot->token.userPinEmpty = 2; - return 0; - } + WP11_Lock_LockRO(&slot->lock); + pinSet = (slot->token.tokenFlags & WP11_TOKEN_FLAG_USER_PIN_SET) != 0; + cached = slot->token.userPinEmpty; + XMEMCPY(seed, slot->token.userPinSeed, sizeof(seed)); + WP11_Lock_UnlockRO(&slot->lock); + + if (pinSet) { + if (cached == 0) { + /* Cache result as WP11_Slot_CheckUserPin is very expensive */ + ret = (WP11_Slot_CheckUserPin(slot, (char*)"", 0) == 0); + WP11_Lock_LockRW(&slot->lock); + /* Only cache a verdict for the PIN that was checked. */ + if (slot->token.userPinEmpty == 0 && + XMEMCMP(seed, slot->token.userPinSeed, sizeof(seed)) == 0) { + slot->token.userPinEmpty = ret ? 1 : 2; + } + WP11_Lock_UnlockRW(&slot->lock); + } + else { + ret = (cached == 1); } } - return 0; + return ret; } /** @@ -8458,6 +8463,7 @@ int WP11_Slot_CheckUserPin(WP11_Slot* slot, char* pin, int pinLen) int ret = 0; WP11_Token* token; byte hash[PIN_HASH_SZ]; + byte seed[PIN_SEED_SZ]; WP11_Lock_LockRO(&slot->lock); token = &slot->token; @@ -8469,11 +8475,13 @@ int WP11_Slot_CheckUserPin(WP11_Slot* slot, char* pin, int pinLen) ret = PIN_NOT_SET_E; if (ret == 0) { + /* C_SetPIN may replace the seed while the hash runs unlocked. */ + XMEMCPY(seed, token->userPinSeed, sizeof(seed)); WP11_Lock_UnlockRO(&slot->lock); /* Costly Operation done out of lock. */ - ret = HashPIN(pin, pinLen, token->userPinSeed, - sizeof(token->userPinSeed), hash, sizeof(hash), slot); + ret = HashPIN(pin, pinLen, seed, sizeof(seed), hash, sizeof(hash), + slot); WP11_Lock_LockRO(&slot->lock); } @@ -8974,7 +8982,13 @@ time_t WP11_Slot_TokenFailedExpire(WP11_Slot* slot, int login) */ int WP11_Slot_IsTokenUserPinInitialized(WP11_Slot* slot) { - return slot->token.tokenFlags & WP11_TOKEN_FLAG_USER_PIN_SET; + int ret; + + WP11_Lock_LockRO(&slot->lock); + ret = slot->token.tokenFlags & WP11_TOKEN_FLAG_USER_PIN_SET; + WP11_Lock_UnlockRO(&slot->lock); + + return ret; } /** diff --git a/tests/include.am b/tests/include.am index 769f36ac..d3162c44 100644 --- a/tests/include.am +++ b/tests/include.am @@ -300,6 +300,11 @@ noinst_PROGRAMS += tests/nss_configdir_prefix_test tests_nss_configdir_prefix_test_SOURCES = tests/nss_configdir_prefix_test.c tests_nss_configdir_prefix_test_LDADD = +check_PROGRAMS += tests/login_pin_state_test +noinst_PROGRAMS += tests/login_pin_state_test +tests_login_pin_state_test_SOURCES = tests/login_pin_state_test.c +tests_login_pin_state_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -361,6 +366,7 @@ tests_copy_data_object_token_test_LDADD += src/libwolfpkcs11.la tests_data_object_partial_update_test_LDADD += src/libwolfpkcs11.la tests_wrapkey_length_query_test_LDADD += src/libwolfpkcs11.la tests_nss_configdir_prefix_test_LDADD += src/libwolfpkcs11.la +tests_login_pin_state_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -414,6 +420,7 @@ tests_copy_data_object_token_test_LDADD += src/libwolfpkcs11.la tests_data_object_partial_update_test_LDADD += src/libwolfpkcs11.la tests_wrapkey_length_query_test_LDADD += src/libwolfpkcs11.la tests_nss_configdir_prefix_test_LDADD += src/libwolfpkcs11.la +tests_login_pin_state_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ diff --git a/tests/login_pin_state_test.c b/tests/login_pin_state_test.c new file mode 100644 index 00000000..f15e9f71 --- /dev/null +++ b/tests/login_pin_state_test.c @@ -0,0 +1,277 @@ +/* login_pin_state_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Login, PIN and session state invariants of the token. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#ifndef SINGLE_THREADED +#include +#endif + +#define TEST_DIR "./store/login_pin_state_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +/* Not every helper is used in every build configuration. */ +#if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-variable" + #pragma GCC diagnostic ignored "-Wunused-function" +#endif + +static const char* soPin = "password123456"; +static const char* userPin = "wolfpkcs11-test"; +static const char tokenLabel[] = "login-pin-state"; + +/* Start from an unprovisioned token so earlier runs cannot affect this one. */ +static void cleanup_test_files(void) +{ + char filepath[512]; + + snprintf(filepath, sizeof(filepath), "%s" PATH_SEP "%s", TEST_DIR, + WOLFPKCS11_TOKEN_FILENAME); + (void)remove(filepath); +} + +static CK_RV lib_init(void) +{ + CK_C_INITIALIZE_ARGS args; + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + return funcList->C_Initialize(&args); +} + +/* Initialize the library, re-initialize the token and set the user PIN. */ +static CK_RV token_setup(CK_SLOT_ID* slot, const char* pin) +{ + CK_RV rv; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + CK_SESSION_HANDLE soSession = CK_INVALID_HANDLE; + unsigned char label[32]; + + rv = lib_init(); + if (rv == CKR_OK) + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv == CKR_OK && slotCount == 0) + rv = CKR_TOKEN_NOT_PRESENT; + if (rv == CKR_OK) { + *slot = slotList[0]; + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenLabel, XSTRLEN(tokenLabel)); + rv = funcList->C_InitToken(*slot, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin), label); + } + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(*slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &soSession); + } + if (rv == CKR_OK) { + rv = funcList->C_Login(soSession, CKU_SO, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin)); + } + if (rv == CKR_OK) { + rv = funcList->C_InitPIN(soSession, (CK_UTF8CHAR_PTR)pin, + (CK_ULONG)XSTRLEN(pin)); + funcList->C_Logout(soSession); + } + if (soSession != CK_INVALID_HANDLE) + funcList->C_CloseSession(soSession); + return rv; +} + +static CK_RV open_rw(CK_SLOT_ID slot, CK_SESSION_HANDLE* session) +{ + return funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, session); +} + +static CK_RV user_login(CK_SESSION_HANDLE session, const char* pin) +{ + return funcList->C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR)pin, + (CK_ULONG)XSTRLEN(pin)); +} + +#if defined(__GNUC__) + #pragma GCC diagnostic pop +#endif + +#ifndef SINGLE_THREADED +#define EMPTY_PIN_ROUNDS 8 + +static pthread_mutex_t infoMutex = PTHREAD_MUTEX_INITIALIZER; +static int infoStop = 0; +static CK_SLOT_ID infoSlot = 0; + +static int info_should_stop(void) +{ + int stop; + + pthread_mutex_lock(&infoMutex); + stop = infoStop; + pthread_mutex_unlock(&infoMutex); + return stop; +} + +static void info_set_stop(int stop) +{ + pthread_mutex_lock(&infoMutex); + infoStop = stop; + pthread_mutex_unlock(&infoMutex); +} + +static void* token_info_loop(void* arg) +{ + CK_TOKEN_INFO info; + + (void)arg; + while (!info_should_stop()) + (void)funcList->C_GetTokenInfo(infoSlot, &info); + return NULL; +} + +/* Once a non-empty user PIN is set, the token requires login and private + * objects cannot be created without one, even with concurrent token queries. */ +static void empty_pin_change_requires_login_test(void) +{ + CK_RV rv; + int i; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_TOKEN_INFO info; + pthread_t thread; + const char* newPin = "non-empty-pin"; + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_BBOOL privTrue = CK_TRUE; + CK_ATTRIBUTE privTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_PRIVATE, &privTrue, sizeof(privTrue) }, + }; + int loginRequired = 1; + int privateDenied = 1; + + printf("--- empty PIN replaced by a non-empty PIN ---\n"); + for (i = 0; i < EMPTY_PIN_ROUNDS; i++) { + rv = token_setup(&slot, ""); + if (i == 0 && rv == CKR_PIN_LEN_RANGE) { + printf("SKIP: empty user PIN not supported by this build\n"); + funcList->C_Finalize(NULL); + return; + } + if (rv == CKR_OK) + rv = open_rw(slot, &session); + CHECK_RV(rv, "token setup with empty user PIN", CKR_OK); + if (rv != CKR_OK) { + funcList->C_Finalize(NULL); + return; + } + + infoSlot = slot; + info_set_stop(0); + if (pthread_create(&thread, NULL, token_info_loop, NULL) != 0) { + CHECK_TRUE(0, "create token info thread"); + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); + return; + } + rv = funcList->C_SetPIN(session, (CK_UTF8CHAR_PTR)"", 0, + (CK_UTF8CHAR_PTR)newPin, + (CK_ULONG)XSTRLEN(newPin)); + info_set_stop(1); + pthread_join(thread, NULL); + CHECK_RV(rv, "C_SetPIN from empty to non-empty PIN", CKR_OK); + + if (funcList->C_GetTokenInfo(slot, &info) != CKR_OK || + (info.flags & CKF_LOGIN_REQUIRED) == 0) { + loginRequired = 0; + } + obj = CK_INVALID_HANDLE; + rv = funcList->C_CreateObject(session, privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &obj); + if (rv != CKR_USER_NOT_LOGGED_IN) + privateDenied = 0; + + funcList->C_CloseSession(session); + session = CK_INVALID_HANDLE; + funcList->C_Finalize(NULL); + } + CHECK_TRUE(loginRequired, "token reports login required after PIN set"); + CHECK_TRUE(privateDenied, + "private object creation needs login after PIN set"); +} +#endif + +static int run_test(void) +{ + CK_RV rv; + + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv != CKR_OK) + return -1; + +#ifndef SINGLE_THREADED + empty_pin_change_requires_login_test(); +#endif + + pkcs11_unload(); + return 0; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + + printf("=== wolfPKCS11 login and PIN state test ===\n"); + cleanup_test_files(); + run_test(); + return pkcs11_test_summary(); +} From 26ae078499577f037bc7d31e39df3b757d3c7876 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 08:44:58 -0700 Subject: [PATCH 002/204] F-8662 - Clear the derived key and decoded objects on failed login --- src/internal.c | 147 +++++++++++++++++++++++++++++++++-- tests/login_pin_state_test.c | 110 ++++++++++++++++++++++++++ wolfpkcs11/internal.h | 1 + 3 files changed, 250 insertions(+), 8 deletions(-) diff --git a/src/internal.c b/src/internal.c index f16bf98d..cf591ccc 100644 --- a/src/internal.c +++ b/src/internal.c @@ -6965,9 +6965,15 @@ static int wp11_Object_EncodeData(WP11_Object* object, int protect) return ret; } -static int wp11_Object_Encode(WP11_Object* object, int protect) +/** + * Check whether the object's key material is encrypted with the token key. + * + * @param [in] object Key object. + * @return 1 when encrypted with the token key. + * @return 0 otherwise. + */ +static int wp11_Object_IsEncrypted(WP11_Object* object) { - int ret = 0; int encrypt = object->objClass == CKO_PRIVATE_KEY || object->type == CKK_AES || object->type == CKK_GENERIC_SECRET; @@ -6976,10 +6982,17 @@ static int wp11_Object_Encode(WP11_Object* object, int protect) encrypt = encrypt || object->type == CKK_HKDF; #endif + return encrypt; +} + +static int wp11_Object_Encode(WP11_Object* object, int protect) +{ + int ret = 0; + /* Every AES-GCM encryption under the token key needs a fresh nonce. Do * this immediately before encoding, while the plaintext is still the * source of the ciphertext that will be persisted. */ - if (encrypt) { + if (wp11_Object_IsEncrypted(object)) { WP11_Lock_LockRW(&object->slot->token.rngLock); ret = wc_RNG_GenerateBlock(&object->slot->token.rng, object->iv, sizeof(object->iv)); @@ -6991,6 +7004,69 @@ static int wp11_Object_Encode(WP11_Object* object, int protect) return ret; } +/** + * Discard the decoded key material of an object, keeping its encrypted form. + * + * @param [in, out] object Key object. + */ +static void wp11_Object_Scrub(WP11_Object* object) +{ + switch (object->type) { + #ifndef NO_RSA + case CKK_RSA: + if (object->objClass == CKO_PRIVATE_KEY) { + wc_FreeRsaKey(object->data.rsaKey); + object->encoded = 1; + } + break; + #endif + #ifdef HAVE_ECC + case CKK_EC: + if (object->objClass == CKO_PRIVATE_KEY) { + wc_ecc_free(object->data.ecKey); + object->encoded = 1; + } + break; + #endif + #ifdef WOLFPKCS11_MLDSA + case CKK_ML_DSA: + if (object->objClass == CKO_PRIVATE_KEY) { + wc_MlDsaKey_Free(object->data.mldsaKey); + object->encoded = 1; + } + break; + #endif + #ifndef NO_DH + case CKK_DH: + if (object->objClass == CKO_PRIVATE_KEY) { + wc_ForceZero(object->data.dhKey->key, object->data.dhKey->len); + object->encoded = 1; + } + break; + #endif + #ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + if (object->objClass == CKO_PRIVATE_KEY) { + wc_MlKemKey_Free(object->data.mlKemKey); + object->encoded = 1; + } + break; + #endif + #ifndef NO_AES + case CKK_AES: + #endif + #ifdef WOLFPKCS11_HKDF + case CKK_HKDF: + #endif + case CKK_GENERIC_SECRET: + wc_ForceZero(object->data.symmKey->data, object->data.symmKey->len); + object->encoded = 1; + break; + default: + break; + } +} + /** * Unstore a key object to storage. * @@ -8616,6 +8692,12 @@ int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen) #endif int state; WP11_Token* token = &slot->token; +#ifndef WOLFPKCS11_NO_STORE + byte key[AES_256_KEY_SIZE]; + byte prevKey[AES_256_KEY_SIZE]; + WP11_Object* object; + WP11_Object* failed = NULL; +#endif #ifndef WOLFPKCS11_NO_TIME if (wc_GetTime(&now, sizeof(now)) != 0) @@ -8655,7 +8737,7 @@ int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen) /* Re-create token->key from PIN + token->seed (HashPIN) on load. */ if (ret == 0) { ret = HashPIN(pin, pinLen, token->seed, sizeof(token->seed), - token->key, sizeof(token->key), slot); + key, sizeof(key), slot); } #endif WP11_Lock_LockRW(&slot->lock); @@ -8671,20 +8753,35 @@ int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen) } /* Worked - clear failure info. */ else if (ret == 0) { - #ifndef WOLFPKCS11_NO_STORE - WP11_Object* object; - #endif - token->userFailedLogin = 0; token->userLastFailedLogin = 0; token->userFailLoginTimeout = 0; #ifndef WOLFPKCS11_NO_STORE + /* Lock order is slot then token; the token lock keeps the object + * list stable across the decode and any rollback. */ + WP11_Lock_LockRW(&token->lock); + XMEMCPY(prevKey, token->key, sizeof(prevKey)); + XMEMCPY(token->key, key, sizeof(token->key)); object = token->object; while (ret == 0 && object != NULL) { ret = wp11_Object_Decode(object); + if (ret != 0) + failed = object; object = object->next; } + if (ret != 0) { + /* Login failed: drop what was decoded and the key derived + * from the PIN. The stored ciphertext is left untouched. */ + for (object = token->object; + pinLen > 0 && object != NULL && object != failed; + object = object->next) { + if (wp11_Object_IsEncrypted(object) && !object->encoded) + wp11_Object_Scrub(object); + } + XMEMCPY(token->key, prevKey, sizeof(token->key)); + } + WP11_Lock_UnlockRW(&token->lock); #endif } WP11_Lock_UnlockRW(&slot->lock); @@ -8696,6 +8793,10 @@ int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen) WP11_Lock_UnlockRW(&slot->lock); } +#ifndef WOLFPKCS11_NO_STORE + wc_ForceZero(key, sizeof(key)); + wc_ForceZero(prevKey, sizeof(prevKey)); +#endif return ret; } @@ -8898,6 +8999,36 @@ WP11_API int WP11_Slot_TokenKeyIsZero(CK_SLOT_ID slotId) return acc == 0 ? 1 : 0; } + +/** + * Test hook: count the token objects whose encrypted key material is held + * decoded in memory. + * + * @param slotId [in] Slot id (1-based, as used by the PKCS#11 API). + * @return Number of decoded encrypted objects, -1 on a bad slot id. + */ +WP11_API int WP11_Slot_TokenDecodedObjectCount(CK_SLOT_ID slotId) +{ + WP11_Slot* slot = NULL; + int cnt = 0; +#ifndef WOLFPKCS11_NO_STORE + WP11_Object* object; +#endif + + if (WP11_Slot_Get(slotId, &slot) != 0 || slot == NULL) + return -1; + +#ifndef WOLFPKCS11_NO_STORE + WP11_Lock_LockRO(&slot->token.lock); + for (object = slot->token.object; object != NULL; object = object->next) { + if (wp11_Object_IsEncrypted(object) && !object->encoded) + cnt++; + } + WP11_Lock_UnlockRO(&slot->token.lock); +#endif + + return cnt; +} #endif /* DEBUG_WOLFPKCS11 */ /** diff --git a/tests/login_pin_state_test.c b/tests/login_pin_state_test.c index f15e9f71..c6840aae 100644 --- a/tests/login_pin_state_test.c +++ b/tests/login_pin_state_test.c @@ -53,6 +53,13 @@ #define TEST_DIR "./store/login_pin_state_test" #define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" +/* These debug tests damage the default file store under TEST_DIR. */ +#if defined(DEBUG_WOLFPKCS11) && !defined(WOLFPKCS11_NO_STORE) && \ + !defined(WOLFPKCS11_TPM_STORE) && !defined(WOLFPKCS11_CUSTOM_STORE) && \ + !defined(WOLFPKCS11_NO_ENV) && !defined(NO_AES) + #define LOGIN_TEST_FILE_STORE +#endif + /* Not every helper is used in every build configuration. */ #if defined(__GNUC__) #pragma GCC diagnostic push @@ -134,10 +141,110 @@ static CK_RV user_login(CK_SESSION_HANDLE session, const char* pin) (CK_ULONG)XSTRLEN(pin)); } +#if !defined(WOLFPKCS11_NO_STORE) && !defined(NO_AES) +static CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; +static CK_KEY_TYPE aesType = CKK_AES; +static CK_BBOOL ckTrue = CK_TRUE; +static byte aesValue[16] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff +}; +#endif + #if defined(__GNUC__) #pragma GCC diagnostic pop #endif +#ifdef LOGIN_TEST_FILE_STORE +/* DEBUG_WOLFPKCS11-only introspection hooks exported by libwolfpkcs11. */ +extern int WP11_Slot_TokenKeyIsZero(CK_SLOT_ID slotId); +extern int WP11_Slot_TokenDecodedObjectCount(CK_SLOT_ID slotId); + +#define SYMMKEY_FILENAME "wp11_symmkey_0000000000000001_0000000000000000" + +/* Replace the oldest stored secret key with a record too short to decrypt. */ +static int truncate_stored_key(void) +{ + FILE* f; + char filepath[512]; + static const byte shortRecord[12] = { 0x00, 0x00, 0x00, 0x08 }; + int ret = -1; + + snprintf(filepath, sizeof(filepath), "%s" PATH_SEP "%s", TEST_DIR, + SYMMKEY_FILENAME); + f = fopen(filepath, "wb"); + if (f != NULL) { + if (fwrite(shortRecord, 1, sizeof(shortRecord), f) == + sizeof(shortRecord)) { + ret = 0; + } + if (fclose(f) != 0) + ret = -1; + } + return ret; +} + +/* A login that fails after the PIN is verified must not leave the key derived + * from the PIN, or any key decoded before the failure, resident in memory. */ +static void failed_login_leaves_no_token_key_test(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_VALUE, aesValue, sizeof(aesValue) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + }; + + printf("--- failed login leaves no token key ---\n"); + rv = token_setup(&slot, userPin); + if (rv == CKR_OK) + rv = open_rw(slot, &session); + if (rv == CKR_OK) + rv = user_login(session, userPin); + /* Objects decode newest first, so the second key decodes before the + * damaged first one. */ + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &obj); + } + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &obj); + } + CHECK_RV(rv, "create private token keys", CKR_OK); + if (session != CK_INVALID_HANDLE) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + session = CK_INVALID_HANDLE; + } + funcList->C_Finalize(NULL); + if (rv != CKR_OK) + return; + + CHECK_TRUE(truncate_stored_key() == 0, "replace stored key record"); + rv = lib_init(); + if (rv == CKR_OK) + rv = open_rw(slot, &session); + CHECK_RV(rv, "reload token", CKR_OK); + if (rv == CKR_OK) { + rv = user_login(session, userPin); + CHECK_TRUE(rv != CKR_OK, "login fails on undecodable token object"); + CHECK_TRUE(WP11_Slot_TokenKeyIsZero(slot) == 1, + "token key is clear after failed login"); + CHECK_TRUE(WP11_Slot_TokenDecodedObjectCount(slot) == 0, + "no decoded keys left after failed login"); + funcList->C_CloseSession(session); + } + funcList->C_Finalize(NULL); + cleanup_test_files(); +} +#endif + #ifndef SINGLE_THREADED #define EMPTY_PIN_ROUNDS 8 @@ -256,6 +363,9 @@ static int run_test(void) #ifndef SINGLE_THREADED empty_pin_change_requires_login_test(); #endif +#ifdef LOGIN_TEST_FILE_STORE + failed_login_leaves_no_token_key_test(); +#endif pkcs11_unload(); return 0; diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index c4f95fe4..09f8d7ed 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -413,6 +413,7 @@ WP11_LOCAL int WP11_Slot_IsUserLoggedIn(WP11_Slot* slot); WP11_LOCAL void WP11_Slot_Logout(WP11_Slot* slot); #ifdef DEBUG_WOLFPKCS11 WP11_API int WP11_Slot_TokenKeyIsZero(CK_SLOT_ID slotId); +WP11_API int WP11_Slot_TokenDecodedObjectCount(CK_SLOT_ID slotId); #if defined(WOLFPKCS11_TPM) && (!defined(NO_RSA) || defined(HAVE_ECC)) WP11_API int WP11_Test_DecodeTpmKey(CK_SLOT_ID slotId, unsigned char* keyData, int keyDataLen); From 651cf9e3f5a76f906b780fce7bdcf226a845a7bc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:10:43 -0700 Subject: [PATCH 003/204] F-5080 - Keep the PIN lockout armed when failed logins race --- src/internal.c | 58 +++++++++++----- src/slot.c | 4 +- tests/login_pin_state_test.c | 131 +++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 20 deletions(-) diff --git a/src/internal.c b/src/internal.c index cf591ccc..eb6933f2 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8486,7 +8486,7 @@ int WP11_Slot_CheckSOPinLockout(WP11_Slot* slot, char* pin, int pinLen) /* Check for too many fails and whether the timeout has elapsed. */ WP11_Lock_LockRW(&slot->lock); - if (slot->token.soFailedLogin == WP11_MAX_LOGIN_FAILS_SO) { + if (slot->token.soFailedLogin >= WP11_MAX_LOGIN_FAILS_SO) { allowed = slot->token.soLastFailedLogin + slot->token.soFailLoginTimeout; if (allowed < now) @@ -8504,7 +8504,8 @@ int WP11_Slot_CheckSOPinLockout(WP11_Slot* slot, char* pin, int pinLen) #ifndef WOLFPKCS11_NO_TIME WP11_Lock_LockRW(&slot->lock); /* PIN failed - update failure info. */ - if (ret == PIN_INVALID_E) { + if (ret == PIN_INVALID_E && + slot->token.soFailedLogin < WP11_MAX_LOGIN_FAILS_SO) { slot->token.soFailedLogin++; if (slot->token.soFailedLogin == WP11_MAX_LOGIN_FAILS_SO) { slot->token.soLastFailedLogin = now; @@ -8597,7 +8598,8 @@ int WP11_Slot_SOLogin(WP11_Slot* slot, char* pin, int pinLen) ret = PIN_INVALID_E; #endif - WP11_Lock_LockRO(&slot->lock); + /* Write lock: the lockout check below may reset the failure count. */ + WP11_Lock_LockRW(&slot->lock); if (ret == 0) { /* SO already logged in is the same user type; a logged-in USER is a * different one. */ @@ -8612,7 +8614,7 @@ int WP11_Slot_SOLogin(WP11_Slot* slot, char* pin, int pinLen) } #ifndef WOLFPKCS11_NO_TIME /* Check for too many fails and timeout. */ - if (ret == 0 && slot->token.soFailedLogin == WP11_MAX_LOGIN_FAILS_SO) { + if (ret == 0 && slot->token.soFailedLogin >= WP11_MAX_LOGIN_FAILS_SO) { allowed = slot->token.soLastFailedLogin + slot->token.soFailLoginTimeout; if (allowed < now) @@ -8638,7 +8640,7 @@ int WP11_Slot_SOLogin(WP11_Slot* slot, char* pin, int pinLen) ret = READ_ONLY_E; } #endif - WP11_Lock_UnlockRO(&slot->lock); + WP11_Lock_UnlockRW(&slot->lock); if (ret == 0) { ret = WP11_Slot_CheckSOPin(slot, pin, pinLen); @@ -8646,10 +8648,14 @@ int WP11_Slot_SOLogin(WP11_Slot* slot, char* pin, int pinLen) /* PIN Failed - Update failure info. */ if (ret == PIN_INVALID_E) { #ifndef WOLFPKCS11_NO_TIME - slot->token.soFailedLogin++; - if (slot->token.soFailedLogin == WP11_MAX_LOGIN_FAILS_SO) { - slot->token.soLastFailedLogin = now; - slot->token.soFailLoginTimeout += WP11_SO_LOGIN_FAIL_TIMEOUT; + /* Concurrent failures must not push the count past the limit. */ + if (slot->token.soFailedLogin < WP11_MAX_LOGIN_FAILS_SO) { + slot->token.soFailedLogin++; + if (slot->token.soFailedLogin == WP11_MAX_LOGIN_FAILS_SO) { + slot->token.soLastFailedLogin = now; + slot->token.soFailLoginTimeout += + WP11_SO_LOGIN_FAIL_TIMEOUT; + } } #endif } @@ -8719,7 +8725,7 @@ int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen) } #ifndef WOLFPKCS11_NO_TIME /* Check for too many fails */ - if (ret == 0 && token->userFailedLogin == WP11_MAX_LOGIN_FAILS_USER) { + if (ret == 0 && token->userFailedLogin >= WP11_MAX_LOGIN_FAILS_USER) { allowed = token->userLastFailedLogin + token->userFailLoginTimeout; if (allowed < now) token->userFailedLogin = 0; @@ -8744,10 +8750,14 @@ int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen) /* PIN Failed - Update failure info. */ if (ret == PIN_INVALID_E) { #ifndef WOLFPKCS11_NO_TIME - token->userFailedLogin++; - if (token->userFailedLogin == WP11_MAX_LOGIN_FAILS_USER) { - token->userLastFailedLogin = now; - token->userFailLoginTimeout += WP11_USER_LOGIN_FAIL_TIMEOUT; + /* Concurrent failures must not push the count past the limit. */ + if (token->userFailedLogin < WP11_MAX_LOGIN_FAILS_USER) { + token->userFailedLogin++; + if (token->userFailedLogin == WP11_MAX_LOGIN_FAILS_USER) { + token->userLastFailedLogin = now; + token->userFailLoginTimeout += + WP11_USER_LOGIN_FAIL_TIMEOUT; + } } #endif } @@ -9080,10 +9090,16 @@ int WP11_Slot_IsTokenInitialized(WP11_Slot* slot) */ int WP11_Slot_TokenFailedLogin(WP11_Slot* slot, int login) { + int ret; + + WP11_Lock_LockRO(&slot->lock); if (login == WP11_LOGIN_SO) - return slot->token.soFailedLogin; + ret = slot->token.soFailedLogin; else - return slot->token.userFailedLogin; + ret = slot->token.userFailedLogin; + WP11_Lock_UnlockRO(&slot->lock); + + return ret; } /** @@ -9096,12 +9112,18 @@ int WP11_Slot_TokenFailedLogin(WP11_Slot* slot, int login) */ time_t WP11_Slot_TokenFailedExpire(WP11_Slot* slot, int login) { + time_t ret; + + WP11_Lock_LockRO(&slot->lock); if (login == WP11_LOGIN_SO) - return slot->token.soLastFailedLogin + slot->token.soFailLoginTimeout; + ret = slot->token.soLastFailedLogin + slot->token.soFailLoginTimeout; else { - return slot->token.userLastFailedLogin + + ret = slot->token.userLastFailedLogin + slot->token.userFailLoginTimeout; } + WP11_Lock_UnlockRO(&slot->lock); + + return ret; } /** diff --git a/src/slot.c b/src/slot.c index 5f7a21bb..b8dcc429 100644 --- a/src/slot.c +++ b/src/slot.c @@ -274,7 +274,7 @@ CK_RV C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) if (cnt == WP11_MAX_LOGIN_FAILS_SO - 1) pInfo->flags |= CKF_SO_PIN_FINAL_TRY; #ifndef WOLFPKCS11_NO_TIME - else if (cnt == WP11_MAX_LOGIN_FAILS_SO && now < expire) + else if (cnt >= WP11_MAX_LOGIN_FAILS_SO && now < expire) pInfo->flags |= CKF_SO_PIN_LOCKED; #endif @@ -287,7 +287,7 @@ CK_RV C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) if (cnt == WP11_MAX_LOGIN_FAILS_USER - 1) pInfo->flags |= CKF_USER_PIN_FINAL_TRY; #ifndef WOLFPKCS11_NO_TIME - else if (cnt == WP11_MAX_LOGIN_FAILS_USER && now < expire) + else if (cnt >= WP11_MAX_LOGIN_FAILS_USER && now < expire) pInfo->flags |= CKF_USER_PIN_LOCKED; #endif diff --git a/tests/login_pin_state_test.c b/tests/login_pin_state_test.c index c6840aae..d4838cc6 100644 --- a/tests/login_pin_state_test.c +++ b/tests/login_pin_state_test.c @@ -43,6 +43,8 @@ #include #endif +#include + #include "testdata.h" #include "pkcs11_test_util.h" @@ -351,6 +353,132 @@ static void empty_pin_change_requires_login_test(void) } #endif +#if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_TIME) +#define LOCKOUT_THREADS 6 +#define LOCKOUT_ROUNDS 4 + +static const char* wrongPin = "not-the-right-pin"; + +typedef struct login_ctx { + CK_SESSION_HANDLE session; + CK_USER_TYPE type; + CK_RV rv; +} login_ctx; + +static pthread_mutex_t goMutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t goCond = PTHREAD_COND_INITIALIZER; +static int goFlag = 0; + +static void* wrong_login(void* arg) +{ + login_ctx* ctx = (login_ctx*)arg; + + pthread_mutex_lock(&goMutex); + while (!goFlag) + pthread_cond_wait(&goCond, &goMutex); + pthread_mutex_unlock(&goMutex); + + ctx->rv = funcList->C_Login(ctx->session, ctx->type, + (CK_UTF8CHAR_PTR)wrongPin, + (CK_ULONG)XSTRLEN(wrongPin)); + return NULL; +} + +/* Run simultaneous wrong-PIN logins, one per session. Returns 1 when every + * one of them was rejected as an incorrect PIN. */ +static int concurrent_wrong_logins(CK_SLOT_ID slot, CK_USER_TYPE type) +{ + int i; + int started = 0; + int rejected = 1; + pthread_t threads[LOCKOUT_THREADS]; + login_ctx ctx[LOCKOUT_THREADS]; + + goFlag = 0; + for (i = 0; i < LOCKOUT_THREADS; i++) { + ctx[i].session = CK_INVALID_HANDLE; + ctx[i].type = type; + ctx[i].rv = CKR_OK; + if (open_rw(slot, &ctx[i].session) != CKR_OK) + break; + if (pthread_create(&threads[i], NULL, wrong_login, &ctx[i]) != 0) { + funcList->C_CloseSession(ctx[i].session); + break; + } + started++; + } + pthread_mutex_lock(&goMutex); + goFlag = 1; + pthread_cond_broadcast(&goCond); + pthread_mutex_unlock(&goMutex); + for (i = 0; i < started; i++) { + pthread_join(threads[i], NULL); + funcList->C_CloseSession(ctx[i].session); + if (ctx[i].rv != CKR_PIN_INCORRECT) + rejected = 0; + } + CHECK_TRUE(started == LOCKOUT_THREADS, "start concurrent logins"); + return rejected; +} + +/* Failed logins that race each other still lock out the correct PIN. */ +static void lockout_after_concurrent_failures(CK_USER_TYPE type, + const char* pin, int maxFails, + const char* what) +{ + CK_RV rv = CKR_OK; + int i; + int round; + int locked = 1; + int rejected = 1; + CK_RV final; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + + for (round = 0; rv == CKR_OK && round < LOCKOUT_ROUNDS; round++) { + rv = token_setup(&slot, userPin); + if (rv == CKR_OK) + rv = open_rw(slot, &session); + for (i = 0; rv == CKR_OK && i < maxFails - 1; i++) { + rv = funcList->C_Login(session, type, (CK_UTF8CHAR_PTR)wrongPin, + (CK_ULONG)XSTRLEN(wrongPin)); + if (rv == CKR_PIN_INCORRECT) + rv = CKR_OK; + } + if (rv == CKR_OK) { + if (!concurrent_wrong_logins(slot, type)) + rejected = 0; + final = funcList->C_Login(session, type, (CK_UTF8CHAR_PTR)pin, + (CK_ULONG)XSTRLEN(pin)); + if (final != CKR_PIN_INCORRECT && final != CKR_PIN_LOCKED) + locked = 0; + if (final == CKR_OK) + funcList->C_Logout(session); + } + if (session != CK_INVALID_HANDLE) { + funcList->C_CloseSession(session); + session = CK_INVALID_HANDLE; + } + funcList->C_Finalize(NULL); + /* The lockout is persisted; start each round from a fresh token. */ + cleanup_test_files(); + } + CHECK_RV(rv, "set up failed logins", CKR_OK); + CHECK_TRUE(rejected, "concurrent wrong PINs rejected"); + CHECK_TRUE(locked, what); +} + +static void concurrent_login_lockout_test(void) +{ + printf("--- concurrent failed logins lock out the PIN ---\n"); + lockout_after_concurrent_failures(CKU_USER, userPin, + WP11_MAX_LOGIN_FAILS_USER, + "user PIN locked after racing failures"); + lockout_after_concurrent_failures(CKU_SO, soPin, WP11_MAX_LOGIN_FAILS_SO, + "SO PIN locked after racing failures"); +} +#endif + static int run_test(void) { CK_RV rv; @@ -366,6 +494,9 @@ static int run_test(void) #ifdef LOGIN_TEST_FILE_STORE failed_login_leaves_no_token_key_test(); #endif +#if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_TIME) + concurrent_login_lockout_test(); +#endif pkcs11_unload(); return 0; From 60f671449dd60dbfd60a1491251940473fb6d104 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:22:56 -0700 Subject: [PATCH 004/204] F-4962 - Apply the SO login lockout to C_SetPIN --- src/slot.c | 2 +- tests/login_pin_state_test.c | 51 ++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/slot.c b/src/slot.c index b8dcc429..aa6748b8 100644 --- a/src/slot.c +++ b/src/slot.c @@ -1494,7 +1494,7 @@ CK_RV C_SetPIN(CK_SESSION_HANDLE hSession, CK_UTF8CHAR_PTR pOldPin, slot = WP11_Session_GetSlot(session); if (state == WP11_APP_STATE_RW_SO) { - ret = WP11_Slot_CheckSOPin(slot, (char*)pOldPin, (int)ulOldLen); + ret = WP11_Slot_CheckSOPinLockout(slot, (char*)pOldPin, (int)ulOldLen); if (ret == PIN_NOT_SET_E) { rv = CKR_USER_PIN_NOT_INITIALIZED; WOLFPKCS11_LEAVE("C_SetPIN", rv); diff --git a/tests/login_pin_state_test.c b/tests/login_pin_state_test.c index d4838cc6..6cb4d591 100644 --- a/tests/login_pin_state_test.c +++ b/tests/login_pin_state_test.c @@ -72,6 +72,9 @@ static const char* soPin = "password123456"; static const char* userPin = "wolfpkcs11-test"; static const char tokenLabel[] = "login-pin-state"; +#ifndef WOLFPKCS11_NO_TIME +static const char* wrongOldPin = "wrong-old-pin"; +#endif /* Start from an unprovisioned token so earlier runs cannot affect this one. */ static void cleanup_test_files(void) @@ -353,6 +356,49 @@ static void empty_pin_change_requires_login_test(void) } #endif +#ifndef WOLFPKCS11_NO_TIME +/* Changing a PIN counts wrong old PINs toward the login lockout. */ +static void set_pin_lockout(CK_USER_TYPE type, const char* pin, int maxFails, + const char* what) +{ + CK_RV rv; + int i; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + const char* newPin = "replacement-pin"; + + rv = token_setup(&slot, userPin); + if (rv == CKR_OK) + rv = open_rw(slot, &session); + if (rv == CKR_OK && type == CKU_SO) { + rv = funcList->C_Login(session, CKU_SO, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin)); + } + CHECK_RV(rv, "token setup", CKR_OK); + if (rv != CKR_OK) { + funcList->C_Finalize(NULL); + return; + } + for (i = 0; i < maxFails; i++) { + rv = funcList->C_SetPIN(session, (CK_UTF8CHAR_PTR)wrongOldPin, + (CK_ULONG)XSTRLEN(wrongOldPin), + (CK_UTF8CHAR_PTR)newPin, + (CK_ULONG)XSTRLEN(newPin)); + CHECK_RV(rv, "C_SetPIN with wrong old PIN", CKR_PIN_INCORRECT); + } + rv = funcList->C_SetPIN(session, (CK_UTF8CHAR_PTR)pin, + (CK_ULONG)XSTRLEN(pin), (CK_UTF8CHAR_PTR)newPin, + (CK_ULONG)XSTRLEN(newPin)); + CHECK_TRUE(rv == CKR_PIN_INCORRECT || rv == CKR_PIN_LOCKED, what); + if (type == CKU_SO) + funcList->C_Logout(session); + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); + /* The lockout is persisted; start later tests from a fresh token. */ + cleanup_test_files(); +} +#endif + #if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_TIME) #define LOCKOUT_THREADS 6 #define LOCKOUT_ROUNDS 4 @@ -497,6 +543,11 @@ static int run_test(void) #if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_TIME) concurrent_login_lockout_test(); #endif +#ifndef WOLFPKCS11_NO_TIME + printf("--- C_SetPIN counts failed SO PIN checks ---\n"); + set_pin_lockout(CKU_SO, soPin, WP11_MAX_LOGIN_FAILS_SO, + "SO C_SetPIN locked after failures"); +#endif pkcs11_unload(); return 0; From 98bbcde8cf11f63735660ede824c3c8f1424690b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:30:10 -0700 Subject: [PATCH 005/204] F-6495 - Apply the user login lockout to C_SetPIN --- src/internal.c | 63 ++++++++++++++++++++++++++++++++++++ src/slot.c | 3 +- tests/login_pin_state_test.c | 3 ++ wolfpkcs11/internal.h | 2 ++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index eb6933f2..ae941d95 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8572,6 +8572,69 @@ int WP11_Slot_CheckUserPin(WP11_Slot* slot, char* pin, int pinLen) return ret; } +/** + * Check the user PIN with the failed-login lockout applied, but without + * logging in. Wrong PINs count toward the same WP11_MAX_LOGIN_FAILS_USER limit + * that WP11_Slot_UserLogin enforces. + * + * @param slot [in] Slot object. + * @param pin [in] PIN to check. + * @param pinLen [in] Length of PIN. + * @return PIN_NOT_SET_E when the user PIN is not set. + * PIN_INVALID_E when the PIN is not correct or the user is locked out. + * Other -ve value when hashing PIN fails. + * 0 when PIN is correct. + */ +int WP11_Slot_CheckUserPinLockout(WP11_Slot* slot, char* pin, int pinLen) +{ + int ret; +#ifndef WOLFPKCS11_NO_TIME + time_t now; + time_t allowed; + + if (wc_GetTime(&now, sizeof(now)) != 0) + return PIN_INVALID_E; + + /* Check for too many fails and whether the timeout has elapsed. */ + WP11_Lock_LockRW(&slot->lock); + if (slot->token.userFailedLogin >= WP11_MAX_LOGIN_FAILS_USER) { + allowed = slot->token.userLastFailedLogin + + slot->token.userFailLoginTimeout; + if (allowed < now) + slot->token.userFailedLogin = 0; + else { + WP11_Lock_UnlockRW(&slot->lock); + return PIN_INVALID_E; + } + } + WP11_Lock_UnlockRW(&slot->lock); +#endif + + ret = WP11_Slot_CheckUserPin(slot, pin, pinLen); + +#ifndef WOLFPKCS11_NO_TIME + WP11_Lock_LockRW(&slot->lock); + /* PIN failed - update failure info. */ + if (ret == PIN_INVALID_E && + slot->token.userFailedLogin < WP11_MAX_LOGIN_FAILS_USER) { + slot->token.userFailedLogin++; + if (slot->token.userFailedLogin == WP11_MAX_LOGIN_FAILS_USER) { + slot->token.userLastFailedLogin = now; + slot->token.userFailLoginTimeout += WP11_USER_LOGIN_FAIL_TIMEOUT; + } + } + /* Worked - clear failure info. */ + else if (ret == 0) { + slot->token.userFailedLogin = 0; + slot->token.userLastFailedLogin = 0; + slot->token.userFailLoginTimeout = 0; + } + WP11_Lock_UnlockRW(&slot->lock); +#endif + + return ret; +} + /** * Log the SO (Security Officer) into the token. * diff --git a/src/slot.c b/src/slot.c index aa6748b8..95938e00 100644 --- a/src/slot.c +++ b/src/slot.c @@ -1514,7 +1514,8 @@ CK_RV C_SetPIN(CK_SESSION_HANDLE hSession, CK_UTF8CHAR_PTR pOldPin, } } else { - ret = WP11_Slot_CheckUserPin(slot, (char*)pOldPin, (int)ulOldLen); + ret = WP11_Slot_CheckUserPinLockout(slot, (char*)pOldPin, + (int)ulOldLen); if (ret == PIN_NOT_SET_E) { rv = CKR_USER_PIN_NOT_INITIALIZED; WOLFPKCS11_LEAVE("C_SetPIN", rv); diff --git a/tests/login_pin_state_test.c b/tests/login_pin_state_test.c index 6cb4d591..787ef893 100644 --- a/tests/login_pin_state_test.c +++ b/tests/login_pin_state_test.c @@ -547,6 +547,9 @@ static int run_test(void) printf("--- C_SetPIN counts failed SO PIN checks ---\n"); set_pin_lockout(CKU_SO, soPin, WP11_MAX_LOGIN_FAILS_SO, "SO C_SetPIN locked after failures"); + printf("--- C_SetPIN counts failed user PIN checks ---\n"); + set_pin_lockout(CKU_USER, userPin, WP11_MAX_LOGIN_FAILS_USER, + "user C_SetPIN locked after failures"); #endif pkcs11_unload(); diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 09f8d7ed..313fe55c 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -404,6 +404,8 @@ WP11_LOCAL int WP11_Slot_CheckSOPin(WP11_Slot* slot, char* pin, int pinLen); WP11_LOCAL int WP11_Slot_CheckSOPinLockout(WP11_Slot* slot, char* pin, int pinLen); WP11_LOCAL int WP11_Slot_CheckUserPin(WP11_Slot* slot, char* pin, int pinLen); +WP11_LOCAL int WP11_Slot_CheckUserPinLockout(WP11_Slot* slot, char* pin, + int pinLen); WP11_LOCAL int WP11_Slot_Has_Empty_Pin(WP11_Slot* slot); WP11_LOCAL int WP11_Slot_SOPin_IsSet(WP11_Slot* slot); WP11_LOCAL int WP11_Slot_SOLogin(WP11_Slot* slot, char* pin, int pinLen); From 636184cb44c5280f229e817c89c17fce94dad9d4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:38:11 -0700 Subject: [PATCH 006/204] F-13244 - Decide and log out on last session close under one lock --- src/internal.c | 38 ++++++++++----- tests/login_pin_state_test.c | 94 ++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 13 deletions(-) diff --git a/src/internal.c b/src/internal.c index ae941d95..78a12125 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8178,6 +8178,8 @@ int WP11_Slot_OpenSession(WP11_Slot* slot, unsigned long flags, void* app, return ret; } +static void wp11_Slot_Logout(WP11_Slot* slot); + /** * Close a session associated with a slot. * @@ -8216,18 +8218,18 @@ void WP11_Slot_CloseSession(WP11_Slot* slot, WP11_Session* session) wp11_Slot_FreeSession(slot, session); else wp11_Session_Final(session); - WP11_Lock_UnlockRW(&slot->lock); - WP11_Lock_LockRO(&slot->lock); + /* Decide and log out under the same lock so a concurrent open is either + * seen here or opens after the logout. */ for (curr = slot->session; curr != NULL; curr = curr->next) { if (curr->inUse) { noMore = 0; break; } } - WP11_Lock_UnlockRO(&slot->lock); if (noMore) - WP11_Slot_Logout(slot); + wp11_Slot_Logout(slot); + WP11_Lock_UnlockRW(&slot->lock); } /** @@ -8252,13 +8254,10 @@ void WP11_Slot_CloseSessions(WP11_Slot* slot) /* Finalize the rest. */ for (curr = slot->session; curr != NULL; curr = curr->next) wp11_Session_Final(curr); - WP11_Lock_UnlockRW(&slot->lock); - /* PKCS#11: closing an application's last session with a token logs the - * application out. Mirror the single-session close path and reset the - * token login state (outside the slot lock, as WP11_Slot_Logout takes it). - */ - WP11_Slot_Logout(slot); + * application out. Done under the same lock as the close. */ + wp11_Slot_Logout(slot); + WP11_Lock_UnlockRW(&slot->lock); } /** @@ -9019,15 +9018,18 @@ int WP11_Slot_IsUserLoggedIn(WP11_Slot* slot) return wp11_LoginStateIsUser(state); } -void WP11_Slot_Logout(WP11_Slot* slot) +/** + * Logout of the token. Caller holds the slot lock for writing. + * + * @param slot [in] Slot object referencing token. + */ +static void wp11_Slot_Logout(WP11_Slot* slot) { #ifndef WOLFPKCS11_NO_STORE int state; int ret = 0; #endif - WP11_Lock_LockRW(&slot->lock); - #ifndef WOLFPKCS11_NO_STORE state = slot->token.loginState; if (state == WP11_APP_STATE_RO_USER || state == WP11_APP_STATE_RW_USER) { @@ -9042,7 +9044,17 @@ void WP11_Slot_Logout(WP11_Slot* slot) } #endif slot->token.loginState = WP11_APP_STATE_RW_PUBLIC; +} +/** + * Logout of the token. + * + * @param slot [in] Slot object referencing token. + */ +void WP11_Slot_Logout(WP11_Slot* slot) +{ + WP11_Lock_LockRW(&slot->lock); + wp11_Slot_Logout(slot); WP11_Lock_UnlockRW(&slot->lock); } diff --git a/tests/login_pin_state_test.c b/tests/login_pin_state_test.c index 787ef893..a964f9c2 100644 --- a/tests/login_pin_state_test.c +++ b/tests/login_pin_state_test.c @@ -525,6 +525,97 @@ static void concurrent_login_lockout_test(void) } #endif +#ifndef SINGLE_THREADED +#define CLOSE_OPEN_ROUNDS 10 + +static pthread_mutex_t openMutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t openCond = PTHREAD_COND_INITIALIZER; +static int openFlag = 0; + +typedef struct open_ctx { + CK_SLOT_ID slot; + CK_SESSION_HANDLE session; + CK_STATE firstState; + CK_STATE lastState; + CK_RV rv; +} open_ctx; + +static void* open_during_close(void* arg) +{ + open_ctx* ctx = (open_ctx*)arg; + CK_SESSION_INFO info; + + pthread_mutex_lock(&openMutex); + while (!openFlag) + pthread_cond_wait(&openCond, &openMutex); + pthread_mutex_unlock(&openMutex); + + ctx->rv = open_rw(ctx->slot, &ctx->session); + if (ctx->rv == CKR_OK) + ctx->rv = funcList->C_GetSessionInfo(ctx->session, &info); + if (ctx->rv == CKR_OK) + ctx->firstState = info.state; + return NULL; +} + +/* A session opened while another one closes is never logged out after + * C_OpenSession reported it as logged in. */ +static void close_last_session_race_test(void) +{ + CK_RV rv; + int i; + int consistent = 1; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_SESSION_INFO info; + pthread_t thread; + open_ctx ctx; + + printf("--- closing the last session races an open ---\n"); + rv = token_setup(&slot, userPin); + CHECK_RV(rv, "token setup", CKR_OK); + for (i = 0; rv == CKR_OK && i < CLOSE_OPEN_ROUNDS; i++) { + rv = open_rw(slot, &session); + if (rv == CKR_OK) + rv = user_login(session, userPin); + if (rv != CKR_OK) + break; + + XMEMSET(&ctx, 0, sizeof(ctx)); + XMEMSET(&info, 0, sizeof(info)); + ctx.slot = slot; + ctx.session = CK_INVALID_HANDLE; + openFlag = 0; + if (pthread_create(&thread, NULL, open_during_close, &ctx) != 0) { + rv = CKR_GENERAL_ERROR; + break; + } + pthread_mutex_lock(&openMutex); + openFlag = 1; + pthread_cond_broadcast(&openCond); + pthread_mutex_unlock(&openMutex); + rv = funcList->C_CloseSession(session); + pthread_join(thread, NULL); + if (rv == CKR_OK) + rv = ctx.rv; + if (rv == CKR_OK) + rv = funcList->C_GetSessionInfo(ctx.session, &info); + if (rv == CKR_OK && ctx.firstState == CKS_RW_USER_FUNCTIONS && + info.state != CKS_RW_USER_FUNCTIONS) { + consistent = 0; + } + if (ctx.session != CK_INVALID_HANDLE) { + if (info.state == CKS_RW_USER_FUNCTIONS) + funcList->C_Logout(ctx.session); + funcList->C_CloseSession(ctx.session); + } + } + CHECK_RV(rv, "open and close sessions concurrently", CKR_OK); + CHECK_TRUE(consistent, "opened session keeps its reported login state"); + funcList->C_Finalize(NULL); +} +#endif + static int run_test(void) { CK_RV rv; @@ -540,6 +631,9 @@ static int run_test(void) #ifdef LOGIN_TEST_FILE_STORE failed_login_leaves_no_token_key_test(); #endif +#ifndef SINGLE_THREADED + close_last_session_race_test(); +#endif #if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_TIME) concurrent_login_lockout_test(); #endif From 5019387febf20acd7c06bf7b8874d59adc8b1ffa Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:34:40 -0700 Subject: [PATCH 007/204] F-5253 - Hold the token lock while walking session objects in find --- src/internal.c | 11 ++--- tests/login_pin_state_test.c | 84 ++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/src/internal.c b/src/internal.c index 78a12125..b4bd0159 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10595,15 +10595,11 @@ static WP11_Object* wp11_Session_FindNext(WP11_Session* session, int onToken, * C_Login and enumerates private keys (e.g. certutil) from a public * session - matching the by-handle WP11_Object_Find check below. */ if ((ret->opFlag & WP11_FLAG_PRIVATE) == WP11_FLAG_PRIVATE) { - if (!onToken) - WP11_Lock_LockRO(&session->slot->token.lock); if (!wp11_LoginStateIsUser( session->slot->token.loginState)) { object = ret; ret = NULL; } - if (!onToken) - WP11_Lock_UnlockRO(&session->slot->token.lock); } #endif } @@ -10660,8 +10656,8 @@ int WP11_Session_Find(WP11_Session* session, int onToken, int i; CK_ATTRIBUTE* attr; - if (onToken) - WP11_Lock_LockRO(&session->slot->token.lock); + /* Session object lists change under the token lock too. */ + WP11_Lock_LockRO(&session->slot->token.lock); while (ret == 0 && (obj = wp11_Session_FindNext(session, onToken, obj)) != NULL) { for (i = 0; i < (int)ulCount; i++) { @@ -10675,8 +10671,7 @@ int WP11_Session_Find(WP11_Session* session, int onToken, if (i == (int)ulCount) ret = wp11_Session_FindMatched(session, obj); } - if (onToken) - WP11_Lock_UnlockRO(&session->slot->token.lock); + WP11_Lock_UnlockRO(&session->slot->token.lock); return ret; } diff --git a/tests/login_pin_state_test.c b/tests/login_pin_state_test.c index a964f9c2..bd60a9aa 100644 --- a/tests/login_pin_state_test.c +++ b/tests/login_pin_state_test.c @@ -616,6 +616,87 @@ static void close_last_session_race_test(void) } #endif +#ifndef SINGLE_THREADED +#define CHURN_ROUNDS 200 + +typedef struct churn_ctx { + CK_SESSION_HANDLE session; + CK_RV rv; +} churn_ctx; + +static void* churn_session_objects(void* arg) +{ + churn_ctx* ctx = (churn_ctx*)arg; + int i; + CK_OBJECT_HANDLE obj; + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_BBOOL no = CK_FALSE; + byte value[4] = { 5, 6, 7, 8 }; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &no, sizeof(no) }, + { CKA_VALUE, value, sizeof(value) }, + }; + + for (i = 0; ctx->rv == CKR_OK && i < CHURN_ROUNDS; i++) { + ctx->rv = funcList->C_CreateObject(ctx->session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); + if (ctx->rv == CKR_OK) + ctx->rv = funcList->C_DestroyObject(ctx->session, obj); + } + return NULL; +} + +/* Finding session objects is safe while session objects come and go. */ +static void find_during_object_churn_test(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE found[8]; + CK_ULONG cnt; + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_BBOOL no = CK_FALSE; + CK_ATTRIBUTE findTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &no, sizeof(no) }, + }; + pthread_t thread; + churn_ctx ctx; + int i; + + printf("--- find session objects while they change ---\n"); + rv = token_setup(&slot, userPin); + if (rv == CKR_OK) + rv = open_rw(slot, &session); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv != CKR_OK) { + funcList->C_Finalize(NULL); + return; + } + ctx.session = session; + ctx.rv = CKR_OK; + if (pthread_create(&thread, NULL, churn_session_objects, &ctx) != 0) { + CHECK_TRUE(0, "create object churn thread"); + funcList->C_Finalize(NULL); + return; + } + for (i = 0; rv == CKR_OK && i < CHURN_ROUNDS; i++) { + rv = funcList->C_FindObjectsInit(session, findTmpl, 2); + if (rv == CKR_OK) { + rv = funcList->C_FindObjects(session, found, 8, &cnt); + funcList->C_FindObjectsFinal(session); + } + } + pthread_join(thread, NULL); + CHECK_RV(rv, "find session objects concurrently", CKR_OK); + CHECK_RV(ctx.rv, "create and destroy session objects concurrently", + CKR_OK); + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + static int run_test(void) { CK_RV rv; @@ -637,6 +718,9 @@ static int run_test(void) #if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_TIME) concurrent_login_lockout_test(); #endif +#ifndef SINGLE_THREADED + find_during_object_churn_test(); +#endif #ifndef WOLFPKCS11_NO_TIME printf("--- C_SetPIN counts failed SO PIN checks ---\n"); set_pin_lockout(CKU_SO, soPin, WP11_MAX_LOGIN_FAILS_SO, From da70ef3edf44a0f5f9cb42003fb0598a981f0048 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:57:25 -0700 Subject: [PATCH 008/204] F-8663 - Protect every token object on logout despite a failure --- src/internal.c | 40 +++++++++++-- tests/login_pin_state_test.c | 113 +++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 5 deletions(-) diff --git a/src/internal.c b/src/internal.c index b4bd0159..e96c279a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -7067,6 +7067,31 @@ static void wp11_Object_Scrub(WP11_Object* object) } } +/** + * Encrypt the token's decoded objects with the current token key. + * + * @param [in, out] token Token object. + * @param [in] protect Unencrypted private key data is cleared. + * @return 0 on success. + * @return -ve on failure of any object. + */ +static int wp11_Token_EncodeObjects(WP11_Token* token, int protect) +{ + int ret = 0; + int err; + WP11_Object* object; + + for (object = token->object; object != NULL; object = object->next) { + if (wp11_Object_IsEncrypted(object) && !object->encoded) { + err = wp11_Object_Encode(object, protect); + if (ret == 0) + ret = err; + } + } + + return ret; +} + /** * Unstore a key object to storage. * @@ -9027,16 +9052,21 @@ static void wp11_Slot_Logout(WP11_Slot* slot) { #ifndef WOLFPKCS11_NO_STORE int state; - int ret = 0; + WP11_Object* object; #endif #ifndef WOLFPKCS11_NO_STORE state = slot->token.loginState; if (state == WP11_APP_STATE_RO_USER || state == WP11_APP_STATE_RW_USER) { - WP11_Object* object = slot->token.object; - while (ret == 0 && object != NULL) { - ret = wp11_Object_Encode(object, 1); - object = object->next; + /* Protect every decoded object; one failure must not stop the rest. */ + if (wp11_Token_EncodeObjects(&slot->token, 1) != 0) { + WOLFPKCS11_MSG("Logout: failed to protect a token object"); + /* Never let a later store encrypt it under the zeroed key. */ + for (object = slot->token.object; object != NULL; + object = object->next) { + if (wp11_Object_IsEncrypted(object) && !object->encoded) + wp11_Object_Scrub(object); + } } /* Zero token key only on user logout — SO logout must preserve it * for subsequent object encryption (e.g., empty-PIN flow). */ diff --git a/tests/login_pin_state_test.c b/tests/login_pin_state_test.c index bd60a9aa..b8548a1b 100644 --- a/tests/login_pin_state_test.c +++ b/tests/login_pin_state_test.c @@ -250,6 +250,116 @@ static void failed_login_leaves_no_token_key_test(void) } #endif +#if defined(LOGIN_TEST_FILE_STORE) && defined(HAVE_ECC) +static char ecLabel[] = "pin-state-ec-key"; +static CK_BYTE ecP256Params[] = { + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 +}; + +static CK_RV create_ec_key(CK_SESSION_HANDLE session) +{ + CK_MECHANISM mech = { CKM_EC_KEY_PAIR_GEN, NULL, 0 }; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_EC_PARAMS, ecP256Params, sizeof(ecP256Params) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_LABEL, ecLabel, sizeof(ecLabel) - 1 }, + }; + + return funcList->C_GenerateKeyPair(session, &mech, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &pub, &priv); +} + +/* Flip a byte of the stored EC private key so it no longer authenticates. */ +static int damage_stored_ec_key(void) +{ + FILE* f; + char filepath[512]; + int id; + int ret = -1; + int c; + + for (id = 0; id < 3 && ret != 0; id++) { + snprintf(filepath, sizeof(filepath), "%s" PATH_SEP + "wp11_ecckey_priv_0000000000000001_%016x", TEST_DIR, id); + f = fopen(filepath, "r+b"); + if (f == NULL) + continue; + if (fseek(f, -1, SEEK_END) == 0 && (c = fgetc(f)) != EOF && + fseek(f, -1, SEEK_END) == 0 && fputc(c ^ 0xff, f) != EOF) { + ret = 0; + } + if (fclose(f) != 0) + ret = -1; + } + return ret; +} + +/* Logout protects every decoded token object even when one cannot be. */ +static void logout_protects_all_objects_test(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_VALUE, aesValue, sizeof(aesValue) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + }; + + printf("--- logout protects all token objects ---\n"); + rv = token_setup(&slot, userPin); + if (rv == CKR_OK) + rv = open_rw(slot, &session); + if (rv == CKR_OK) + rv = user_login(session, userPin); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &obj); + } + if (rv == CKR_OK) + rv = create_ec_key(session); + CHECK_RV(rv, "create private token keys", CKR_OK); + if (session != CK_INVALID_HANDLE) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + session = CK_INVALID_HANDLE; + } + funcList->C_Finalize(NULL); + if (rv != CKR_OK) + return; + + CHECK_TRUE(damage_stored_ec_key() == 0, "damage stored EC key"); + rv = lib_init(); + if (rv == CKR_OK) + rv = open_rw(slot, &session); + if (rv == CKR_OK) + rv = user_login(session, userPin); + CHECK_RV(rv, "login with one undecodable object", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(WP11_Slot_TokenDecodedObjectCount(slot) > 0, + "objects decoded while logged in"); + funcList->C_Logout(session); + CHECK_TRUE(WP11_Slot_TokenDecodedObjectCount(slot) == 0, + "no decoded objects left after logout"); + } + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); + cleanup_test_files(); +} +#endif + #ifndef SINGLE_THREADED #define EMPTY_PIN_ROUNDS 8 @@ -721,6 +831,9 @@ static int run_test(void) #ifndef SINGLE_THREADED find_during_object_churn_test(); #endif +#if defined(LOGIN_TEST_FILE_STORE) && defined(HAVE_ECC) + logout_protects_all_objects_test(); +#endif #ifndef WOLFPKCS11_NO_TIME printf("--- C_SetPIN counts failed SO PIN checks ---\n"); set_pin_lockout(CKU_SO, soPin, WP11_MAX_LOGIN_FAILS_SO, From 4d9ff63c537402ebc96ef7685ce98ef18bf5c554 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:02:55 -0700 Subject: [PATCH 009/204] F-8642 - Stop token reset when token initialization fails --- src/internal.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/internal.c b/src/internal.c index e96c279a..52d8ecdd 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8403,15 +8403,17 @@ int WP11_Slot_TokenReset(WP11_Slot* slot, char* pin, int pinLen, char* label) /* Zeroizes token. */ token = &slot->token; wp11_Token_Final(token); - wp11_Token_Init(token, label); + ret = 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; + if (ret == 0) + token->state = WP11_TOKEN_STATE_INITIALIZED; WP11_Lock_UnlockRW(&slot->lock); /* Locking used in setting SO PIN. */ - ret = WP11_Slot_SetSOPin(slot, pin, pinLen); + if (ret == 0) + ret = WP11_Slot_SetSOPin(slot, pin, pinLen); return ret; } From 983f4545a68af7f0974ed9718b2ec43d9b9a97d7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:18:19 -0700 Subject: [PATCH 010/204] F-4958 - Read login state under the slot lock when finding objects --- src/internal.c | 45 +++++-- tests/login_pin_state_test.c | 239 +++++++++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 1 + 3 files changed, 276 insertions(+), 9 deletions(-) diff --git a/src/internal.c b/src/internal.c index 52d8ecdd..a68580bd 100644 --- a/src/internal.c +++ b/src/internal.c @@ -9057,6 +9057,8 @@ static void wp11_Slot_Logout(WP11_Slot* slot) WP11_Object* object; #endif + /* Object state is guarded by the token lock. */ + WP11_Lock_LockRW(&slot->token.lock); #ifndef WOLFPKCS11_NO_STORE state = slot->token.loginState; if (state == WP11_APP_STATE_RO_USER || state == WP11_APP_STATE_RW_USER) { @@ -9076,6 +9078,7 @@ static void wp11_Slot_Logout(WP11_Slot* slot) } #endif slot->token.loginState = WP11_APP_STATE_RW_PUBLIC; + WP11_Lock_UnlockRW(&slot->token.lock); } /** @@ -10589,7 +10592,7 @@ int WP11_Session_FindInit(WP11_Session* session) * @return The next object in session or token. */ static WP11_Object* wp11_Session_FindNext(WP11_Session* session, int onToken, - WP11_Object* object) + WP11_Object* object, int userLoggedIn) { WP11_Object* ret = NULL; @@ -10626,13 +10629,13 @@ static WP11_Object* wp11_Session_FindNext(WP11_Session* session, int onToken, * mode, which operates as the internal crypto module without calling * C_Login and enumerates private keys (e.g. certutil) from a public * session - matching the by-handle WP11_Object_Find check below. */ - if ((ret->opFlag & WP11_FLAG_PRIVATE) == WP11_FLAG_PRIVATE) { - if (!wp11_LoginStateIsUser( - session->slot->token.loginState)) { - object = ret; - ret = NULL; - } + if ((ret->opFlag & WP11_FLAG_PRIVATE) == WP11_FLAG_PRIVATE && + !userLoggedIn) { + object = ret; + ret = NULL; } +#else + (void)userLoggedIn; #endif } @@ -10672,6 +10675,20 @@ static int wp11_Session_FindMatched(WP11_Session* session, WP11_Object* object) return ret; } +#ifdef DEBUG_WOLFPKCS11 +static void (*wp11_findHook)(void) = NULL; + +/** + * Test hook: called by WP11_Session_Find once it has read the login state. + * + * @param hook [in] Function to call, or NULL to remove the hook. + */ +WP11_API void WP11_Session_SetFindHook(void (*hook)(void)) +{ + wp11_findHook = hook; +} +#endif + /** * Find objects on session or token with attributes matching template. * @@ -10686,12 +10703,21 @@ int WP11_Session_Find(WP11_Session* session, int onToken, WP11_Object* obj = NULL; int ret = 0; int i; + int userLoggedIn; CK_ATTRIBUTE* attr; + /* Slot then token lock, held across the walk so logout cannot race it. */ + WP11_Lock_LockRO(&session->slot->lock); + userLoggedIn = wp11_LoginStateIsUser(session->slot->token.loginState); +#ifdef DEBUG_WOLFPKCS11 + if (wp11_findHook != NULL) + wp11_findHook(); +#endif + /* Session object lists change under the token lock too. */ WP11_Lock_LockRO(&session->slot->token.lock); - while (ret == 0 && - (obj = wp11_Session_FindNext(session, onToken, obj)) != NULL) { + while (ret == 0 && (obj = wp11_Session_FindNext(session, onToken, obj, + userLoggedIn)) != NULL) { for (i = 0; i < (int)ulCount; i++) { attr = &pTemplate[i]; if (!WP11_Object_MatchAttr(obj, attr->type, (byte*)attr->pValue, @@ -10704,6 +10730,7 @@ int WP11_Session_Find(WP11_Session* session, int onToken, ret = wp11_Session_FindMatched(session, obj); } WP11_Lock_UnlockRO(&session->slot->token.lock); + WP11_Lock_UnlockRO(&session->slot->lock); return ret; } diff --git a/tests/login_pin_state_test.c b/tests/login_pin_state_test.c index b8548a1b..a0c96b96 100644 --- a/tests/login_pin_state_test.c +++ b/tests/login_pin_state_test.c @@ -50,6 +50,7 @@ #ifndef SINGLE_THREADED #include +#include #endif #define TEST_DIR "./store/login_pin_state_test" @@ -807,6 +808,236 @@ static void find_during_object_churn_test(void) } #endif +#if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_STORE) && \ + !defined(NO_AES) +#define FIND_LOGIN_ROUNDS 3 + +static pthread_mutex_t findMutex = PTHREAD_MUTEX_INITIALIZER; +static int findStop = 0; + +typedef struct find_ctx { + CK_SESSION_HANDLE session; + CK_RV rv; +} find_ctx; + +static int find_should_stop(void) +{ + int stop; + + pthread_mutex_lock(&findMutex); + stop = findStop; + pthread_mutex_unlock(&findMutex); + return stop; +} + +static void find_set_stop(int stop) +{ + pthread_mutex_lock(&findMutex); + findStop = stop; + pthread_mutex_unlock(&findMutex); +} + +static void* find_loop(void* arg) +{ + find_ctx* ctx = (find_ctx*)arg; + CK_OBJECT_HANDLE found[8]; + CK_ULONG cnt; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + }; + + while (ctx->rv == CKR_OK && !find_should_stop()) { + ctx->rv = funcList->C_FindObjectsInit(ctx->session, tmpl, 1); + if (ctx->rv == CKR_OK) { + ctx->rv = funcList->C_FindObjects(ctx->session, found, 8, &cnt); + funcList->C_FindObjectsFinal(ctx->session); + } + } + return NULL; +} + +/* Finding objects stays well behaved while the user logs in and out, and + * private objects are not listed once logged out. */ +static void find_during_login_changes_test(void) +{ + CK_RV rv; + int i; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_SESSION_HANDLE findSession = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE found[8]; + CK_ULONG cnt = 0; + pthread_t thread; + find_ctx ctx; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_VALUE, aesValue, sizeof(aesValue) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + }; + CK_ATTRIBUTE findTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + }; + + printf("--- find objects while logging in and out ---\n"); + rv = token_setup(&slot, userPin); + if (rv == CKR_OK) + rv = open_rw(slot, &session); + if (rv == CKR_OK) + rv = open_rw(slot, &findSession); + if (rv == CKR_OK) + rv = user_login(session, userPin); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &obj); + } + CHECK_RV(rv, "create private token key", CKR_OK); + if (rv != CKR_OK) { + funcList->C_Finalize(NULL); + return; + } + + ctx.session = findSession; + ctx.rv = CKR_OK; + find_set_stop(0); + if (pthread_create(&thread, NULL, find_loop, &ctx) != 0) { + CHECK_TRUE(0, "create find thread"); + funcList->C_Finalize(NULL); + return; + } + for (i = 0; rv == CKR_OK && i < FIND_LOGIN_ROUNDS; i++) { + rv = funcList->C_Logout(session); + if (rv == CKR_OK) + rv = user_login(session, userPin); + } + if (rv == CKR_OK) + rv = funcList->C_Logout(session); + find_set_stop(1); + pthread_join(thread, NULL); + CHECK_RV(rv, "log in and out while finding", CKR_OK); + CHECK_RV(ctx.rv, "find objects concurrently", CKR_OK); + + rv = funcList->C_FindObjectsInit(findSession, findTmpl, 1); + if (rv == CKR_OK) { + rv = funcList->C_FindObjects(findSession, found, 8, &cnt); + funcList->C_FindObjectsFinal(findSession); + } + CHECK_TRUE(rv == CKR_OK && cnt == 0, + "private object not listed after logout"); + funcList->C_Finalize(NULL); +} +#endif + +#if defined(DEBUG_WOLFPKCS11) && !defined(SINGLE_THREADED) && \ + !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_NSS) +#define FIND_HOOK_WAIT_MS 200 + +static pthread_mutex_t hookMutex = PTHREAD_MUTEX_INITIALIZER; +static CK_SESSION_HANDLE hookSession = CK_INVALID_HANDLE; +static pthread_t hookThread; +static int hookStarted = 0; +static int hookLogoutDone = 0; +static int hookLogoutBeforeWalk = 0; + +static int hook_logout_done(void) +{ + int done; + + pthread_mutex_lock(&hookMutex); + done = hookLogoutDone; + pthread_mutex_unlock(&hookMutex); + return done; +} + +static void* hook_logout(void* arg) +{ + (void)arg; + (void)funcList->C_Logout(hookSession); + pthread_mutex_lock(&hookMutex); + hookLogoutDone = 1; + pthread_mutex_unlock(&hookMutex); + return NULL; +} + +/* Runs in C_FindObjectsInit between the login state read and the walk. */ +static void find_hook_logout(void) +{ + int i; + + if (hookStarted) + return; + hookStarted = (pthread_create(&hookThread, NULL, hook_logout, NULL) == 0); + for (i = 0; hookStarted && i < FIND_HOOK_WAIT_MS && !hook_logout_done(); + i++) { + usleep(1000); + } + hookLogoutBeforeWalk = hook_logout_done(); +} + +/* A logout racing a find must not let the walk list private objects. */ +static void find_with_racing_logout_test(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_SESSION_HANDLE findSession = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE found[4]; + CK_ULONG cnt = 0; + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_BBOOL isTrue = CK_TRUE; + static byte privData[] = "private data value"; + CK_ATTRIBUTE dataTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &isTrue, sizeof(isTrue) }, + { CKA_PRIVATE, &isTrue, sizeof(isTrue) }, + { CKA_VALUE, privData, sizeof(privData) - 1 }, + }; + CK_ATTRIBUTE findTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + }; + + printf("--- find with a logout racing the object walk ---\n"); + rv = token_setup(&slot, userPin); + if (rv == CKR_OK) + rv = open_rw(slot, &session); + if (rv == CKR_OK) + rv = open_rw(slot, &findSession); + if (rv == CKR_OK) + rv = user_login(session, userPin); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, dataTmpl, + sizeof(dataTmpl) / sizeof(*dataTmpl), &obj); + } + CHECK_RV(rv, "create private token data object", CKR_OK); + if (rv == CKR_OK) { + hookSession = session; + hookStarted = 0; + hookLogoutBeforeWalk = 0; + pthread_mutex_lock(&hookMutex); + hookLogoutDone = 0; + pthread_mutex_unlock(&hookMutex); + WP11_Session_SetFindHook(find_hook_logout); + rv = funcList->C_FindObjectsInit(findSession, findTmpl, 1); + WP11_Session_SetFindHook(NULL); + if (hookStarted) + pthread_join(hookThread, NULL); + if (rv == CKR_OK) { + rv = funcList->C_FindObjects(findSession, found, 4, &cnt); + funcList->C_FindObjectsFinal(findSession); + } + CHECK_RV(rv, "find objects with a racing logout", CKR_OK); + CHECK_TRUE(hookStarted && hook_logout_done(), "racing logout ran"); + CHECK_TRUE(!(hookLogoutBeforeWalk && cnt > 0), + "walk after logout lists no private object"); + } + funcList->C_Finalize(NULL); + cleanup_test_files(); +} +#endif + static int run_test(void) { CK_RV rv; @@ -831,6 +1062,14 @@ static int run_test(void) #ifndef SINGLE_THREADED find_during_object_churn_test(); #endif +#if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_STORE) && \ + !defined(NO_AES) + find_during_login_changes_test(); +#endif +#if defined(DEBUG_WOLFPKCS11) && !defined(SINGLE_THREADED) && \ + !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_NSS) + find_with_racing_logout_test(); +#endif #if defined(LOGIN_TEST_FILE_STORE) && defined(HAVE_ECC) logout_protects_all_objects_test(); #endif diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 313fe55c..de846042 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -416,6 +416,7 @@ WP11_LOCAL void WP11_Slot_Logout(WP11_Slot* slot); #ifdef DEBUG_WOLFPKCS11 WP11_API int WP11_Slot_TokenKeyIsZero(CK_SLOT_ID slotId); WP11_API int WP11_Slot_TokenDecodedObjectCount(CK_SLOT_ID slotId); +WP11_API void WP11_Session_SetFindHook(void (*hook)(void)); #if defined(WOLFPKCS11_TPM) && (!defined(NO_RSA) || defined(HAVE_ECC)) WP11_API int WP11_Test_DecodeTpmKey(CK_SLOT_ID slotId, unsigned char* keyData, int keyDataLen); From bff74d775ac64d4a714fe1f85f5b250ce4c33248 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:27:26 -0700 Subject: [PATCH 011/204] F-4267 - Read object flags under the token lock in object lookup --- src/internal.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index a68580bd..c83eb5ba 100644 --- a/src/internal.c +++ b/src/internal.c @@ -11885,6 +11885,9 @@ int WP11_Object_Find(WP11_Session* session, CK_OBJECT_HANDLE objHandle, { int ret = BAD_FUNC_ARG; WP11_Object* obj; +#ifndef WOLFPKCS11_NSS + word32 opFlag = 0; +#endif #ifdef WOLFPKCS11_NSS WP11_Session* scan; #endif @@ -11906,6 +11909,9 @@ int WP11_Object_Find(WP11_Session* session, CK_OBJECT_HANDLE objHandle, obj = session->object; while (obj != NULL) { if (obj->handle == objHandle) { + #ifndef WOLFPKCS11_NSS + opFlag = obj->opFlag; + #endif ret = 0; break; } @@ -11936,6 +11942,9 @@ int WP11_Object_Find(WP11_Session* session, CK_OBJECT_HANDLE objHandle, obj = session->slot->token.object; while (obj != NULL) { if (obj->handle == objHandle) { + #ifndef WOLFPKCS11_NSS + opFlag = obj->opFlag; + #endif ret = 0; break; } @@ -11944,12 +11953,14 @@ int WP11_Object_Find(WP11_Session* session, CK_OBJECT_HANDLE objHandle, WP11_Lock_UnlockRO(&session->slot->token.lock); } - if (ret == 0 && obj != NULL && (obj->handle == objHandle)) { + /* The flags were captured under the token lock: the object may be freed + * by a concurrent destroy once the lock is released. */ + if (ret == 0 && obj != NULL) { #ifndef WOLFPKCS11_NSS /* Enforce CKA_PRIVATE: reject private objects from public sessions. * Skipped in NSS mode because NSS operates as the internal crypto * module without calling C_Login. */ - if ((obj->opFlag & WP11_FLAG_PRIVATE) == WP11_FLAG_PRIVATE) { + if ((opFlag & WP11_FLAG_PRIVATE) == WP11_FLAG_PRIVATE) { int loginState; WP11_Lock_LockRO(&session->slot->lock); loginState = session->slot->token.loginState; From 130c882b6f057bc6c89ff6f8bc37d34c2abd91e4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:37:52 -0700 Subject: [PATCH 012/204] F-11509 - Invalidate private session objects on logout --- src/crypto.c | 657 +++++++++++++++++++++++++++++------ src/internal.c | 365 ++++++++++++++++++- tests/include.am | 7 + tests/login_pin_state_test.c | 116 +++++++ tests/logout_retire_test.c | 643 ++++++++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 6 + 6 files changed, 1691 insertions(+), 103 deletions(-) create mode 100644 tests/logout_retire_test.c diff --git a/src/crypto.c b/src/crypto.c index 32ad7e0f..335bb181 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1572,8 +1572,9 @@ static CK_RV CreateObject(WP11_Session* session, CK_ATTRIBUTE_PTR pTemplate, * CKR_FUNCTION_FAILED when creating the object fails. * CKR_OK on success. */ -CK_RV C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, - CK_ULONG ulCount, CK_OBJECT_HANDLE_PTR phObject) +static CK_RV wp11_C_CreateObject(CK_SESSION_HANDLE hSession, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, + CK_OBJECT_HANDLE_PTR phObject) { CK_RV rv; WP11_Session* session; @@ -1652,6 +1653,18 @@ CK_RV C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, return rv; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulCount, CK_OBJECT_HANDLE_PTR phObject) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_CreateObject(hSession, pTemplate, ulCount, phObject); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Copy the object in the session or on the token associated with the session. * @@ -1675,9 +1688,10 @@ CK_RV C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, * CK_UNAVAILABLE_INFORMATION when an attribute type is not supported. * CKR_OK on success. */ -CK_RV C_CopyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, - CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, - CK_OBJECT_HANDLE_PTR phNewObject) +static CK_RV wp11_C_CopyObject(CK_SESSION_HANDLE hSession, + CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, + CK_OBJECT_HANDLE_PTR phNewObject) { int ret; CK_RV rv; @@ -1831,6 +1845,19 @@ CK_RV C_CopyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_CopyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, + CK_OBJECT_HANDLE_PTR phNewObject) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_CopyObject(hSession, hObject, pTemplate, ulCount, phNewObject); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Destroy object in session or on token. * @@ -1842,8 +1869,8 @@ CK_RV C_CopyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, * CKR_OBJECT_HANDLE_INVALID when handle is not to a valid object. * CKR_OK on success. */ -CK_RV C_DestroyObject(CK_SESSION_HANDLE hSession, - CK_OBJECT_HANDLE hObject) +static CK_RV wp11_C_DestroyObject(CK_SESSION_HANDLE hSession, + CK_OBJECT_HANDLE hObject) { int ret; int onToken; @@ -1911,7 +1938,8 @@ CK_RV C_DestroyObject(CK_SESSION_HANDLE hSession, /* Drop any active-operation reference to this object before freeing it so a * pending operation cannot use freed memory. */ WP11_Slot_ClearActiveObject(WP11_Session_GetSlot(session), obj); - WP11_Object_Free(obj); + /* Freed once no call that looked the object up can still be using it. */ + WP11_Slot_DiscardObject(WP11_Session_GetSlot(session), obj); /* The object was unlinked; a negative status means persisting the token * afterwards failed. Surface it rather than reporting success. */ @@ -1920,6 +1948,18 @@ CK_RV C_DestroyObject(CK_SESSION_HANDLE hSession, return rv; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_DestroyObject(CK_SESSION_HANDLE hSession, + CK_OBJECT_HANDLE hObject) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_DestroyObject(hSession, hObject); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Get the size of an specific object. * Not supported. @@ -1935,8 +1975,9 @@ CK_RV C_DestroyObject(CK_SESSION_HANDLE hSession, * CKR_OBJECT_HANDLE_INVALID when handle is not to a valid object. * CKR_OK on success. */ -CK_RV C_GetObjectSize(CK_SESSION_HANDLE hSession, - CK_OBJECT_HANDLE hObject, CK_ULONG_PTR pulSize) +static CK_RV wp11_C_GetObjectSize(CK_SESSION_HANDLE hSession, + CK_OBJECT_HANDLE hObject, + CK_ULONG_PTR pulSize) { CK_RV rv; int ret; @@ -1980,6 +2021,18 @@ CK_RV C_GetObjectSize(CK_SESSION_HANDLE hSession, return rv; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_GetObjectSize(CK_SESSION_HANDLE hSession, + CK_OBJECT_HANDLE hObject, CK_ULONG_PTR pulSize) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_GetObjectSize(hSession, hObject, pulSize); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Get the values of the attributes from the object. @@ -1998,9 +2051,10 @@ CK_RV C_GetObjectSize(CK_SESSION_HANDLE hSession, * CKR_FUNCTION_FAILED when getting a value fails. * CKR_OK on success. */ -CK_RV C_GetAttributeValue(CK_SESSION_HANDLE hSession, - CK_OBJECT_HANDLE hObject, - CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) +static CK_RV wp11_C_GetAttributeValue(CK_SESSION_HANDLE hSession, + CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulCount) { int ret; CK_RV rv; @@ -2082,6 +2136,19 @@ CK_RV C_GetAttributeValue(CK_SESSION_HANDLE hSession, return rv; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_GetAttributeValue(CK_SESSION_HANDLE hSession, + CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_GetAttributeValue(hSession, hObject, pTemplate, ulCount); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Set the values of the attributes into the object. * @@ -2103,9 +2170,10 @@ CK_RV C_GetAttributeValue(CK_SESSION_HANDLE hSession, * CKR_FUNCTION_FAILED when getting a value fails. * CKR_OK on success. */ -CK_RV C_SetAttributeValue(CK_SESSION_HANDLE hSession, - CK_OBJECT_HANDLE hObject, - CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) +static CK_RV wp11_C_SetAttributeValue(CK_SESSION_HANDLE hSession, + CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulCount) { CK_RV rv; int ret; @@ -2166,6 +2234,19 @@ CK_RV C_SetAttributeValue(CK_SESSION_HANDLE hSession, return rv; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_SetAttributeValue(CK_SESSION_HANDLE hSession, + CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_SetAttributeValue(hSession, hObject, pTemplate, ulCount); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Initialize the finding of an object associated with the session. * All matching objects are found by this call. @@ -2661,7 +2742,12 @@ static CK_RV EncryptInit(CK_SESSION_HANDLE hSession, CK_RV C_EncryptInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey) { - return EncryptInit(hSession, pMechanism, hKey, 0); + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = EncryptInit(hSession, pMechanism, hKey, 0); + WP11_Slot_ObjectCallLeave(calls); + return rv; } /** @@ -2685,9 +2771,9 @@ CK_RV C_EncryptInit(CK_SESSION_HANDLE hSession, * CKR_FUNCTION_FAILED when encrypting failed. * CKR_OK on success. */ -CK_RV C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, - CK_ULONG ulDataLen, CK_BYTE_PTR pEncryptedData, - CK_ULONG_PTR pulEncryptedDataLen) +static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, + CK_ULONG ulDataLen, CK_BYTE_PTR pEncryptedData, + CK_ULONG_PTR pulEncryptedDataLen) { CK_RV rv; int ret = 0; @@ -3031,6 +3117,20 @@ CK_RV C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, + CK_ULONG ulDataLen, CK_BYTE_PTR pEncryptedData, + CK_ULONG_PTR pulEncryptedDataLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_Encrypt(hSession, pData, ulDataLen, pEncryptedData, + pulEncryptedDataLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Continue encrypting multi-part data. * @@ -3052,9 +3152,10 @@ CK_RV C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, * CKR_FUNCTION_FAILED when encrypting failed. * CKR_OK on success. */ -CK_RV C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, - CK_ULONG ulPartLen, CK_BYTE_PTR pEncryptedPart, - CK_ULONG_PTR pulEncryptedPartLen) +static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen, + CK_BYTE_PTR pEncryptedPart, + CK_ULONG_PTR pulEncryptedPartLen) { int ret; WP11_Session* session; @@ -3246,6 +3347,20 @@ CK_RV C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, return rv; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen, CK_BYTE_PTR pEncryptedPart, + CK_ULONG_PTR pulEncryptedPartLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_EncryptUpdate(hSession, pPart, ulPartLen, pEncryptedPart, + pulEncryptedPartLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Finishes encrypting multi-part data. * @@ -3265,9 +3380,9 @@ CK_RV C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, * CKR_FUNCTION_FAILED when encrypting failed. * CKR_OK on success. */ -CK_RV C_EncryptFinal(CK_SESSION_HANDLE hSession, - CK_BYTE_PTR pLastEncryptedPart, - CK_ULONG_PTR pulLastEncryptedPartLen) +static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pLastEncryptedPart, + CK_ULONG_PTR pulLastEncryptedPartLen) { int ret; WP11_Session* session; @@ -3418,6 +3533,20 @@ CK_RV C_EncryptFinal(CK_SESSION_HANDLE hSession, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_EncryptFinal(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pLastEncryptedPart, + CK_ULONG_PTR pulLastEncryptedPartLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_EncryptFinal(hSession, pLastEncryptedPart, + pulLastEncryptedPartLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + static CK_RV DecryptInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey, byte skipOpCheck) @@ -3700,7 +3829,12 @@ static CK_RV DecryptInit(CK_SESSION_HANDLE hSession, CK_RV C_DecryptInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey) { - return DecryptInit(hSession, pMechanism, hKey, 0); + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = DecryptInit(hSession, pMechanism, hKey, 0); + WP11_Slot_ObjectCallLeave(calls); + return rv; } /** @@ -3725,9 +3859,10 @@ CK_RV C_DecryptInit(CK_SESSION_HANDLE hSession, * CKR_MECHANISM_INVALID when wrong initialization function was used. * CKR_OK on success. */ -CK_RV C_Decrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, - CK_ULONG ulEncryptedDataLen, CK_BYTE_PTR pData, - CK_ULONG_PTR pulDataLen) +static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pEncryptedData, + CK_ULONG ulEncryptedDataLen, CK_BYTE_PTR pData, + CK_ULONG_PTR pulDataLen) { int ret = 0; WP11_Session* session; @@ -4086,6 +4221,20 @@ CK_RV C_Decrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_Decrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, + CK_ULONG ulEncryptedDataLen, CK_BYTE_PTR pData, + CK_ULONG_PTR pulDataLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_Decrypt(hSession, pEncryptedData, ulEncryptedDataLen, pData, + pulDataLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Continue decrypting multi-part data. * @@ -4108,10 +4257,10 @@ CK_RV C_Decrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, * CKR_MECHANISM_INVALID when wrong initialization function was used. * CKR_OK on success. */ -CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, - CK_BYTE_PTR pEncryptedPart, - CK_ULONG ulEncryptedPartLen, CK_BYTE_PTR pPart, - CK_ULONG_PTR pulPartLen) +static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pEncryptedPart, + CK_ULONG ulEncryptedPartLen, + CK_BYTE_PTR pPart, CK_ULONG_PTR pulPartLen) { int ret; WP11_Session* session; @@ -4289,6 +4438,21 @@ CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pEncryptedPart, + CK_ULONG ulEncryptedPartLen, CK_BYTE_PTR pPart, + CK_ULONG_PTR pulPartLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_DecryptUpdate(hSession, pEncryptedPart, ulEncryptedPartLen, + pPart, pulPartLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Finishes decrypting multi-part data. * @@ -4308,8 +4472,9 @@ CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, * CKR_MECHANISM_INVALID when wrong initialization function was used. * CKR_OK on success. */ -CK_RV C_DecryptFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pLastPart, - CK_ULONG_PTR pulLastPartLen) +static CK_RV wp11_C_DecryptFinal(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pLastPart, + CK_ULONG_PTR pulLastPartLen) { int ret; WP11_Session* session; @@ -4480,6 +4645,18 @@ CK_RV C_DecryptFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pLastPart, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_DecryptFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pLastPart, + CK_ULONG_PTR pulLastPartLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_DecryptFinal(hSession, pLastPart, pulLastPartLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Initialize digest operation. * No digest mechanisms are supported. @@ -4667,7 +4844,7 @@ CK_RV C_DigestUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, * CKR_OPERATION_NOT_INITIALIZED when C_DigestInit has not been * successfully called. */ -CK_RV C_DigestKey(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hKey) +static CK_RV wp11_C_DigestKey(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hKey) { int ret; WP11_Session* session; @@ -4713,6 +4890,17 @@ CK_RV C_DigestKey(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hKey) return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_DigestKey(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hKey) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_DigestKey(hSession, hKey); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Finished digesting multi-part data. * No digest mechanisms are supported. @@ -4917,8 +5105,8 @@ static int GetInitValue(CK_MECHANISM_TYPE mechanism) { * CKR_FUNCTION_FAILED when initializing fails. * CKR_OK on success. */ -CK_RV C_SignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, - CK_OBJECT_HANDLE hKey) +static CK_RV wp11_C_SignInit(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey) { int ret; WP11_Session* session; @@ -5237,6 +5425,18 @@ CK_RV C_SignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_SignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_SignInit(hSession, pMechanism, hKey); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Sign the single-part data. * @@ -5258,9 +5458,9 @@ CK_RV C_SignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, * CKR_MECHANISM_INVALID when wrong initialization function was used. * CKR_OK on success. */ -CK_RV C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, - CK_ULONG ulDataLen, CK_BYTE_PTR pSignature, - CK_ULONG_PTR pulSignatureLen) +static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, + CK_ULONG ulDataLen, CK_BYTE_PTR pSignature, + CK_ULONG_PTR pulSignatureLen) { int ret = 0; #ifndef NO_RSA @@ -5611,6 +5811,19 @@ CK_RV C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, + CK_ULONG ulDataLen, CK_BYTE_PTR pSignature, + CK_ULONG_PTR pulSignatureLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_Sign(hSession, pData, ulDataLen, pSignature, pulSignatureLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Continue signing the multi-part data. * @@ -5626,8 +5839,8 @@ CK_RV C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, * CKR_MECHANISM_INVALID when wrong initialization function was used. * CKR_OK on success. */ -CK_RV C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, - CK_ULONG ulPartLen) +static CK_RV wp11_C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen) { int ret; WP11_Session* session; @@ -5739,6 +5952,18 @@ CK_RV C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_SignUpdate(hSession, pPart, ulPartLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Finish signing the multi-part data. * @@ -5759,8 +5984,9 @@ CK_RV C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, * CKR_MECHANISM_INVALID when wrong initialization function was used. * CKR_OK on success. */ -CK_RV C_SignFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, - CK_ULONG_PTR pulSignatureLen) +static CK_RV wp11_C_SignFinal(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pSignature, + CK_ULONG_PTR pulSignatureLen) { int ret; WP11_Session* session; @@ -5902,6 +6128,18 @@ CK_RV C_SignFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_SignFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, + CK_ULONG_PTR pulSignatureLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_SignFinal(hSession, pSignature, pulSignatureLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Initialize signing operation that recovers data from signature. * No mechanisms are supported. @@ -5916,9 +6154,9 @@ CK_RV C_SignFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, * CKR_MECHANISM_INVALID when the mechanism is not supported with this * type of operation. */ -CK_RV C_SignRecoverInit(CK_SESSION_HANDLE hSession, - CK_MECHANISM_PTR pMechanism, - CK_OBJECT_HANDLE hKey) +static CK_RV wp11_C_SignRecoverInit(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) { int ret; WP11_Session* session; @@ -5960,6 +6198,19 @@ CK_RV C_SignRecoverInit(CK_SESSION_HANDLE hSession, return rv; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_SignRecoverInit(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_SignRecoverInit(hSession, pMechanism, hKey); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Sign the data such that the data can be recovered from the signature. * @@ -6033,8 +6284,9 @@ CK_RV C_SignRecover(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, * CKR_FUNCTION_FAILED when initializing fails. * CKR_OK on success. */ -CK_RV C_VerifyInit(CK_SESSION_HANDLE hSession, - CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey) +static CK_RV wp11_C_VerifyInit(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) { int ret; WP11_Session* session; @@ -6357,6 +6609,18 @@ CK_RV C_VerifyInit(CK_SESSION_HANDLE hSession, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_VerifyInit(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_VerifyInit(hSession, pMechanism, hKey); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Verify the single-part data. * @@ -6375,9 +6639,9 @@ CK_RV C_VerifyInit(CK_SESSION_HANDLE hSession, * CKR_SIGNATURE_INVALID when the signature does not verify the data. * CKR_OK on success. */ -CK_RV C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, - CK_ULONG ulDataLen, CK_BYTE_PTR pSignature, - CK_ULONG ulSignatureLen) +static CK_RV wp11_C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, + CK_ULONG ulDataLen, CK_BYTE_PTR pSignature, + CK_ULONG ulSignatureLen) { int ret = 0; int stat = 0; @@ -6687,6 +6951,19 @@ CK_RV C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, + CK_ULONG ulDataLen, CK_BYTE_PTR pSignature, + CK_ULONG ulSignatureLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_Verify(hSession, pData, ulDataLen, pSignature, ulSignatureLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Continue verifying the multi-part data. * @@ -6702,8 +6979,8 @@ CK_RV C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, * CKR_MECHANISM_INVALID when wrong initialization function was used. * CKR_OK on success. */ -CK_RV C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, - CK_ULONG ulPartLen) +static CK_RV wp11_C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen) { int ret; WP11_Session* session; @@ -6806,6 +7083,18 @@ CK_RV C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_VerifyUpdate(hSession, pPart, ulPartLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Finishes verifying the multi-part data. * @@ -6822,8 +7111,8 @@ CK_RV C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, * CKR_SIGNATURE_INVALID when the signature does not verify the data. * CKR_OK on success. */ -CK_RV C_VerifyFinal(CK_SESSION_HANDLE hSession, - CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen) +static CK_RV wp11_C_VerifyFinal(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen) { int ret = 0; int stat = 0; @@ -6930,6 +7219,18 @@ CK_RV C_VerifyFinal(CK_SESSION_HANDLE hSession, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_VerifyFinal(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_VerifyFinal(hSession, pSignature, ulSignatureLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Initialize verification operation where data is recovered from the signature. * @@ -6943,9 +7244,9 @@ CK_RV C_VerifyFinal(CK_SESSION_HANDLE hSession, * CKR_MECHANISM_INVALID when the mechanism is not supported with this * type of operation. */ -CK_RV C_VerifyRecoverInit(CK_SESSION_HANDLE hSession, - CK_MECHANISM_PTR pMechanism, - CK_OBJECT_HANDLE hKey) +static CK_RV wp11_C_VerifyRecoverInit(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) { int ret; int init = 0; @@ -7028,6 +7329,19 @@ CK_RV C_VerifyRecoverInit(CK_SESSION_HANDLE hSession, return CKR_OK; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_VerifyRecoverInit(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_VerifyRecoverInit(hSession, pMechanism, hKey); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Verify the signature where the data is recovered from the signature. * @@ -7045,9 +7359,10 @@ CK_RV C_VerifyRecoverInit(CK_SESSION_HANDLE hSession, * successfully called. * CKR_OK on success. */ -CK_RV C_VerifyRecover(CK_SESSION_HANDLE hSession, - CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen, - CK_BYTE_PTR pData, CK_ULONG_PTR pulDataLen) +static CK_RV wp11_C_VerifyRecover(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pSignature, + CK_ULONG ulSignatureLen, CK_BYTE_PTR pData, + CK_ULONG_PTR pulDataLen) { WP11_Session* session; #if !defined(NO_RSA) && defined(WC_RSA_DIRECT) @@ -7141,6 +7456,20 @@ CK_RV C_VerifyRecover(CK_SESSION_HANDLE hSession, #endif } +/* Logout must not free an object this call may still be using. */ +CK_RV C_VerifyRecover(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen, + CK_BYTE_PTR pData, CK_ULONG_PTR pulDataLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_VerifyRecover(hSession, pSignature, ulSignatureLen, pData, + pulDataLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Continue digesting and encrypting multi-part data. * @@ -7386,10 +7715,10 @@ CK_RV C_DecryptVerifyUpdate(CK_SESSION_HANDLE hSession, * type of operation. * CKR_OK on success. */ -CK_RV C_GenerateKey(CK_SESSION_HANDLE hSession, - CK_MECHANISM_PTR pMechanism, - CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, - CK_OBJECT_HANDLE_PTR phKey) +static CK_RV wp11_C_GenerateKey(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, + CK_OBJECT_HANDLE_PTR phKey) { CK_RV rv = CKR_OK; WP11_Session* session = NULL; @@ -7845,6 +8174,20 @@ CK_RV C_GenerateKey(CK_SESSION_HANDLE hSession, return rv; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_GenerateKey(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, + CK_OBJECT_HANDLE_PTR phKey) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_GenerateKey(hSession, pMechanism, pTemplate, ulCount, phKey); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Generate a public/private key pair into new key objects. @@ -7908,14 +8251,14 @@ static CK_RV CheckGenPairAttrs(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, return CKR_OK; } -CK_RV C_GenerateKeyPair(CK_SESSION_HANDLE hSession, - CK_MECHANISM_PTR pMechanism, - CK_ATTRIBUTE_PTR pPublicKeyTemplate, - CK_ULONG ulPublicKeyAttributeCount, - CK_ATTRIBUTE_PTR pPrivateKeyTemplate, - CK_ULONG ulPrivateKeyAttributeCount, - CK_OBJECT_HANDLE_PTR phPublicKey, - CK_OBJECT_HANDLE_PTR phPrivateKey) +static CK_RV wp11_C_GenerateKeyPair(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_ATTRIBUTE_PTR pPublicKeyTemplate, + CK_ULONG ulPublicKeyAttributeCount, + CK_ATTRIBUTE_PTR pPrivateKeyTemplate, + CK_ULONG ulPrivateKeyAttributeCount, + CK_OBJECT_HANDLE_PTR phPublicKey, + CK_OBJECT_HANDLE_PTR phPrivateKey) { int ret; CK_RV rv = CKR_OK; @@ -8302,6 +8645,27 @@ CK_RV C_GenerateKeyPair(CK_SESSION_HANDLE hSession, return rv; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_GenerateKeyPair(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_ATTRIBUTE_PTR pPublicKeyTemplate, + CK_ULONG ulPublicKeyAttributeCount, + CK_ATTRIBUTE_PTR pPrivateKeyTemplate, + CK_ULONG ulPrivateKeyAttributeCount, + CK_OBJECT_HANDLE_PTR phPublicKey, + CK_OBJECT_HANDLE_PTR phPrivateKey) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_GenerateKeyPair(hSession, pMechanism, pPublicKeyTemplate, + ulPublicKeyAttributeCount, pPrivateKeyTemplate, + ulPrivateKeyAttributeCount, phPublicKey, + phPrivateKey); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Wrap a key using another key. * @@ -8328,11 +8692,11 @@ CK_RV C_GenerateKeyPair(CK_SESSION_HANDLE hSession, * CKR_MECHANISM_INVALID when the mechanism is not supported with this * type of operation. */ -CK_RV C_WrapKey(CK_SESSION_HANDLE hSession, - CK_MECHANISM_PTR pMechanism, - CK_OBJECT_HANDLE hWrappingKey, CK_OBJECT_HANDLE hKey, - CK_BYTE_PTR pWrappedKey, - CK_ULONG_PTR pulWrappedKeyLen) +static CK_RV wp11_C_WrapKey(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hWrappingKey, + CK_OBJECT_HANDLE hKey, CK_BYTE_PTR pWrappedKey, + CK_ULONG_PTR pulWrappedKeyLen) { int ret; CK_RV rv; @@ -8569,6 +8933,22 @@ CK_RV C_WrapKey(CK_SESSION_HANDLE hSession, return rv; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_WrapKey(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hWrappingKey, CK_OBJECT_HANDLE hKey, + CK_BYTE_PTR pWrappedKey, + CK_ULONG_PTR pulWrappedKeyLen) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_WrapKey(hSession, pMechanism, hWrappingKey, hKey, pWrappedKey, + pulWrappedKeyLen); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Unwrap a key using a wrap key. * Support only RSA private key wrapped by AESCBCPAD mechanism @@ -8590,13 +8970,13 @@ CK_RV C_WrapKey(CK_SESSION_HANDLE hSession, * CKR_MECHANISM_INVALID when the mechanism is not supported with this * type of operation. */ -CK_RV C_UnwrapKey(CK_SESSION_HANDLE hSession, - CK_MECHANISM_PTR pMechanism, - CK_OBJECT_HANDLE hUnwrappingKey, - CK_BYTE_PTR pWrappedKey, CK_ULONG ulWrappedKeyLen, - CK_ATTRIBUTE_PTR pTemplate, - CK_ULONG ulAttributeCount, - CK_OBJECT_HANDLE_PTR phKey) +static CK_RV wp11_C_UnwrapKey(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hUnwrappingKey, + CK_BYTE_PTR pWrappedKey, CK_ULONG ulWrappedKeyLen, + CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, + CK_OBJECT_HANDLE_PTR phKey) { CK_RV rv; int ret; @@ -8821,6 +9201,24 @@ CK_RV C_UnwrapKey(CK_SESSION_HANDLE hSession, return rv; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_UnwrapKey(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hUnwrappingKey, + CK_BYTE_PTR pWrappedKey, CK_ULONG ulWrappedKeyLen, + CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, + CK_OBJECT_HANDLE_PTR phKey) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_UnwrapKey(hSession, pMechanism, hUnwrappingKey, pWrappedKey, + ulWrappedKeyLen, pTemplate, ulAttributeCount, phKey); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + #if defined(HAVE_ECC) || !defined(NO_DH) /** * Determine the key length of the object. @@ -9045,12 +9443,12 @@ static int Tls12_Extract_Keys(WP11_Session* session, * type of operation. * CKR_OK on success. */ -CK_RV C_DeriveKey(CK_SESSION_HANDLE hSession, - CK_MECHANISM_PTR pMechanism, - CK_OBJECT_HANDLE hBaseKey, - CK_ATTRIBUTE_PTR pTemplate, - CK_ULONG ulAttributeCount, - CK_OBJECT_HANDLE_PTR phKey) +static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hBaseKey, + CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, + CK_OBJECT_HANDLE_PTR phKey) { int ret; CK_RV rv = CKR_OK; @@ -9492,6 +9890,23 @@ CK_RV C_DeriveKey(CK_SESSION_HANDLE hSession, return rv; } +/* Logout must not free an object this call may still be using. */ +CK_RV C_DeriveKey(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hBaseKey, + CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, + CK_OBJECT_HANDLE_PTR phKey) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_DeriveKey(hSession, pMechanism, hBaseKey, pTemplate, + ulAttributeCount, phKey); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + /** * Seed the token's random number generator. * @@ -10023,10 +10438,14 @@ static CK_RV CheckPrivateObjectLogin(WP11_Session* session, } #endif /* WOLFPKCS11_MLKEM */ -CK_RV C_EncapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, - CK_OBJECT_HANDLE hPublicKey, CK_ATTRIBUTE_PTR pTemplate, - CK_ULONG ulAttributeCount, CK_BYTE_PTR pCiphertext, - CK_ULONG_PTR pulCiphertextLen, CK_OBJECT_HANDLE_PTR phKey) +static CK_RV wp11_C_EncapsulateKey(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hPublicKey, + CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, + CK_BYTE_PTR pCiphertext, + CK_ULONG_PTR pulCiphertextLen, + CK_OBJECT_HANDLE_PTR phKey) { #ifdef WOLFPKCS11_MLKEM int ret; @@ -10143,10 +10562,30 @@ CK_RV C_EncapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, #endif } -CK_RV C_DecapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, - CK_OBJECT_HANDLE hPrivateKey, CK_ATTRIBUTE_PTR pTemplate, +/* Logout must not free an object this call may still be using. */ +CK_RV C_EncapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hPublicKey, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulAttributeCount, CK_BYTE_PTR pCiphertext, - CK_ULONG ulCiphertextLen, CK_OBJECT_HANDLE_PTR phKey) + CK_ULONG_PTR pulCiphertextLen, CK_OBJECT_HANDLE_PTR phKey) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_EncapsulateKey(hSession, pMechanism, hPublicKey, pTemplate, + ulAttributeCount, pCiphertext, pulCiphertextLen, + phKey); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + +static CK_RV wp11_C_DecapsulateKey(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hPrivateKey, + CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, + CK_BYTE_PTR pCiphertext, + CK_ULONG ulCiphertextLen, + CK_OBJECT_HANDLE_PTR phKey) { #ifdef WOLFPKCS11_MLKEM int ret; @@ -10257,6 +10696,22 @@ CK_RV C_DecapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, #endif } +/* Logout must not free an object this call may still be using. */ +CK_RV C_DecapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hPrivateKey, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, CK_BYTE_PTR pCiphertext, + CK_ULONG ulCiphertextLen, CK_OBJECT_HANDLE_PTR phKey) +{ + CK_RV rv; + WP11_ObjectCalls* calls = WP11_Slot_ObjectCallEnter(hSession); + + rv = wp11_C_DecapsulateKey(hSession, pMechanism, hPrivateKey, pTemplate, + ulAttributeCount, pCiphertext, ulCiphertextLen, + phKey); + WP11_Slot_ObjectCallLeave(calls); + return rv; +} + CK_RV C_VerifySignatureInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey, CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen) diff --git a/src/internal.c b/src/internal.c index c83eb5ba..65884bed 100644 --- a/src/internal.c +++ b/src/internal.c @@ -304,6 +304,7 @@ struct WP11_Object { byte onToken:1; /* Object on token or session */ byte local:1; /* Locally created object */ word32 opFlag; /* Flags of operations allowed */ + word32 freeEpoch; /* Call epoch the object was unlinked */ char startDate[8]; /* Start date of usage */ char endDate[8]; /* End data of usage */ @@ -528,6 +529,8 @@ struct WP11_Session { int devId; WP11_Session* next; /* Next session for slot */ + WP11_Object* retired; /* Private objects invalidated by logout + * and freed once nothing uses them */ }; typedef struct WP11_Token { @@ -563,6 +566,11 @@ typedef struct WP11_Token { * 2 = not empty */ } WP11_Token; +struct WP11_ObjectCalls { + WP11_Slot* slot; /* Slot the calls are made on */ + int cnt; /* Calls in progress from this epoch */ +}; + struct WP11_Slot { CK_SLOT_ID id; /* CryptoKi API slot id value */ WP11_Token token; /* Token information for slot */ @@ -576,6 +584,10 @@ struct WP11_Slot { WOLFTPM2_SESSION tpmSession; TpmCryptoDevCtx tpmCtx; #endif + WP11_ObjectCalls calls[2]; /* Object calls of the last two epochs */ + word32 epoch; /* Current object call epoch */ + WP11_Object* discarded; /* Destroyed objects awaiting free */ + byte reclaim:1; /* Retired objects may await free */ }; #if defined(HAVE_FIPS) && FIPS_VERSION_LT(6,0) @@ -951,6 +963,7 @@ static int wp11_Slot_AddSession(WP11_Slot* slot, WP11_Session** session) static void wp11_Session_Final(WP11_Session* session) { WP11_Object* obj; + WP11_Object* retired; if (session->inUse) { /* Free objects in session. */ @@ -961,6 +974,15 @@ static void wp11_Session_Final(WP11_Session* session) } session->inUse = 0; } + /* Retired objects change only under the slot lock, which callers hold + * (or the library is being torn down). */ + retired = session->retired; + session->retired = NULL; + while (retired != NULL) { + obj = retired; + retired = retired->next; + WP11_Object_Free(obj); + } session->curr = NULL; /* Finalize any find. */ WP11_Session_FindFinal(session); @@ -7800,12 +7822,19 @@ static void wp11_TpmFinal(WP11_Slot* slot) */ static void wp11_Slot_Final(WP11_Slot* slot) { + WP11_Object* obj; + if (slot == NULL) { return; } while (slot->session != NULL) { wp11_Slot_FreeSession(slot, slot->session); } + while (slot->discarded != NULL) { + obj = slot->discarded; + slot->discarded = obj->next; + WP11_Object_Free(obj); + } wp11_Token_Final(&slot->token); #ifdef WOLFPKCS11_TPM wp11_TpmFinal(slot); @@ -7833,6 +7862,8 @@ static int wp11_Slot_Init(WP11_Slot* slot, int id) slot->id = id; slot->token.state = WP11_TOKEN_STATE_UNKNOWN; slot->token.tokenFlags = 0; + slot->calls[0].slot = slot; + slot->calls[1].slot = slot; ret = WP11_Lock_Init(&slot->lock); if (ret == 0) { @@ -8204,6 +8235,7 @@ int WP11_Slot_OpenSession(WP11_Slot* slot, unsigned long flags, void* app, } static void wp11_Slot_Logout(WP11_Slot* slot); +static void wp11_Slot_FreeDiscardedObjects(WP11_Slot* slot); /** * Close a session associated with a slot. @@ -8243,6 +8275,10 @@ void WP11_Slot_CloseSession(WP11_Slot* slot, WP11_Session* session) wp11_Slot_FreeSession(slot, session); else wp11_Session_Final(session); + /* Its operation no longer holds objects destroyed while it ran. */ + WP11_Lock_LockRW(&slot->token.lock); + wp11_Slot_FreeDiscardedObjects(slot); + WP11_Lock_UnlockRW(&slot->token.lock); /* Decide and log out under the same lock so a concurrent open is either * seen here or opens after the logout. */ @@ -8766,6 +8802,207 @@ int WP11_Slot_SOLogin(WP11_Slot* slot, char* pin, int pinLen) return ret; } +/** + * Check whether a session's initialized operation uses the object as its key. + * + * @param slot [in] Slot object. + * @param object [in] Object to look for. + * @return 1 when an operation key, 0 otherwise. + */ +static int wp11_Slot_ObjectIsActive(WP11_Slot* slot, WP11_Object* object) +{ + WP11_Session* sess; + + for (sess = slot->session; sess != NULL; sess = sess->next) { + if (sess->curr == object && sess->init != 0) + return 1; + } + return 0; +} + +/** + * Start a new object call epoch once no call from the previous one runs, so + * calls only ever run in the current and previous epochs. + * Caller holds the slot lock for writing. + * + * @param slot [in] Slot object. + */ +static void wp11_Slot_AdvanceEpoch(WP11_Slot* slot) +{ + if (slot->calls[(slot->epoch + 1) & 1].cnt == 0) + slot->epoch++; +} + +/** + * Check whether an unlinked object can be freed: every object call that began + * by the epoch it was unlinked in has ended and no operation uses it. + * Caller holds the slot and token locks for writing. + * + * @param slot [in] Slot object. + * @param object [in] Unlinked object. + * @return 1 when nothing can still use the object, 0 otherwise. + */ +static int wp11_Slot_ObjectFreeable(WP11_Slot* slot, WP11_Object* object) +{ + int done = 1; + + if (object->freeEpoch == slot->epoch) { + done = (slot->calls[0].cnt == 0) && (slot->calls[1].cnt == 0); + } + else if (object->freeEpoch == slot->epoch - 1) { + done = (slot->calls[object->freeEpoch & 1].cnt == 0); + } + if (done) + done = !wp11_Slot_ObjectIsActive(slot, object); + + return done; +} + +/** + * Free an object nothing uses any more, dropping stale operation pointers. + * Caller holds the slot and token locks for writing. + * + * @param slot [in] Slot object. + * @param object [in] Object to free. + */ +static void wp11_Slot_FreeUnused(WP11_Slot* slot, WP11_Object* object) +{ + WP11_Session* sess; + + for (sess = slot->session; sess != NULL; sess = sess->next) { + if (sess->curr == object) + sess->curr = NULL; + } + WP11_Object_Free(object); +} + +/** + * Free the destroyed objects that no object call or operation can still use. + * Caller holds the slot and token locks for writing. + * + * @param slot [in] Slot object. + */ +static void wp11_Slot_FreeDiscardedObjects(WP11_Slot* slot) +{ + WP11_Object** curr = &slot->discarded; + WP11_Object* obj; + + while (*curr != NULL) { + obj = *curr; + if (!wp11_Slot_ObjectFreeable(slot, obj)) { + curr = &obj->next; + } + else { + *curr = obj->next; + wp11_Slot_FreeUnused(slot, obj); + } + } +} + +/** + * Hand over an object that C_DestroyObject has unlinked. It is freed once no + * object call that began before the unlink and no operation can use it. + * + * @param slot [in] Slot object. + * @param object [in] Unlinked object. + */ +void WP11_Slot_DiscardObject(WP11_Slot* slot, WP11_Object* object) +{ + WP11_Lock_LockRW(&slot->lock); + WP11_Lock_LockRW(&slot->token.lock); + object->freeEpoch = slot->epoch; + object->next = slot->discarded; + slot->discarded = object; + wp11_Slot_AdvanceEpoch(slot); + wp11_Slot_FreeDiscardedObjects(slot); + WP11_Lock_UnlockRW(&slot->token.lock); + WP11_Lock_UnlockRW(&slot->lock); +} + +#ifndef WOLFPKCS11_NSS + +/** + * Free (and so zeroize) the retired objects that nothing can still use: every + * object call that began before logout retired them has ended and no + * operation uses them. Caller holds the slot and token locks for writing. + * + * @param slot [in] Slot object. + */ +static void wp11_Slot_FreeRetiredObjects(WP11_Slot* slot) +{ + WP11_Session* sess; + WP11_Object** curr; + WP11_Object* obj; + int left = 0; + + for (sess = slot->session; sess != NULL; sess = sess->next) { + curr = &sess->retired; + while (*curr != NULL) { + obj = *curr; + if (!wp11_Slot_ObjectFreeable(slot, obj)) { + curr = &obj->next; + left = 1; + } + else { + *curr = obj->next; + wp11_Slot_FreeUnused(slot, obj); + } + } + } + slot->reclaim = (byte)left; +} +#endif + +/** + * Note the start of a call that may use object pointers looked up by handle. + * Objects unlinked while it runs are not freed until it has finished. + * + * @param hSession [in] Session handle the call is made on. + * @return Calls to pass to WP11_Slot_ObjectCallLeave, or NULL. + */ +WP11_ObjectCalls* WP11_Slot_ObjectCallEnter(CK_SESSION_HANDLE hSession) +{ + WP11_Slot* slot = NULL; + WP11_ObjectCalls* calls = NULL; + + if (WP11_Library_IsInitialized() && + WP11_Slot_Get(SESS_HANDLE_SLOT_ID(hSession), &slot) == 0) { + WP11_Lock_LockRW(&slot->lock); + calls = &slot->calls[slot->epoch & 1]; + calls->cnt++; + WP11_Lock_UnlockRW(&slot->lock); + } + + return calls; +} + +/** + * Note the end of a call started with WP11_Slot_ObjectCallEnter and free what + * logout retired or destroy unlinked that no running call can still use. + * + * @param calls [in] Calls returned by WP11_Slot_ObjectCallEnter. + */ +void WP11_Slot_ObjectCallLeave(WP11_ObjectCalls* calls) +{ + WP11_Slot* slot; + + if (calls != NULL) { + slot = calls->slot; + WP11_Lock_LockRW(&slot->lock); + calls->cnt--; + wp11_Slot_AdvanceEpoch(slot); + if (slot->reclaim || slot->discarded != NULL) { + WP11_Lock_LockRW(&slot->token.lock); + #ifndef WOLFPKCS11_NSS + wp11_Slot_FreeRetiredObjects(slot); + #endif + wp11_Slot_FreeDiscardedObjects(slot); + WP11_Lock_UnlockRW(&slot->token.lock); + } + WP11_Lock_UnlockRW(&slot->lock); + } +} + /** * Log the user into the token. * @@ -8889,6 +9126,11 @@ int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen) if (ret == 0) { WP11_Lock_LockRW(&slot->lock); token->loginState = WP11_APP_STATE_RW_USER; + #ifndef WOLFPKCS11_NSS + WP11_Lock_LockRW(&token->lock); + wp11_Slot_FreeRetiredObjects(slot); + WP11_Lock_UnlockRW(&token->lock); + #endif WP11_Lock_UnlockRW(&slot->lock); } @@ -9045,6 +9287,44 @@ int WP11_Slot_IsUserLoggedIn(WP11_Slot* slot) return wp11_LoginStateIsUser(state); } +#ifndef WOLFPKCS11_NSS +/** + * Invalidate the private session objects of all sessions on the slot, as + * logging out requires: their handles stop resolving immediately. They are + * freed, which zeroizes their key material, as soon as no object call is in + * progress and no operation uses them. Caller holds the slot and token locks + * for writing. + * + * @param slot [in] Slot object. + */ +static void wp11_Slot_RetirePrivateSessionObjects(WP11_Slot* slot) +{ + WP11_Session* sess; + WP11_Object** curr; + WP11_Object* obj; + + for (sess = slot->session; sess != NULL; sess = sess->next) { + curr = &sess->object; + while (*curr != NULL) { + obj = *curr; + if ((obj->opFlag & WP11_FLAG_PRIVATE) == WP11_FLAG_PRIVATE) { + *curr = obj->next; + sess->objCnt--; + obj->freeEpoch = slot->epoch; + obj->next = sess->retired; + sess->retired = obj; + slot->reclaim = 1; + } + else { + curr = &obj->next; + } + } + } + wp11_Slot_AdvanceEpoch(slot); + wp11_Slot_FreeRetiredObjects(slot); +} +#endif + /** * Logout of the token. Caller holds the slot lock for writing. * @@ -9076,6 +9356,9 @@ static void wp11_Slot_Logout(WP11_Slot* slot) * for subsequent object encryption (e.g., empty-PIN flow). */ wc_ForceZero(slot->token.key, sizeof(slot->token.key)); } +#endif +#ifndef WOLFPKCS11_NSS + wp11_Slot_RetirePrivateSessionObjects(slot); #endif slot->token.loginState = WP11_APP_STATE_RW_PUBLIC; WP11_Lock_UnlockRW(&slot->token.lock); @@ -10215,6 +10498,28 @@ int WP11_Session_SetCtsParams(WP11_Session* session, unsigned char* iv, #endif /* HAVE_AESCTS */ #endif /* !NO_AES */ +#ifndef WOLFPKCS11_NSS +/** + * Count the retired objects a session still holds because a call was in + * progress. The operation key is not counted: there is only one per session. + * Caller holds the token lock. + * + * @param session [in] Session object. + * @return Number of retired objects held. + */ +static int wp11_Session_RetiredHeld(WP11_Session* session) +{ + int cnt = 0; + WP11_Object* obj; + + for (obj = session->retired; obj != NULL; obj = obj->next) { + if (obj != session->curr || session->init == 0) + cnt++; + } + return cnt; +} +#endif + /** * Add object to the session or token. * @@ -10229,12 +10534,28 @@ int WP11_Session_AddObject(WP11_Session* session, int onToken, int ret = 0; WP11_Object* next; WP11_Token* token; +#ifndef WOLFPKCS11_NSS + int privSession; +#endif object->onToken = onToken; if (!onToken) object->session = session; token = &session->slot->token; +#ifndef WOLFPKCS11_NSS + privSession = !onToken && + (object->opFlag & WP11_FLAG_PRIVATE) == WP11_FLAG_PRIVATE; + if (privSession) { + /* Held to the add so a logout cannot slip between check and add. */ + WP11_Lock_LockRO(&session->slot->lock); + if ((token->tokenFlags & WP11_TOKEN_FLAG_USER_PIN_SET) && + token->userPinEmpty != 1 && + !wp11_LoginStateIsUser(token->loginState)) { + ret = PIN_INVALID_E; + } + } +#endif WP11_Lock_LockRW(&token->lock); if (onToken) { #ifndef WOLFPKCS11_NO_STORE @@ -10276,6 +10597,13 @@ int WP11_Session_AddObject(WP11_Session* session, int onToken, else { if (session->objCnt >= WP11_SESSION_OBJECT_CNT_MAX) ret = OBJ_COUNT_E; + #ifndef WOLFPKCS11_NSS + /* Retired objects awaiting free still use memory. */ + if (session->objCnt + wp11_Session_RetiredHeld(session) >= + WP11_SESSION_OBJECT_CNT_MAX) { + ret = OBJ_COUNT_E; + } + #endif if (ret == 0) { session->objCnt++; /* Get next item in list after this object has been added. */ @@ -10288,6 +10616,10 @@ int WP11_Session_AddObject(WP11_Session* session, int onToken, } } WP11_Lock_UnlockRW(&token->lock); +#ifndef WOLFPKCS11_NSS + if (privSession) + WP11_Lock_UnlockRO(&session->slot->lock); +#endif return ret; } @@ -10380,6 +10712,18 @@ int WP11_Session_RemoveObjectByHandle(WP11_Session* session, } } } +#endif +#ifndef WOLFPKCS11_NSS + /* Give cleanup back an object it added that logout then retired. */ + for (curr = &owner->retired; !found && !checkDestroyable && + *curr != NULL; curr = &(*curr)->next) { + if (*curr == object) { + *curr = object->next; + object->next = NULL; + WP11_Lock_UnlockRW(&token->lock); + return 0; + } + } #endif } @@ -11871,6 +12215,20 @@ int WP11_Object_HandleOnToken(CK_OBJECT_HANDLE handle) return OBJ_HANDLE_ON_TOKEN(handle); } +#ifdef DEBUG_WOLFPKCS11 +static void (*wp11_objectFindHook)(void) = NULL; + +/** + * Test hook: called by WP11_Object_Find before returning a found object. + * + * @param hook [in] Function to call, or NULL to remove the hook. + */ +WP11_API void WP11_Object_SetFindHook(void (*hook)(void)) +{ + wp11_objectFindHook = hook; +} +#endif + /** * Find an object based on the handle. * @@ -11953,8 +12311,7 @@ int WP11_Object_Find(WP11_Session* session, CK_OBJECT_HANDLE objHandle, WP11_Lock_UnlockRO(&session->slot->token.lock); } - /* The flags were captured under the token lock: the object may be freed - * by a concurrent destroy once the lock is released. */ + /* A destroyed object stays allocated until the caller's object call ends. */ if (ret == 0 && obj != NULL) { #ifndef WOLFPKCS11_NSS /* Enforce CKA_PRIVATE: reject private objects from public sessions. @@ -11976,6 +12333,10 @@ int WP11_Object_Find(WP11_Session* session, CK_OBJECT_HANDLE objHandle, if (ret == 0) *object = obj; } +#ifdef DEBUG_WOLFPKCS11 + if (ret == 0 && wp11_objectFindHook != NULL) + wp11_objectFindHook(); +#endif return ret; } diff --git a/tests/include.am b/tests/include.am index d3162c44..d7ef4a12 100644 --- a/tests/include.am +++ b/tests/include.am @@ -300,6 +300,11 @@ noinst_PROGRAMS += tests/nss_configdir_prefix_test tests_nss_configdir_prefix_test_SOURCES = tests/nss_configdir_prefix_test.c tests_nss_configdir_prefix_test_LDADD = +check_PROGRAMS += tests/logout_retire_test +noinst_PROGRAMS += tests/logout_retire_test +tests_logout_retire_test_SOURCES = tests/logout_retire_test.c +tests_logout_retire_test_LDADD = + check_PROGRAMS += tests/login_pin_state_test noinst_PROGRAMS += tests/login_pin_state_test tests_login_pin_state_test_SOURCES = tests/login_pin_state_test.c @@ -366,6 +371,7 @@ tests_copy_data_object_token_test_LDADD += src/libwolfpkcs11.la tests_data_object_partial_update_test_LDADD += src/libwolfpkcs11.la tests_wrapkey_length_query_test_LDADD += src/libwolfpkcs11.la tests_nss_configdir_prefix_test_LDADD += src/libwolfpkcs11.la +tests_logout_retire_test_LDADD += src/libwolfpkcs11.la tests_login_pin_state_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la @@ -420,6 +426,7 @@ tests_copy_data_object_token_test_LDADD += src/libwolfpkcs11.la tests_data_object_partial_update_test_LDADD += src/libwolfpkcs11.la tests_wrapkey_length_query_test_LDADD += src/libwolfpkcs11.la tests_nss_configdir_prefix_test_LDADD += src/libwolfpkcs11.la +tests_logout_retire_test_LDADD += src/libwolfpkcs11.la tests_login_pin_state_test_LDADD += src/libwolfpkcs11.la endif diff --git a/tests/login_pin_state_test.c b/tests/login_pin_state_test.c index a0c96b96..3f132961 100644 --- a/tests/login_pin_state_test.c +++ b/tests/login_pin_state_test.c @@ -930,6 +930,119 @@ static void find_during_login_changes_test(void) } #endif +#ifndef WOLFPKCS11_NSS +/* Logging out invalidates the handles of private session objects. */ +static void logout_drops_private_session_objects_test(void) +{ + CK_RV rv; + int i; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE privObj = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pubObj = CK_INVALID_HANDLE; + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_BBOOL yes = CK_TRUE; + CK_BBOOL no = CK_FALSE; + byte value[4] = { 1, 2, 3, 4 }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &no, sizeof(no) }, + { CKA_PRIVATE, &yes, sizeof(yes) }, + { CKA_VALUE, value, sizeof(value) }, + }; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &no, sizeof(no) }, + { CKA_PRIVATE, &no, sizeof(no) }, + { CKA_VALUE, value, sizeof(value) }, + }; + CK_OBJECT_CLASS gotClass; +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_AES; + byte keyValue[16] = { 0 }; + CK_OBJECT_HANDLE keyObj = CK_INVALID_HANDLE; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_VALUE, keyValue, sizeof(keyValue) }, + { CKA_TOKEN, &no, sizeof(no) }, + { CKA_PRIVATE, &yes, sizeof(yes) }, + { CKA_ENCRYPT, &yes, sizeof(yes) }, + }; + byte iv[16] = { 0 }; + byte plain[16] = { 0 }; + byte enc[16]; + CK_ULONG encLen = sizeof(enc); + CK_MECHANISM cbc = { CKM_AES_CBC, iv, sizeof(iv) }; +#endif + CK_ATTRIBUTE getTmpl[] = { + { CKA_CLASS, &gotClass, sizeof(gotClass) }, + }; + + printf("--- logout drops private session objects ---\n"); + rv = token_setup(&slot, userPin); + if (rv == CKR_OK) + rv = open_rw(slot, &session); + if (rv == CKR_OK) + rv = user_login(session, userPin); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &privObj); + } + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), &pubObj); + } +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &keyObj); + } + if (rv == CKR_OK) + rv = funcList->C_EncryptInit(session, &cbc, keyObj); +#endif + if (rv == CKR_OK) + rv = funcList->C_Logout(session); + if (rv == CKR_OK) + rv = user_login(session, userPin); + CHECK_RV(rv, "create session objects, log out and back in", CKR_OK); +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + /* PKCS#11 leaves it to the token whether the operation survives logout; + * either way it must complete safely. */ + if (rv == CKR_OK) { + rv = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encLen); + CHECK_TRUE(rv == CKR_OK || rv == CKR_OPERATION_NOT_INITIALIZED, + "operation started before logout completes safely"); + rv = CKR_OK; + } +#endif + if (rv == CKR_OK) { + rv = funcList->C_GetAttributeValue(session, privObj, getTmpl, 1); + CHECK_RV(rv, "private session object gone after logout", + CKR_OBJECT_HANDLE_INVALID); + rv = funcList->C_GetAttributeValue(session, pubObj, getTmpl, 1); + CHECK_RV(rv, "public session object kept after logout", CKR_OK); + rv = CKR_OK; + /* Logged-out private objects must not use up the session limit. */ + for (i = 0; rv == CKR_OK && i < WP11_SESSION_OBJECT_CNT_MAX + 2; i++) { + rv = funcList->C_CreateObject(session, privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &privObj); + if (rv == CKR_OK) + rv = funcList->C_Logout(session); + if (rv == CKR_OK) + rv = user_login(session, userPin); + } + CHECK_RV(rv, "repeated private object create and logout cycles", + CKR_OK); + funcList->C_Logout(session); + } + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + #if defined(DEBUG_WOLFPKCS11) && !defined(SINGLE_THREADED) && \ !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_NSS) #define FIND_HOOK_WAIT_MS 200 @@ -1056,6 +1169,9 @@ static int run_test(void) #ifndef SINGLE_THREADED close_last_session_race_test(); #endif +#ifndef WOLFPKCS11_NSS + logout_drops_private_session_objects_test(); +#endif #if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_TIME) concurrent_login_lockout_test(); #endif diff --git a/tests/logout_retire_test.c b/tests/logout_retire_test.c new file mode 100644 index 00000000..4c91fe39 --- /dev/null +++ b/tests/logout_retire_test.c @@ -0,0 +1,643 @@ +/* logout_retire_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Checks that logging out clears the key material of private session objects + * and that the objects it retires do not pile up across login cycles. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#define TEST_DIR "./store/logout_retire_test" + +#if defined(DEBUG_WOLFPKCS11) && !defined(SINGLE_THREADED) && \ + !defined(WOLFPKCS11_SINGLE_THREADED) + #define RETIRE_TEST_THREADS + #include +#endif + +#ifndef WOLFPKCS11_NSS + +#define RETIRE_CYCLES 40 +#define RETIRE_SETTLE 5 + +static const char* soPin = "password123456"; +static const char* userPin = "wolfpkcs11-test"; +static const char tokenLabel[] = "logout-retire"; +static unsigned char keyValue[32] = "logout-retire-secret-key-012345"; + +/* Every live block is linked through its header so it can be scanned. */ +typedef struct TrackHdr { + struct TrackHdr* prev; + struct TrackHdr* next; + size_t sz; +} TrackHdr; + +#define TRACK_HDR_SZ 32 + +static TrackHdr* liveHead = NULL; +static long liveBlocks = 0; +#ifdef RETIRE_TEST_THREADS +static pthread_mutex_t trackMutex = PTHREAD_MUTEX_INITIALIZER; +#define TRACK_LOCK() pthread_mutex_lock(&trackMutex) +#define TRACK_UNLOCK() pthread_mutex_unlock(&trackMutex) +#else +#define TRACK_LOCK() +#define TRACK_UNLOCK() +#endif + +static void* track_malloc(size_t sz) +{ + unsigned char* p = (unsigned char*)malloc(sz + TRACK_HDR_SZ); + TrackHdr* hdr; + + if (p == NULL) + return NULL; + memset(p, 0, sz + TRACK_HDR_SZ); + hdr = (TrackHdr*)p; + hdr->sz = sz; + TRACK_LOCK(); + hdr->next = liveHead; + if (liveHead != NULL) + liveHead->prev = hdr; + liveHead = hdr; + liveBlocks++; + TRACK_UNLOCK(); + return p + TRACK_HDR_SZ; +} + +static void track_free(void* ptr) +{ + TrackHdr* hdr; + + if (ptr == NULL) + return; + hdr = (TrackHdr*)((unsigned char*)ptr - TRACK_HDR_SZ); + TRACK_LOCK(); + if (hdr->prev != NULL) + hdr->prev->next = hdr->next; + else + liveHead = hdr->next; + if (hdr->next != NULL) + hdr->next->prev = hdr->prev; + liveBlocks--; + TRACK_UNLOCK(); + /* Poison so a use after free reads garbage, not the old key. */ + memset(ptr, 0xA5, hdr->sz); + free(hdr); +} + +static void* track_realloc(void* ptr, size_t sz) +{ + void* np; + size_t old; + + if (ptr == NULL) + return track_malloc(sz); + np = track_malloc(sz); + if (np == NULL) + return NULL; + old = ((TrackHdr*)((unsigned char*)ptr - TRACK_HDR_SZ))->sz; + memcpy(np, ptr, old < sz ? old : sz); + track_free(ptr); + return np; +} + +static int live_holds_key(void) +{ + TrackHdr* hdr; + unsigned char* buf; + size_t i; + int found = 0; + + TRACK_LOCK(); + for (hdr = liveHead; hdr != NULL; hdr = hdr->next) { + buf = (unsigned char*)hdr + TRACK_HDR_SZ; + for (i = 0; hdr->sz >= sizeof(keyValue) && + i <= hdr->sz - sizeof(keyValue); i++) { + if (memcmp(buf + i, keyValue, sizeof(keyValue)) == 0) + found = 1; + } + } + TRACK_UNLOCK(); + return found; +} + +static CK_RV token_setup(CK_SLOT_ID* slot) +{ + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + CK_SESSION_HANDLE soSession = CK_INVALID_HANDLE; + unsigned char label[32]; + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + if (rv == CKR_OK) + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv == CKR_OK && slotCount == 0) + rv = CKR_TOKEN_NOT_PRESENT; + if (rv == CKR_OK) { + *slot = slotList[0]; + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenLabel, XSTRLEN(tokenLabel)); + rv = funcList->C_InitToken(*slot, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin), label); + } + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(*slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &soSession); + } + if (rv == CKR_OK) { + rv = funcList->C_Login(soSession, CKU_SO, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin)); + } + if (rv == CKR_OK) { + rv = funcList->C_InitPIN(soSession, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + funcList->C_Logout(soSession); + } + if (soSession != CK_INVALID_HANDLE) + funcList->C_CloseSession(soSession); + return rv; +} + +static CK_RV create_key(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* key) +{ + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_GENERIC_SECRET; + CK_BBOOL yes = CK_TRUE; + CK_BBOOL no = CK_FALSE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &no, sizeof(no) }, + { CKA_PRIVATE, &yes, sizeof(yes) }, + { CKA_SENSITIVE, &no, sizeof(no) }, + { CKA_EXTRACTABLE, &yes, sizeof(yes) }, + { CKA_VALUE, keyValue, sizeof(keyValue) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), key); +} + +static CK_RV login_create_key(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key) +{ + CK_RV rv; + + rv = funcList->C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + if (rv == CKR_OK) + rv = create_key(session, key); + return rv; +} + +/* A private session key's bytes must not stay in memory after C_Logout. */ +static void logout_clears_retired_key_test(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + + printf("--- logout clears retired session key material ---\n"); + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + if (rv == CKR_OK) + rv = login_create_key(session, &key); + CHECK_RV(rv, "log in and create private session key", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(live_holds_key(), "key bytes held while logged in"); + rv = funcList->C_Logout(session); + CHECK_RV(rv, "C_Logout", CKR_OK); + CHECK_TRUE(!live_holds_key(), "no key bytes left after logout"); + } + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); +} + +/* Login, create and logout cycles on one session keep memory use flat. */ +static void retired_objects_bounded_test(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + long settled = 0; + int i; + + printf("--- retired session objects stay bounded ---\n"); + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + for (i = 0; rv == CKR_OK && i < RETIRE_CYCLES; i++) { + rv = login_create_key(session, &key); + if (rv == CKR_OK) + rv = funcList->C_Logout(session); + if (i == RETIRE_SETTLE) + settled = liveBlocks; + } + CHECK_RV(rv, "login, create and logout cycles", CKR_OK); + if (rv == CKR_OK) { + if (liveBlocks > settled) { + fprintf(stderr, " live blocks grew from %ld to %ld\n", settled, + liveBlocks); + } + CHECK_TRUE(liveBlocks <= settled, "retired objects do not accumulate"); + } + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); +} + +#ifdef DEBUG_WOLFPKCS11 +extern void WP11_Object_SetFindHook(void (*hook)(void)); + +static CK_SESSION_HANDLE hookLogoutSession = CK_INVALID_HANDLE; +static CK_RV hookLogoutRv = CKR_GENERAL_ERROR; + +/* Runs inside C_GetAttributeValue once it holds the object pointer. */ +static void hook_logout(void) +{ + WP11_Object_SetFindHook(NULL); + hookLogoutRv = funcList->C_Logout(hookLogoutSession); +} + +static CK_SESSION_HANDLE hookDestroySession = CK_INVALID_HANDLE; +static CK_OBJECT_HANDLE hookDestroyObject = CK_INVALID_HANDLE; +static CK_RV hookDestroyRv = CKR_GENERAL_ERROR; + +/* Runs inside C_GetAttributeValue once it holds the object pointer. */ +static void hook_destroy(void) +{ + WP11_Object_SetFindHook(NULL); + hookDestroyRv = funcList->C_DestroyObject(hookDestroySession, + hookDestroyObject); +} + +/* Destroying a key while a call uses it must not free it under that call. */ +static void destroy_during_call_test(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + unsigned char value[sizeof(keyValue)]; + CK_ATTRIBUTE valAttr = { CKA_VALUE, value, sizeof(value) }; + + printf("--- destroy during a call keeps the key until the call ends ---\n"); + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + if (rv == CKR_OK) + rv = login_create_key(session, &key); + CHECK_RV(rv, "log in and create session key", CKR_OK); + if (rv == CKR_OK) { + hookDestroySession = session; + hookDestroyObject = key; + hookDestroyRv = CKR_GENERAL_ERROR; + XMEMSET(value, 0, sizeof(value)); + WP11_Object_SetFindHook(hook_destroy); + rv = funcList->C_GetAttributeValue(session, key, &valAttr, 1); + WP11_Object_SetFindHook(NULL); + CHECK_RV(hookDestroyRv, "C_DestroyObject inside the call", CKR_OK); + CHECK_RV(rv, "C_GetAttributeValue", CKR_OK); + CHECK_TRUE(rv == CKR_OK && valAttr.ulValueLen == sizeof(keyValue) && + XMEMCMP(value, keyValue, sizeof(keyValue)) == 0, + "call reads the intact key"); + CHECK_TRUE(!live_holds_key(), "key freed once the call ends"); + funcList->C_Logout(session); + } + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); +} + +/* A logout while a call uses a private session key must not free the key. */ +static void logout_during_call_test(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_SESSION_HANDLE other = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + unsigned char value[sizeof(keyValue)]; + CK_ATTRIBUTE valAttr = { CKA_VALUE, value, sizeof(value) }; + + printf("--- logout during a call keeps the key until the call ends ---\n"); + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &other); + } + if (rv == CKR_OK) + rv = login_create_key(session, &key); + CHECK_RV(rv, "log in and create private session key", CKR_OK); + if (rv == CKR_OK) { + hookLogoutSession = other; + hookLogoutRv = CKR_GENERAL_ERROR; + XMEMSET(value, 0, sizeof(value)); + WP11_Object_SetFindHook(hook_logout); + rv = funcList->C_GetAttributeValue(session, key, &valAttr, 1); + WP11_Object_SetFindHook(NULL); + CHECK_RV(hookLogoutRv, "C_Logout inside the call", CKR_OK); + CHECK_RV(rv, "C_GetAttributeValue", CKR_OK); + CHECK_TRUE(rv == CKR_OK && valAttr.ulValueLen == sizeof(keyValue) && + XMEMCMP(value, keyValue, sizeof(keyValue)) == 0, + "call reads the intact key"); + CHECK_TRUE(!live_holds_key(), "key freed once the call ends"); + } + if (other != CK_INVALID_HANDLE) + funcList->C_CloseSession(other); + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); +} + +/* A logout while C_DestroyObject holds the key leaves one owner of the key. */ +static void logout_during_destroy_test(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_SESSION_HANDLE other = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + + printf("--- logout during destroy frees the key once ---\n"); + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &other); + } + if (rv == CKR_OK) + rv = login_create_key(session, &key); + CHECK_RV(rv, "log in and create private session key", CKR_OK); + if (rv == CKR_OK) { + hookLogoutSession = other; + hookLogoutRv = CKR_GENERAL_ERROR; + WP11_Object_SetFindHook(hook_logout); + rv = funcList->C_DestroyObject(session, key); + WP11_Object_SetFindHook(NULL); + CHECK_RV(hookLogoutRv, "C_Logout inside C_DestroyObject", CKR_OK); + CHECK_RV(rv, "C_DestroyObject of the retired key", + CKR_OBJECT_HANDLE_INVALID); + CHECK_TRUE(!live_holds_key(), "key freed once the call ends"); + } + if (other != CK_INVALID_HANDLE) + funcList->C_CloseSession(other); + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); +} + +#ifdef RETIRE_TEST_THREADS +#define OVERLAP_STEPS 5 +#define KEY_TAG_IDX (sizeof(keyValue) - 2) + +typedef struct HoldArg { + CK_SESSION_HANDLE session; + CK_OBJECT_HANDLE object; + CK_RV rv; +} HoldArg; + +static pthread_mutex_t holdMutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t holdCond = PTHREAD_COND_INITIALIZER; +static int holdArmed = 0; +static int holdEntered[OVERLAP_STEPS]; +static int holdRelease[OVERLAP_STEPS]; + +/* The armed holder's call waits here, inside its object call. */ +static void hook_hold(void) +{ + int idx; + + pthread_mutex_lock(&holdMutex); + idx = holdArmed - 1; + holdArmed = 0; + if (idx >= 0) { + holdEntered[idx] = 1; + pthread_cond_broadcast(&holdCond); + while (!holdRelease[idx]) + pthread_cond_wait(&holdCond, &holdMutex); + } + pthread_mutex_unlock(&holdMutex); +} + +static void* hold_thread(void* p) +{ + HoldArg* arg = (HoldArg*)p; + CK_OBJECT_CLASS cls; + CK_ATTRIBUTE attr = { CKA_CLASS, &cls, sizeof(cls) }; + + arg->rv = funcList->C_GetAttributeValue(arg->session, arg->object, &attr, + 1); + return NULL; +} + +static int hold_start(pthread_t* th, HoldArg* arg, int idx) +{ + int ret; + + pthread_mutex_lock(&holdMutex); + holdArmed = idx + 1; + holdEntered[idx] = 0; + holdRelease[idx] = 0; + pthread_mutex_unlock(&holdMutex); + ret = pthread_create(th, NULL, hold_thread, arg); + if (ret == 0) { + pthread_mutex_lock(&holdMutex); + while (!holdEntered[idx]) + pthread_cond_wait(&holdCond, &holdMutex); + pthread_mutex_unlock(&holdMutex); + } + return ret; +} + +static void hold_end(pthread_t th, int idx) +{ + pthread_mutex_lock(&holdMutex); + holdRelease[idx] = 1; + pthread_cond_broadcast(&holdCond); + pthread_mutex_unlock(&holdMutex); + pthread_join(th, NULL); +} + +static int live_holds_tagged_key(int idx) +{ + unsigned char saved = keyValue[KEY_TAG_IDX]; + int found; + + keyValue[KEY_TAG_IDX] = (unsigned char)('a' + idx); + found = live_holds_key(); + keyValue[KEY_TAG_IDX] = saved; + return found; +} + +/* Overlapping calls that never all end at once must not hold destroyed keys: + * each one is freed once the calls that began before its destroy end. */ +static void destroy_while_calls_overlap_test(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_SESSION_HANDLE other = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE data = CK_INVALID_HANDLE; + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_BBOOL no = CK_FALSE; + unsigned char dataValue[] = "hold"; + CK_ATTRIBUTE dataTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &no, sizeof(no) }, + { CKA_PRIVATE, &no, sizeof(no) }, + { CKA_VALUE, dataValue, sizeof(dataValue) }, + }; + pthread_t th[OVERLAP_STEPS]; + HoldArg arg[OVERLAP_STEPS]; + unsigned char saved = keyValue[KEY_TAG_IDX]; + int started = 0; + int i; + + printf("--- destroyed keys are freed while calls keep overlapping ---\n"); + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &other); + } + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(other, dataTmpl, + sizeof(dataTmpl) / sizeof(*dataTmpl), &data); + } + if (rv == CKR_OK) { + rv = funcList->C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + } + CHECK_RV(rv, "open sessions, create data object and log in", CKR_OK); + if (rv == CKR_OK) + WP11_Object_SetFindHook(hook_hold); + for (i = 0; rv == CKR_OK && i < OVERLAP_STEPS; i++) { + arg[i].session = other; + arg[i].object = data; + arg[i].rv = CKR_GENERAL_ERROR; + if (hold_start(&th[i], &arg[i], i) != 0) { + rv = CKR_GENERAL_ERROR; + break; + } + started = i + 1; + if (i > 0) + hold_end(th[i - 1], i - 1); + if (i > 1) { + CHECK_TRUE(!live_holds_tagged_key(i - 2), + "key destroyed before two calls ended is freed"); + } + keyValue[KEY_TAG_IDX] = (unsigned char)('a' + i); + rv = create_key(session, &key); + keyValue[KEY_TAG_IDX] = saved; + if (rv == CKR_OK) + rv = funcList->C_DestroyObject(session, key); + } + CHECK_RV(rv, "create and destroy keys while calls overlap", CKR_OK); + if (started > 0) + hold_end(th[started - 1], started - 1); + WP11_Object_SetFindHook(NULL); + for (i = 0; i < started; i++) { + CHECK_RV(arg[i].rv, "held C_GetAttributeValue", CKR_OK); + CHECK_TRUE(!live_holds_tagged_key(i), "destroyed key freed at end"); + } + funcList->C_Logout(session); + if (other != CK_INVALID_HANDLE) + funcList->C_CloseSession(other); + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); +} +#endif +#endif + +int main(int argc, char* argv[]) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + + (void)argc; + (void)argv; + +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + if (wolfSSL_SetAllocators(track_malloc, track_free, track_realloc) != 0) { + printf("Allocator hooks not available, skipping test\n"); + return 77; + } + + printf("=== wolfPKCS11 logout retire test ===\n"); + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv == CKR_OK) { + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + } + if (rv == CKR_OK) { + logout_clears_retired_key_test(slot); + retired_objects_bounded_test(slot); +#ifdef DEBUG_WOLFPKCS11 + logout_during_call_test(slot); + destroy_during_call_test(slot); + logout_during_destroy_test(slot); +#endif +#ifdef RETIRE_TEST_THREADS + destroy_while_calls_overlap_test(slot); +#endif + } + if (funcList != NULL) + funcList->C_Finalize(NULL); + pkcs11_unload(); + return pkcs11_test_summary(); +} + +#else + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + printf("NSS keeps private session objects across logout, skipping test\n"); + return 77; +} + +#endif diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index de846042..709e3da4 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -384,6 +384,7 @@ C_EXTRA_FLAGS="-DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT" typedef struct WP11_Object WP11_Object; typedef struct WP11_Session WP11_Session; typedef struct WP11_Slot WP11_Slot; +typedef struct WP11_ObjectCalls WP11_ObjectCalls; WP11_LOCAL int WP11_Library_Init(void); @@ -413,10 +414,15 @@ WP11_LOCAL int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen); WP11_LOCAL int WP11_Slot_IsLoggedIn(WP11_Slot* slot); WP11_LOCAL int WP11_Slot_IsUserLoggedIn(WP11_Slot* slot); WP11_LOCAL void WP11_Slot_Logout(WP11_Slot* slot); +WP11_LOCAL WP11_ObjectCalls* WP11_Slot_ObjectCallEnter( + CK_SESSION_HANDLE hSession); +WP11_LOCAL void WP11_Slot_ObjectCallLeave(WP11_ObjectCalls* calls); +WP11_LOCAL void WP11_Slot_DiscardObject(WP11_Slot* slot, WP11_Object* object); #ifdef DEBUG_WOLFPKCS11 WP11_API int WP11_Slot_TokenKeyIsZero(CK_SLOT_ID slotId); WP11_API int WP11_Slot_TokenDecodedObjectCount(CK_SLOT_ID slotId); WP11_API void WP11_Session_SetFindHook(void (*hook)(void)); +WP11_API void WP11_Object_SetFindHook(void (*hook)(void)); #if defined(WOLFPKCS11_TPM) && (!defined(NO_RSA) || defined(HAVE_ECC)) WP11_API int WP11_Test_DecodeTpmKey(CK_SLOT_ID slotId, unsigned char* keyData, int keyDataLen); From 7f7dc2ddd190a59d1d0ae227cc7aed024547932a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:31:32 -0700 Subject: [PATCH 013/204] Count object calls under a dedicated epoch lock --- src/internal.c | 213 +++++++++++++++++++++++++++---------------------- 1 file changed, 118 insertions(+), 95 deletions(-) diff --git a/src/internal.c b/src/internal.c index 65884bed..92ba0084 100644 --- a/src/internal.c +++ b/src/internal.c @@ -587,7 +587,8 @@ struct WP11_Slot { WP11_ObjectCalls calls[2]; /* Object calls of the last two epochs */ word32 epoch; /* Current object call epoch */ WP11_Object* discarded; /* Destroyed objects awaiting free */ - byte reclaim:1; /* Retired objects may await free */ + WP11_Lock callLock; /* Lock for object call epochs */ + byte reclaim:1; /* Unlinked objects await free */ }; #if defined(HAVE_FIPS) && FIPS_VERSION_LT(6,0) @@ -955,15 +956,20 @@ static int wp11_Slot_AddSession(WP11_Slot* slot, WP11_Session** session) return ret; } +static void wp11_Slot_UnlinkObject(WP11_Slot* slot, WP11_Object* object, + WP11_Object** list); +static void wp11_Slot_FreeUnlinkedObjects(WP11_Slot* slot); + /** * Finalize a session - clean-up but don't clear out. + * Caller holds the slot lock for writing. * * @param session [in] Session object. */ static void wp11_Session_Final(WP11_Session* session) { WP11_Object* obj; - WP11_Object* retired; + WP11_Slot* slot = session->slot; if (session->inUse) { /* Free objects in session. */ @@ -974,16 +980,18 @@ static void wp11_Session_Final(WP11_Session* session) } session->inUse = 0; } - /* Retired objects change only under the slot lock, which callers hold - * (or the library is being torn down). */ - retired = session->retired; - session->retired = NULL; - while (retired != NULL) { - obj = retired; - retired = retired->next; - WP11_Object_Free(obj); - } session->curr = NULL; + /* Rollback paths unlink retired objects under the token lock, and a call + * that found one before logout may still be running. */ + if (session->retired != NULL) { + WP11_Lock_LockRW(&slot->token.lock); + while ((obj = session->retired) != NULL) { + session->retired = obj->next; + wp11_Slot_UnlinkObject(slot, obj, &slot->discarded); + } + wp11_Slot_FreeUnlinkedObjects(slot); + WP11_Lock_UnlockRW(&slot->token.lock); + } /* Finalize any find. */ WP11_Session_FindFinal(session); @@ -7839,6 +7847,7 @@ static void wp11_Slot_Final(WP11_Slot* slot) #ifdef WOLFPKCS11_TPM wp11_TpmFinal(slot); #endif + WP11_Lock_Free(&slot->callLock); WP11_Lock_Free(&slot->lock); } @@ -7866,6 +7875,12 @@ static int wp11_Slot_Init(WP11_Slot* slot, int id) slot->calls[1].slot = slot; ret = WP11_Lock_Init(&slot->lock); + if (ret == 0) { + ret = WP11_Lock_Init(&slot->callLock); + if (ret != 0) { + WP11_Lock_Free(&slot->lock); + } + } if (ret == 0) { #if defined(WOLFPKCS11_TPM) ret = wp11_TpmInit(slot); @@ -8235,7 +8250,6 @@ int WP11_Slot_OpenSession(WP11_Slot* slot, unsigned long flags, void* app, } static void wp11_Slot_Logout(WP11_Slot* slot); -static void wp11_Slot_FreeDiscardedObjects(WP11_Slot* slot); /** * Close a session associated with a slot. @@ -8277,7 +8291,7 @@ void WP11_Slot_CloseSession(WP11_Slot* slot, WP11_Session* session) wp11_Session_Final(session); /* Its operation no longer holds objects destroyed while it ran. */ WP11_Lock_LockRW(&slot->token.lock); - wp11_Slot_FreeDiscardedObjects(slot); + wp11_Slot_FreeUnlinkedObjects(slot); WP11_Lock_UnlockRW(&slot->token.lock); /* Decide and log out under the same lock so a concurrent open is either @@ -8803,7 +8817,10 @@ int WP11_Slot_SOLogin(WP11_Slot* slot, char* pin, int pinLen) } /** - * Check whether a session's initialized operation uses the object as its key. + * Check whether a session's initialized key operation uses the object as its + * key. A digest-only operation and a finished operation leave a stale + * pointer behind that must not keep the object alive. + * Caller holds the slot lock. * * @param slot [in] Slot object. * @param object [in] Object to look for. @@ -8814,8 +8831,10 @@ static int wp11_Slot_ObjectIsActive(WP11_Slot* slot, WP11_Object* object) WP11_Session* sess; for (sess = slot->session; sess != NULL; sess = sess->next) { - if (sess->curr == object && sess->init != 0) + if (sess->curr == object && + (sess->init & ~WP11_INIT_DIGEST_MASK) != 0) { return 1; + } } return 0; } @@ -8823,7 +8842,7 @@ static int wp11_Slot_ObjectIsActive(WP11_Slot* slot, WP11_Object* object) /** * Start a new object call epoch once no call from the previous one runs, so * calls only ever run in the current and previous epochs. - * Caller holds the slot lock for writing. + * Caller holds the call lock. * * @param slot [in] Slot object. */ @@ -8836,7 +8855,7 @@ static void wp11_Slot_AdvanceEpoch(WP11_Slot* slot) /** * Check whether an unlinked object can be freed: every object call that began * by the epoch it was unlinked in has ended and no operation uses it. - * Caller holds the slot and token locks for writing. + * Caller holds the slot and call locks. * * @param slot [in] Slot object. * @param object [in] Unlinked object. @@ -8859,46 +8878,90 @@ static int wp11_Slot_ObjectFreeable(WP11_Slot* slot, WP11_Object* object) } /** - * Free an object nothing uses any more, dropping stale operation pointers. - * Caller holds the slot and token locks for writing. + * Move the unlinked objects of a list that nothing can still use onto the + * free list. Caller holds the slot, token and call locks. * - * @param slot [in] Slot object. - * @param object [in] Object to free. + * @param slot [in] Slot object. + * @param list [in,out] Head of the list of unlinked objects. + * @param free [in,out] Head of the list of objects to free. + * @return 1 when objects are left on the list, 0 otherwise. */ -static void wp11_Slot_FreeUnused(WP11_Slot* slot, WP11_Object* object) +static int wp11_Slot_CollectFreeable(WP11_Slot* slot, WP11_Object** list, + WP11_Object** free) { - WP11_Session* sess; + WP11_Object* obj; + int left = 0; - for (sess = slot->session; sess != NULL; sess = sess->next) { - if (sess->curr == object) - sess->curr = NULL; + while (*list != NULL) { + obj = *list; + if (!wp11_Slot_ObjectFreeable(slot, obj)) { + list = &obj->next; + left = 1; + } + else { + *list = obj->next; + obj->next = *free; + *free = obj; + } } - WP11_Object_Free(object); + + return left; } /** - * Free the destroyed objects that no object call or operation can still use. + * Free (and so zeroize) the objects that destroy unlinked or logout retired + * once no object call or key operation can still use them. * Caller holds the slot and token locks for writing. * * @param slot [in] Slot object. */ -static void wp11_Slot_FreeDiscardedObjects(WP11_Slot* slot) +static void wp11_Slot_FreeUnlinkedObjects(WP11_Slot* slot) { - WP11_Object** curr = &slot->discarded; + WP11_Object* free = NULL; WP11_Object* obj; + int left; +#ifndef WOLFPKCS11_NSS + WP11_Session* sess; +#endif - while (*curr != NULL) { - obj = *curr; - if (!wp11_Slot_ObjectFreeable(slot, obj)) { - curr = &obj->next; - } - else { - *curr = obj->next; - wp11_Slot_FreeUnused(slot, obj); - } + WP11_Lock_LockRW(&slot->callLock); + wp11_Slot_AdvanceEpoch(slot); + left = wp11_Slot_CollectFreeable(slot, &slot->discarded, &free); +#ifndef WOLFPKCS11_NSS + for (sess = slot->session; sess != NULL; sess = sess->next) { + if (wp11_Slot_CollectFreeable(slot, &sess->retired, &free)) + left = 1; + } +#endif + slot->reclaim = (byte)left; + WP11_Lock_UnlockRW(&slot->callLock); + + while (free != NULL) { + obj = free; + free = obj->next; + WP11_Object_Free(obj); } } +/** + * Put an unlinked object on a list of objects awaiting free, stamped with the + * current epoch. Caller holds the slot and token locks for writing. + * + * @param slot [in] Slot object. + * @param object [in] Unlinked object. + * @param list [in,out] Head of the list to put it on. + */ +static void wp11_Slot_UnlinkObject(WP11_Slot* slot, WP11_Object* object, + WP11_Object** list) +{ + WP11_Lock_LockRW(&slot->callLock); + object->freeEpoch = slot->epoch; + object->next = *list; + *list = object; + slot->reclaim = 1; + WP11_Lock_UnlockRW(&slot->callLock); +} + /** * Hand over an object that C_DestroyObject has unlinked. It is freed once no * object call that began before the unlink and no operation can use it. @@ -8910,49 +8973,12 @@ void WP11_Slot_DiscardObject(WP11_Slot* slot, WP11_Object* object) { WP11_Lock_LockRW(&slot->lock); WP11_Lock_LockRW(&slot->token.lock); - object->freeEpoch = slot->epoch; - object->next = slot->discarded; - slot->discarded = object; - wp11_Slot_AdvanceEpoch(slot); - wp11_Slot_FreeDiscardedObjects(slot); + wp11_Slot_UnlinkObject(slot, object, &slot->discarded); + wp11_Slot_FreeUnlinkedObjects(slot); WP11_Lock_UnlockRW(&slot->token.lock); WP11_Lock_UnlockRW(&slot->lock); } -#ifndef WOLFPKCS11_NSS - -/** - * Free (and so zeroize) the retired objects that nothing can still use: every - * object call that began before logout retired them has ended and no - * operation uses them. Caller holds the slot and token locks for writing. - * - * @param slot [in] Slot object. - */ -static void wp11_Slot_FreeRetiredObjects(WP11_Slot* slot) -{ - WP11_Session* sess; - WP11_Object** curr; - WP11_Object* obj; - int left = 0; - - for (sess = slot->session; sess != NULL; sess = sess->next) { - curr = &sess->retired; - while (*curr != NULL) { - obj = *curr; - if (!wp11_Slot_ObjectFreeable(slot, obj)) { - curr = &obj->next; - left = 1; - } - else { - *curr = obj->next; - wp11_Slot_FreeUnused(slot, obj); - } - } - } - slot->reclaim = (byte)left; -} -#endif - /** * Note the start of a call that may use object pointers looked up by handle. * Objects unlinked while it runs are not freed until it has finished. @@ -8967,10 +8993,10 @@ WP11_ObjectCalls* WP11_Slot_ObjectCallEnter(CK_SESSION_HANDLE hSession) if (WP11_Library_IsInitialized() && WP11_Slot_Get(SESS_HANDLE_SLOT_ID(hSession), &slot) == 0) { - WP11_Lock_LockRW(&slot->lock); + WP11_Lock_LockRW(&slot->callLock); calls = &slot->calls[slot->epoch & 1]; calls->cnt++; - WP11_Lock_UnlockRW(&slot->lock); + WP11_Lock_UnlockRW(&slot->callLock); } return calls; @@ -8985,21 +9011,22 @@ WP11_ObjectCalls* WP11_Slot_ObjectCallEnter(CK_SESSION_HANDLE hSession) void WP11_Slot_ObjectCallLeave(WP11_ObjectCalls* calls) { WP11_Slot* slot; + int scan; if (calls != NULL) { slot = calls->slot; - WP11_Lock_LockRW(&slot->lock); + WP11_Lock_LockRW(&slot->callLock); calls->cnt--; - wp11_Slot_AdvanceEpoch(slot); - if (slot->reclaim || slot->discarded != NULL) { + /* Only an epoch that drains can make an unlinked object freeable. */ + scan = (calls->cnt == 0) && slot->reclaim; + WP11_Lock_UnlockRW(&slot->callLock); + if (scan) { + WP11_Lock_LockRW(&slot->lock); WP11_Lock_LockRW(&slot->token.lock); - #ifndef WOLFPKCS11_NSS - wp11_Slot_FreeRetiredObjects(slot); - #endif - wp11_Slot_FreeDiscardedObjects(slot); + wp11_Slot_FreeUnlinkedObjects(slot); WP11_Lock_UnlockRW(&slot->token.lock); + WP11_Lock_UnlockRW(&slot->lock); } - WP11_Lock_UnlockRW(&slot->lock); } } @@ -9128,7 +9155,7 @@ int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen) token->loginState = WP11_APP_STATE_RW_USER; #ifndef WOLFPKCS11_NSS WP11_Lock_LockRW(&token->lock); - wp11_Slot_FreeRetiredObjects(slot); + wp11_Slot_FreeUnlinkedObjects(slot); WP11_Lock_UnlockRW(&token->lock); #endif WP11_Lock_UnlockRW(&slot->lock); @@ -9310,18 +9337,14 @@ static void wp11_Slot_RetirePrivateSessionObjects(WP11_Slot* slot) if ((obj->opFlag & WP11_FLAG_PRIVATE) == WP11_FLAG_PRIVATE) { *curr = obj->next; sess->objCnt--; - obj->freeEpoch = slot->epoch; - obj->next = sess->retired; - sess->retired = obj; - slot->reclaim = 1; + wp11_Slot_UnlinkObject(slot, obj, &sess->retired); } else { curr = &obj->next; } } } - wp11_Slot_AdvanceEpoch(slot); - wp11_Slot_FreeRetiredObjects(slot); + wp11_Slot_FreeUnlinkedObjects(slot); } #endif From f3e227e1628b95641b61ba4b9fec22805f67a0b4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:31:32 -0700 Subject: [PATCH 014/204] Ignore digest-only operations for destroyed keys --- src/internal.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 92ba0084..175beab4 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10536,8 +10536,10 @@ static int wp11_Session_RetiredHeld(WP11_Session* session) WP11_Object* obj; for (obj = session->retired; obj != NULL; obj = obj->next) { - if (obj != session->curr || session->init == 0) + if (obj != session->curr || + (session->init & ~WP11_INIT_DIGEST_MASK) == 0) { cnt++; + } } return cnt; } From 05e7c49e02b64f59c02350e2915de8e565be1083 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:31:33 -0700 Subject: [PATCH 015/204] Return CKR_USER_NOT_LOGGED_IN for private adds --- src/crypto.c | 5 +- src/internal.c | 24 +++- tests/logout_retire_test.c | 232 +++++++++++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 1 + 4 files changed, 259 insertions(+), 3 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 335bb181..92de2f52 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1298,6 +1298,8 @@ static CK_RV AddObject(WP11_Session* session, WP11_Object* object, } ret = WP11_Session_AddObject(session, onToken, object); + if (ret == PIN_INVALID_E) + return CKR_USER_NOT_LOGGED_IN; if (ret != 0) return CKR_FUNCTION_FAILED; @@ -1837,7 +1839,8 @@ static CK_RV wp11_C_CopyObject(CK_SESSION_HANDLE hSession, ret = WP11_Session_AddObject(session, onToken, newObj); if (ret != 0) { WP11_Object_Free(newObj); - return CKR_FUNCTION_FAILED; + return (ret == PIN_INVALID_E) ? CKR_USER_NOT_LOGGED_IN : + CKR_FUNCTION_FAILED; } *phNewObject = WP11_Object_GetHandle(newObj); diff --git a/src/internal.c b/src/internal.c index 175beab4..a111941c 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10545,6 +10545,20 @@ static int wp11_Session_RetiredHeld(WP11_Session* session) } #endif +#ifdef DEBUG_WOLFPKCS11 +static void (*wp11_addObjectHook)(void) = NULL; + +/** + * Test hook: called by WP11_Session_AddObject before it takes any lock. + * + * @param hook [in] Function to call, or NULL to remove the hook. + */ +WP11_API void WP11_Session_SetAddObjectHook(void (*hook)(void)) +{ + wp11_addObjectHook = hook; +} +#endif + /** * Add object to the session or token. * @@ -10561,8 +10575,13 @@ int WP11_Session_AddObject(WP11_Session* session, int onToken, WP11_Token* token; #ifndef WOLFPKCS11_NSS int privSession; + int emptyPin = 0; #endif +#ifdef DEBUG_WOLFPKCS11 + if (wp11_addObjectHook != NULL) + wp11_addObjectHook(); +#endif object->onToken = onToken; if (!onToken) object->session = session; @@ -10572,10 +10591,11 @@ int WP11_Session_AddObject(WP11_Session* session, int onToken, privSession = !onToken && (object->opFlag & WP11_FLAG_PRIVATE) == WP11_FLAG_PRIVATE; if (privSession) { + /* The cached verdict may be unset, so compute it before locking. */ + emptyPin = WP11_Slot_Has_Empty_Pin(session->slot); /* Held to the add so a logout cannot slip between check and add. */ WP11_Lock_LockRO(&session->slot->lock); - if ((token->tokenFlags & WP11_TOKEN_FLAG_USER_PIN_SET) && - token->userPinEmpty != 1 && + if ((token->tokenFlags & WP11_TOKEN_FLAG_USER_PIN_SET) && !emptyPin && !wp11_LoginStateIsUser(token->loginState)) { ret = PIN_INVALID_E; } diff --git a/tests/logout_retire_test.c b/tests/logout_retire_test.c index 4c91fe39..05dc0464 100644 --- a/tests/logout_retire_test.c +++ b/tests/logout_retire_test.c @@ -293,6 +293,7 @@ static void retired_objects_bounded_test(CK_SLOT_ID slot) #ifdef DEBUG_WOLFPKCS11 extern void WP11_Object_SetFindHook(void (*hook)(void)); +extern void WP11_Session_SetAddObjectHook(void (*hook)(void)); static CK_SESSION_HANDLE hookLogoutSession = CK_INVALID_HANDLE; static CK_RV hookLogoutRv = CKR_GENERAL_ERROR; @@ -426,6 +427,151 @@ static void logout_during_destroy_test(CK_SLOT_ID slot) funcList->C_CloseSession(session); } +static CK_SESSION_HANDLE hookCloseSession = CK_INVALID_HANDLE; +static CK_RV hookCloseRv = CKR_GENERAL_ERROR; +static int hookCloseKeyLive = 0; + +/* Runs inside C_GetAttributeValue on another session's object. */ +static void hook_logout_close(void) +{ + WP11_Object_SetFindHook(NULL); + hookLogoutRv = funcList->C_Logout(hookLogoutSession); + hookCloseRv = funcList->C_CloseSession(hookCloseSession); + hookCloseKeyLive = live_holds_key(); +} + +/* Closing a session with a retired key keeps it until the running call ends. */ +static void close_with_retired_test(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_SESSION_HANDLE other = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE data = CK_INVALID_HANDLE; + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_OBJECT_CLASS cls; + CK_BBOOL no = CK_FALSE; + unsigned char dataValue[] = "hold"; + CK_ATTRIBUTE dataTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &no, sizeof(no) }, + { CKA_PRIVATE, &no, sizeof(no) }, + { CKA_VALUE, dataValue, sizeof(dataValue) }, + }; + CK_ATTRIBUTE clsAttr = { CKA_CLASS, &cls, sizeof(cls) }; + + printf("--- close session with retired key during a call ---\n"); + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &other); + } + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(other, dataTmpl, + sizeof(dataTmpl) / sizeof(*dataTmpl), &data); + } + if (rv == CKR_OK) + rv = login_create_key(session, &key); + CHECK_RV(rv, "log in and create private session key", CKR_OK); + if (rv == CKR_OK) { + hookLogoutSession = other; + hookCloseSession = session; + hookLogoutRv = CKR_GENERAL_ERROR; + hookCloseRv = CKR_GENERAL_ERROR; + hookCloseKeyLive = 0; + WP11_Object_SetFindHook(hook_logout_close); + rv = funcList->C_GetAttributeValue(other, data, &clsAttr, 1); + WP11_Object_SetFindHook(NULL); + session = CK_INVALID_HANDLE; + CHECK_RV(hookLogoutRv, "C_Logout inside the call", CKR_OK); + CHECK_RV(hookCloseRv, "C_CloseSession inside the call", CKR_OK); + CHECK_TRUE(hookCloseKeyLive, "retired key kept while the call runs"); + CHECK_RV(rv, "C_GetAttributeValue", CKR_OK); + CHECK_TRUE(!live_holds_key(), "key freed once the call ends"); + } + if (other != CK_INVALID_HANDLE) + funcList->C_CloseSession(other); + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); +} + +#ifdef HAVE_ECC +static int hookAddCalls = 0; + +/* Runs at the start of each object add; logs out before the second one. */ +static void hook_add_logout(void) +{ + hookAddCalls++; + if (hookAddCalls == 2) { + WP11_Session_SetAddObjectHook(NULL); + hookLogoutRv = funcList->C_Logout(hookLogoutSession); + } +} + +/* A logout between the two adds of a key pair retires the first key; the + * rollback must get it back and free it exactly once. */ +static void keypair_rollback_after_logout_test(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_SESSION_HANDLE other = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_EC_KEY_PAIR_GEN, NULL, 0 }; + CK_BBOOL yes = CK_TRUE; + CK_BBOOL no = CK_FALSE; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_TOKEN, &no, sizeof(no) }, + { CKA_PRIVATE, &yes, sizeof(yes) }, + { CKA_VERIFY, &yes, sizeof(yes) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_TOKEN, &no, sizeof(no) }, + { CKA_PRIVATE, &yes, sizeof(yes) }, + { CKA_SIGN, &yes, sizeof(yes) }, + }; + long before; + + printf("--- key pair rollback after a logout between the adds ---\n"); + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &other); + } + if (rv == CKR_OK) { + rv = funcList->C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + } + CHECK_RV(rv, "open sessions and log in", CKR_OK); + if (rv == CKR_OK) { + hookLogoutSession = other; + hookLogoutRv = CKR_GENERAL_ERROR; + hookAddCalls = 0; + before = liveBlocks; + WP11_Session_SetAddObjectHook(hook_add_logout); + rv = funcList->C_GenerateKeyPair(session, &mech, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &pub, &priv); + WP11_Session_SetAddObjectHook(NULL); + CHECK_RV(hookLogoutRv, "C_Logout between the two adds", CKR_OK); + CHECK_RV(rv, "C_GenerateKeyPair after the logout", + CKR_USER_NOT_LOGGED_IN); + if (liveBlocks != before) { + fprintf(stderr, " live blocks went from %ld to %ld\n", before, + liveBlocks); + } + CHECK_TRUE(liveBlocks == before, "rolled back key pair freed"); + } + if (other != CK_INVALID_HANDLE) + funcList->C_CloseSession(other); + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); +} +#endif + #ifdef RETIRE_TEST_THREADS #define OVERLAP_STEPS 5 #define KEY_TAG_IDX (sizeof(keyValue) - 2) @@ -589,6 +735,85 @@ static void destroy_while_calls_overlap_test(CK_SLOT_ID slot) #endif #endif +#if !defined(NO_AES) && defined(HAVE_AES_CBC) +/* Right after C_InitPIN("") the empty PIN verdict is not cached yet; deriving + * a private session key without a login must still be allowed. */ +static void empty_pin_derive_test(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_KEY_TYPE aesType = CKK_AES; + CK_KEY_TYPE genericType = CKK_GENERIC_SECRET; + CK_BBOOL yes = CK_TRUE; + CK_BBOOL no = CK_FALSE; + unsigned char aesKey[16] = "empty-pin-base"; + unsigned char plain[32] = "empty-pin-derive-data-012345678"; + CK_ULONG len = sizeof(plain); + CK_AES_CBC_ENCRYPT_DATA_PARAMS params; + CK_MECHANISM mech = { CKM_AES_CBC_ENCRYPT_DATA, ¶ms, sizeof(params) }; + CK_ATTRIBUTE baseTmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_TOKEN, &no, sizeof(no) }, + { CKA_PRIVATE, &no, sizeof(no) }, + { CKA_DERIVE, &yes, sizeof(yes) }, + { CKA_VALUE, aesKey, sizeof(aesKey) }, + }; + CK_ATTRIBUTE derivedTmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_TOKEN, &no, sizeof(no) }, + { CKA_VALUE_LEN, &len, sizeof(len) }, + }; + + printf("--- derive a private session key right after an empty PIN ---\n"); + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + if (rv == CKR_OK) { + rv = funcList->C_Login(session, CKU_SO, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin)); + } + if (rv == CKR_OK) { + rv = funcList->C_InitPIN(session, (CK_UTF8CHAR_PTR)"", 0); + if (rv == CKR_PIN_LEN_RANGE) { + printf(" empty PIN not allowed by this build, skipping\n"); + funcList->C_Logout(session); + funcList->C_CloseSession(session); + return; + } + } + CHECK_RV(rv, "log in as SO and set an empty user PIN", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Logout(session); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, baseTmpl, + sizeof(baseTmpl) / sizeof(*baseTmpl), &base); + } + if (rv == CKR_OK) { + XMEMSET(¶ms, 0, sizeof(params)); + params.pData = plain; + params.length = sizeof(plain); + rv = funcList->C_DeriveKey(session, &mech, base, derivedTmpl, + sizeof(derivedTmpl) / sizeof(*derivedTmpl), &derived); + } + CHECK_RV(rv, "derive a private session key without login", CKR_OK); + rv = funcList->C_Login(session, CKU_SO, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin)); + if (rv == CKR_OK) { + rv = funcList->C_InitPIN(session, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + funcList->C_Logout(session); + } + CHECK_RV(rv, "restore the user PIN", CKR_OK); + } + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); +} +#endif + int main(int argc, char* argv[]) { CK_RV rv; @@ -619,9 +844,16 @@ int main(int argc, char* argv[]) logout_during_call_test(slot); destroy_during_call_test(slot); logout_during_destroy_test(slot); + close_with_retired_test(slot); +#ifdef HAVE_ECC + keypair_rollback_after_logout_test(slot); +#endif #endif #ifdef RETIRE_TEST_THREADS destroy_while_calls_overlap_test(slot); +#endif +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + empty_pin_derive_test(slot); #endif } if (funcList != NULL) diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 709e3da4..29b59538 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -423,6 +423,7 @@ WP11_API int WP11_Slot_TokenKeyIsZero(CK_SLOT_ID slotId); WP11_API int WP11_Slot_TokenDecodedObjectCount(CK_SLOT_ID slotId); WP11_API void WP11_Session_SetFindHook(void (*hook)(void)); WP11_API void WP11_Object_SetFindHook(void (*hook)(void)); +WP11_API void WP11_Session_SetAddObjectHook(void (*hook)(void)); #if defined(WOLFPKCS11_TPM) && (!defined(NO_RSA) || defined(HAVE_ECC)) WP11_API int WP11_Test_DecodeTpmKey(CK_SLOT_ID slotId, unsigned char* keyData, int keyDataLen); From 75c7195463ef20609a604dcbdca80f45954997ae Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:32:08 -0700 Subject: [PATCH 016/204] Reserve each PIN guess before hashing it --- src/internal.c | 273 ++++++++++++++++++++--------------- tests/login_pin_state_test.c | 131 +++++++++++++++++ wolfpkcs11/internal.h | 1 + 3 files changed, 287 insertions(+), 118 deletions(-) diff --git a/src/internal.c b/src/internal.c index a111941c..8d8721d6 100644 --- a/src/internal.c +++ b/src/internal.c @@ -542,12 +542,14 @@ typedef struct WP11_Token { int soFailedLogin; /* Count of consecutive failed logins */ time_t soLastFailedLogin; /* Time of last login if it failed */ time_t soFailLoginTimeout; /* Timeout after max login fails */ + int soPinInFlight; /* SO PIN checks in progress */ byte userPin[PIN_HASH_SZ]; /* User's PIN hashed with seed */ int userPinLen; /* Used to indicate PIN set */ byte userPinSeed[PIN_SEED_SZ]; /* Seed for calculating user's PIN */ int userFailedLogin; /* Count of consecutive failed logins */ time_t userLastFailedLogin; /* Time of last login if it failed */ time_t userFailLoginTimeout; /* Timeout after max login fails */ + int userPinInFlight; /* User PIN checks in progress */ #ifndef WOLFPKCS11_NO_STORE byte seed[PIN_SEED_SZ]; /* Seed used to calculate key */ byte key[AES_256_KEY_SIZE]; /* Key to en/decrypt private data */ @@ -8468,6 +8470,90 @@ int WP11_Slot_TokenReset(WP11_Slot* slot, char* pin, int pinLen, char* label) return ret; } +#ifndef WOLFPKCS11_NO_TIME +/** + * Reserve a PIN check against the lockout. Refused once the failures so far + * plus the checks in flight reach the limit, so racing guesses cannot exceed + * it. Caller holds the slot lock for writing. + * + * @param failed [in,out] Count of consecutive failed checks. + * @param inFlight [in,out] Count of checks in progress. + * @param last [in] Time of the failure that locked the PIN. + * @param timeout [in] Lockout duration. + * @param max [in] Failures allowed before lockout. + * @param now [in] Current time. + * @return PIN_INVALID_E when locked out or no attempt is available. + * 0 when the check may proceed. + */ +static int wp11_PinCheckReserve(int* failed, int* inFlight, time_t last, + time_t timeout, int max, time_t now) +{ + int ret = 0; + + if (*failed >= max) { + if (last + timeout < now) + *failed = 0; + else + ret = PIN_INVALID_E; + } + if (ret == 0 && *failed + *inFlight >= max) + ret = PIN_INVALID_E; + if (ret == 0) + (*inFlight)++; + + return ret; +} + +/** + * Give back a reserved PIN check and record its result. + * Caller holds the slot lock for writing. + * + * @param failed [in,out] Count of consecutive failed checks. + * @param inFlight [in,out] Count of checks in progress. + * @param last [out] Time of the failure that locked the PIN. + * @param timeout [in,out] Lockout duration. + * @param max [in] Failures allowed before lockout. + * @param now [in] Current time. + * @param timeoutInc [in] Lockout duration added on each lockout. + * @param ret [in] Result of the PIN check. + */ +static void wp11_PinCheckDone(int* failed, int* inFlight, time_t* last, + time_t* timeout, int max, time_t now, + time_t timeoutInc, int ret) +{ + (*inFlight)--; + if (ret == PIN_INVALID_E) { + /* Concurrent failures must not push the count past the limit. */ + if (*failed < max) { + (*failed)++; + if (*failed == max) { + *last = now; + *timeout += timeoutInc; + } + } + } + else if (ret == 0) { + *failed = 0; + *last = 0; + *timeout = 0; + } +} +#endif + +#ifdef DEBUG_WOLFPKCS11 +static void (*wp11_pinCheckHook)(void) = NULL; + +/** + * Test hook: called before a PIN is hashed for checking, outside any lock. + * + * @param hook [in] Function to call, or NULL to remove the hook. + */ +WP11_API void WP11_Slot_SetPinCheckHook(void (*hook)(void)) +{ + wp11_pinCheckHook = hook; +} +#endif + /** * Check the PIN is correct for SO (Security officer). * @@ -8514,6 +8600,10 @@ int WP11_Slot_CheckSOPin(WP11_Slot* slot, char* pin, int pinLen) if (ret == 0) { WP11_Lock_UnlockRO(&slot->lock); + #ifdef DEBUG_WOLFPKCS11 + if (wp11_pinCheckHook != NULL) + wp11_pinCheckHook(); + #endif /* Costly Operation done out of lock. */ ret = HashPIN(pin, pinLen, token->soPinSeed, sizeof(token->soPinSeed), hash, sizeof(hash), slot); @@ -8555,45 +8645,30 @@ int WP11_Slot_CheckSOPinLockout(WP11_Slot* slot, char* pin, int pinLen) int ret; #ifndef WOLFPKCS11_NO_TIME time_t now; - time_t allowed; + WP11_Token* token = &slot->token; if (wc_GetTime(&now, sizeof(now)) != 0) return PIN_INVALID_E; - /* Check for too many fails and whether the timeout has elapsed. */ + /* Reserve the check so racing guesses cannot exceed the limit. */ WP11_Lock_LockRW(&slot->lock); - if (slot->token.soFailedLogin >= WP11_MAX_LOGIN_FAILS_SO) { - allowed = slot->token.soLastFailedLogin + - slot->token.soFailLoginTimeout; - if (allowed < now) - slot->token.soFailedLogin = 0; - else { - WP11_Lock_UnlockRW(&slot->lock); - return PIN_INVALID_E; - } - } + ret = wp11_PinCheckReserve(&token->soFailedLogin, &token->soPinInFlight, + token->soLastFailedLogin, + token->soFailLoginTimeout, + WP11_MAX_LOGIN_FAILS_SO, now); WP11_Lock_UnlockRW(&slot->lock); + if (ret != 0) + return ret; #endif ret = WP11_Slot_CheckSOPin(slot, pin, pinLen); #ifndef WOLFPKCS11_NO_TIME WP11_Lock_LockRW(&slot->lock); - /* PIN failed - update failure info. */ - if (ret == PIN_INVALID_E && - slot->token.soFailedLogin < WP11_MAX_LOGIN_FAILS_SO) { - slot->token.soFailedLogin++; - if (slot->token.soFailedLogin == WP11_MAX_LOGIN_FAILS_SO) { - slot->token.soLastFailedLogin = now; - slot->token.soFailLoginTimeout += WP11_SO_LOGIN_FAIL_TIMEOUT; - } - } - /* Worked - clear failure info. */ - else if (ret == 0) { - slot->token.soFailedLogin = 0; - slot->token.soLastFailedLogin = 0; - slot->token.soFailLoginTimeout = 0; - } + wp11_PinCheckDone(&token->soFailedLogin, &token->soPinInFlight, + &token->soLastFailedLogin, &token->soFailLoginTimeout, + WP11_MAX_LOGIN_FAILS_SO, now, WP11_SO_LOGIN_FAIL_TIMEOUT, + ret); WP11_Lock_UnlockRW(&slot->lock); #endif @@ -8632,6 +8707,10 @@ int WP11_Slot_CheckUserPin(WP11_Slot* slot, char* pin, int pinLen) XMEMCPY(seed, token->userPinSeed, sizeof(seed)); WP11_Lock_UnlockRO(&slot->lock); + #ifdef DEBUG_WOLFPKCS11 + if (wp11_pinCheckHook != NULL) + wp11_pinCheckHook(); + #endif /* Costly Operation done out of lock. */ ret = HashPIN(pin, pinLen, seed, sizeof(seed), hash, sizeof(hash), slot); @@ -8666,45 +8745,31 @@ int WP11_Slot_CheckUserPinLockout(WP11_Slot* slot, char* pin, int pinLen) int ret; #ifndef WOLFPKCS11_NO_TIME time_t now; - time_t allowed; + WP11_Token* token = &slot->token; if (wc_GetTime(&now, sizeof(now)) != 0) return PIN_INVALID_E; - /* Check for too many fails and whether the timeout has elapsed. */ + /* Reserve the check so racing guesses cannot exceed the limit. */ WP11_Lock_LockRW(&slot->lock); - if (slot->token.userFailedLogin >= WP11_MAX_LOGIN_FAILS_USER) { - allowed = slot->token.userLastFailedLogin + - slot->token.userFailLoginTimeout; - if (allowed < now) - slot->token.userFailedLogin = 0; - else { - WP11_Lock_UnlockRW(&slot->lock); - return PIN_INVALID_E; - } - } + ret = wp11_PinCheckReserve(&token->userFailedLogin, + &token->userPinInFlight, + token->userLastFailedLogin, + token->userFailLoginTimeout, + WP11_MAX_LOGIN_FAILS_USER, now); WP11_Lock_UnlockRW(&slot->lock); + if (ret != 0) + return ret; #endif ret = WP11_Slot_CheckUserPin(slot, pin, pinLen); #ifndef WOLFPKCS11_NO_TIME WP11_Lock_LockRW(&slot->lock); - /* PIN failed - update failure info. */ - if (ret == PIN_INVALID_E && - slot->token.userFailedLogin < WP11_MAX_LOGIN_FAILS_USER) { - slot->token.userFailedLogin++; - if (slot->token.userFailedLogin == WP11_MAX_LOGIN_FAILS_USER) { - slot->token.userLastFailedLogin = now; - slot->token.userFailLoginTimeout += WP11_USER_LOGIN_FAIL_TIMEOUT; - } - } - /* Worked - clear failure info. */ - else if (ret == 0) { - slot->token.userFailedLogin = 0; - slot->token.userLastFailedLogin = 0; - slot->token.userFailLoginTimeout = 0; - } + wp11_PinCheckDone(&token->userFailedLogin, &token->userPinInFlight, + &token->userLastFailedLogin, + &token->userFailLoginTimeout, WP11_MAX_LOGIN_FAILS_USER, + now, WP11_USER_LOGIN_FAIL_TIMEOUT, ret); WP11_Lock_UnlockRW(&slot->lock); #endif @@ -8728,9 +8793,9 @@ int WP11_Slot_SOLogin(WP11_Slot* slot, char* pin, int pinLen) int ret = 0; #ifndef WOLFPKCS11_NO_TIME time_t now; - time_t allowed; #endif int state; + WP11_Token* token = &slot->token; #ifndef WOLFPKCS11_NO_TIME if (wc_GetTime(&now, sizeof(now)) != 0) @@ -8742,7 +8807,7 @@ int WP11_Slot_SOLogin(WP11_Slot* slot, char* pin, int pinLen) if (ret == 0) { /* SO already logged in is the same user type; a logged-in USER is a * different one. */ - state = slot->token.loginState; + state = token->loginState; if (state == WP11_APP_STATE_RW_SO) { ret = LOGGED_IN_E; } @@ -8751,19 +8816,6 @@ int WP11_Slot_SOLogin(WP11_Slot* slot, char* pin, int pinLen) ret = LOGGED_IN_ANOTHER_E; } } -#ifndef WOLFPKCS11_NO_TIME - /* Check for too many fails and timeout. */ - if (ret == 0 && slot->token.soFailedLogin >= WP11_MAX_LOGIN_FAILS_SO) { - allowed = slot->token.soLastFailedLogin + - slot->token.soFailLoginTimeout; - if (allowed < now) - slot->token.soFailedLogin = 0; - else - ret = PIN_INVALID_E; - } -#else - slot->token.soFailedLogin = 0; -#endif #ifndef WOLFPKCS11_NSS /* NSS creates a read-only session at the start and doesn't close this, * so when the RW session is opened for this, it fails. @@ -8778,38 +8830,36 @@ int WP11_Slot_SOLogin(WP11_Slot* slot, char* pin, int pinLen) if (curr != NULL) ret = READ_ONLY_E; } +#endif +#ifndef WOLFPKCS11_NO_TIME + /* Reserve the check so racing guesses cannot exceed the limit. */ + if (ret == 0) { + ret = wp11_PinCheckReserve(&token->soFailedLogin, + &token->soPinInFlight, + token->soLastFailedLogin, + token->soFailLoginTimeout, + WP11_MAX_LOGIN_FAILS_SO, now); + } +#else + token->soFailedLogin = 0; #endif WP11_Lock_UnlockRW(&slot->lock); if (ret == 0) { ret = WP11_Slot_CheckSOPin(slot, pin, pinLen); + #ifndef WOLFPKCS11_NO_TIME WP11_Lock_LockRW(&slot->lock); - /* PIN Failed - Update failure info. */ - if (ret == PIN_INVALID_E) { -#ifndef WOLFPKCS11_NO_TIME - /* Concurrent failures must not push the count past the limit. */ - if (slot->token.soFailedLogin < WP11_MAX_LOGIN_FAILS_SO) { - slot->token.soFailedLogin++; - if (slot->token.soFailedLogin == WP11_MAX_LOGIN_FAILS_SO) { - slot->token.soLastFailedLogin = now; - slot->token.soFailLoginTimeout += - WP11_SO_LOGIN_FAIL_TIMEOUT; - } - } -#endif - } - /* Worked - clear failure info. */ - else if (ret == 0) { - slot->token.soFailedLogin = 0; - slot->token.soLastFailedLogin = 0; - slot->token.soFailLoginTimeout = 0; - } + wp11_PinCheckDone(&token->soFailedLogin, &token->soPinInFlight, + &token->soLastFailedLogin, + &token->soFailLoginTimeout, WP11_MAX_LOGIN_FAILS_SO, + now, WP11_SO_LOGIN_FAIL_TIMEOUT, ret); WP11_Lock_UnlockRW(&slot->lock); + #endif } if (ret == 0) { WP11_Lock_LockRW(&slot->lock); - slot->token.loginState = WP11_APP_STATE_RW_SO; + token->loginState = WP11_APP_STATE_RW_SO; WP11_Lock_UnlockRW(&slot->lock); } @@ -9047,7 +9097,6 @@ int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen) int ret = 0; #ifndef WOLFPKCS11_NO_TIME time_t now; - time_t allowed; #endif int state; WP11_Token* token = &slot->token; @@ -9077,13 +9126,13 @@ int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen) } } #ifndef WOLFPKCS11_NO_TIME - /* Check for too many fails */ - if (ret == 0 && token->userFailedLogin >= WP11_MAX_LOGIN_FAILS_USER) { - allowed = token->userLastFailedLogin + token->userFailLoginTimeout; - if (allowed < now) - token->userFailedLogin = 0; - else - ret = PIN_INVALID_E; + /* Reserve the check so racing guesses cannot exceed the limit. */ + if (ret == 0) { + ret = wp11_PinCheckReserve(&token->userFailedLogin, + &token->userPinInFlight, + token->userLastFailedLogin, + token->userFailLoginTimeout, + WP11_MAX_LOGIN_FAILS_USER, now); } #else token->userFailedLogin = 0; @@ -9100,26 +9149,14 @@ int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen) } #endif WP11_Lock_LockRW(&slot->lock); - /* PIN Failed - Update failure info. */ - if (ret == PIN_INVALID_E) { -#ifndef WOLFPKCS11_NO_TIME - /* Concurrent failures must not push the count past the limit. */ - if (token->userFailedLogin < WP11_MAX_LOGIN_FAILS_USER) { - token->userFailedLogin++; - if (token->userFailedLogin == WP11_MAX_LOGIN_FAILS_USER) { - token->userLastFailedLogin = now; - token->userFailLoginTimeout += - WP11_USER_LOGIN_FAIL_TIMEOUT; - } - } -#endif - } - /* Worked - clear failure info. */ - else if (ret == 0) { - token->userFailedLogin = 0; - token->userLastFailedLogin = 0; - token->userFailLoginTimeout = 0; - + #ifndef WOLFPKCS11_NO_TIME + wp11_PinCheckDone(&token->userFailedLogin, &token->userPinInFlight, + &token->userLastFailedLogin, + &token->userFailLoginTimeout, + WP11_MAX_LOGIN_FAILS_USER, now, + WP11_USER_LOGIN_FAIL_TIMEOUT, ret); + #endif + if (ret == 0) { #ifndef WOLFPKCS11_NO_STORE /* Lock order is slot then token; the token lock keeps the object * list stable across the decode and any rollback. */ diff --git a/tests/login_pin_state_test.c b/tests/login_pin_state_test.c index 3f132961..fd12f0b6 100644 --- a/tests/login_pin_state_test.c +++ b/tests/login_pin_state_test.c @@ -1151,6 +1151,133 @@ static void find_with_racing_logout_test(void) } #endif +#if defined(DEBUG_WOLFPKCS11) && defined(LOGIN_TEST_THREADS) && \ + !defined(WOLFPKCS11_NO_TIME) +extern void WP11_Slot_SetPinCheckHook(void (*hook)(void)); + +static pthread_mutex_t pinMutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t pinCond = PTHREAD_COND_INITIALIZER; +static int pinHookCalls = 0; +static int pinHookEntered = 0; +static int pinHookRelease = 0; + +/* The first PIN check blocks here, before hashing, until released. */ +static void pin_check_hook(void) +{ + pthread_mutex_lock(&pinMutex); + pinHookCalls++; + if (pinHookCalls == 1) { + pinHookEntered = 1; + pthread_cond_broadcast(&pinCond); + while (!pinHookRelease) + pthread_cond_wait(&pinCond, &pinMutex); + } + pthread_mutex_unlock(&pinMutex); +} + +static int pin_hook_calls(void) +{ + int calls; + + pthread_mutex_lock(&pinMutex); + calls = pinHookCalls; + pthread_mutex_unlock(&pinMutex); + return calls; +} + +/* With one failure left before lockout, a guess in flight must stop any other + * guess from being checked at all. */ +static void guess_in_flight_caps_attempts(CK_USER_TYPE type, const char* pin, + int maxFails, const char* what) +{ + CK_RV rv; + int i; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_SESSION_HANDLE other = CK_INVALID_HANDLE; + const char* newPin = "replacement-pin"; + login_ctx ctx; + pthread_t thread; + int started = 0; + + rv = token_setup(&slot, userPin); + if (rv == CKR_OK) + rv = open_rw(slot, &session); + if (rv == CKR_OK) + rv = open_rw(slot, &other); + for (i = 0; rv == CKR_OK && i < maxFails - 1; i++) { + rv = funcList->C_Login(session, type, (CK_UTF8CHAR_PTR)wrongPin, + (CK_ULONG)XSTRLEN(wrongPin)); + if (rv == CKR_PIN_INCORRECT) + rv = CKR_OK; + } + CHECK_RV(rv, "set up one failure short of lockout", CKR_OK); + if (rv == CKR_OK) { + ctx.session = session; + ctx.type = type; + ctx.rv = CKR_OK; + pthread_mutex_lock(&pinMutex); + pinHookCalls = 0; + pinHookEntered = 0; + pinHookRelease = 0; + goFlag = 1; + pthread_mutex_unlock(&pinMutex); + WP11_Slot_SetPinCheckHook(pin_check_hook); + started = (pthread_create(&thread, NULL, wrong_login, &ctx) == 0); + CHECK_TRUE(started, "start the guess that stays in flight"); + } + if (started) { + pthread_mutex_lock(&pinMutex); + while (!pinHookEntered) + pthread_cond_wait(&pinCond, &pinMutex); + pthread_mutex_unlock(&pinMutex); + if (type == CKU_USER) { + rv = funcList->C_SetPIN(other, (CK_UTF8CHAR_PTR)wrongPin, + (CK_ULONG)XSTRLEN(wrongPin), + (CK_UTF8CHAR_PTR)newPin, + (CK_ULONG)XSTRLEN(newPin)); + } + else { + rv = funcList->C_Login(other, type, (CK_UTF8CHAR_PTR)wrongPin, + (CK_ULONG)XSTRLEN(wrongPin)); + } + CHECK_RV(rv, "second guess refused while one is in flight", + CKR_PIN_INCORRECT); + CHECK_TRUE(pin_hook_calls() == 1, what); + pthread_mutex_lock(&pinMutex); + pinHookRelease = 1; + pthread_cond_broadcast(&pinCond); + pthread_mutex_unlock(&pinMutex); + pthread_join(thread, NULL); + WP11_Slot_SetPinCheckHook(NULL); + CHECK_RV(ctx.rv, "guess in flight rejected", CKR_PIN_INCORRECT); + rv = funcList->C_Login(other, type, (CK_UTF8CHAR_PTR)pin, + (CK_ULONG)XSTRLEN(pin)); + CHECK_TRUE(rv == CKR_PIN_INCORRECT || rv == CKR_PIN_LOCKED, + "PIN locked once the guess in flight fails"); + if (rv == CKR_OK) + funcList->C_Logout(other); + } + WP11_Slot_SetPinCheckHook(NULL); + if (other != CK_INVALID_HANDLE) + funcList->C_CloseSession(other); + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); + /* The lockout is persisted; start later tests from a fresh token. */ + cleanup_test_files(); +} + +static void guess_in_flight_test(void) +{ + printf("--- a guess in flight counts toward the lockout ---\n"); + guess_in_flight_caps_attempts(CKU_USER, userPin, WP11_MAX_LOGIN_FAILS_USER, + "user C_SetPIN guess not checked"); + guess_in_flight_caps_attempts(CKU_SO, soPin, WP11_MAX_LOGIN_FAILS_SO, + "SO login guess not checked"); +} +#endif + static int run_test(void) { CK_RV rv; @@ -1176,6 +1303,10 @@ static int run_test(void) concurrent_login_lockout_test(); #endif #ifndef SINGLE_THREADED +#if defined(DEBUG_WOLFPKCS11) && defined(LOGIN_TEST_THREADS) && \ + !defined(WOLFPKCS11_NO_TIME) + guess_in_flight_test(); +#endif find_during_object_churn_test(); #endif #if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_STORE) && \ diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 29b59538..315a7090 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -424,6 +424,7 @@ WP11_API int WP11_Slot_TokenDecodedObjectCount(CK_SLOT_ID slotId); WP11_API void WP11_Session_SetFindHook(void (*hook)(void)); WP11_API void WP11_Object_SetFindHook(void (*hook)(void)); WP11_API void WP11_Session_SetAddObjectHook(void (*hook)(void)); +WP11_API void WP11_Slot_SetPinCheckHook(void (*hook)(void)); #if defined(WOLFPKCS11_TPM) && (!defined(NO_RSA) || defined(HAVE_ECC)) WP11_API int WP11_Test_DecodeTpmKey(CK_SLOT_ID slotId, unsigned char* keyData, int keyDataLen); From 71f133474f9a7e398c976ce65dcfe5d7d0b46476 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:32:08 -0700 Subject: [PATCH 017/204] Publish the new user PIN seed and hash together --- src/internal.c | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/internal.c b/src/internal.c index 8d8721d6..74e2838a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -9262,36 +9262,46 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) { int ret = 0; WP11_Token* token; + byte pinSeed[PIN_SEED_SZ]; + byte pinHash[PIN_HASH_SZ]; +#ifndef WOLFPKCS11_NO_STORE + byte seed[PIN_SEED_SZ]; + byte key[AES_256_KEY_SIZE]; +#endif WP11_Lock_LockRW(&slot->lock); token = &slot->token; /* New seed each time. */ WP11_Lock_LockRW(&slot->token.rngLock); - ret = wc_RNG_GenerateBlock(&slot->token.rng, token->userPinSeed, - sizeof(token->userPinSeed)); + ret = wc_RNG_GenerateBlock(&slot->token.rng, pinSeed, sizeof(pinSeed)); #ifndef WOLFPKCS11_NO_STORE - if (ret == 0) { - ret = wc_RNG_GenerateBlock(&slot->token.rng, token->seed, - sizeof(token->seed)); - } + if (ret == 0) + ret = wc_RNG_GenerateBlock(&slot->token.rng, seed, sizeof(seed)); #endif WP11_Lock_UnlockRW(&slot->token.rngLock); if (ret == 0) { WP11_Lock_UnlockRW(&slot->lock); /* Costly Operation done out of lock. */ - token->userPinEmpty = 0; - ret = HashPIN(pin, pinLen, token->userPinSeed, - sizeof(token->userPinSeed), token->userPin, - sizeof(token->userPin), slot); + ret = HashPIN(pin, pinLen, pinSeed, sizeof(pinSeed), pinHash, + sizeof(pinHash), slot); #ifndef WOLFPKCS11_NO_STORE if (ret == 0) { - ret = HashPIN(pin, pinLen, token->seed, sizeof(token->seed), - token->key, sizeof(token->key), slot); + ret = HashPIN(pin, pinLen, seed, sizeof(seed), key, sizeof(key), + slot); } #endif WP11_Lock_LockRW(&slot->lock); } if (ret == 0) { + /* Publish seed, hash and cache state together so a check that sees + * the new seed also sees the hash it belongs to. */ + XMEMCPY(token->userPinSeed, pinSeed, sizeof(token->userPinSeed)); + XMEMCPY(token->userPin, pinHash, sizeof(token->userPin)); + #ifndef WOLFPKCS11_NO_STORE + XMEMCPY(token->seed, seed, sizeof(token->seed)); + XMEMCPY(token->key, key, sizeof(token->key)); + #endif + token->userPinEmpty = 0; token->userPinLen = sizeof(token->userPin); token->tokenFlags |= WP11_TOKEN_FLAG_USER_PIN_SET; #ifndef WOLFPKCS11_NO_STORE @@ -9299,6 +9309,10 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) #endif } WP11_Lock_UnlockRW(&slot->lock); + wc_ForceZero(pinHash, sizeof(pinHash)); +#ifndef WOLFPKCS11_NO_STORE + wc_ForceZero(key, sizeof(key)); +#endif return ret; } From 79a9bb02cf2ab2fcc1ec157a8113db73f2ed65a4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:32:09 -0700 Subject: [PATCH 018/204] Test token reset when token initialization fails --- src/internal.c | 20 ++++ tests/include.am | 7 ++ tests/token_reset_fail_test.c | 209 ++++++++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 1 + 4 files changed, 237 insertions(+) create mode 100644 tests/token_reset_fail_test.c diff --git a/src/internal.c b/src/internal.c index 74e2838a..c377d6ce 100644 --- a/src/internal.c +++ b/src/internal.c @@ -7236,6 +7236,20 @@ static int wp11_Object_Unstore(WP11_Object* object, int tokenId, int objId) * * @param token [in] Token object. */ +#ifdef DEBUG_WOLFPKCS11 +static int wp11_tokenInitFail = 0; + +/** + * Test hook: make the next token initialization fail at the RNG step. + * + * @param fail [in] Non-zero to fail the next initialization. + */ +WP11_API void WP11_Test_FailTokenInit(int fail) +{ + wp11_tokenInitFail = fail; +} +#endif + static int wp11_Token_Init(WP11_Token* token, const char* label) { int ret; @@ -7243,6 +7257,12 @@ static int wp11_Token_Init(WP11_Token* token, const char* label) ret = WP11_Lock_Init(&token->lock); if (ret == 0) ret = WP11_Lock_Init(&token->rngLock); +#ifdef DEBUG_WOLFPKCS11 + if (ret == 0 && wp11_tokenInitFail) { + wp11_tokenInitFail = 0; + ret = RNG_FAILURE_E; + } +#endif if (ret == 0) ret = Rng_New(&globalRandom, &globalLock, &token->rng); if (ret == 0) { diff --git a/tests/include.am b/tests/include.am index d7ef4a12..95334d84 100644 --- a/tests/include.am +++ b/tests/include.am @@ -260,6 +260,11 @@ noinst_PROGRAMS += tests/unique_id_test tests_unique_id_test_SOURCES = tests/unique_id_test.c tests_unique_id_test_LDADD = +check_PROGRAMS += tests/token_reset_fail_test +noinst_PROGRAMS += tests/token_reset_fail_test +tests_token_reset_fail_test_SOURCES = tests/token_reset_fail_test.c +tests_token_reset_fail_test_LDADD = + check_PROGRAMS += tests/nss_email_store_test noinst_PROGRAMS += tests/nss_email_store_test tests_nss_email_store_test_SOURCES = tests/nss_email_store_test.c @@ -363,6 +368,7 @@ tests_concurrent_destroy_object_test_LDADD += src/libwolfpkcs11.la tests_hbs_persistence_test_LDADD += src/libwolfpkcs11.la tests_hkdf_persistence_test_LDADD += src/libwolfpkcs11.la tests_unique_id_test_LDADD += src/libwolfpkcs11.la +tests_token_reset_fail_test_LDADD += src/libwolfpkcs11.la tests_nss_email_store_test_LDADD += src/libwolfpkcs11.la tests_generate_key_class_test_LDADD += src/libwolfpkcs11.la tests_generate_keypair_class_test_LDADD += src/libwolfpkcs11.la @@ -418,6 +424,7 @@ tests_concurrent_destroy_object_test_LDADD += src/libwolfpkcs11.la tests_hbs_persistence_test_LDADD += src/libwolfpkcs11.la tests_hkdf_persistence_test_LDADD += src/libwolfpkcs11.la tests_unique_id_test_LDADD += src/libwolfpkcs11.la +tests_token_reset_fail_test_LDADD += src/libwolfpkcs11.la tests_nss_email_store_test_LDADD += src/libwolfpkcs11.la tests_generate_key_class_test_LDADD += src/libwolfpkcs11.la tests_generate_keypair_class_test_LDADD += src/libwolfpkcs11.la diff --git a/tests/token_reset_fail_test.c b/tests/token_reset_fail_test.c new file mode 100644 index 00000000..eb52abc6 --- /dev/null +++ b/tests/token_reset_fail_test.c @@ -0,0 +1,209 @@ +/* token_reset_fail_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Checks that C_InitToken reports a token initialization failure, leaves the + * token unprovisioned and lets a later C_InitToken succeed without leaking. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#define TEST_DIR "./store/token_reset_fail_test" + +#ifdef DEBUG_WOLFPKCS11 + +extern void WP11_Test_FailTokenInit(int fail); + +static const char* soPin = "password123456"; +static const char* userPin = "wolfpkcs11-test"; + +static long liveBlocks = 0; + +static void* count_malloc(size_t sz) +{ + void* p = malloc(sz); + + if (p != NULL) + liveBlocks++; + return p; +} + +static void count_free(void* ptr) +{ + if (ptr != NULL) + liveBlocks--; + free(ptr); +} + +static void* count_realloc(void* ptr, size_t sz) +{ + void* np = realloc(ptr, sz); + + if (ptr == NULL && np != NULL) + liveBlocks++; + return np; +} + +static CK_RV init_token(CK_SLOT_ID slot) +{ + unsigned char label[32]; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, "reset-fail", 10); + return funcList->C_InitToken(slot, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin), label); +} + +/* Provision the token and log the user in on a fresh session. */ +static CK_RV provision_and_login(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + if (rv == CKR_OK) { + rv = funcList->C_Login(session, CKU_SO, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin)); + } + if (rv == CKR_OK) { + rv = funcList->C_InitPIN(session, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + } + if (rv == CKR_OK) + rv = funcList->C_Logout(session); + if (rv == CKR_OK) { + rv = funcList->C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + } + if (rv == CKR_OK) + rv = funcList->C_Logout(session); + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + return rv; +} + +static void token_reset_fail_test(void) +{ + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + CK_SLOT_ID slot = 0; + CK_TOKEN_INFO info; + long before; + + printf("--- token reset stops when token initialization fails ---\n"); + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + if (rv == CKR_OK) + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv == CKR_OK && slotCount == 0) + rv = CKR_TOKEN_NOT_PRESENT; + CHECK_RV(rv, "initialize and find a slot", CKR_OK); + if (rv == CKR_OK) { + slot = slotList[0]; + before = liveBlocks; + WP11_Test_FailTokenInit(1); + rv = init_token(slot); + WP11_Test_FailTokenInit(0); + CHECK_RV(rv, "C_InitToken with failing token initialization", + CKR_FUNCTION_FAILED); + rv = funcList->C_GetTokenInfo(slot, &info); + CHECK_RV(rv, "C_GetTokenInfo after the failure", CKR_OK); + CHECK_TRUE(rv == CKR_OK && (info.flags & CKF_TOKEN_INITIALIZED) == 0, + "token not reported initialized"); + /* The old token state is released; the failed one allocates nothing. */ + if (liveBlocks > before) { + fprintf(stderr, " live blocks went from %ld to %ld\n", before, + liveBlocks); + } + CHECK_TRUE(liveBlocks <= before, "failed reset leaks nothing"); + rv = init_token(slot); + CHECK_RV(rv, "C_InitToken again", CKR_OK); + rv = funcList->C_GetTokenInfo(slot, &info); + CHECK_TRUE(rv == CKR_OK && (info.flags & CKF_TOKEN_INITIALIZED) != 0, + "token reported initialized"); + rv = provision_and_login(slot); + CHECK_RV(rv, "set the user PIN and log in", CKR_OK); + } + funcList->C_Finalize(NULL); +} + +int main(int argc, char* argv[]) +{ + CK_RV rv; + + (void)argc; + (void)argv; + +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + if (wolfSSL_SetAllocators(count_malloc, count_free, count_realloc) != 0) { + printf("Allocator hooks not available, skipping test\n"); + return 77; + } + + printf("=== wolfPKCS11 token reset failure test ===\n"); + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv == CKR_OK) + token_reset_fail_test(); + pkcs11_unload(); + return pkcs11_test_summary(); +} + +#else + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + printf("Debug test hooks not built, skipping test\n"); + return 77; +} + +#endif diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 315a7090..bb45e54b 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -425,6 +425,7 @@ WP11_API void WP11_Session_SetFindHook(void (*hook)(void)); WP11_API void WP11_Object_SetFindHook(void (*hook)(void)); WP11_API void WP11_Session_SetAddObjectHook(void (*hook)(void)); WP11_API void WP11_Slot_SetPinCheckHook(void (*hook)(void)); +WP11_API void WP11_Test_FailTokenInit(int fail); #if defined(WOLFPKCS11_TPM) && (!defined(NO_RSA) || defined(HAVE_ECC)) WP11_API int WP11_Test_DecodeTpmKey(CK_SLOT_ID slotId, unsigned char* keyData, int keyDataLen); From e9021618566d3428bc30a46b0f8430521679fa3b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:57:04 -0700 Subject: [PATCH 019/204] F-4262 - Reject negative stored array lengths when loading token objects --- src/internal.c | 6 + tests/include.am | 7 + tests/object_store_decode_test.c | 436 +++++++++++++++++++++++++++++++ 3 files changed, 449 insertions(+) create mode 100644 tests/object_store_decode_test.c diff --git a/src/internal.c b/src/internal.c index c377d6ce..a7473dbc 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2455,6 +2455,7 @@ static int wp11_storage_write_array(void* storage, * @param [in] len Length of data to read. * @return 0 on success. * @return BUFFER_E to indicate failure. + * @return ASN_PARSE_E when the stored length is negative. * @return MEMORY_E when dynamic memory allocation fails. */ static int wp11_storage_read_alloc_array(void* storage, @@ -2464,6 +2465,11 @@ static int wp11_storage_read_alloc_array(void* storage, /* Read length of array. */ ret = wp11_storage_read_int(storage, len); + if (ret == 0 && *len < 0) { + *len = 0; + /* Not BUFFER_E, which callers accept as an older record's end. */ + ret = ASN_PARSE_E; + } if (ret == 0 && *len > 0) { /* Allocate buffer to hold data. */ *buffer = (unsigned char*)XMALLOC(*len, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/tests/include.am b/tests/include.am index 95334d84..39172fc5 100644 --- a/tests/include.am +++ b/tests/include.am @@ -315,6 +315,11 @@ noinst_PROGRAMS += tests/login_pin_state_test tests_login_pin_state_test_SOURCES = tests/login_pin_state_test.c tests_login_pin_state_test_LDADD = +check_PROGRAMS += tests/object_store_decode_test +noinst_PROGRAMS += tests/object_store_decode_test +tests_object_store_decode_test_SOURCES = tests/object_store_decode_test.c +tests_object_store_decode_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -379,6 +384,7 @@ tests_wrapkey_length_query_test_LDADD += src/libwolfpkcs11.la tests_nss_configdir_prefix_test_LDADD += src/libwolfpkcs11.la tests_logout_retire_test_LDADD += src/libwolfpkcs11.la tests_login_pin_state_test_LDADD += src/libwolfpkcs11.la +tests_object_store_decode_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -435,6 +441,7 @@ tests_wrapkey_length_query_test_LDADD += src/libwolfpkcs11.la tests_nss_configdir_prefix_test_LDADD += src/libwolfpkcs11.la tests_logout_retire_test_LDADD += src/libwolfpkcs11.la tests_login_pin_state_test_LDADD += src/libwolfpkcs11.la +tests_object_store_decode_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ diff --git a/tests/object_store_decode_test.c b/tests/object_store_decode_test.c new file mode 100644 index 00000000..b240c46f --- /dev/null +++ b/tests/object_store_decode_test.c @@ -0,0 +1,436 @@ +/* object_store_decode_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Stored token objects whose recorded lengths do not describe valid data must + * be rejected cleanly when the token is loaded or logged into. Each reload runs + * in a child process so a failure is reported instead of ending the test. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC + #include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#if !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_TPM_STORE) && \ + !defined(WOLFPKCS11_CUSTOM_STORE) && !defined(WOLFPKCS11_NO_ENV) && \ + !defined(_WIN32) + +#include +#include +#include + +#define TEST_DIR "./store/object_store_decode_test" +#define MAX_OBJ_ID 8 +#define MAX_FILE_SZ 16384 + +/* Offset of the first variable-length attribute in a stored object record. */ +#define OBJ_ID_LEN_OFF (12 + 3 * sizeof(CK_ULONG) + 1 + 1 + 4 + 4 + 8 + 8) +#define OBJ_LABEL_LEN_OFF (OBJ_ID_LEN_OFF + 4) +#define OBJ_ISSUER_LEN_OFF (OBJ_LABEL_LEN_OFF + 4) + +#define CHILD_PASS 0 +#define CHILD_FAIL 1 +#define CHILD_SETUP 2 + +static byte soPin[] = "password123456"; +static byte userPin[] = "wolfpkcs11-test"; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; + +static const char* storeKinds[] = { + "obj", "data", "symmkey", "rsakey_priv", "rsakey_pub", "ecckey_priv", + "ecckey_pub", "dhkey_priv", "dhkey_pub", "cert", "trust" +}; + +static void store_path(char* path, size_t sz, const char* kind, int objId) +{ + if (objId < 0) { + (void)snprintf(path, sz, "%s/wp11_%s_%016lx", TEST_DIR, kind, 1UL); + } + else { + (void)snprintf(path, sz, "%s/wp11_%s_%016lx_%016lx", TEST_DIR, kind, + 1UL, (unsigned long)objId); + } +} + +static void cleanup_test_files(void) +{ + char path[256]; + size_t k; + int i; + + for (k = 0; k < sizeof(storeKinds) / sizeof(*storeKinds); k++) { + for (i = 0; i < MAX_OBJ_ID; i++) { + store_path(path, sizeof(path), storeKinds[k], i); + (void)remove(path); + } + } + store_path(path, sizeof(path), "token", -1); + (void)remove(path); +} + +/* Locate the stored file of the given kind for the single object of that kind. */ +static int find_store_file(const char* kind, char* path, size_t sz) +{ + FILE* f; + int i; + + for (i = 0; i < MAX_OBJ_ID; i++) { + store_path(path, sz, kind, i); + f = fopen(path, "rb"); + if (f != NULL) { + fclose(f); + return 0; + } + } + return -1; +} + +static int read_file(const char* path, byte* data, size_t sz, size_t* readSz) +{ + FILE* f = fopen(path, "rb"); + + if (f == NULL) + return -1; + *readSz = fread(data, 1, sz, f); + if (ferror(f) || !feof(f)) { + fclose(f); + return -1; + } + fclose(f); + return 0; +} + +static int write_file(const char* path, const byte* data, size_t sz) +{ + FILE* f = fopen(path, "wb"); + size_t written; + + if (f == NULL) + return -1; + written = fwrite(data, 1, sz, f); + if (fclose(f) != 0 || written != sz) + return -1; + return 0; +} + +static word32 get_be32(const byte* p) +{ + return ((word32)p[0] << 24) | ((word32)p[1] << 16) | + ((word32)p[2] << 8) | (word32)p[3]; +} + +static void put_be32(byte* p, word32 v) +{ + p[0] = (byte)(v >> 24); + p[1] = (byte)(v >> 16); + p[2] = (byte)(v >> 8); + p[3] = (byte)v; +} + +/* Overwrite a 4-byte length field that is expected to hold 'expect'. */ +static int patch_length(const char* path, size_t off, word32 expect, + word32 value) +{ + static byte data[MAX_FILE_SZ]; + size_t sz = 0; + + if (read_file(path, data, sizeof(data), &sz) != 0 || sz < off + 4) + return -1; + if (get_be32(data + off) != expect) + return -1; + put_be32(data + off, value); + return write_file(path, data, sz); +} + +static CK_RV init_slot(CK_SLOT_ID* slot) +{ + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slots[16]; + CK_ULONG count = sizeof(slots) / sizeof(*slots); + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + if (rv == CKR_OK) + rv = funcList->C_GetSlotList(CK_TRUE, slots, &count); + if (rv == CKR_OK && count == 0) + rv = CKR_TOKEN_NOT_PRESENT; + if (rv == CKR_OK) + *slot = slots[0]; + return rv; +} + +static CK_RV provision_token(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_UTF8CHAR label[32]; + CK_FLAGS flags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, "wolfpkcs11", 10); + rv = funcList->C_InitToken(slot, soPin, sizeof(soPin) - 1, label); + if (rv == CKR_OK) + rv = funcList->C_OpenSession(slot, flags, NULL, NULL, &session); + if (rv == CKR_OK) + rv = funcList->C_Login(session, CKU_SO, soPin, sizeof(soPin) - 1); + if (rv == CKR_OK) + rv = funcList->C_InitPIN(session, userPin, sizeof(userPin) - 1); + if (session != CK_INVALID_HANDLE) { + (void)funcList->C_Logout(session); + (void)funcList->C_CloseSession(session); + } + return rv; +} + +static CK_RV open_session(CK_SLOT_ID slot, int login, + CK_SESSION_HANDLE* session) +{ + CK_RV rv; + CK_FLAGS flags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + rv = funcList->C_OpenSession(slot, flags, NULL, NULL, session); + if (rv == CKR_OK && login) { + rv = funcList->C_Login(*session, CKU_USER, userPin, + sizeof(userPin) - 1); + } + return rv; +} + +typedef CK_RV (*create_fn)(CK_SESSION_HANDLE session); + +/* Provision a fresh token, create objects with 'create' and persist them. */ +static CK_RV store_objects(create_fn create) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + + cleanup_test_files(); + rv = pkcs11_load(); + if (rv != CKR_OK) + return rv; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = provision_token(slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) + rv = create(session); + if (session != CK_INVALID_HANDLE) + (void)funcList->C_CloseSession(session); + (void)funcList->C_Finalize(NULL); + pkcs11_unload(); + return rv; +} + +typedef int (*child_fn)(void); + +/* Run fn in a child process. Returns CHILD_PASS only when it exits normally + * reporting that the invariant held. */ +static int run_in_child(child_fn fn) +{ + pid_t pid; + int status = 0; + + fflush(stdout); + fflush(stderr); + pid = fork(); + if (pid < 0) + return CHILD_SETUP; + if (pid == 0) { + int res = fn(); + fflush(stdout); + fflush(stderr); + _exit(res); + } + if (waitpid(pid, &status, 0) != pid) + return CHILD_SETUP; + if (WIFSIGNALED(status)) { + fprintf(stderr, " reload terminated by signal %d\n", + WTERMSIG(status)); + return CHILD_FAIL; + } + if (!WIFEXITED(status)) + return CHILD_FAIL; + return WEXITSTATUS(status); +} + +static CK_RV create_data_object(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + static byte app[] = "wolfpkcs11"; + static byte value[] = "stored data object value"; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_APPLICATION, app, sizeof(app) - 1 }, + { CKA_VALUE, value, sizeof(value) - 1 }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); +} + +/* Either the token refuses to load or the data object reports a real label + * length. */ +static int child_data_label_length(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ULONG count = 0; + CK_ATTRIBUTE findTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + }; + CK_ATTRIBUTE label = { CKA_LABEL, NULL, 0 }; + int res = CHILD_SETUP; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv != CKR_OK) + return CHILD_PASS; + + rv = open_session(slot, 0, &session); + if (rv == CKR_OK) + rv = funcList->C_FindObjectsInit(session, findTmpl, 1); + if (rv == CKR_OK) + rv = funcList->C_FindObjects(session, &obj, 1, &count); + if (rv == CKR_OK) + rv = funcList->C_FindObjectsFinal(session); + if (rv == CKR_OK && count == 1) + rv = funcList->C_GetAttributeValue(session, obj, &label, 1); + if (rv == CKR_OK && count == 1) { + res = (label.ulValueLen == CK_UNAVAILABLE_INFORMATION || + label.ulValueLen > 64) ? CHILD_FAIL : CHILD_PASS; + if (res != CHILD_PASS) { + fprintf(stderr, " loaded label length 0x%lx\n", + (unsigned long)label.ulValueLen); + } + } + (void)funcList->C_Finalize(NULL); + return res; +} + +/* A negative length must not pass as the end of an older record format. */ +static int child_token_not_loaded(void) +{ + CK_SLOT_ID slot = 0; + int res; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + res = (init_slot(&slot) != CKR_OK) ? CHILD_PASS : CHILD_FAIL; + (void)funcList->C_Finalize(NULL); + return res; +} + +static void test_negative_optional_length(void) +{ + CK_RV rv; + char path[256]; + int res = CHILD_SETUP; + + printf("\n--- stored issuer length must not be negative ---\n"); + rv = store_objects(create_data_object); + CHECK_RV(rv, "store data object", CKR_OK); + if (rv != CKR_OK) + return; + if (find_store_file("obj", path, sizeof(path)) == 0 && + patch_length(path, OBJ_ISSUER_LEN_OFF, 0, 0xFFFFFFFFUL) == 0) { + res = run_in_child(child_token_not_loaded); + } + CHECK_TRUE(res == CHILD_PASS, + "negative stored issuer length is not loaded"); + cleanup_test_files(); +} + +static void test_negative_attribute_length(void) +{ + CK_RV rv; + char path[256]; + int res = CHILD_SETUP; + + printf("\n--- stored attribute length must not be negative ---\n"); + rv = store_objects(create_data_object); + CHECK_RV(rv, "store data object", CKR_OK); + if (rv != CKR_OK) + return; + if (find_store_file("obj", path, sizeof(path)) == 0 && + patch_length(path, OBJ_LABEL_LEN_OFF, 0, 0xFFFFFFFFUL) == 0) { + res = run_in_child(child_data_label_length); + } + CHECK_TRUE(res == CHILD_PASS, + "negative stored attribute length is not loaded"); + cleanup_test_files(); +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); + + printf("=== wolfPKCS11 stored object decode test ===\n"); + test_negative_optional_length(); + test_negative_attribute_length(); + + return pkcs11_test_summary(); +} + +#else + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + printf("File-backed token storage not available, skipping test\n"); + return 77; +} + +#endif From 39990df65ecec53d4ed903a2c28a3d21ed0ffa91 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:16:19 -0700 Subject: [PATCH 020/204] F-5078 - Grow stored array buffers only as data is read --- src/internal.c | 118 +++++++++++++++-- tests/object_store_decode_test.c | 219 +++++++++++++++++++++++++++++++ 2 files changed, 327 insertions(+), 10 deletions(-) diff --git a/src/internal.c b/src/internal.c index a7473dbc..7cff0584 100644 --- a/src/internal.c +++ b/src/internal.c @@ -142,6 +142,11 @@ #define WP11_MAX_CERT_SZ 4096 #endif +/* Initial allocation when reading a variable-length array from storage. */ +#ifndef WP11_STORE_READ_CHUNK_SZ +#define WP11_STORE_READ_CHUNK_SZ 4096 +#endif + #define PKCS11_CHECK_VALUE_SIZE 3 /* Sizes for storage. */ @@ -1972,6 +1977,36 @@ int wolfPKCS11_Store_Read(void* store, unsigned char* buffer, int len) return ret; } +#ifndef WOLFPKCS11_TPM_STORE +/** + * Get the number of bytes left to read from a store file. + * + * @param [in] store Context for operation. + * @param [out] left Number of bytes after the read position. + * @return 0 on success. + * @return BUFFER_E when the position cannot be worked out or restored. + */ +static int wolfPKCS11_Store_Left(void* store, long* left) +{ + WP11_FileStoreCtx* ctx = (WP11_FileStoreCtx*)store; + long pos = -1; + long end = -1; + + /* stdio directly: the file store's handles are FILE* (see fdopen()). */ + if (ctx != NULL && ctx->file != XBADFILE && ctx->file != NULL) { + pos = ftell(ctx->file); + if (pos >= 0 && fseek(ctx->file, 0, SEEK_END) == 0) + end = ftell(ctx->file); + if (pos >= 0 && fseek(ctx->file, pos, SEEK_SET) != 0) + end = -1; + } + if (pos < 0 || end < pos) + return BUFFER_E; + *left = end - pos; + return 0; +} +#endif + /** * Writes a specific number of bytes from buffer. * @@ -2073,6 +2108,26 @@ static void wp11_storage_close(void* storage) wolfPKCS11_Store_Close(storage); } +/** + * Get the number of bytes left to read, when the store can tell. + * + * @param [in] storage Context for operation. + * @param [out] left Number of bytes left to read. + * @return 0 on success. + * @return NOT_AVAILABLE_E when the store cannot tell. + * @return Other value on failure. + */ +static int wp11_storage_left(void* storage, long* left) +{ +#if !defined(WOLFPKCS11_CUSTOM_STORE) && !defined(WOLFPKCS11_TPM_STORE) + return wolfPKCS11_Store_Left(storage, left); +#else + (void)storage; + (void)left; + return NOT_AVAILABLE_E; +#endif +} + /** * Reads a specific number of bytes into buffer. * @@ -2462,6 +2517,13 @@ static int wp11_storage_read_alloc_array(void* storage, unsigned char** buffer, int* len) { int ret; + int have = 0; + int bufSz = 0; + int newSz; + int first = WP11_STORE_READ_CHUNK_SZ; + long left = 0; + unsigned char* buf = NULL; + unsigned char* tmp; /* Read length of array. */ ret = wp11_storage_read_int(storage, len); @@ -2471,21 +2533,57 @@ static int wp11_storage_read_alloc_array(void* storage, ret = ASN_PARSE_E; } if (ret == 0 && *len > 0) { - /* Allocate buffer to hold data. */ - *buffer = (unsigned char*)XMALLOC(*len, NULL, DYNAMIC_TYPE_TMP_BUFFER); - if (*buffer == NULL) - ret = MEMORY_E; + /* When the store knows its size, allocate once if the data is there. */ + ret = wp11_storage_left(storage, &left); + if (ret == 0 && (long)*len > left) + ret = BUFFER_E; + else if (ret == 0) + first = *len; + else if (ret == NOT_AVAILABLE_E) + ret = 0; + } + /* Otherwise grow the buffer only as stored data is read, so a corrupt + * length cannot drive an allocation larger than the data present. */ + while (ret == 0 && have < *len) { + if (bufSz == 0) + newSz = first; + else if (bufSz <= *len / 2) + newSz = bufSz * 2; + else + newSz = *len; + if (newSz > *len) + newSz = *len; - if (ret == 0) { - /* Read array data into allocated buffer. */ - ret = wp11_storage_read(storage, *buffer, *len); - if (ret != 0) { - XFREE(*buffer, NULL, DYNAMIC_TYPE_TMP_BUFFER); - *buffer = NULL; + tmp = (unsigned char*)XMALLOC(newSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) { + ret = MEMORY_E; + } + else { + if (buf != NULL) { + XMEMCPY(tmp, buf, have); + wc_ForceZero(buf, (word32)have); + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); } + buf = tmp; + bufSz = newSz; + /* Read array data into the newly allocated space. */ + ret = wp11_storage_read(storage, buf + have, bufSz - have); + if (ret == 0) + have = bufSz; } } + if (ret != 0 && (buf != NULL || ret == MEMORY_E)) { + if (buf != NULL) { + wc_ForceZero(buf, (word32)bufSz); + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + *buffer = NULL; + } + else if (buf != NULL) { + *buffer = buf; + } + return ret; } diff --git a/tests/object_store_decode_test.c b/tests/object_store_decode_test.c index b240c46f..e26332f6 100644 --- a/tests/object_store_decode_test.c +++ b/tests/object_store_decode_test.c @@ -36,6 +36,7 @@ #endif #include #include +#include #ifndef WOLFPKCS11_USER_SETTINGS #include @@ -80,6 +81,74 @@ static const char* storeKinds[] = { "ecckey_pub", "dhkey_priv", "dhkey_pub", "cert", "trust" }; +#if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY) && \ + !defined(WOLFSSL_DEBUG_MEMORY) +#define HAVE_ALLOC_TRACKING +#define ALLOC_REFUSE_SZ ((size_t)64 * 1024 * 1024) + +#define TRACK_HDR_SZ 16 + +static size_t largestAlloc = 0; +/* Live bytes in blocks no larger than peakCap, and their peak. */ +static size_t peakCap = 0; +static size_t liveBytes = 0; +static size_t peakLive = 0; + +static void* track_malloc(size_t sz) +{ + unsigned char* p; + + if (sz > largestAlloc) + largestAlloc = sz; + if (sz >= ALLOC_REFUSE_SZ) + return NULL; + p = (unsigned char*)malloc(sz + TRACK_HDR_SZ); + if (p == NULL) + return NULL; + XMEMCPY(p, &sz, sizeof(sz)); + if (sz <= peakCap) { + liveBytes += sz; + if (liveBytes > peakLive) + peakLive = liveBytes; + } + return p + TRACK_HDR_SZ; +} + +static void track_free(void* ptr) +{ + unsigned char* p; + size_t sz; + + if (ptr == NULL) + return; + p = (unsigned char*)ptr - TRACK_HDR_SZ; + XMEMCPY(&sz, p, sizeof(sz)); + if (sz <= peakCap) + liveBytes -= sz; + free(p); +} + +static void* track_realloc(void* ptr, size_t sz) +{ + void* n; + size_t old; + + if (ptr == NULL) + return track_malloc(sz); + if (sz == 0) { + track_free(ptr); + return NULL; + } + n = track_malloc(sz); + if (n == NULL) + return NULL; + XMEMCPY(&old, (unsigned char*)ptr - TRACK_HDR_SZ, sizeof(old)); + XMEMCPY(n, ptr, (old < sz) ? old : sz); + track_free(ptr); + return n; +} +#endif + static void store_path(char* path, size_t sz, const char* kind, int objId) { if (objId < 0) { @@ -410,6 +479,149 @@ static void test_negative_attribute_length(void) cleanup_test_files(); } +#define LARGE_VALUE_SZ 300001 +static byte largeValue[LARGE_VALUE_SZ]; +static byte largeRead[LARGE_VALUE_SZ]; + +static CK_RV create_large_data_object(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, largeValue, sizeof(largeValue) }, + }; + size_t i; + + for (i = 0; i < sizeof(largeValue); i++) + largeValue[i] = (byte)(i * 7 + (i >> 8)); + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); +} + +static void test_large_stored_value_round_trip(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ULONG count = 0; + CK_ATTRIBUTE findTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + }; + CK_ATTRIBUTE value = { CKA_VALUE, largeRead, sizeof(largeRead) }; + + printf("\n--- large stored value reloads intact ---\n"); + rv = store_objects(create_large_data_object); + CHECK_RV(rv, "store large data object", CKR_OK); + if (rv != CKR_OK) + return; + rv = pkcs11_load(); + if (rv == CKR_OK) { + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 0, &session); + if (rv == CKR_OK) + rv = funcList->C_FindObjectsInit(session, findTmpl, 1); + if (rv == CKR_OK) + rv = funcList->C_FindObjects(session, &obj, 1, &count); + if (rv == CKR_OK) + rv = funcList->C_FindObjectsFinal(session); + if (rv == CKR_OK && count != 1) + rv = CKR_GENERAL_ERROR; + if (rv == CKR_OK) + rv = funcList->C_GetAttributeValue(session, obj, &value, 1); + (void)funcList->C_Finalize(NULL); + pkcs11_unload(); + } + CHECK_RV(rv, "reload large data object", CKR_OK); + CHECK_TRUE(rv == CKR_OK && value.ulValueLen == sizeof(largeValue) && + XMEMCMP(largeRead, largeValue, sizeof(largeValue)) == 0, + "large stored value is unchanged after reload"); + cleanup_test_files(); +} + +#ifdef HAVE_ALLOC_TRACKING +/* Loading must not size an allocation from a length the data cannot back. */ +static int child_load_allocation_bound(void) +{ + CK_SLOT_ID slot = 0; + + if (wolfSSL_SetAllocators(track_malloc, track_free, track_realloc) != 0) + return CHILD_SETUP; + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + if (init_slot(&slot) == CKR_OK) + (void)funcList->C_Finalize(NULL); + if (largestAlloc >= ALLOC_REFUSE_SZ) { + fprintf(stderr, " largest allocation while loading: %lu\n", + (unsigned long)largestAlloc); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +static void test_oversized_attribute_length(void) +{ + CK_RV rv; + char path[256]; + int res = CHILD_SETUP; + + printf("\n--- stored attribute length must be bounded ---\n"); + rv = store_objects(create_data_object); + CHECK_RV(rv, "store data object", CKR_OK); + if (rv != CKR_OK) + return; + if (find_store_file("obj", path, sizeof(path)) == 0 && + patch_length(path, OBJ_LABEL_LEN_OFF, 0, 0x7FFFFFF8UL) == 0) { + res = run_in_child(child_load_allocation_bound); + } + CHECK_TRUE(res == CHILD_PASS, + "oversized stored attribute length is not allocated"); + cleanup_test_files(); +} + +/* Loading a large stored value holds about one copy of it, not two. Larger + * blocks cannot be partial copies of it, so they are not counted. */ +static int child_large_value_peak(void) +{ + CK_SLOT_ID slot = 0; + + peakCap = LARGE_VALUE_SZ; + if (wolfSSL_SetAllocators(track_malloc, track_free, track_realloc) != 0) + return CHILD_SETUP; + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + if (init_slot(&slot) == CKR_OK) + (void)funcList->C_Finalize(NULL); + if (peakLive >= (size_t)LARGE_VALUE_SZ + LARGE_VALUE_SZ / 2) { + fprintf(stderr, " peak live bytes while loading: %lu\n", + (unsigned long)peakLive); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +static void test_large_stored_value_peak(void) +{ + CK_RV rv; + int res; + + printf("\n--- large stored value loads without a second copy ---\n"); + rv = store_objects(create_large_data_object); + CHECK_RV(rv, "store large data object", CKR_OK); + if (rv != CKR_OK) + return; + res = run_in_child(child_large_value_peak); + CHECK_TRUE(res == CHILD_PASS, + "peak memory while loading stays near one copy"); + cleanup_test_files(); +} +#endif + int main(int argc, char* argv[]) { (void)argc; @@ -419,6 +631,13 @@ int main(int argc, char* argv[]) printf("=== wolfPKCS11 stored object decode test ===\n"); test_negative_optional_length(); test_negative_attribute_length(); +#ifdef HAVE_ALLOC_TRACKING + test_oversized_attribute_length(); +#endif + test_large_stored_value_round_trip(); +#ifdef HAVE_ALLOC_TRACKING + test_large_stored_value_peak(); +#endif return pkcs11_test_summary(); } From 7b6a974e43469782771dc7e14363a235b39ba0e1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:25:41 -0700 Subject: [PATCH 021/204] F-13814 - Set data object lengths only after a successful storage read --- src/internal.c | 11 +++-- tests/object_store_decode_test.c | 75 +++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/internal.c b/src/internal.c index 7cff0584..0136b1e6 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3589,7 +3589,7 @@ static int wp11_Object_Load_Data(WP11_Object* object, int tokenId, int objId) { int ret; void* storage = NULL; - int tempLen; + int tempLen = 0; /* Open access to data. */ ret = wp11_storage_open_readonly(WOLFPKCS11_STORE_DATA, tokenId, objId, @@ -3598,7 +3598,8 @@ static int wp11_Object_Load_Data(WP11_Object* object, int tokenId, int objId) ret = wp11_storage_read_alloc_array(storage, &object->data.genericData.data, &tempLen); - object->data.genericData.dataLen = (word32)tempLen; + if (ret == 0) + object->data.genericData.dataLen = (word32)tempLen; } /* Read application length and application. */ @@ -3606,7 +3607,8 @@ static int wp11_Object_Load_Data(WP11_Object* object, int tokenId, int objId) ret = wp11_storage_read_alloc_array(storage, &object->data.genericData.application, &tempLen); - object->data.genericData.applicationLen = (word32)tempLen; + if (ret == 0) + object->data.genericData.applicationLen = (word32)tempLen; } /* Read object ID length and object ID. */ @@ -3614,7 +3616,8 @@ static int wp11_Object_Load_Data(WP11_Object* object, int tokenId, int objId) ret = wp11_storage_read_alloc_array(storage, &object->data.genericData.objectId, &tempLen); - object->data.genericData.objectIdLen = (word32)tempLen; + if (ret == 0) + object->data.genericData.objectIdLen = (word32)tempLen; } wp11_storage_close(storage); diff --git a/tests/object_store_decode_test.c b/tests/object_store_decode_test.c index e26332f6..d90771cc 100644 --- a/tests/object_store_decode_test.c +++ b/tests/object_store_decode_test.c @@ -75,6 +75,7 @@ static byte soPin[] = "password123456"; static byte userPin[] = "wolfpkcs11-test"; static CK_BBOOL ckTrue = CK_TRUE; static CK_BBOOL ckFalse = CK_FALSE; +static byte dataValue[] = "stored data object value"; static const char* storeKinds[] = { "obj", "data", "symmkey", "rsakey_priv", "rsakey_pub", "ecckey_priv", @@ -370,13 +371,12 @@ static CK_RV create_data_object(CK_SESSION_HANDLE session) CK_OBJECT_CLASS dataClass = CKO_DATA; CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; static byte app[] = "wolfpkcs11"; - static byte value[] = "stored data object value"; CK_ATTRIBUTE tmpl[] = { { CKA_CLASS, &dataClass, sizeof(dataClass) }, { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, { CKA_APPLICATION, app, sizeof(app) - 1 }, - { CKA_VALUE, value, sizeof(value) - 1 }, + { CKA_VALUE, dataValue, sizeof(dataValue) - 1 }, }; return funcList->C_CreateObject(session, tmpl, @@ -479,6 +479,76 @@ static void test_negative_attribute_length(void) cleanup_test_files(); } +static int truncate_file(const char* path, size_t keep) +{ + static byte data[MAX_FILE_SZ]; + size_t sz = 0; + + if (read_file(path, data, sizeof(data), &sz) != 0 || sz <= keep) + return -1; + return write_file(path, data, keep); +} + +/* Either the token refuses to load or the data object reads back its full + * stored value. */ +static int child_data_value_intact(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ULONG count = 0; + CK_ATTRIBUTE findTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + }; + byte value[64]; + CK_ATTRIBUTE attr = { CKA_VALUE, value, sizeof(value) }; + int res = CHILD_FAIL; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv != CKR_OK) + return CHILD_PASS; + + rv = open_session(slot, 0, &session); + if (rv == CKR_OK) + rv = funcList->C_FindObjectsInit(session, findTmpl, 1); + if (rv == CKR_OK) + rv = funcList->C_FindObjects(session, &obj, 1, &count); + if (rv == CKR_OK) + rv = funcList->C_FindObjectsFinal(session); + if (rv == CKR_OK && count == 1) + rv = funcList->C_GetAttributeValue(session, obj, &attr, 1); + if (rv == CKR_OK && count == 1 && + attr.ulValueLen == sizeof(dataValue) - 1 && + XMEMCMP(value, dataValue, sizeof(dataValue) - 1) == 0) { + res = CHILD_PASS; + } + (void)funcList->C_Finalize(NULL); + return res; +} + +static void test_truncated_data_record(void) +{ + CK_RV rv; + char path[256]; + int res = CHILD_SETUP; + + printf("\n--- truncated data object record is not loaded ---\n"); + rv = store_objects(create_data_object); + CHECK_RV(rv, "store data object", CKR_OK); + if (rv != CKR_OK) + return; + if (find_store_file("data", path, sizeof(path)) == 0 && + truncate_file(path, 2) == 0) { + res = run_in_child(child_data_value_intact); + } + CHECK_TRUE(res == CHILD_PASS, "truncated data object record is rejected"); + cleanup_test_files(); +} + #define LARGE_VALUE_SZ 300001 static byte largeValue[LARGE_VALUE_SZ]; static byte largeRead[LARGE_VALUE_SZ]; @@ -638,6 +708,7 @@ int main(int argc, char* argv[]) #ifdef HAVE_ALLOC_TRACKING test_large_stored_value_peak(); #endif + test_truncated_data_record(); return pkcs11_test_summary(); } From 912c4bd23f5a6c88ef0fe416228830d188f42b0f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:45:11 -0700 Subject: [PATCH 022/204] F-4264 - Reject stored private key records shorter than the tag --- src/internal.c | 44 ++++--- tests/object_store_decode_test.c | 189 ++++++++++++++++++++++++++++++- 2 files changed, 218 insertions(+), 15 deletions(-) diff --git a/src/internal.c b/src/internal.c index 0136b1e6..5e666217 100644 --- a/src/internal.c +++ b/src/internal.c @@ -4225,11 +4225,14 @@ static int wp11_Object_Decode_RsaKey(WP11_Object* object) else #endif if (object->objClass == CKO_PRIVATE_KEY) { - unsigned char* der; + unsigned char* der = NULL; int len = object->keyDataLen - AES_BLOCK_SIZE; - der = (unsigned char*)XMALLOC(len, NULL, DYNAMIC_TYPE_TMP_BUFFER); - if (der == NULL) { + if (object->keyDataLen <= AES_BLOCK_SIZE) + ret = BAD_FUNC_ARG; + if (ret == 0) + der = (unsigned char*)XMALLOC(len, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (ret == 0 && der == NULL) { ret = MEMORY_E; } if (ret == 0) { @@ -4573,11 +4576,14 @@ static int wp11_Object_Decode_EccKey(WP11_Object* object) else #endif if (object->objClass == CKO_PRIVATE_KEY) { - unsigned char* der; + unsigned char* der = NULL; int len = object->keyDataLen - AES_BLOCK_SIZE; - der = (unsigned char*)XMALLOC(len, NULL, DYNAMIC_TYPE_TMP_BUFFER); - if (der == NULL) { + if (object->keyDataLen <= AES_BLOCK_SIZE) + ret = BAD_FUNC_ARG; + if (ret == 0) + der = (unsigned char*)XMALLOC(len, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (ret == 0 && der == NULL) { ret = MEMORY_E; } if (ret == 0) { @@ -4813,11 +4819,14 @@ static int wp11_Object_Decode_MldsaKey(WP11_Object* object) int ret = 0; if (object->objClass == CKO_PRIVATE_KEY) { - unsigned char* der; + unsigned char* der = NULL; int len = object->keyDataLen - AES_BLOCK_SIZE; - der = (unsigned char*)XMALLOC(len, NULL, DYNAMIC_TYPE_TMP_BUFFER); - if (der == NULL) { + if (object->keyDataLen <= AES_BLOCK_SIZE) + ret = BAD_FUNC_ARG; + if (ret == 0) + der = (unsigned char*)XMALLOC(len, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (ret == 0 && der == NULL) { ret = MEMORY_E; } if (ret == 0) { @@ -5030,11 +5039,18 @@ static int wp11_Object_Decode_DhKey(WP11_Object* object) int ret = 0; if (object->objClass == CKO_PRIVATE_KEY) { - ret = wp11_DecryptData(object->data.dhKey->key, object->keyData, - object->keyDataLen - AES_BLOCK_SIZE, - object->slot->token.key, - sizeof(object->slot->token.key), object->iv, - sizeof(object->iv), object->devId); + if (object->keyDataLen <= AES_BLOCK_SIZE) + ret = BAD_FUNC_ARG; + else if (object->keyDataLen - AES_BLOCK_SIZE > WP11_MAX_DH_KEY_SZ) + ret = BUFFER_E; + if (ret == 0) { + ret = wp11_DecryptData(object->data.dhKey->key, object->keyData, + object->keyDataLen - AES_BLOCK_SIZE, + object->slot->token.key, + sizeof(object->slot->token.key), + object->iv, sizeof(object->iv), + object->devId); + } if (ret == 0) object->data.dhKey->len = object->keyDataLen - AES_BLOCK_SIZE; } diff --git a/tests/object_store_decode_test.c b/tests/object_store_decode_test.c index d90771cc..963e34b6 100644 --- a/tests/object_store_decode_test.c +++ b/tests/object_store_decode_test.c @@ -79,7 +79,8 @@ static byte dataValue[] = "stored data object value"; static const char* storeKinds[] = { "obj", "data", "symmkey", "rsakey_priv", "rsakey_pub", "ecckey_priv", - "ecckey_pub", "dhkey_priv", "dhkey_pub", "cert", "trust" + "ecckey_pub", "dhkey_priv", "dhkey_pub", "cert", "trust", "mldsakey_priv", + "mldsakey_pub" }; #if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY) && \ @@ -549,6 +550,191 @@ static void test_truncated_data_record(void) cleanup_test_files(); } +/* Replace the first length-prefixed array in a stored key record with newLen + * bytes, keeping the rest of the record unchanged. */ +static int rewrite_first_array(const char* path, word32 newLen) +{ + static byte data[MAX_FILE_SZ]; + static byte out[MAX_FILE_SZ]; + size_t sz = 0; + word32 oldLen; + size_t tail; + + if (read_file(path, data, sizeof(data), &sz) != 0 || sz < 4) + return -1; + oldLen = get_be32(data); + if (oldLen > sz - 4 || 4 + (size_t)newLen + (sz - 4 - oldLen) > sizeof(out)) + return -1; + tail = sz - 4 - oldLen; + put_be32(out, newLen); + XMEMSET(out + 4, 0x5A, newLen); + XMEMCPY(out + 4, data + 4, (newLen < oldLen) ? newLen : oldLen); + XMEMCPY(out + 4 + newLen, data + 4 + oldLen, tail); + return write_file(path, out, 4 + newLen + tail); +} + +/* Loading may succeed but logging in must reject the damaged key record. */ +static int child_login_rejected(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + int res = CHILD_FAIL; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv != CKR_OK) + return CHILD_PASS; + rv = open_session(slot, 1, &session); + if (rv != CKR_OK) + res = CHILD_PASS; + (void)funcList->C_Finalize(NULL); + return res; +} + +static void check_damaged_key_record(const char* name, create_fn create, + const char* kind, word32 newLen) +{ + CK_RV rv; + char path[256]; + char msg[128]; + int res = CHILD_SETUP; + + rv = store_objects(create); + (void)snprintf(msg, sizeof(msg), "store %s", name); + CHECK_RV(rv, msg, CKR_OK); + if (rv != CKR_OK) + return; + if (find_store_file(kind, path, sizeof(path)) == 0 && + rewrite_first_array(path, newLen) == 0) { + res = run_in_child(child_login_rejected); + } + (void)snprintf(msg, sizeof(msg), "damaged %s record is rejected", name); + CHECK_TRUE(res == CHILD_PASS, msg); + cleanup_test_files(); +} + +#ifndef NO_DH +static CK_RV create_dh_private_key(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_DH; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + { 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) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); +} +#endif + +#ifdef HAVE_ECC +static CK_RV create_ecc_private_key(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_EC; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); +} +#endif + +#ifndef NO_RSA +static CK_RV create_rsa_private_key(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_RSA; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SIGN, &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_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) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); +} +#endif + +#ifdef WOLFPKCS11_MLDSA +static CK_RV create_mldsa_key_pair(CK_SESSION_HANDLE session) +{ + CK_MECHANISM mech = { CKM_ML_DSA_KEY_PAIR_GEN, NULL, 0 }; + CK_ULONG paramSet = CKP_ML_DSA_44; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + }; + + return funcList->C_GenerateKeyPair(session, &mech, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &pub, &priv); +} +#endif + +/* A stored private key record shorter than its authentication tag, or + * longer than the key it decrypts into. */ +static void test_short_private_key_record(void) +{ + printf("\n--- short private key record is rejected ---\n"); +#ifndef NO_DH + check_damaged_key_record("DH private key", create_dh_private_key, + "dhkey_priv", 8); + check_damaged_key_record("tag-only DH private key", create_dh_private_key, + "dhkey_priv", 16); + check_damaged_key_record("oversized DH private key", + create_dh_private_key, "dhkey_priv", 4096 + 16); +#endif +#ifdef HAVE_ECC + check_damaged_key_record("tag-only ECC private key", + create_ecc_private_key, "ecckey_priv", 16); +#endif +#ifndef NO_RSA + check_damaged_key_record("tag-only RSA private key", + create_rsa_private_key, "rsakey_priv", 16); +#endif +#ifdef WOLFPKCS11_MLDSA + check_damaged_key_record("tag-only ML-DSA private key", + create_mldsa_key_pair, "mldsakey_priv", 16); +#endif +} + #define LARGE_VALUE_SZ 300001 static byte largeValue[LARGE_VALUE_SZ]; static byte largeRead[LARGE_VALUE_SZ]; @@ -709,6 +895,7 @@ int main(int argc, char* argv[]) test_large_stored_value_peak(); #endif test_truncated_data_record(); + test_short_private_key_record(); return pkcs11_test_summary(); } From ed735ef59a5aba66c0cf5b4c1061d9348377be59 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:53:42 -0700 Subject: [PATCH 023/204] F-4733 - Bound stored DH public key length to the key buffer --- src/internal.c | 3 +++ tests/object_store_decode_test.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/internal.c b/src/internal.c index 5e666217..c23fa06c 100644 --- a/src/internal.c +++ b/src/internal.c @@ -5054,6 +5054,9 @@ static int wp11_Object_Decode_DhKey(WP11_Object* object) if (ret == 0) object->data.dhKey->len = object->keyDataLen - AES_BLOCK_SIZE; } + else if (object->keyDataLen > WP11_MAX_DH_KEY_SZ) { + ret = BUFFER_E; + } else { XMEMCPY(object->data.dhKey->key, object->keyData, object->keyDataLen); object->data.dhKey->len = object->keyDataLen; diff --git a/tests/object_store_decode_test.c b/tests/object_store_decode_test.c index 963e34b6..15a57d96 100644 --- a/tests/object_store_decode_test.c +++ b/tests/object_store_decode_test.c @@ -735,6 +735,34 @@ static void test_short_private_key_record(void) #endif } +#ifndef NO_DH +static CK_RV create_dh_public_key(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_DH; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIME, dh_ffdhe2048_p, sizeof(dh_ffdhe2048_p) }, + { CKA_BASE, dh_ffdhe2048_g, sizeof(dh_ffdhe2048_g) }, + { CKA_VALUE, dh_2048_pub, sizeof(dh_2048_pub) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); +} + +/* A stored public key record longer than the largest supported key. */ +static void test_oversized_public_key_record(void) +{ + printf("\n--- oversized public key record is rejected ---\n"); + check_damaged_key_record("oversized DH public key", create_dh_public_key, + "dhkey_pub", 4096); +} +#endif + #define LARGE_VALUE_SZ 300001 static byte largeValue[LARGE_VALUE_SZ]; static byte largeRead[LARGE_VALUE_SZ]; @@ -896,6 +924,9 @@ int main(int argc, char* argv[]) #endif test_truncated_data_record(); test_short_private_key_record(); +#ifndef NO_DH + test_oversized_public_key_record(); +#endif return pkcs11_test_summary(); } From 9e25a1e96f985bca80420965170e469fc9432a88 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:06:36 -0700 Subject: [PATCH 024/204] F-3641 - Reject stored trust records that are not the trust size --- src/internal.c | 28 ++++++++++++++++++------ tests/object_store_decode_test.c | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/internal.c b/src/internal.c index c23fa06c..3106a122 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3504,14 +3504,25 @@ static void wp11_Object_Decode_Cert(WP11_Object* object) * Trust is not encrypted. * * @param [in, out] object Trust object. + * @return 0 on success. + * @return BUFFER_E when the stored trust data is not the expected size. */ -static void wp11_Object_Decode_Trust(WP11_Object* object) +static int wp11_Object_Decode_Trust(WP11_Object* object) { + int ret = 0; + if (object->keyData != NULL) { - XMEMCPY((unsigned char*)&object->data.trust, object->keyData, - object->keyDataLen); + if (object->keyDataLen != (int)sizeof(WP11_Trust)) { + ret = BUFFER_E; + } + else { + XMEMCPY((unsigned char*)&object->data.trust, object->keyData, + object->keyDataLen); + } } - object->encoded = 0; + object->encoded = (ret != 0); + + return ret; } #endif @@ -3578,7 +3589,11 @@ static int wp11_Object_Load_Trust(WP11_Object* object, int tokenId, int objId) ret = wp11_storage_read_alloc_array(storage, &object->keyData, &object->keyDataLen); wp11_storage_close(storage); - wp11_Object_Decode_Trust(object); + /* An empty record would leave the trust zeroed but decoded. */ + if (ret == 0 && object->keyData == NULL) + ret = BUFFER_E; + if (ret == 0) + ret = wp11_Object_Decode_Trust(object); } return ret; @@ -6955,8 +6970,7 @@ static int wp11_Object_Decode(WP11_Object* object) } #ifdef WOLFPKCS11_NSS else if (object->objClass == CKO_NSS_TRUST) { - wp11_Object_Decode_Trust(object); - ret = 0; + ret = wp11_Object_Decode_Trust(object); } #endif else if (object->objClass == CKO_DATA) { diff --git a/tests/object_store_decode_test.c b/tests/object_store_decode_test.c index 15a57d96..43d034be 100644 --- a/tests/object_store_decode_test.c +++ b/tests/object_store_decode_test.c @@ -763,6 +763,40 @@ static void test_oversized_public_key_record(void) } #endif +#ifdef WOLFPKCS11_NSS +static CK_RV create_trust_object(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS trustClass = CKO_NSS_TRUST; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + static byte issuer[] = "CN=Test,O=wolfSSL,C=US"; + static byte serial[] = { 0x02, 0x05, 0x00, 0xC6, 0xA7, 0x91, 0x84 }; + static byte sha1Hash[20] = { 0 }; + static byte md5Hash[16] = { 0 }; + CK_ULONG trustValue = 0xCE534352; + CK_ATTRIBUTE tmpl[] = { + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_CLASS, &trustClass, sizeof(trustClass) }, + { CKA_ISSUER, issuer, sizeof(issuer) - 1 }, + { CKA_SERIAL_NUMBER, serial, sizeof(serial) }, + { CKA_CERT_SHA1_HASH, sha1Hash, sizeof(sha1Hash) }, + { CKA_CERT_MD5_HASH, md5Hash, sizeof(md5Hash) }, + { CKA_TRUST_SERVER_AUTH, &trustValue, sizeof(trustValue) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); +} + +/* Trust records have a fixed size and are stored unencrypted. */ +static void test_trust_record_length(void) +{ + printf("\n--- trust record length must match ---\n"); + check_damaged_key_record("oversized trust", create_trust_object, "trust", + 1024); + check_damaged_key_record("empty trust", create_trust_object, "trust", 0); +} +#endif + #define LARGE_VALUE_SZ 300001 static byte largeValue[LARGE_VALUE_SZ]; static byte largeRead[LARGE_VALUE_SZ]; @@ -927,6 +961,9 @@ int main(int argc, char* argv[]) #ifndef NO_DH test_oversized_public_key_record(); #endif +#ifdef WOLFPKCS11_NSS + test_trust_record_length(); +#endif return pkcs11_test_summary(); } From 4072a25483d7925ae15ea0d215dcb334a8c1f1fa Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:14:03 -0700 Subject: [PATCH 025/204] F-3404 - Write the encoded TPM key into the caller's buffer --- src/internal.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/internal.c b/src/internal.c index 3106a122..7ac20239 100644 --- a/src/internal.c +++ b/src/internal.c @@ -4145,15 +4145,15 @@ static int WP11_Object_EncodeTpmKey(WP11_Object* object, byte* keyData, if (keyData != NULL) { if (ret <= keyDataLen) { /* Write size marker for the public part */ - XMEMCPY(object->keyData, &pubAreaSize, + XMEMCPY(keyData, &pubAreaSize, sizeof(UINT16)); idx += sizeof(UINT16); /* Write the public part with bytes aligned */ - XMEMCPY(object->keyData + idx, pubAreaBuffer, + XMEMCPY(keyData + idx, pubAreaBuffer, sizeof(UINT16) + pubAreaSize); idx += sizeof(UINT16) + pubAreaSize; /* Write the private part, size marker is included */ - XMEMCPY(object->keyData + idx, &object->tpmKey->priv, + XMEMCPY(keyData + idx, &object->tpmKey->priv, sizeof(UINT16) + object->tpmKey->priv.size); idx += sizeof(UINT16) + object->tpmKey->priv.size; From b5b0aa621b9746ee0e3e62e56b30ce4dc7c835a3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:20:21 -0700 Subject: [PATCH 026/204] F-8643 - Keep key buffer allocation errors in TPM RSA and ECC encoders --- src/internal.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index 7ac20239..7f33d767 100644 --- a/src/internal.c +++ b/src/internal.c @@ -4331,7 +4331,10 @@ static int wp11_Object_Encode_RsaKey(WP11_Object* object) } #ifdef WOLFPKCS11_TPM - ret = WP11_Object_EncodeTpmKey(object, object->keyData, object->keyDataLen); + if (ret == 0) { + ret = WP11_Object_EncodeTpmKey(object, object->keyData, + object->keyDataLen); + } if (ret > 0) { ret = 0; } @@ -4673,7 +4676,10 @@ static int wp11_Object_Encode_EccKey(WP11_Object* object) } #ifdef WOLFPKCS11_TPM - ret = WP11_Object_EncodeTpmKey(object, object->keyData, object->keyDataLen); + if (ret == 0) { + ret = WP11_Object_EncodeTpmKey(object, object->keyData, + object->keyDataLen); + } if (ret > 0) { ret = 0; } From e7c429b447d49f32f5a668c5f201eeba57514fcc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:27:25 -0700 Subject: [PATCH 027/204] F-13813 - Release DH and symmetric key data when encoding fails --- src/internal.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/internal.c b/src/internal.c index 7f33d767..9e3d3ca0 100644 --- a/src/internal.c +++ b/src/internal.c @@ -5125,6 +5125,14 @@ static int wp11_Object_Encode_DhKey(WP11_Object* object) } } + if (ret != 0) { + if (object->keyData != NULL) + wc_ForceZero(object->keyData, object->keyDataLen); + XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); + object->keyData = NULL; + object->keyDataLen = 0; + } + return ret; } @@ -6502,6 +6510,14 @@ static int wp11_Object_Encode_SymmKey(WP11_Object* object) object->keyDataLen = object->data.symmKey->len + AES_BLOCK_SIZE; } + if (ret != 0) { + if (object->keyData != NULL) + wc_ForceZero(object->keyData, object->keyDataLen); + XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); + object->keyData = NULL; + object->keyDataLen = 0; + } + return ret; } From d66444ade819708352745fd14ad1a4a251f807ff Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:33:20 -0700 Subject: [PATCH 028/204] F-3017 - Zeroize encoded private keys when RSA, ECC or ML-DSA encoding fails --- src/internal.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/internal.c b/src/internal.c index 9e3d3ca0..be409185 100644 --- a/src/internal.c +++ b/src/internal.c @@ -4365,6 +4365,8 @@ static int wp11_Object_Encode_RsaKey(WP11_Object* object) } if (ret != 0) { + if (object->keyData != NULL) + wc_ForceZero(object->keyData, object->keyDataLen); XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); object->keyData = NULL; object->keyDataLen = 0; @@ -4706,6 +4708,8 @@ static int wp11_Object_Encode_EccKey(WP11_Object* object) } if (ret != 0) { + if (object->keyData != NULL) + wc_ForceZero(object->keyData, object->keyDataLen); XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); object->keyData = NULL; object->keyDataLen = 0; @@ -4956,6 +4960,8 @@ static int wp11_Object_Encode_MldsaKey(WP11_Object* object) } if (ret != 0) { + if (object->keyData != NULL) + wc_ForceZero(object->keyData, object->keyDataLen); XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); object->keyData = NULL; object->keyDataLen = 0; From cc3256703a6e2bcc9b255dc17b61d0e007814996 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:47:05 -0700 Subject: [PATCH 029/204] F-3413 - Zeroize decrypted private key buffers on every decode path --- src/internal.c | 21 ++-- tests/object_store_decode_test.c | 170 +++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 6 deletions(-) diff --git a/src/internal.c b/src/internal.c index be409185..ee06dc28 100644 --- a/src/internal.c +++ b/src/internal.c @@ -4259,16 +4259,20 @@ static int wp11_Object_Decode_RsaKey(WP11_Object* object) if (ret == 0) { /* Decode RSA private key. */ ret = wc_RsaPrivateKeyDecode(der, &idx, key, len); - wc_ForceZero(der, len); } - if (der != NULL) + if (der != NULL) { + wc_ForceZero(der, len); XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } } else { /* Decode RSA public key. */ ret = wc_RsaPublicKeyDecode(object->keyData, &idx, key, object->keyDataLen); } + /* Drop partially decoded components and the init before a retry. */ + if (ret != 0) + wc_FreeRsaKey(key); object->encoded = (ret != 0); return ret; @@ -4615,16 +4619,20 @@ static int wp11_Object_Decode_EccKey(WP11_Object* object) if (ret == 0) { /* Decode ECC private key. */ ret = wc_EccPrivateKeyDecode(der, &idx, key, len); - wc_ForceZero(der, len); } - if (der != NULL) + if (der != NULL) { + wc_ForceZero(der, len); XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } } else { /* Decode ECC public key. */ ret = wc_EccPublicKeyDecode(object->keyData, &idx, key, object->keyDataLen); } + /* Drop partially decoded components and the init before a retry. */ + if (ret != 0) + wc_ecc_free(key); object->encoded = (ret != 0); return ret; @@ -4872,10 +4880,11 @@ static int wp11_Object_Decode_MldsaKey(WP11_Object* object) ret = MldsaKeyTryDecode(object->data.mldsaKey, WC_ML_DSA_87, der, len, object->objClass); } - wc_ForceZero(der, len); } - if (der != NULL) + if (der != NULL) { + wc_ForceZero(der, len); XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } } else { /* Decode ML-DSA public key. */ diff --git a/tests/object_store_decode_test.c b/tests/object_store_decode_test.c index 43d034be..6432fc66 100644 --- a/tests/object_store_decode_test.c +++ b/tests/object_store_decode_test.c @@ -940,6 +940,173 @@ static void test_large_stored_value_peak(void) } #endif +#ifdef HAVE_ALLOC_TRACKING +#define MAX_TRACKED 8192 + +typedef struct TrackedAlloc { + void* ptr; + size_t sz; +} TrackedAlloc; + +static TrackedAlloc tracked[MAX_TRACKED]; +static const byte* secretNeedle = NULL; +static size_t secretNeedleSz = 0; +static int secretFreed = 0; + +static void track_add(void* ptr, size_t sz) +{ + int i; + + for (i = 0; ptr != NULL && i < MAX_TRACKED; i++) { + if (tracked[i].ptr == NULL) { + tracked[i].ptr = ptr; + tracked[i].sz = sz; + break; + } + } +} + +static int track_find(void* ptr) +{ + int i; + + for (i = 0; ptr != NULL && i < MAX_TRACKED; i++) { + if (tracked[i].ptr == ptr) + return i; + } + return -1; +} + +static int holds_secret(const byte* buf, size_t sz) +{ + size_t i; + + for (i = 0; sz >= secretNeedleSz && i <= sz - secretNeedleSz; i++) { + if (XMEMCMP(buf + i, secretNeedle, secretNeedleSz) == 0) + return 1; + } + return 0; +} + +static void* scan_malloc(size_t sz) +{ + void* ptr = malloc(sz); + + /* Zeroed so a scan only sees bytes the library wrote. */ + if (ptr != NULL) + XMEMSET(ptr, 0, sz); + track_add(ptr, sz); + return ptr; +} + +static void scan_free(void* ptr) +{ + int i = track_find(ptr); + + if (i >= 0) { + if (holds_secret((const byte*)ptr, tracked[i].sz)) + secretFreed++; + tracked[i].ptr = NULL; + } + free(ptr); +} + +static void* scan_realloc(void* ptr, size_t sz) +{ + int i = track_find(ptr); + int oldKnown = (ptr == NULL || i >= 0); + size_t oldSz = (i >= 0) ? tracked[i].sz : 0; + void* newPtr = realloc(ptr, sz); + + if (newPtr != NULL) { + if (i >= 0) + tracked[i].ptr = NULL; + if (oldKnown && sz > oldSz) + XMEMSET((byte*)newPtr + oldSz, 0, sz - oldSz); + track_add(newPtr, sz); + } + return newPtr; +} + +/* Logging in with a stored private key that fails authentication must not + * release decrypted key material to the heap. */ +static int child_no_key_material_freed(void) +{ + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + + if (wolfSSL_SetAllocators(scan_malloc, scan_free, scan_realloc) != 0) + return CHILD_SETUP; + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + if (init_slot(&slot) == CKR_OK) { + (void)open_session(slot, 1, &session); + (void)funcList->C_Finalize(NULL); + } + if (secretFreed != 0) { + fprintf(stderr, " %d freed buffer(s) held key material\n", + secretFreed); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +/* Flip the last byte of the first length-prefixed array (the GCM tag). */ +static int corrupt_first_array_tag(const char* path) +{ + static byte data[MAX_FILE_SZ]; + size_t sz = 0; + word32 len; + + if (read_file(path, data, sizeof(data), &sz) != 0 || sz < 4) + return -1; + len = get_be32(data); + if (len == 0 || len > sz - 4) + return -1; + data[4 + len - 1] ^= 0x01; + return write_file(path, data, sz); +} + +static void check_failed_decrypt_scrubbed(const char* name, create_fn create, + const char* kind, + const byte* secret, size_t secretSz) +{ + CK_RV rv; + char path[256]; + char msg[128]; + int res = CHILD_SETUP; + + rv = store_objects(create); + (void)snprintf(msg, sizeof(msg), "store %s", name); + CHECK_RV(rv, msg, CKR_OK); + if (rv != CKR_OK) + return; + secretNeedle = secret; + secretNeedleSz = secretSz; + if (find_store_file(kind, path, sizeof(path)) == 0 && + corrupt_first_array_tag(path) == 0) { + res = run_in_child(child_no_key_material_freed); + } + (void)snprintf(msg, sizeof(msg), + "%s buffer is cleared after failed decrypt", name); + CHECK_TRUE(res == CHILD_PASS, msg); + cleanup_test_files(); +} + +static void test_failed_decrypt_scrubbed(void) +{ + printf("\n--- private key buffer cleared after failed decrypt ---\n"); +#ifdef HAVE_ECC + check_failed_decrypt_scrubbed("ECC private key", create_ecc_private_key, + "ecckey_priv", ecc_p256_priv, sizeof(ecc_p256_priv)); +#endif +#ifndef NO_RSA + check_failed_decrypt_scrubbed("RSA private key", create_rsa_private_key, + "rsakey_priv", rsa_2048_priv_exp, 32); +#endif +} +#endif + int main(int argc, char* argv[]) { (void)argc; @@ -964,6 +1131,9 @@ int main(int argc, char* argv[]) #ifdef WOLFPKCS11_NSS test_trust_record_length(); #endif +#ifdef HAVE_ALLOC_TRACKING + test_failed_decrypt_scrubbed(); +#endif return pkcs11_test_summary(); } From 286c885b1ea38d84139cfd99e99eecd0c2d04b0f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:53:02 -0700 Subject: [PATCH 030/204] F-8667 - Scrub symmetric and DH key buffers after a failed decrypt --- src/internal.c | 8 +++++++ tests/object_store_decode_test.c | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/internal.c b/src/internal.c index ee06dc28..143ab266 100644 --- a/src/internal.c +++ b/src/internal.c @@ -5086,6 +5086,10 @@ static int wp11_Object_Decode_DhKey(WP11_Object* object) sizeof(object->slot->token.key), object->iv, sizeof(object->iv), object->devId); + if (ret != 0) { + wc_ForceZero(object->data.dhKey->key, + (word32)(object->keyDataLen - AES_BLOCK_SIZE)); + } } if (ret == 0) object->data.dhKey->len = object->keyDataLen - AES_BLOCK_SIZE; @@ -6488,6 +6492,10 @@ static int wp11_Object_Decode_SymmKey(WP11_Object* object) object->slot->token.key, sizeof(object->slot->token.key), object->iv, sizeof(object->iv), object->devId); + if (ret != 0) { + wc_ForceZero(object->data.symmKey->data, + (word32)(object->keyDataLen - AES_BLOCK_SIZE)); + } } if (ret == 0) object->data.symmKey->len = object->keyDataLen - AES_BLOCK_SIZE; diff --git a/tests/object_store_decode_test.c b/tests/object_store_decode_test.c index 6432fc66..dc3f434b 100644 --- a/tests/object_store_decode_test.c +++ b/tests/object_store_decode_test.c @@ -1105,6 +1105,46 @@ static void test_failed_decrypt_scrubbed(void) "rsakey_priv", rsa_2048_priv_exp, 32); #endif } + +#ifndef NO_AES +static byte aesKeyValue[32] = { + 0x5b, 0x1e, 0xc4, 0x27, 0x9a, 0x30, 0xe8, 0x71, + 0x0d, 0xb6, 0x43, 0xf9, 0x82, 0x2c, 0x95, 0x6e, + 0xa7, 0x14, 0xd0, 0x3b, 0x68, 0xcf, 0x51, 0x0a, + 0xe3, 0x9d, 0x26, 0x7c, 0xb1, 0x48, 0xf5, 0x13 +}; + +static CK_RV create_aes_key(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_AES; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aesKeyValue, sizeof(aesKeyValue) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); +} +#endif + +static void test_failed_key_decrypt_scrubbed(void) +{ + printf("\n--- secret key storage cleared after failed decrypt ---\n"); +#ifndef NO_AES + check_failed_decrypt_scrubbed("AES key", create_aes_key, "symmkey", + aesKeyValue, sizeof(aesKeyValue)); +#endif +#ifndef NO_DH + check_failed_decrypt_scrubbed("DH private key value", create_dh_private_key, + "dhkey_priv", dh_2048_priv, sizeof(dh_2048_priv)); +#endif +} #endif int main(int argc, char* argv[]) @@ -1133,6 +1173,7 @@ int main(int argc, char* argv[]) #endif #ifdef HAVE_ALLOC_TRACKING test_failed_decrypt_scrubbed(); + test_failed_key_decrypt_scrubbed(); #endif return pkcs11_test_summary(); From 0742e3418efc9c5580286e9ac0eba1e68e831bd8 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:32:45 -0700 Subject: [PATCH 031/204] Keep the old key record when encoding fails --- src/internal.c | 144 +++++++++++++++---------------- tests/object_store_decode_test.c | 90 ++++++++++++++++++- 2 files changed, 157 insertions(+), 77 deletions(-) diff --git a/src/internal.c b/src/internal.c index 143ab266..6d4baaf3 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3481,6 +3481,33 @@ static int wp11_DecryptData(byte* out, byte* data, int len, byte* key, return ret; } +/** + * Finish encoding key data into a new buffer: on success release the previous + * record, on failure drop the new buffer and put the previous record back so + * the key still decodes at the next login. + * + * @param [in, out] object Key object. + * @param [in] oldData Key data buffer before the encode. + * @param [in] oldLen Length of key data before the encode. + * @param [in] newLen Size of the buffer allocated for the encode. + * @param [in] ret Result of the encode. + */ +static void wp11_Object_EncodeDone(WP11_Object* object, unsigned char* oldData, + int oldLen, word32 newLen, int ret) +{ + if (ret == 0) { + XFREE(oldData, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + else { + if (object->keyData != oldData && object->keyData != NULL) { + wc_ForceZero(object->keyData, newLen); + XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + object->keyData = oldData; + object->keyDataLen = oldLen; + } +} + /** * "Decode" the certificate. * @@ -4290,6 +4317,8 @@ static int wp11_Object_Decode_RsaKey(WP11_Object* object) static int wp11_Object_Encode_RsaKey(WP11_Object* object) { int ret; + unsigned char* oldData = object->keyData; + int oldLen = object->keyDataLen; #ifdef WOLFPKCS11_TPM ret = WP11_Object_EncodeTpmKey(object, NULL, 0); @@ -4326,7 +4355,6 @@ static int wp11_Object_Encode_RsaKey(WP11_Object* object) } if (ret == 0) { - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); /* Allocate buffer to hold encoded key. */ object->keyData = (unsigned char*)XMALLOC(object->keyDataLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -4368,13 +4396,8 @@ static int wp11_Object_Encode_RsaKey(WP11_Object* object) } } - if (ret != 0) { - if (object->keyData != NULL) - wc_ForceZero(object->keyData, object->keyDataLen); - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); - object->keyData = NULL; - object->keyDataLen = 0; - } + wp11_Object_EncodeDone(object, oldData, oldLen, (word32)object->keyDataLen, + ret); return ret; } @@ -4650,6 +4673,8 @@ static int wp11_Object_Decode_EccKey(WP11_Object* object) static int wp11_Object_Encode_EccKey(WP11_Object* object) { int ret; + unsigned char* oldData = object->keyData; + int oldLen = object->keyDataLen; #ifdef WOLFPKCS11_TPM ret = WP11_Object_EncodeTpmKey(object, NULL, 0); @@ -4677,7 +4702,6 @@ static int wp11_Object_Encode_EccKey(WP11_Object* object) } if (ret == 0) { - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); /* Allocate buffer to hold encoded key. */ object->keyData = (unsigned char*)XMALLOC(object->keyDataLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -4715,13 +4739,8 @@ static int wp11_Object_Encode_EccKey(WP11_Object* object) } } - if (ret != 0) { - if (object->keyData != NULL) - wc_ForceZero(object->keyData, object->keyDataLen); - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); - object->keyData = NULL; - object->keyDataLen = 0; - } + wp11_Object_EncodeDone(object, oldData, oldLen, (word32)object->keyDataLen, + ret); return ret; } @@ -4919,6 +4938,8 @@ static int wp11_Object_Decode_MldsaKey(WP11_Object* object) static int wp11_Object_Encode_MldsaKey(WP11_Object* object) { int ret; + unsigned char* oldData = object->keyData; + int oldLen = object->keyDataLen; if (object->objClass == CKO_PRIVATE_KEY) { /* Get length of encoded private key. */ @@ -4938,7 +4959,6 @@ static int wp11_Object_Encode_MldsaKey(WP11_Object* object) } if (ret == 0) { - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); /* Allocate buffer to hold encoded key. */ object->keyData = (unsigned char*)XMALLOC(object->keyDataLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -4968,13 +4988,8 @@ static int wp11_Object_Encode_MldsaKey(WP11_Object* object) } } - if (ret != 0) { - if (object->keyData != NULL) - wc_ForceZero(object->keyData, object->keyDataLen); - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); - object->keyData = NULL; - object->keyDataLen = 0; - } + wp11_Object_EncodeDone(object, oldData, oldLen, (word32)object->keyDataLen, + ret); return ret; } @@ -5118,12 +5133,14 @@ static int wp11_Object_Decode_DhKey(WP11_Object* object) static int wp11_Object_Encode_DhKey(WP11_Object* object) { int ret = 0; + unsigned char* oldData = object->keyData; + int oldLen = object->keyDataLen; + word32 newLen = object->data.dhKey->len + AES_BLOCK_SIZE; - object->keyDataLen = object->data.dhKey->len + AES_BLOCK_SIZE; - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); + object->keyDataLen = (int)newLen; /* Allocate buffer to hold encoded key. */ - object->keyData = (unsigned char*)XMALLOC(object->keyDataLen, NULL, - DYNAMIC_TYPE_TMP_BUFFER); + object->keyData = (unsigned char*)XMALLOC(newLen, NULL, + DYNAMIC_TYPE_TMP_BUFFER); if (object->keyData == NULL) ret = MEMORY_E; @@ -5143,14 +5160,7 @@ static int wp11_Object_Encode_DhKey(WP11_Object* object) object->keyDataLen = object->data.dhKey->len; } } - - if (ret != 0) { - if (object->keyData != NULL) - wc_ForceZero(object->keyData, object->keyDataLen); - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); - object->keyData = NULL; - object->keyDataLen = 0; - } + wp11_Object_EncodeDone(object, oldData, oldLen, newLen, ret); return ret; } @@ -5522,6 +5532,8 @@ static int wp11_Object_Encode_MlKemKey(WP11_Object* object) { int ret; word32 keyLen = 0; + unsigned char* oldData = object->keyData; + int oldLen = object->keyDataLen; if (object->objClass == CKO_PRIVATE_KEY) { ret = wc_MlKemKey_PrivateKeySize(object->data.mlKemKey, &keyLen); @@ -5537,7 +5549,6 @@ static int wp11_Object_Encode_MlKemKey(WP11_Object* object) } if (ret == 0) { - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); object->keyData = (unsigned char*)XMALLOC(object->keyDataLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (object->keyData == NULL) @@ -5559,13 +5570,8 @@ static int wp11_Object_Encode_MlKemKey(WP11_Object* object) object->keyData, keyLen); } - if (ret != 0) { - if (object->keyData != NULL) - wc_ForceZero(object->keyData, object->keyDataLen); - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); - object->keyData = NULL; - object->keyDataLen = 0; - } + wp11_Object_EncodeDone(object, oldData, oldLen, (word32)object->keyDataLen, + ret); return ret; } @@ -5744,13 +5750,14 @@ static int wp11_Object_Encode_HssKey(WP11_Object* object) { int ret; word32 pubLen = 0; + unsigned char* oldData = object->keyData; + int oldLen = object->keyDataLen; if (object->objClass != CKO_PUBLIC_KEY) return NOT_AVAILABLE_E; /* verify-only: no private-key objects */ ret = wc_LmsKey_GetPubLen(object->data.lmsKey, &pubLen); if (ret == 0) { - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); object->keyData = (unsigned char*)XMALLOC(pubLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (object->keyData == NULL) @@ -5762,14 +5769,7 @@ static int wp11_Object_Encode_HssKey(WP11_Object* object) if (ret == 0) object->keyDataLen = (int)pubLen; } - /* On failure release the buffer so a later store does not skip re-encoding - * (keyData != NULL) and persist an uninitialised buffer with a stale - * length. This is public key material only, so no zeroization is needed. */ - if (ret != 0) { - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); - object->keyData = NULL; - object->keyDataLen = 0; - } + wp11_Object_EncodeDone(object, oldData, oldLen, pubLen, ret); return ret; } @@ -6189,13 +6189,14 @@ static int wp11_Object_Encode_XmssKey(WP11_Object* object) { int ret; word32 pubLen = 0; + unsigned char* oldData = object->keyData; + int oldLen = object->keyDataLen; if (object->objClass != CKO_PUBLIC_KEY) return NOT_AVAILABLE_E; ret = wc_XmssKey_GetPubLen(object->data.xmssKey, &pubLen); if (ret == 0) { - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); object->keyData = (unsigned char*)XMALLOC(pubLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (object->keyData == NULL) @@ -6207,14 +6208,7 @@ static int wp11_Object_Encode_XmssKey(WP11_Object* object) if (ret == 0) object->keyDataLen = (int)pubLen; } - /* On failure release the buffer so a later store does not skip re-encoding - * (keyData != NULL) and persist an uninitialised buffer with a stale - * length. This is public key material only, so no zeroization is needed. */ - if (ret != 0) { - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); - object->keyData = NULL; - object->keyDataLen = 0; - } + wp11_Object_EncodeDone(object, oldData, oldLen, pubLen, ret); return ret; } @@ -6514,12 +6508,14 @@ static int wp11_Object_Decode_SymmKey(WP11_Object* object) static int wp11_Object_Encode_SymmKey(WP11_Object* object) { int ret = 0; + unsigned char* oldData = object->keyData; + int oldLen = object->keyDataLen; + word32 newLen = object->data.symmKey->len + AES_BLOCK_SIZE; - object->keyDataLen = object->data.symmKey->len + AES_BLOCK_SIZE; - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); + object->keyDataLen = (int)newLen; /* Allocate buffer to hold encoded key. */ - object->keyData = (unsigned char*)XMALLOC(object->keyDataLen, NULL, - DYNAMIC_TYPE_TMP_BUFFER); + object->keyData = (unsigned char*)XMALLOC(newLen, NULL, + DYNAMIC_TYPE_TMP_BUFFER); if (object->keyData == NULL) ret = MEMORY_E; @@ -6529,17 +6525,8 @@ static int wp11_Object_Encode_SymmKey(WP11_Object* object) object->slot->token.key, sizeof(object->slot->token.key), object->iv, sizeof(object->iv), object->devId); - if (ret == 0) - object->keyDataLen = object->data.symmKey->len + AES_BLOCK_SIZE; - } - - if (ret != 0) { - if (object->keyData != NULL) - wc_ForceZero(object->keyData, object->keyDataLen); - XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); - object->keyData = NULL; - object->keyDataLen = 0; } + wp11_Object_EncodeDone(object, oldData, oldLen, newLen, ret); return ret; } @@ -7205,10 +7192,12 @@ static int wp11_Object_IsEncrypted(WP11_Object* object) static int wp11_Object_Encode(WP11_Object* object, int protect) { int ret = 0; + byte oldIv[GCM_NONCE_MID_SZ]; /* Every AES-GCM encryption under the token key needs a fresh nonce. Do * this immediately before encoding, while the plaintext is still the * source of the ciphertext that will be persisted. */ + XMEMCPY(oldIv, object->iv, sizeof(oldIv)); if (wp11_Object_IsEncrypted(object)) { WP11_Lock_LockRW(&object->slot->token.rngLock); ret = wc_RNG_GenerateBlock(&object->slot->token.rng, object->iv, @@ -7217,6 +7206,9 @@ static int wp11_Object_Encode(WP11_Object* object, int protect) } if (ret == 0) ret = wp11_Object_EncodeData(object, protect); + /* The previous record stays in use when the encode fails. */ + if (ret != 0) + XMEMCPY(object->iv, oldIv, sizeof(object->iv)); return ret; } diff --git a/tests/object_store_decode_test.c b/tests/object_store_decode_test.c index dc3f434b..74fcdb2f 100644 --- a/tests/object_store_decode_test.c +++ b/tests/object_store_decode_test.c @@ -95,6 +95,7 @@ static size_t largestAlloc = 0; static size_t peakCap = 0; static size_t liveBytes = 0; static size_t peakLive = 0; +static int refuseAllocs = 0; static void* track_malloc(size_t sz) { @@ -102,7 +103,7 @@ static void* track_malloc(size_t sz) if (sz > largestAlloc) largestAlloc = sz; - if (sz >= ALLOC_REFUSE_SZ) + if (sz >= ALLOC_REFUSE_SZ || refuseAllocs) return NULL; p = (unsigned char*)malloc(sz + TRACK_HDR_SZ); if (p == NULL) @@ -1133,6 +1134,90 @@ static CK_RV create_aes_key(CK_SESSION_HANDLE session) } #endif +#ifndef NO_AES +/* Count the AES keys visible after a login and check one still encrypts. */ +static CK_RV use_stored_aes_key(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_AES; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + }; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_ULONG count = 0; + byte iv[16]; + byte plain[16]; + byte enc[32]; + CK_ULONG encLen = sizeof(enc); + CK_MECHANISM mech = { CKM_AES_CBC, iv, sizeof(iv) }; + + XMEMSET(iv, 0, sizeof(iv)); + XMEMSET(plain, 0, sizeof(plain)); + rv = funcList->C_FindObjectsInit(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl)); + if (rv == CKR_OK) { + rv = funcList->C_FindObjects(session, &key, 1, &count); + (void)funcList->C_FindObjectsFinal(session); + } + if (rv == CKR_OK && count != 1) + rv = CKR_OBJECT_HANDLE_INVALID; + if (rv == CKR_OK) + rv = funcList->C_EncryptInit(session, &mech, key); + if (rv == CKR_OK) + rv = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encLen); + return rv; +} + +/* A logout whose re-encryption of a stored key fails must leave the stored + * record in place, so the next login still decodes it. */ +static int child_login_after_failed_encode(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + int res = CHILD_FAIL; + + if (wolfSSL_SetAllocators(track_malloc, track_free, track_realloc) != 0) + return CHILD_SETUP; + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv != CKR_OK) + return CHILD_SETUP; + refuseAllocs = 1; + (void)funcList->C_Logout(session); + refuseAllocs = 0; + rv = funcList->C_Login(session, CKU_USER, userPin, sizeof(userPin) - 1); + if (rv == CKR_OK) + rv = use_stored_aes_key(session); + if (rv == CKR_OK) + res = CHILD_PASS; + else + fprintf(stderr, " login or key use after failed encode: 0x%lx\n", + (unsigned long)rv); + (void)funcList->C_Finalize(NULL); + return res; +} + +static void test_login_after_failed_encode(void) +{ + CK_RV rv; + int res = CHILD_SETUP; + + printf("\n--- stored key survives a failed re-encryption at logout ---\n"); + rv = store_objects(create_aes_key); + CHECK_RV(rv, "store AES key", CKR_OK); + if (rv == CKR_OK) + res = run_in_child(child_login_after_failed_encode); + CHECK_TRUE(res == CHILD_PASS, "login decodes the key after a failed encode"); + cleanup_test_files(); +} +#endif + static void test_failed_key_decrypt_scrubbed(void) { printf("\n--- secret key storage cleared after failed decrypt ---\n"); @@ -1174,6 +1259,9 @@ int main(int argc, char* argv[]) #ifdef HAVE_ALLOC_TRACKING test_failed_decrypt_scrubbed(); test_failed_key_decrypt_scrubbed(); +#ifndef NO_AES + test_login_after_failed_encode(); +#endif #endif return pkcs11_test_summary(); From d2774464b06f1ace950c6a4d024b95bcb1f02010 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 17:11:24 -0700 Subject: [PATCH 032/204] F-10400 - Authenticate stored object policy with the key encryption --- src/crypto.c | 6 + src/internal.c | 940 ++++++++++++++-- tests/include.am | 8 + tests/object_policy_integrity_test.c | 1538 ++++++++++++++++++++++++++ wolfpkcs11/internal.h | 6 + 5 files changed, 2427 insertions(+), 71 deletions(-) create mode 100644 tests/object_policy_integrity_test.c diff --git a/src/crypto.c b/src/crypto.c index 92de2f52..a301c4d2 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1189,6 +1189,12 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, return CKR_ATTRIBUTE_VALUE_INVALID; else if (ret == BUFFER_E) return CKR_BUFFER_TOO_SMALL; + else if (ret == BAD_STATE_E) { + /* Logged in yet undecoded: the stored key failed to verify. */ + if (WP11_Slot_IsUserLoggedIn(WP11_Session_GetSlot(session))) + return CKR_FUNCTION_FAILED; + return CKR_USER_NOT_LOGGED_IN; + } else if (ret != 0) return CKR_FUNCTION_FAILED; } diff --git a/src/internal.c b/src/internal.c index 6d4baaf3..38e29780 100644 --- a/src/internal.c +++ b/src/internal.c @@ -298,6 +298,8 @@ struct WP11_Object { int keyDataLen; /* Length of encoded key data */ byte iv[GCM_NONCE_MID_SZ]; /* IV/nonce for encrypt/decrypt */ byte encoded:1; /* Key isn't in decoded form */ + byte undecoded:1; /* Loaded key data not yet decoded */ + byte unbound:1; /* Key data not bound to the policy */ #endif WP11_Session* session; /* Session object belongs to */ @@ -596,6 +598,9 @@ struct WP11_Slot { WP11_Object* discarded; /* Destroyed objects awaiting free */ WP11_Lock callLock; /* Lock for object call epochs */ byte reclaim:1; /* Unlinked objects await free */ +#ifndef WOLFPKCS11_NO_STORE + WP11_Lock storeLock; /* Held for a whole token store pass */ +#endif }; #if defined(HAVE_FIPS) && FIPS_VERSION_LT(6,0) @@ -1267,25 +1272,52 @@ static void wolfPKCS11_StoreAbortTemp(WP11_FileStoreCtx* ctx) } } -static int wolfPKCS11_StoreCommitTemp(WP11_FileStoreCtx* ctx) +#if defined(DEBUG_WOLFPKCS11) && !defined(_WIN32) && !defined(_MSC_VER) +static int wp11_storeExitAfter = 0; + +/** + * Test hook: end the process right after the given number of store renames, + * standing in for a crash part way through a store pass. + * + * @param renames [in] Renames to allow, or 0 to disable the hook. + */ +WP11_API void WP11_Test_StoreExitAfterRenames(int renames) { - int ret = 0; + wp11_storeExitAfter = renames; +} +#endif - if (ctx == NULL || ctx->has_temp == 0) { - return 0; - } +static int wolfPKCS11_StoreRename(const char* from, const char* to) +{ + int ret = 0; #if defined(_WIN32) || defined(_MSC_VER) - if (!MoveFileExA(ctx->temp_name, ctx->final_name, + if (!MoveFileExA(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { ret = READ_ONLY_E; } #else - if (rename(ctx->temp_name, ctx->final_name) != 0) { + if (rename(from, to) != 0) { ret = READ_ONLY_E; } #endif +#if defined(DEBUG_WOLFPKCS11) && !defined(_WIN32) && !defined(_MSC_VER) + if (ret == 0 && wp11_storeExitAfter > 0 && --wp11_storeExitAfter == 0) + _exit(WP11_TEST_STORE_EXIT_CODE); +#endif + + return ret; +} + +static int wolfPKCS11_StoreCommitTemp(WP11_FileStoreCtx* ctx) +{ + int ret = 0; + if (ctx == NULL || ctx->has_temp == 0) { + return 0; + } + + ret = wolfPKCS11_StoreRename(ctx->temp_name, ctx->final_name); if (ret == 0) { ctx->has_temp = 0; } @@ -1607,6 +1639,478 @@ static int wolfPKCS11_Store_Name(int type, CK_ULONG id1, CK_ULONG id2, char* nam return ret; } + +/* One token store pass commits as a unit: staged records, then a marker. */ +#define WP11_STORE_BATCH +#define WP11_STORE_STAGED_MAX (WP11_STORE_MAX_PATH + 8) + +#define WP11_BATCH_DISCARD 0 +#define WP11_BATCH_APPLY 1 +#define WP11_BATCH_CHECK 2 + +typedef struct WP11_StoreBatch { + WP11_Slot* slot; + XFILE intent; +#if defined(_WIN32) || defined(_MSC_VER) + HANDLE lockFile; +#else + int lockFd; +#endif + int locked; + int active; + int failed; + size_t dirLen; + char dir[WP11_STORE_MAX_PATH]; + char intentName[WP11_STORE_STAGED_MAX]; + char commitName[WP11_STORE_STAGED_MAX]; + char lockName[WP11_STORE_STAGED_MAX]; +} WP11_StoreBatch; + +static WP11_StoreBatch storeBatch; + +/** + * Work out the store directory and the batch file names for a token. + * + * @param [in] tokenId Id of token. + * @return 0 on success. + * @return READ_ONLY_E when the names cannot be built. + */ +static int wolfPKCS11_StoreBatchNames(int tokenId) +{ + int ret = 0; + int len; + int i; + int sep = -1; + + len = wolfPKCS11_Store_Name(WOLFPKCS11_STORE_TOKEN, (CK_ULONG)tokenId, 0, + storeBatch.dir, sizeof(storeBatch.dir)); + if (len <= 0 || len >= (int)sizeof(storeBatch.dir)) + ret = READ_ONLY_E; + for (i = 0; ret == 0 && i < len; i++) { + if (storeBatch.dir[i] == '/' || storeBatch.dir[i] == '\\') + sep = i; + } + if (ret == 0 && sep <= 0) + ret = READ_ONLY_E; + if (ret == 0) { + storeBatch.dir[sep] = '\0'; + storeBatch.dirLen = (size_t)sep; + len = XSNPRINTF(storeBatch.intentName, sizeof(storeBatch.intentName), + "%s/wp11_txn_%016lx_intent", storeBatch.dir, + (unsigned long)tokenId); + if (len <= 0 || len >= (int)sizeof(storeBatch.intentName)) + ret = READ_ONLY_E; + } + if (ret == 0) { + len = XSNPRINTF(storeBatch.commitName, sizeof(storeBatch.commitName), + "%s/wp11_txn_%016lx_commit", storeBatch.dir, + (unsigned long)tokenId); + if (len <= 0 || len >= (int)sizeof(storeBatch.commitName)) + ret = READ_ONLY_E; + } + if (ret == 0) { + len = XSNPRINTF(storeBatch.lockName, sizeof(storeBatch.lockName), + "%s/wp11_txn_%016lx_lock", storeBatch.dir, + (unsigned long)tokenId); + if (len <= 0 || len >= (int)sizeof(storeBatch.lockName)) + ret = READ_ONLY_E; + } + + return ret; +} + +/** + * Take or release the lock file that keeps other processes out of the batch + * files while a pass or a recovery runs. + * + * @param [in] lock 1 to take the lock, 0 to release it. + * @return 0 on success. + * @return READ_ONLY_E when the lock cannot be taken. + */ +static int wolfPKCS11_StoreBatchLockFile(int lock) +{ + int ret = 0; +#if defined(_WIN32) || defined(_MSC_VER) + OVERLAPPED ov; + + XMEMSET(&ov, 0, sizeof(ov)); + if (lock) { + storeBatch.lockFile = CreateFileA(storeBatch.lockName, + GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, NULL); + if (storeBatch.lockFile == INVALID_HANDLE_VALUE) + ret = READ_ONLY_E; + if (ret == 0 && !LockFileEx(storeBatch.lockFile, + LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &ov)) { + CloseHandle(storeBatch.lockFile); + ret = READ_ONLY_E; + } + storeBatch.locked = (ret == 0); + } + else if (storeBatch.locked) { + (void)UnlockFileEx(storeBatch.lockFile, 0, 1, 0, &ov); + CloseHandle(storeBatch.lockFile); + storeBatch.locked = 0; + } +#else + struct flock fl; + + XMEMSET(&fl, 0, sizeof(fl)); + fl.l_whence = SEEK_SET; + if (lock) { + storeBatch.lockFd = open(storeBatch.lockName, + O_RDWR | O_CREAT | O_NOFOLLOW, S_IRUSR | S_IWUSR); + if (storeBatch.lockFd < 0) + ret = READ_ONLY_E; + fl.l_type = F_WRLCK; + while (ret == 0 && fcntl(storeBatch.lockFd, F_SETLKW, &fl) != 0) { + if (errno != EINTR) { + close(storeBatch.lockFd); + ret = READ_ONLY_E; + } + } + storeBatch.locked = (ret == 0); + } + else if (storeBatch.locked) { + fl.l_type = F_UNLCK; + (void)fcntl(storeBatch.lockFd, F_SETLK, &fl); + close(storeBatch.lockFd); + storeBatch.locked = 0; + } +#endif + + return ret; +} + +/** + * Check that a listed name is a store file directly in the store directory. + * + * @param [in] name Name read from a batch list. + * @return 1 when acceptable, 0 otherwise. + */ +static int wolfPKCS11_StoreBatchNameOk(const char* name) +{ + size_t len = XSTRLEN(name); + size_t i; + int ok; + + ok = len > storeBatch.dirLen + 6 && + XSTRNCMP(name, storeBatch.dir, storeBatch.dirLen) == 0 && + name[storeBatch.dirLen] == '/' && + XSTRNCMP(name + storeBatch.dirLen + 1, "wp11_", 5) == 0; + for (i = storeBatch.dirLen + 1; ok && i < len; i++) { + if (name[i] == '/' || name[i] == '\\') + ok = 0; + } + + return ok; +} + +/** + * Check that a final record name can be replaced by a rename. + * + * @param [in] name Final record name. + * @return 1 when absent or a regular file, 0 otherwise. + */ +static int wolfPKCS11_StoreBatchReplaceable(const char* name) +{ +#if defined(_WIN32) || defined(_MSC_VER) + DWORD attrs = GetFileAttributesA(name); + + return attrs == INVALID_FILE_ATTRIBUTES || + (attrs & (FILE_ATTRIBUTE_DIRECTORY | + FILE_ATTRIBUTE_REPARSE_POINT)) == 0; +#else + struct stat st; + + if (lstat(name, &st) != 0) + return errno == ENOENT; + return S_ISREG(st.st_mode); +#endif +} + +/** + * Act on each staged record a batch list names: remove it, rename it into + * place, or check that it can be renamed into place. + * + * @param [in] listName Intent or commit file. + * @param [in] mode WP11_BATCH_DISCARD, WP11_BATCH_APPLY or + * WP11_BATCH_CHECK. + * @return 0 on success. + * @return READ_ONLY_E when the list is unreadable or malformed, or a record + * cannot be put in place. Discarding never fails. + */ +static int wolfPKCS11_StoreBatchApply(const char* listName, int mode) +{ + int ret = 0; + XFILE list; + XFILE staged; + size_t len; + char line[WP11_STORE_MAX_PATH + 2]; + char stagedName[WP11_STORE_STAGED_MAX]; + + list = XFOPEN(listName, "rb"); + if (list == XBADFILE) + return (mode == WP11_BATCH_DISCARD) ? 0 : READ_ONLY_E; + while (fgets(line, sizeof(line), list) != NULL) { + len = XSTRLEN(line); + /* The intent can end in a line a crash cut short; a marker cannot. */ + if (len == 0 || line[len - 1] != '\n') { + if (mode != WP11_BATCH_DISCARD) + ret = READ_ONLY_E; + continue; + } + line[len - 1] = '\0'; + len = (size_t)XSNPRINTF(stagedName, sizeof(stagedName), "%s.new", + line); + if (!wolfPKCS11_StoreBatchNameOk(line) || len >= sizeof(stagedName)) { + if (mode != WP11_BATCH_DISCARD) + ret = READ_ONLY_E; + continue; + } + if (mode == WP11_BATCH_DISCARD) { + (void)remove(stagedName); + } + else if (mode == WP11_BATCH_CHECK) { + if (!wolfPKCS11_StoreBatchReplaceable(line)) + ret = READ_ONLY_E; + } + else if (wolfPKCS11_StoreRename(stagedName, line) != 0) { + /* Already renamed by an earlier, interrupted roll forward. */ + staged = XFOPEN(stagedName, "rb"); + if (staged != XBADFILE) { + XFCLOSE(staged); + ret = READ_ONLY_E; + } + } + } + if (ferror(list) && mode != WP11_BATCH_DISCARD) + ret = READ_ONLY_E; + XFCLOSE(list); + + return ret; +} + +/** + * Finish a store pass that reached its commit point, or drop one that did + * not. Caller holds the batch locks and has set the batch names. + * + * @return 0 on success. + * @return READ_ONLY_E when a committed pass could not be finished; the + * marker is kept so a later attempt can finish it. + */ +static int wolfPKCS11_StoreBatchRecover(void) +{ + int ret = 0; + XFILE marker; + + marker = XFOPEN(storeBatch.commitName, "rb"); + if (marker != XBADFILE) { + XFCLOSE(marker); + ret = wolfPKCS11_StoreBatchApply(storeBatch.commitName, + WP11_BATCH_APPLY); + if (ret == 0) + (void)remove(storeBatch.commitName); + } + else { + (void)wolfPKCS11_StoreBatchApply(storeBatch.intentName, + WP11_BATCH_DISCARD); + (void)remove(storeBatch.intentName); + } + + return ret; +} + +/** + * Bring the store for a token to a committed state before it is read. + * + * @param [in] tokenId Id of token. + * @return 0 on success. + * @return Other value when an interrupted pass could not be finished. + */ +static int wolfPKCS11_StoreBatchCheck(int tokenId) +{ + int ret = 0; + XFILE marker; + WP11_Slot* slot; + + if (WP11_Slot_Get((CK_SLOT_ID)tokenId, &slot) != 0) + return 0; + WP11_Lock_LockRW(&slot->storeLock); + if (wolfPKCS11_StoreBatchNames(tokenId) == 0) { + if (wolfPKCS11_StoreBatchLockFile(1) == 0) { + ret = wolfPKCS11_StoreBatchRecover(); + (void)wolfPKCS11_StoreBatchLockFile(0); + } + else { + /* A committed pass that cannot be finished must not be read. */ + marker = XFOPEN(storeBatch.commitName, "rb"); + if (marker != XBADFILE) { + XFCLOSE(marker); + ret = READ_ONLY_E; + } + } + } + WP11_Lock_UnlockRW(&slot->storeLock); + + return ret; +} + +/** + * Start a store pass: records closed from now on are staged, not committed. + * On success the batch locks are held until wolfPKCS11_StoreBatchEnd. Without + * a usable store directory the pass runs unstaged and fails as before. + * + * @param [in] tokenId Id of token being stored. + * @return 0 on success. + * @return Other value when an interrupted pass could not be finished. + */ +static int wolfPKCS11_StoreBatchBegin(int tokenId) +{ + int ret = 0; +#ifndef WOLFPKCS11_NO_ENV + const char* str; +#endif +#if defined(_WIN32) || defined(_MSC_VER) + HANDLE h; +#endif + int fd; + +#ifndef WOLFPKCS11_NO_ENV + /* Storage is turned off, so the pass writes nothing. */ + str = XGETENV("WOLFPKCS11_NO_STORE"); + if (str != NULL) + return 0; +#endif + if (WP11_Slot_Get((CK_SLOT_ID)tokenId, &storeBatch.slot) != 0) + return BAD_FUNC_ARG; + WP11_Lock_LockRW(&storeBatch.slot->storeLock); + ret = wolfPKCS11_StoreBatchNames(tokenId); + if (ret == 0) + ret = wolfPKCS11_StoreEnsureDir(storeBatch.dir); + if (ret == 0) + ret = wolfPKCS11_StoreBatchLockFile(1); + if (ret == 0) + ret = wolfPKCS11_StoreBatchRecover(); + if (ret == 0) { + #if defined(_WIN32) || defined(_MSC_VER) + /* Create afresh without following a link planted at the name. */ + (void)DeleteFileA(storeBatch.intentName); + h = CreateFileA(storeBatch.intentName, GENERIC_WRITE, 0, NULL, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, + NULL); + fd = (h != INVALID_HANDLE_VALUE) ? + _open_osfhandle((intptr_t)h, _O_WRONLY | _O_BINARY) : -1; + if (h != INVALID_HANDLE_VALUE && fd < 0) + CloseHandle(h); + storeBatch.intent = (fd >= 0) ? _fdopen(fd, "wb") : XBADFILE; + if (fd >= 0 && storeBatch.intent == XBADFILE) + _close(fd); + #else + fd = open(storeBatch.intentName, + O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, S_IRUSR | S_IWUSR); + storeBatch.intent = (fd >= 0) ? fdopen(fd, "wb") : XBADFILE; + if (fd >= 0 && storeBatch.intent == XBADFILE) + close(fd); + #endif + if (storeBatch.intent == XBADFILE) + ret = READ_ONLY_E; + } + if (ret == 0) { + storeBatch.active = 1; + storeBatch.failed = 0; + } + else { + (void)wolfPKCS11_StoreBatchLockFile(0); + WP11_Lock_UnlockRW(&storeBatch.slot->storeLock); + storeBatch.slot = NULL; + } + + return ret; +} + +/** + * Stage a written record as ".new" and list it in the intent file. + * + * @param [in, out] ctx File store context with a written temporary file. + * @return 0 on success. + * @return READ_ONLY_E when the record could not be staged. + */ +static int wolfPKCS11_StoreStageTemp(WP11_FileStoreCtx* ctx) +{ + int ret = 0; + int len; + char stagedName[WP11_STORE_STAGED_MAX]; + + len = XSNPRINTF(stagedName, sizeof(stagedName), "%s.new", + ctx->final_name); + if (len <= 0 || len >= (int)sizeof(stagedName)) + ret = READ_ONLY_E; + if (ret == 0) + ret = wolfPKCS11_StoreRename(ctx->temp_name, stagedName); + if (ret == 0) { + ctx->has_temp = 0; + if (fputs(ctx->final_name, storeBatch.intent) < 0 || + fputc('\n', storeBatch.intent) == EOF || + fflush(storeBatch.intent) != 0) { + ret = READ_ONLY_E; + } + } + + return ret; +} + +/** + * End a store pass. When committing, the intent becomes the commit marker and + * the staged records are renamed into place; otherwise they are removed. + * Once the marker exists the pass is committed and reported as such, and any + * record left unrenamed is finished by the next store or load. + * Releases the batch locks taken by wolfPKCS11_StoreBatchBegin. + * + * @param [in] commit 1 when every record of the pass was written. + * @return 0 when the pass is committed or there was nothing to commit. + * @return READ_ONLY_E when the pass was not committed. + */ +static int wolfPKCS11_StoreBatchEnd(int commit) +{ + int ret = 0; + + if (storeBatch.active) { + storeBatch.active = 0; + if (XFCLOSE(storeBatch.intent) != 0) + storeBatch.failed = 1; + storeBatch.intent = XBADFILE; + if (storeBatch.failed) + ret = READ_ONLY_E; + if (ret == 0 && commit) { + ret = wolfPKCS11_StoreBatchApply(storeBatch.intentName, + WP11_BATCH_CHECK); + } + if (ret == 0 && commit) { + ret = wolfPKCS11_StoreRename(storeBatch.intentName, + storeBatch.commitName); + } + if (ret == 0 && commit) { + if (wolfPKCS11_StoreBatchApply(storeBatch.commitName, + WP11_BATCH_APPLY) == 0) { + (void)remove(storeBatch.commitName); + } + } + else { + (void)wolfPKCS11_StoreBatchApply(storeBatch.intentName, + WP11_BATCH_DISCARD); + (void)remove(storeBatch.intentName); + } + (void)wolfPKCS11_StoreBatchLockFile(0); + } + if (storeBatch.slot != NULL) { + WP11_Lock_UnlockRW(&storeBatch.slot->storeLock); + storeBatch.slot = NULL; + } + + return ret; +} #endif int wolfPKCS11_Store_Remove(int type, CK_ULONG id1, CK_ULONG id2) @@ -1907,8 +2411,17 @@ void wolfPKCS11_Store_Close(void* store) } if (ctx->is_write && ctx->has_temp) { - commitRet = wolfPKCS11_StoreCommitTemp(ctx); + #ifdef WP11_STORE_BATCH + if (storeBatch.active) + commitRet = wolfPKCS11_StoreStageTemp(ctx); + else + #endif + commitRet = wolfPKCS11_StoreCommitTemp(ctx); if (commitRet != 0) { + #ifdef WP11_STORE_BATCH + if (storeBatch.active) + storeBatch.failed = 1; + #endif wolfPKCS11_StoreAbortTemp(ctx); #ifdef WOLFPKCS11_DEBUG_STORE printf("Store commit failed for %s (ret %d)\n", @@ -2979,6 +3492,8 @@ int WP11_Object_Copy(WP11_Object *src, WP11_Object *dest) OBJ_COPY_DATA(src, dest, keyData); XMEMCPY(dest->iv, src->iv, sizeof(dest->iv)); dest->encoded = src->encoded; + dest->undecoded = src->undecoded; + dest->unbound = src->unbound; #endif dest->objClass = src->objClass; dest->keyGenMech = src->keyGenMech; @@ -3424,11 +3939,14 @@ int WP11_Object_Copy(WP11_Object *src, WP11_Object *dest) * @param [in] keySz Length of AES key in bytes. * @param [in] iv IV/nonce. * @param [in] ivSz Length of IV in bytes. + * @param [in] aad Additional authenticated data. May be NULL. + * @param [in] aadSz Length of additional authenticated data in bytes. * @return 0 on success. * @return -ve on failure. */ static int wp11_EncryptData(byte* out, byte* data, int len, byte* key, - int keySz, byte* iv, int ivSz, int devId) + int keySz, byte* iv, int ivSz, const byte* aad, + word32 aadSz, int devId) { Aes aes; int ret; @@ -3439,7 +3957,7 @@ static int wp11_EncryptData(byte* out, byte* data, int len, byte* key, } if (ret == 0) { ret = wc_AesGcmEncrypt(&aes, out, data, len, iv, ivSz, out + len, - AES_BLOCK_SIZE, NULL, 0); + AES_BLOCK_SIZE, aad, aadSz); } wc_AesFree(&aes); @@ -3458,12 +3976,15 @@ static int wp11_EncryptData(byte* out, byte* data, int len, byte* key, * @param [in] keySz Length of AES key in bytes. * @param [in] iv IV/nonce. * @param [in] ivSz Length of IV in bytes. + * @param [in] aad Additional authenticated data. May be NULL. + * @param [in] aadSz Length of additional authenticated data in bytes. * @return 0 on success. * @return AES_GCM_AUTH_E when encrypted data could not be verified. * @return Other -ve on failure. */ static int wp11_DecryptData(byte* out, byte* data, int len, byte* key, - int keySz, byte* iv, int ivSz, int devId) + int keySz, byte* iv, int ivSz, const byte* aad, + word32 aadSz, int devId) { Aes aes; int ret; @@ -3474,13 +3995,115 @@ static int wp11_DecryptData(byte* out, byte* data, int len, byte* key, } if (ret == 0) { ret = wc_AesGcmDecrypt(&aes, out, data, len, iv, ivSz, data + len, - AES_BLOCK_SIZE, NULL, 0); + AES_BLOCK_SIZE, aad, aadSz); } wc_AesFree(&aes); return ret; } +/* Set by the SO, who never holds the user's key, or by the library itself. */ +#ifdef WOLFPKCS11_TPM +#define WP11_FLAG_UNBOUND (WP11_FLAG_TRUSTED | WP11_FLAG_TPM) +#else +#define WP11_FLAG_UNBOUND WP11_FLAG_TRUSTED +#endif + +#define WP11_POLICY_AAD_VER 1 +#define WP11_POLICY_AAD_SZ (1 + 3 * sizeof(CK_ULONG) + 1 + sizeof(word32)) + +/** + * Serialize the object attributes that govern use of its key material. + * + * @param [in] object Key object. + * @param [out] aad Buffer of WP11_POLICY_AAD_SZ bytes. + */ +static void wp11_Object_PolicyAad(WP11_Object* object, byte* aad) +{ + CK_ULONG vals[3]; + word32 flags = object->opFlag & ~(word32)WP11_FLAG_UNBOUND; + int idx = 0; + int i; + int j; + + vals[0] = object->objClass; + vals[1] = object->type; + vals[2] = object->keyGenMech; + aad[idx++] = WP11_POLICY_AAD_VER; + for (i = 0; i < 3; i++) { + for (j = (int)sizeof(CK_ULONG) - 1; j >= 0; j--) + aad[idx++] = (byte)(vals[i] >> (j * 8)); + } + aad[idx++] = (byte)object->local; + for (j = (int)sizeof(word32) - 1; j >= 0; j--) + aad[idx++] = (byte)(flags >> (j * 8)); +} + +/** + * Encrypt key material with the token key, bound to the object's policy. + * + * @param [in, out] object Key object. + * @param [out] out Buffer for encrypted data and tag. + * @param [in] data Key material. + * @param [in] len Length of key material. + * @return 0 on success. + * @return -ve on failure. + */ +static int wp11_Object_EncryptKeyData(WP11_Object* object, byte* out, + byte* data, int len) +{ + int ret; + byte aad[WP11_POLICY_AAD_SZ]; + + wp11_Object_PolicyAad(object, aad); + ret = wp11_EncryptData(out, data, len, object->slot->token.key, + sizeof(object->slot->token.key), object->iv, + sizeof(object->iv), aad, sizeof(aad), + object->devId); + if (ret == 0) + object->unbound = 0; + + return ret; +} + +/** + * Decrypt key material with the token key, verifying the object's policy. + * Key data stored before the policy was bound is accepted and marked so it is + * bound when next stored. + * + * @param [in, out] object Key object. + * @param [out] out Buffer for decrypted key material. + * @param [in] data Encrypted data and tag. + * @param [in] len Length of encrypted data. + * @return 0 on success. + * @return AES_GCM_AUTH_E when encrypted data could not be verified. + * @return Other -ve on failure. + */ +static int wp11_Object_DecryptKeyData(WP11_Object* object, byte* out, + byte* data, int len) +{ + int ret; + byte aad[WP11_POLICY_AAD_SZ]; + + wp11_Object_PolicyAad(object, aad); + ret = wp11_DecryptData(out, data, len, object->slot->token.key, + sizeof(object->slot->token.key), object->iv, + sizeof(object->iv), aad, sizeof(aad), + object->devId); + if (ret == AES_GCM_AUTH_E) { + ret = wp11_DecryptData(out, data, len, object->slot->token.key, + sizeof(object->slot->token.key), object->iv, + sizeof(object->iv), NULL, 0, object->devId); + if (ret == 0) + object->unbound = 1; + } + + return ret; +} + +static int wp11_Object_IsEncrypted(WP11_Object* object); +static int wp11_Object_Encode(WP11_Object* object, int protect); + /** * Finish encoding key data into a new buffer: on success release the previous * record, on failure drop the new buffer and put the previous record back so @@ -4278,10 +4901,7 @@ static int wp11_Object_Decode_RsaKey(WP11_Object* object) ret = MEMORY_E; } if (ret == 0) { - ret = wp11_DecryptData(der, object->keyData, len, - object->slot->token.key, - sizeof(object->slot->token.key), object->iv, - sizeof(object->iv), object->devId); + ret = wp11_Object_DecryptKeyData(object, der, object->keyData, len); } if (ret == 0) { /* Decode RSA private key. */ @@ -4378,10 +4998,8 @@ static int wp11_Object_Encode_RsaKey(WP11_Object* object) ret = wc_RsaKeyToDer(object->data.rsaKey, object->keyData, object->keyDataLen); if (ret >= 0) { - ret = wp11_EncryptData(object->keyData, object->keyData, ret, - object->slot->token.key, - sizeof(object->slot->token.key), object->iv, - sizeof(object->iv), object->devId); + ret = wp11_Object_EncryptKeyData(object, object->keyData, + object->keyData, ret); } #else ret = NOT_COMPILED_IN; @@ -4634,10 +5252,7 @@ static int wp11_Object_Decode_EccKey(WP11_Object* object) ret = MEMORY_E; } if (ret == 0) { - ret = wp11_DecryptData(der, object->keyData, len, - object->slot->token.key, - sizeof(object->slot->token.key), object->iv, - sizeof(object->iv), object->devId); + ret = wp11_Object_DecryptKeyData(object, der, object->keyData, len); } if (ret == 0) { /* Decode ECC private key. */ @@ -4724,10 +5339,8 @@ static int wp11_Object_Encode_EccKey(WP11_Object* object) ret = wc_EccPrivateKeyToDer(object->data.ecKey, object->keyData, object->keyDataLen); if (ret >= 0) { - ret = wp11_EncryptData(object->keyData, object->keyData, ret, - object->slot->token.key, - sizeof(object->slot->token.key), object->iv, - sizeof(object->iv), object->devId); + ret = wp11_Object_EncryptKeyData(object, object->keyData, + object->keyData, ret); } } else if (ret == 0 && object->objClass == CKO_PUBLIC_KEY) { @@ -4882,10 +5495,7 @@ static int wp11_Object_Decode_MldsaKey(WP11_Object* object) ret = MEMORY_E; } if (ret == 0) { - ret = wp11_DecryptData(der, object->keyData, len, - object->slot->token.key, - sizeof(object->slot->token.key), object->iv, - sizeof(object->iv), object->devId); + ret = wp11_Object_DecryptKeyData(object, der, object->keyData, len); } if (ret == 0) { /* Decode ML-DSA private key. */ @@ -4972,10 +5582,8 @@ static int wp11_Object_Encode_MldsaKey(WP11_Object* object) object->keyData, object->keyDataLen); if (ret >= 0) { - ret = wp11_EncryptData(object->keyData, object->keyData, ret, - object->slot->token.key, - sizeof(object->slot->token.key), object->iv, - sizeof(object->iv), object->devId); + ret = wp11_Object_EncryptKeyData(object, object->keyData, + object->keyData, ret); } } else if (ret == 0 && object->objClass == CKO_PUBLIC_KEY) { @@ -5095,12 +5703,8 @@ static int wp11_Object_Decode_DhKey(WP11_Object* object) else if (object->keyDataLen - AES_BLOCK_SIZE > WP11_MAX_DH_KEY_SZ) ret = BUFFER_E; if (ret == 0) { - ret = wp11_DecryptData(object->data.dhKey->key, object->keyData, - object->keyDataLen - AES_BLOCK_SIZE, - object->slot->token.key, - sizeof(object->slot->token.key), - object->iv, sizeof(object->iv), - object->devId); + ret = wp11_Object_DecryptKeyData(object, object->data.dhKey->key, + object->keyData, object->keyDataLen - AES_BLOCK_SIZE); if (ret != 0) { wc_ForceZero(object->data.dhKey->key, (word32)(object->keyDataLen - AES_BLOCK_SIZE)); @@ -5146,11 +5750,9 @@ static int wp11_Object_Encode_DhKey(WP11_Object* object) if (ret == 0) { if (object->objClass == CKO_PRIVATE_KEY) { - ret = wp11_EncryptData(object->keyData, object->data.dhKey->key, - object->data.dhKey->len, - object->slot->token.key, - sizeof(object->slot->token.key), object->iv, - sizeof(object->iv), object->devId); + ret = wp11_Object_EncryptKeyData(object, object->keyData, + object->data.dhKey->key, + object->data.dhKey->len); if (ret == 0) object->keyDataLen = object->data.dhKey->len + AES_BLOCK_SIZE; } @@ -5475,10 +6077,7 @@ static int wp11_Object_Decode_MlKemKey(WP11_Object* object) ret = MEMORY_E; } if (ret == 0) { - ret = wp11_DecryptData(der, object->keyData, len, - object->slot->token.key, - sizeof(object->slot->token.key), object->iv, - sizeof(object->iv), object->devId); + ret = wp11_Object_DecryptKeyData(object, der, object->keyData, len); } if (ret == 0) { ret = MlKemKeyTryDecode(object->data.mlKemKey, WC_ML_KEM_512, @@ -5559,10 +6158,8 @@ static int wp11_Object_Encode_MlKemKey(WP11_Object* object) ret = wc_MlKemKey_EncodePrivateKey(object->data.mlKemKey, object->keyData, keyLen); if (ret == 0) { - ret = wp11_EncryptData(object->keyData, object->keyData, keyLen, - object->slot->token.key, - sizeof(object->slot->token.key), object->iv, - sizeof(object->iv), object->devId); + ret = wp11_Object_EncryptKeyData(object, object->keyData, + object->keyData, keyLen); } } else if (ret == 0 && object->objClass == CKO_PUBLIC_KEY) { @@ -6481,11 +7078,9 @@ static int wp11_Object_Decode_SymmKey(WP11_Object* object) (word32)(object->keyDataLen - AES_BLOCK_SIZE) > WP11_MAX_SYM_KEY_SZ) ret = BUFFER_E; if (ret == 0) { - ret = wp11_DecryptData(object->data.symmKey->data, object->keyData, - object->keyDataLen - AES_BLOCK_SIZE, - object->slot->token.key, - sizeof(object->slot->token.key), object->iv, - sizeof(object->iv), object->devId); + ret = wp11_Object_DecryptKeyData(object, object->data.symmKey->data, + object->keyData, + object->keyDataLen - AES_BLOCK_SIZE); if (ret != 0) { wc_ForceZero(object->data.symmKey->data, (word32)(object->keyDataLen - AES_BLOCK_SIZE)); @@ -6520,11 +7115,9 @@ static int wp11_Object_Encode_SymmKey(WP11_Object* object) ret = MEMORY_E; if (ret == 0) { - ret = wp11_EncryptData(object->keyData, object->data.symmKey->data, - object->data.symmKey->len, - object->slot->token.key, - sizeof(object->slot->token.key), object->iv, - sizeof(object->iv), object->devId); + ret = wp11_Object_EncryptKeyData(object, object->keyData, + object->data.symmKey->data, + object->data.symmKey->len); } wp11_Object_EncodeDone(object, oldData, oldLen, newLen, ret); @@ -6791,6 +7384,8 @@ static int wp11_Object_Load(WP11_Object* object, int tokenId, int objId) } } } + if (ret == 0 && wp11_Object_IsEncrypted(object)) + object->undecoded = 1; return ret; } @@ -6908,10 +7503,15 @@ static int wp11_Object_Store_Object(WP11_Object* object, int tokenId, int objId) */ static int wp11_Object_Store(WP11_Object* object, int tokenId, int objId) { - int ret; + int ret = 0; + /* Bind decoded key data to the current policy before writing anything, so + * a failure leaves the stored record unchanged. */ + if (object->unbound && !object->encoded && wp11_Object_IsEncrypted(object)) + ret = wp11_Object_Encode(object, 0); /* Open access to key object. */ - ret = wp11_Object_Store_Object(object, tokenId, objId); + if (ret == 0) + ret = wp11_Object_Store_Object(object, tokenId, objId); if (ret == 0) { if (object->objClass == CKO_CERTIFICATE) { @@ -7061,6 +7661,8 @@ static int wp11_Object_Decode(WP11_Object* object) } } + if (ret == 0) + object->undecoded = 0; /* Authentication failure means this object isn't for this user. */ if (ret == AES_GCM_AUTH_E) ret = 0; @@ -7636,9 +8238,16 @@ static int wp11_Token_Load(WP11_Slot* slot, int tokenId, WP11_Token* token) int objCnt = 0; word32 len; + ret = 0; +#ifdef WP11_STORE_BATCH + /* Finish or drop a store pass that a crash interrupted. */ + ret = wolfPKCS11_StoreBatchCheck(tokenId); +#endif /* Open access to token object. */ - ret = wp11_storage_open_readonly(WOLFPKCS11_STORE_TOKEN, tokenId, 0, - &storage); + if (ret == 0) { + ret = wp11_storage_open_readonly(WOLFPKCS11_STORE_TOKEN, tokenId, 0, + &storage); + } if (ret == 0) { /* Read label for token. (32) */ ret = wp11_storage_read_string(storage, token->label, @@ -7793,7 +8402,7 @@ static int wp11_Token_Load(WP11_Slot* slot, int tokenId, WP11_Token* token) * @return BUFFER_E when storing fails. * @return NOT_AVAILABLE_E when unable to write data. */ -static int wp11_Token_Store(WP11_Token* token, int tokenId) +static int wp11_Token_StorePass(WP11_Token* token, int tokenId) { int ret; int i; @@ -7926,6 +8535,34 @@ static int wp11_Token_Store(WP11_Token* token, int tokenId) return ret; } + +/** + * Store the token and its objects as one pass that commits as a unit. + * + * @param [in] token Token object. + * @param [in] tokenId Id of token. + * @return 0 on success. + * @return Other value when the pass did not commit. + */ +static int wp11_Token_Store(WP11_Token* token, int tokenId) +{ + int ret; +#ifdef WP11_STORE_BATCH + int endRet; + + ret = wolfPKCS11_StoreBatchBegin(tokenId); + if (ret == 0) { + ret = wp11_Token_StorePass(token, tokenId); + endRet = wolfPKCS11_StoreBatchEnd(ret == 0); + if (ret == 0) + ret = endRet; + } +#else + ret = wp11_Token_StorePass(token, tokenId); +#endif + + return ret; +} #endif /* !WOLFPKCS11_NO_STORE */ /** @@ -8045,6 +8682,9 @@ static void wp11_Slot_Final(WP11_Slot* slot) wp11_Token_Final(&slot->token); #ifdef WOLFPKCS11_TPM wp11_TpmFinal(slot); +#endif +#ifndef WOLFPKCS11_NO_STORE + WP11_Lock_Free(&slot->storeLock); #endif WP11_Lock_Free(&slot->callLock); WP11_Lock_Free(&slot->lock); @@ -8080,6 +8720,15 @@ static int wp11_Slot_Init(WP11_Slot* slot, int id) WP11_Lock_Free(&slot->lock); } } +#ifndef WOLFPKCS11_NO_STORE + if (ret == 0) { + ret = WP11_Lock_Init(&slot->storeLock); + if (ret != 0) { + WP11_Lock_Free(&slot->callLock); + WP11_Lock_Free(&slot->lock); + } + } +#endif if (ret == 0) { #if defined(WOLFPKCS11_TPM) ret = wp11_TpmInit(slot); @@ -8647,6 +9296,9 @@ int WP11_Slot_TokenReset(WP11_Slot* slot, char* pin, int pinLen, char* label) { int ret; WP11_Token* token; +#ifdef WP11_STORE_BATCH + char emptyLabel[LABEL_SZ]; +#endif WP11_Lock_LockRW(&slot->lock); /* Zeroizes token. */ @@ -8661,8 +9313,25 @@ int WP11_Slot_TokenReset(WP11_Slot* slot, char* pin, int pinLen, char* label) WP11_Lock_UnlockRW(&slot->lock); /* Locking used in setting SO PIN. */ - if (ret == 0) + if (ret == 0) { ret = WP11_Slot_SetSOPin(slot, pin, pinLen); +#ifdef WP11_STORE_BATCH + /* Only a staged store pass is known to leave storage unchanged. */ + if (ret != 0) { + /* Reset not stored: restore the token that is still in storage. */ + XMEMSET(emptyLabel, 0, sizeof(emptyLabel)); + WP11_Lock_LockRW(&slot->lock); + wp11_Token_Final(token); + if (wp11_Token_Init(token, emptyLabel) == 0) { + WP11_Lock_UnlockRW(&slot->lock); + (void)wp11_Slot_Load(slot, (int)slot->id); + } + else { + WP11_Lock_UnlockRW(&slot->lock); + } + } +#endif + } return ret; } @@ -9417,9 +10086,21 @@ int WP11_Slot_SetSOPin(WP11_Slot* slot, char* pin, int pinLen) { int ret = 0; WP11_Token* token; +#ifdef WP11_STORE_BATCH + byte oldPin[PIN_HASH_SZ]; + byte oldPinSeed[PIN_SEED_SZ]; + int oldPinLen; + int oldFlags; +#endif WP11_Lock_LockRW(&slot->lock); token = &slot->token; +#ifdef WP11_STORE_BATCH + XMEMCPY(oldPin, token->soPin, sizeof(oldPin)); + XMEMCPY(oldPinSeed, token->soPinSeed, sizeof(oldPinSeed)); + oldPinLen = token->soPinLen; + oldFlags = token->tokenFlags; +#endif /* New seed each time. */ WP11_Lock_LockRW(&slot->token.rngLock); ret = wc_RNG_GenerateBlock(&slot->token.rng, token->soPinSeed, @@ -9438,9 +10119,22 @@ int WP11_Slot_SetSOPin(WP11_Slot* slot, char* pin, int pinLen) token->tokenFlags |= WP11_TOKEN_FLAG_SO_PIN_SET; #ifndef WOLFPKCS11_NO_STORE ret = wp11_Token_Store(token, (int)slot->id); + #ifdef WP11_STORE_BATCH + if (ret != 0 && (oldFlags & WP11_TOKEN_FLAG_SO_PIN_SET)) { + /* Keep the PIN that is still in storage in effect. */ + XMEMCPY(token->soPin, oldPin, sizeof(oldPin)); + XMEMCPY(token->soPinSeed, oldPinSeed, sizeof(oldPinSeed)); + token->soPinLen = oldPinLen; + token->tokenFlags = oldFlags; + } + #endif #endif } WP11_Lock_UnlockRW(&slot->lock); +#ifdef WP11_STORE_BATCH + wc_ForceZero(oldPin, sizeof(oldPin)); + wc_ForceZero(oldPinSeed, sizeof(oldPinSeed)); +#endif return ret; } @@ -9465,9 +10159,29 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) byte seed[PIN_SEED_SZ]; byte key[AES_256_KEY_SIZE]; #endif +#ifdef WP11_STORE_BATCH + byte oldPin[PIN_HASH_SZ]; + byte oldPinSeed[PIN_SEED_SZ]; + byte oldSeed[PIN_SEED_SZ]; + byte oldKey[AES_256_KEY_SIZE]; + int oldPinLen; + int oldFlags; + byte oldPinEmpty; + WP11_Object* object; + int rebind = 0; +#endif WP11_Lock_LockRW(&slot->lock); token = &slot->token; +#ifdef WP11_STORE_BATCH + XMEMCPY(oldPin, token->userPin, sizeof(oldPin)); + XMEMCPY(oldPinSeed, token->userPinSeed, sizeof(oldPinSeed)); + XMEMCPY(oldSeed, token->seed, sizeof(oldSeed)); + XMEMCPY(oldKey, token->key, sizeof(oldKey)); + oldPinLen = token->userPinLen; + oldFlags = token->tokenFlags; + oldPinEmpty = token->userPinEmpty; +#endif /* New seed each time. */ WP11_Lock_LockRW(&slot->token.rngLock); ret = wc_RNG_GenerateBlock(&slot->token.rng, pinSeed, sizeof(pinSeed)); @@ -9502,7 +10216,38 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) token->userPinLen = sizeof(token->userPin); token->tokenFlags |= WP11_TOKEN_FLAG_USER_PIN_SET; #ifndef WOLFPKCS11_NO_STORE + #ifdef WP11_STORE_BATCH + WP11_Lock_LockRO(&token->lock); + for (object = token->object; object != NULL && !rebind; + object = object->next) { + rebind = object->unbound && !object->encoded && + wp11_Object_IsEncrypted(object); + } + WP11_Lock_UnlockRO(&token->lock); + #endif ret = wp11_Token_Store(token, (int)slot->id); + #ifdef WP11_STORE_BATCH + if (ret != 0) { + /* Keep the PIN and key that are still in storage in effect. */ + XMEMCPY(token->userPin, oldPin, sizeof(oldPin)); + XMEMCPY(token->userPinSeed, oldPinSeed, sizeof(oldPinSeed)); + XMEMCPY(token->seed, oldSeed, sizeof(oldSeed)); + XMEMCPY(token->key, oldKey, sizeof(oldKey)); + token->userPinLen = oldPinLen; + token->tokenFlags = oldFlags; + token->userPinEmpty = oldPinEmpty; + /* The store may have rebound objects under the discarded key. */ + WP11_Lock_LockRW(&token->lock); + for (object = token->object; rebind && object != NULL; + object = object->next) { + if (wp11_Object_IsEncrypted(object) && !object->encoded && + wp11_Object_Encode(object, 0) != 0) { + WOLFPKCS11_MSG("SetUserPin: failed to re-encrypt object"); + } + } + WP11_Lock_UnlockRW(&token->lock); + } + #endif #endif } WP11_Lock_UnlockRW(&slot->lock); @@ -9510,6 +10255,12 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) #ifndef WOLFPKCS11_NO_STORE wc_ForceZero(key, sizeof(key)); #endif +#ifdef WP11_STORE_BATCH + wc_ForceZero(oldPin, sizeof(oldPin)); + wc_ForceZero(oldPinSeed, sizeof(oldPinSeed)); + wc_ForceZero(oldSeed, sizeof(oldSeed)); + wc_ForceZero(oldKey, sizeof(oldKey)); +#endif return ret; } @@ -14034,6 +14785,40 @@ static int WP11_Object_SetEndDate(WP11_Object* object, char* endDate, int len) return ret; } +#ifndef WOLFPKCS11_NO_STORE +/** + * Track a change to a token object's policy so its key data is bound to the + * new policy when next stored. The change is undone when the key data is not + * decoded, as it could not be bound. Caller holds the object's lock when it + * is on a token. + * + * @param object [in] Object object. + * @param prevClass [in] Object class before the change. + * @param prevFlag [in] Operational flags before the change. + * @return BAD_STATE_E when the key data is not decoded. + * 0 on success. + */ +static int wp11_Object_PolicyChanged(WP11_Object* object, + CK_OBJECT_CLASS prevClass, word32 prevFlag) +{ + int ret = 0; + + if (object->onToken && wp11_Object_IsEncrypted(object) && + (object->objClass != prevClass || + ((object->opFlag ^ prevFlag) & ~(word32)WP11_FLAG_UNBOUND) != 0)) { + if (object->encoded || object->undecoded) { + object->objClass = prevClass; + object->opFlag = prevFlag; + ret = BAD_STATE_E; + } + else { + object->unbound = 1; + } + } + + return ret; +} +#endif /** * Set an attribute against the object. @@ -14044,15 +14829,24 @@ static int WP11_Object_SetEndDate(WP11_Object* object, char* endDate, int len) * @param len [in] Length of attribute data in bytes. * @return BUFFER_E when data is too small. * BAD_FUNC_ARG when type is not supported with object. + * BAD_STATE_E when a policy change cannot be bound to the stored key. * 0 on success. */ int WP11_Object_SetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, CK_ULONG len) { int ret = 0; +#ifndef WOLFPKCS11_NO_STORE + CK_OBJECT_CLASS prevClass; + word32 prevFlag; +#endif if (object->onToken) WP11_Lock_LockRW(object->lock); +#ifndef WOLFPKCS11_NO_STORE + prevClass = object->objClass; + prevFlag = object->opFlag; +#endif switch (type) { case CKA_CLASS: @@ -14427,6 +15221,10 @@ int WP11_Object_SetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, ret = BAD_FUNC_ARG; break; } +#ifndef WOLFPKCS11_NO_STORE + if (ret == 0) + ret = wp11_Object_PolicyChanged(object, prevClass, prevFlag); +#endif if (object->onToken) WP11_Lock_UnlockRW(object->lock); diff --git a/tests/include.am b/tests/include.am index 39172fc5..0a7581e7 100644 --- a/tests/include.am +++ b/tests/include.am @@ -320,6 +320,12 @@ noinst_PROGRAMS += tests/object_store_decode_test tests_object_store_decode_test_SOURCES = tests/object_store_decode_test.c tests_object_store_decode_test_LDADD = +check_PROGRAMS += tests/object_policy_integrity_test +noinst_PROGRAMS += tests/object_policy_integrity_test +tests_object_policy_integrity_test_SOURCES = \ + tests/object_policy_integrity_test.c +tests_object_policy_integrity_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -385,6 +391,7 @@ tests_nss_configdir_prefix_test_LDADD += src/libwolfpkcs11.la tests_logout_retire_test_LDADD += src/libwolfpkcs11.la tests_login_pin_state_test_LDADD += src/libwolfpkcs11.la tests_object_store_decode_test_LDADD += src/libwolfpkcs11.la +tests_object_policy_integrity_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -442,6 +449,7 @@ tests_nss_configdir_prefix_test_LDADD += src/libwolfpkcs11.la tests_logout_retire_test_LDADD += src/libwolfpkcs11.la tests_login_pin_state_test_LDADD += src/libwolfpkcs11.la tests_object_store_decode_test_LDADD += src/libwolfpkcs11.la +tests_object_policy_integrity_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ diff --git a/tests/object_policy_integrity_test.c b/tests/object_policy_integrity_test.c new file mode 100644 index 00000000..afda742f --- /dev/null +++ b/tests/object_policy_integrity_test.c @@ -0,0 +1,1538 @@ +/* object_policy_integrity_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * The policy attributes of a protected token key are only honored when they + * match the policy the key material was stored under. Legitimate attribute + * updates, PIN changes and stores written before the policy was bound must + * keep working. Each reload runs in a child process. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef HAVE_PKCS11_STATIC + #include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#if !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_TPM_STORE) && \ + !defined(WOLFPKCS11_CUSTOM_STORE) && !defined(WOLFPKCS11_NO_ENV) && \ + !defined(_WIN32) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(HAVE_AES_CBC) + +#include +#include +#include +#include + +#define TEST_DIR "./store/object_policy_integrity_test" +#define MAX_OBJ_ID 8 +#define MAX_FILE_SZ 16384 + +/* Offsets of the class and operational flags in a stored object record. */ +#define OBJ_CLASS_OFF (12 + sizeof(CK_ULONG)) +#define OBJ_FLAGS_OFF (12 + 3 * sizeof(CK_ULONG) + 1 + 1 + 4) +#define OBJ_IV_SZ 12 +#define TAG_SZ 16 +#define BLOCK_SZ 16 + +#define CHILD_PASS 0 +#define CHILD_FAIL 1 +#define CHILD_SETUP 2 + +static byte soPin[] = "password123456"; +static byte userPin[] = "wolfpkcs11-test"; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; + +static const char* storeKinds[] = { + "obj", "data", "symmkey", "rsakey_priv", "rsakey_pub", "ecckey_priv", + "ecckey_pub", "dhkey_priv", "dhkey_pub", "cert", "trust", "mldsakey_priv", + "mldsakey_pub", "mlkemkey_priv", "mlkemkey_pub" +}; + +static byte aesKeyValue[32] = { + 0x2f, 0x91, 0x0c, 0x6a, 0xd3, 0x48, 0x75, 0xbe, + 0x13, 0xe0, 0x5c, 0x87, 0x29, 0xfa, 0x64, 0x0b, + 0xc8, 0x3d, 0x9e, 0x51, 0x06, 0xb7, 0x7a, 0x22, + 0xe4, 0x1f, 0x98, 0x6d, 0x30, 0xab, 0x55, 0xc1 +}; +static byte refPlain[BLOCK_SZ] = { + 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2d, 0x72, + 0x65, 0x66, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b +}; +static byte refCipher[BLOCK_SZ]; + +/* Parameters of the current check, read by the child process. */ +static CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; +static CK_ATTRIBUTE_TYPE secretAttr = CKA_VALUE; +static const byte* secretNeedle = NULL; +static size_t secretNeedleSz = 0; +static int checkAesUse = 0; +static CK_MECHANISM_TYPE signMech = CKM_VENDOR_DEFINED; +static CK_BBOOL expSensitive = CK_TRUE; +static CK_BBOOL expExtractable = CK_FALSE; +static CK_BBOOL expDecrypt = CK_TRUE; + +static void store_path(char* path, size_t sz, const char* kind, int objId) +{ + if (objId < 0) { + (void)snprintf(path, sz, "%s/wp11_%s_%016lx", TEST_DIR, kind, 1UL); + } + else { + (void)snprintf(path, sz, "%s/wp11_%s_%016lx_%016lx", TEST_DIR, kind, + 1UL, (unsigned long)objId); + } +} + +static void cleanup_test_files(void) +{ + char path[256]; + size_t k; + int i; + + for (k = 0; k < sizeof(storeKinds) / sizeof(*storeKinds); k++) { + for (i = 0; i < MAX_OBJ_ID; i++) { + store_path(path, sizeof(path), storeKinds[k], i); + (void)remove(path); + } + } + store_path(path, sizeof(path), "token", -1); + (void)remove(path); +} + +/* Locate the stored file of the given kind for its single object. */ +static int find_store_file(const char* kind, char* path, size_t sz) +{ + FILE* f; + int i; + + for (i = 0; i < MAX_OBJ_ID; i++) { + store_path(path, sz, kind, i); + f = fopen(path, "rb"); + if (f != NULL) { + fclose(f); + return 0; + } + } + return -1; +} + +static int read_file(const char* path, byte* data, size_t sz, size_t* readSz) +{ + FILE* f = fopen(path, "rb"); + + if (f == NULL) + return -1; + *readSz = fread(data, 1, sz, f); + if (ferror(f) || !feof(f)) { + fclose(f); + return -1; + } + fclose(f); + return 0; +} + +static int write_file(const char* path, const byte* data, size_t sz) +{ + FILE* f = fopen(path, "wb"); + size_t written; + + if (f == NULL) + return -1; + written = fwrite(data, 1, sz, f); + if (fclose(f) != 0 || written != sz) + return -1; + return 0; +} + +static word32 get_be32(const byte* p) +{ + return ((word32)p[0] << 24) | ((word32)p[1] << 16) | + ((word32)p[2] << 8) | (word32)p[3]; +} + +static void put_be32(byte* p, word32 v) +{ + p[0] = (byte)(v >> 24); + p[1] = (byte)(v >> 16); + p[2] = (byte)(v >> 8); + p[3] = (byte)v; +} + +static CK_ULONG get_be_ulong(const byte* p) +{ + CK_ULONG v = 0; + size_t i; + + for (i = 0; i < sizeof(CK_ULONG); i++) + v = (v << 8) | p[i]; + return v; +} + +/* Rewrite the stored flags of the object of the checked class so they read as + * exportable. */ +static int mark_stored_key_exportable(void) +{ + static byte data[MAX_FILE_SZ]; + char path[256]; + size_t sz = 0; + word32 flags; + int i; + int found = 0; + + for (i = 0; i < MAX_OBJ_ID && !found; i++) { + store_path(path, sizeof(path), "obj", i); + if (read_file(path, data, sizeof(data), &sz) == 0 && + sz >= OBJ_FLAGS_OFF + 4 && + get_be_ulong(data + OBJ_CLASS_OFF) == keyClass) { + found = 1; + } + } + if (!found) + return -1; + flags = get_be32(data + OBJ_FLAGS_OFF); + if ((flags & WP11_FLAG_SENSITIVE) == 0 || + (flags & WP11_FLAG_EXTRACTABLE) != 0) + return -1; + flags &= ~(word32)WP11_FLAG_SENSITIVE; + flags |= WP11_FLAG_EXTRACTABLE; + put_be32(data + OBJ_FLAGS_OFF, flags); + return write_file(path, data, sz); +} + +static CK_RV init_slot(CK_SLOT_ID* slot) +{ + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slots[16]; + CK_ULONG count = sizeof(slots) / sizeof(*slots); + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + if (rv == CKR_OK) + rv = funcList->C_GetSlotList(CK_TRUE, slots, &count); + if (rv == CKR_OK && count == 0) + rv = CKR_TOKEN_NOT_PRESENT; + if (rv == CKR_OK) + *slot = slots[0]; + return rv; +} + +static CK_RV provision_token(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_UTF8CHAR label[32]; + CK_FLAGS flags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, "wolfpkcs11", 10); + rv = funcList->C_InitToken(slot, soPin, sizeof(soPin) - 1, label); + if (rv == CKR_OK) + rv = funcList->C_OpenSession(slot, flags, NULL, NULL, &session); + if (rv == CKR_OK) + rv = funcList->C_Login(session, CKU_SO, soPin, sizeof(soPin) - 1); + if (rv == CKR_OK) + rv = funcList->C_InitPIN(session, userPin, sizeof(userPin) - 1); + if (session != CK_INVALID_HANDLE) { + (void)funcList->C_Logout(session); + (void)funcList->C_CloseSession(session); + } + return rv; +} + +static CK_RV open_session(CK_SLOT_ID slot, int login, + CK_SESSION_HANDLE* session) +{ + CK_RV rv; + CK_FLAGS flags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + rv = funcList->C_OpenSession(slot, flags, NULL, NULL, session); + if (rv == CKR_OK && login) { + rv = funcList->C_Login(*session, CKU_USER, userPin, + (CK_ULONG)sizeof(userPin) - 1); + } + return rv; +} + +static CK_RV find_key(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* obj) +{ + CK_RV rv; + CK_ULONG count = 0; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + }; + + rv = funcList->C_FindObjectsInit(session, tmpl, 1); + if (rv == CKR_OK) { + rv = funcList->C_FindObjects(session, obj, 1, &count); + (void)funcList->C_FindObjectsFinal(session); + } + if (rv == CKR_OK && count != 1) + rv = CKR_OBJECT_HANDLE_INVALID; + return rv; +} + +static CK_RV aes_encrypt_block(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + byte* out) +{ + CK_RV rv; + byte iv[BLOCK_SZ]; + CK_MECHANISM mech; + CK_ULONG outSz = BLOCK_SZ; + + XMEMSET(iv, 0, sizeof(iv)); + mech.mechanism = CKM_AES_CBC; + mech.pParameter = iv; + mech.ulParameterLen = sizeof(iv); + rv = funcList->C_EncryptInit(session, &mech, obj); + if (rv == CKR_OK) + rv = funcList->C_Encrypt(session, refPlain, sizeof(refPlain), out, + &outSz); + if (rv == CKR_OK && outSz != BLOCK_SZ) + rv = CKR_FUNCTION_FAILED; + return rv; +} + +/* Sign a fixed digest-sized message with the key. */ +static CK_RV sign_block(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj) +{ + CK_RV rv; + CK_MECHANISM mech; + byte msg[32]; + static byte sig[8192]; + CK_ULONG sigSz = sizeof(sig); + + XMEMSET(msg, 0x5a, sizeof(msg)); + mech.mechanism = signMech; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + rv = funcList->C_SignInit(session, &mech, obj); + if (rv == CKR_OK) + rv = funcList->C_Sign(session, msg, sizeof(msg), sig, &sigSz); + return rv; +} + +static CK_RV get_bool(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + CK_ATTRIBUTE_TYPE type, CK_BBOOL* val) +{ + CK_ATTRIBUTE attr; + + attr.type = type; + attr.pValue = val; + attr.ulValueLen = sizeof(*val); + return funcList->C_GetAttributeValue(session, obj, &attr, 1); +} + +typedef CK_RV (*create_fn)(CK_SESSION_HANDLE session); + +/* Provision a fresh token, create objects with 'create' and persist them. */ +static CK_RV store_objects(create_fn create) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + + cleanup_test_files(); + rv = pkcs11_load(); + if (rv != CKR_OK) + return rv; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = provision_token(slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) + rv = create(session); + if (session != CK_INVALID_HANDLE) + (void)funcList->C_CloseSession(session); + (void)funcList->C_Finalize(NULL); + pkcs11_unload(); + return rv; +} + +typedef int (*child_fn)(void); + +/* Run fn in a child process. Returns CHILD_PASS only when it exits normally + * reporting that the invariant held. */ +static int run_in_child(child_fn fn) +{ + pid_t pid; + int status = 0; + + fflush(stdout); + fflush(stderr); + pid = fork(); + if (pid < 0) + return CHILD_SETUP; + if (pid == 0) { + int res = fn(); + fflush(stdout); + fflush(stderr); + _exit(res); + } + if (waitpid(pid, &status, 0) != pid) + return CHILD_SETUP; + if (WIFSIGNALED(status)) { + fprintf(stderr, " reload terminated by signal %d\n", + WTERMSIG(status)); + return CHILD_FAIL; + } + if (!WIFEXITED(status)) + return CHILD_FAIL; + return WEXITSTATUS(status); +} + +/* After logging in, the key signs. */ +static int child_key_signs(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv == CKR_OK) + rv = sign_block(session, obj); + (void)funcList->C_Finalize(NULL); + if (rv != CKR_OK) { + fprintf(stderr, " signing after reload failed: 0x%lx\n", + (unsigned long)rv); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +static int holds_secret(const byte* buf, size_t sz) +{ + size_t i; + + if (secretNeedle == NULL || sz < secretNeedleSz) + return 0; + for (i = 0; i + secretNeedleSz <= sz; i++) { + if (XMEMCMP(buf + i, secretNeedle, secretNeedleSz) == 0) + return 1; + } + return 0; +} + +static CK_RV create_aes_key_ex(CK_SESSION_HANDLE session, CK_BBOOL priv, + CK_BBOOL sensitive, CK_BBOOL extractable) +{ + CK_RV rv; + CK_OBJECT_CLASS cls = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_AES; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &cls, sizeof(cls) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &priv, sizeof(priv) }, + { CKA_SENSITIVE, &sensitive, sizeof(sensitive) }, + { CKA_EXTRACTABLE, &extractable, sizeof(extractable) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aesKeyValue, sizeof(aesKeyValue) }, + }; + + rv = funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); + if (rv == CKR_OK) + rv = aes_encrypt_block(session, obj, refCipher); + return rv; +} + +static CK_RV create_aes_key(CK_SESSION_HANDLE session) +{ + return create_aes_key_ex(session, CK_TRUE, CK_TRUE, CK_FALSE); +} + +#ifdef HAVE_ECC +static CK_RV create_ecc_private_key(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS cls = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_EC; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &cls, sizeof(cls) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); +} +#endif + +#ifndef NO_RSA +static CK_RV create_rsa_private_key(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS cls = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_RSA; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &cls, sizeof(cls) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_SIGN, &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_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) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); +} +#endif + +#ifndef NO_DH +static CK_RV create_dh_private_key(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS cls = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_DH; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &cls, sizeof(cls) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + { 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) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); +} +#endif + +#ifdef WOLFPKCS11_MLDSA +static CK_RV create_mldsa_key_pair(CK_SESSION_HANDLE session) +{ + CK_MECHANISM mech = { CKM_ML_DSA_KEY_PAIR_GEN, NULL, 0 }; + CK_ULONG paramSet = CKP_ML_DSA_44; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + }; + + return funcList->C_GenerateKeyPair(session, &mech, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &pub, &priv); +} +#endif + +#if defined(WOLFPKCS11_MLDSA) || defined(WOLFPKCS11_MLKEM) +static byte pqValue[8192]; +static CK_ULONG pqValueSz = 0; +static CK_MECHANISM_TYPE pqGenMech = CKM_VENDOR_DEFINED; +static CK_ULONG pqParamSet = 0; + +/* Generate an exportable post-quantum key pair and record the private value. */ +static CK_RV create_pq_exportable_key_pair(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_MECHANISM mech; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_PARAMETER_SET, &pqParamSet, sizeof(pqParamSet) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + }; + CK_ATTRIBUTE attr; + + mech.mechanism = pqGenMech; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + rv = funcList->C_GenerateKeyPair(session, &mech, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &pub, &priv); + if (rv == CKR_OK) { + attr.type = CKA_VALUE; + attr.pValue = pqValue; + attr.ulValueLen = sizeof(pqValue); + rv = funcList->C_GetAttributeValue(session, priv, &attr, 1); + pqValueSz = attr.ulValueLen; + } + if (rv == CKR_OK && (pqValueSz == 0 || pqValueSz > sizeof(pqValue))) + rv = CKR_FUNCTION_FAILED; + return rv; +} + +/* After logging in, the private key value reads back unchanged. */ +static int child_pq_value_kept(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + static byte buf[sizeof(pqValue)]; + CK_ATTRIBUTE attr; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv == CKR_OK) { + attr.type = CKA_VALUE; + attr.pValue = buf; + attr.ulValueLen = sizeof(buf); + rv = funcList->C_GetAttributeValue(session, obj, &attr, 1); + } + (void)funcList->C_Finalize(NULL); + if (rv != CKR_OK || attr.ulValueLen != pqValueSz || + XMEMCMP(buf, pqValue, pqValueSz) != 0) { + fprintf(stderr, " private value not kept: 0x%lx\n", + (unsigned long)rv); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +static void check_pq_round_trip(const char* name, CK_MECHANISM_TYPE genMech, + CK_ULONG paramSet) +{ + CK_RV rv; + char msg[160]; + int res = CHILD_SETUP; + + keyClass = CKO_PRIVATE_KEY; + pqGenMech = genMech; + pqParamSet = paramSet; + rv = store_objects(create_pq_exportable_key_pair); + (void)snprintf(msg, sizeof(msg), "store %s", name); + CHECK_RV(rv, msg, CKR_OK); + if (rv == CKR_OK) + res = run_in_child(child_pq_value_kept); + (void)snprintf(msg, sizeof(msg), "%s private value kept after reload", + name); + CHECK_TRUE(res == CHILD_PASS, msg); + cleanup_test_files(); +} + +static void test_pq_keys_round_trip(void) +{ + printf("\n--- post-quantum private keys round trip ---\n"); +#ifdef WOLFPKCS11_MLDSA + check_pq_round_trip("ML-DSA key pair", CKM_ML_DSA_KEY_PAIR_GEN, + CKP_ML_DSA_44); +#endif +#ifdef WOLFPKCS11_MLKEM + check_pq_round_trip("ML-KEM key pair", CKM_ML_KEM_KEY_PAIR_GEN, + CKP_ML_KEM_768); +#endif +} +#endif + +/* After logging in, the key must not be exported and, for an AES key, must not + * work as the original key. */ +static int child_key_not_exported(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + static byte buf[4096]; + byte out[BLOCK_SZ]; + CK_BBOOL derive = CK_FALSE; + CK_ATTRIBUTE attr; + int res = CHILD_PASS; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv != CKR_OK) { + fprintf(stderr, " login failed: 0x%lx\n", (unsigned long)rv); + res = CHILD_FAIL; + } + if (res == CHILD_PASS && find_key(session, &obj) == CKR_OK) { + attr.type = secretAttr; + attr.pValue = buf; + attr.ulValueLen = sizeof(buf); + rv = funcList->C_GetAttributeValue(session, obj, &attr, 1); + if (rv == CKR_OK && (holds_secret(buf, attr.ulValueLen) || + (secretNeedle == NULL && attr.ulValueLen > 0))) { + fprintf(stderr, " key value was returned\n"); + res = CHILD_FAIL; + } + if (checkAesUse && aes_encrypt_block(session, obj, out) == CKR_OK && + XMEMCMP(out, refCipher, sizeof(out)) == 0) { + fprintf(stderr, " key still operates as the original key\n"); + res = CHILD_FAIL; + } + if (signMech != CKM_VENDOR_DEFINED && + sign_block(session, obj) == CKR_OK) { + fprintf(stderr, " key still signs\n"); + res = CHILD_FAIL; + } + if (get_bool(session, obj, CKA_DERIVE, &derive) == CKR_OK) { + derive = (derive == CK_TRUE) ? CK_FALSE : CK_TRUE; + attr.type = CKA_DERIVE; + attr.pValue = &derive; + attr.ulValueLen = sizeof(derive); + rv = funcList->C_SetAttributeValue(session, obj, &attr, 1); + if (rv == CKR_OK || rv == CKR_USER_NOT_LOGGED_IN) { + fprintf(stderr, " policy update returned 0x%lx\n", + (unsigned long)rv); + res = CHILD_FAIL; + } + } + } + (void)funcList->C_Finalize(NULL); + return res; +} + +/* After logging in, the AES key works as the original key and reports the + * expected policy. */ +static int child_key_intact(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + byte out[BLOCK_SZ]; + byte value[sizeof(aesKeyValue)]; + CK_BBOOL sensitive = CK_FALSE; + CK_BBOOL extractable = CK_TRUE; + CK_BBOOL decrypt = CK_FALSE; + CK_ATTRIBUTE attr; + int res = CHILD_PASS; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv == CKR_OK) + rv = aes_encrypt_block(session, obj, out); + if (rv == CKR_OK && XMEMCMP(out, refCipher, sizeof(out)) != 0) + rv = CKR_FUNCTION_FAILED; + if (rv == CKR_OK) + rv = get_bool(session, obj, CKA_SENSITIVE, &sensitive); + if (rv == CKR_OK) + rv = get_bool(session, obj, CKA_EXTRACTABLE, &extractable); + if (rv == CKR_OK) + rv = get_bool(session, obj, CKA_DECRYPT, &decrypt); + if (rv != CKR_OK) { + fprintf(stderr, " key unusable after reload: 0x%lx\n", + (unsigned long)rv); + res = CHILD_FAIL; + } + else if (sensitive != expSensitive || extractable != expExtractable || + decrypt != expDecrypt) { + fprintf(stderr, " policy not kept: sensitive %d extractable %d " + "decrypt %d\n", sensitive, extractable, decrypt); + res = CHILD_FAIL; + } + else if (sensitive == CK_TRUE) { + attr.type = CKA_VALUE; + attr.pValue = value; + attr.ulValueLen = sizeof(value); + if (funcList->C_GetAttributeValue(session, obj, &attr, 1) == CKR_OK) { + fprintf(stderr, " sensitive key value was returned\n"); + res = CHILD_FAIL; + } + } + (void)funcList->C_Finalize(NULL); + return res; +} + +static void set_aes_check(void) +{ + keyClass = CKO_SECRET_KEY; + secretAttr = CKA_VALUE; + secretNeedle = aesKeyValue + sizeof(aesKeyValue) - 16; + secretNeedleSz = 16; + checkAesUse = 1; + expSensitive = CK_TRUE; + expExtractable = CK_FALSE; + expDecrypt = CK_TRUE; +} + +static void check_stored_policy(const char* name, create_fn create, + CK_OBJECT_CLASS cls, CK_ATTRIBUTE_TYPE type, + const byte* secret, size_t secretSz, int aes, + CK_MECHANISM_TYPE sign) +{ + CK_RV rv; + char msg[160]; + int res = CHILD_SETUP; + + rv = store_objects(create); + (void)snprintf(msg, sizeof(msg), "store %s", name); + CHECK_RV(rv, msg, CKR_OK); + if (rv != CKR_OK) + return; + keyClass = cls; + secretAttr = type; + secretNeedle = NULL; + secretNeedleSz = 0; + if (secret != NULL && secretSz >= 16) { + secretNeedle = secret + secretSz - 16; + secretNeedleSz = 16; + } + checkAesUse = aes; + signMech = sign; + if (sign != CKM_VENDOR_DEFINED) { + res = run_in_child(child_key_signs); + (void)snprintf(msg, sizeof(msg), "%s signs after reload", name); + CHECK_TRUE(res == CHILD_PASS, msg); + res = CHILD_SETUP; + } + if (mark_stored_key_exportable() == 0) + res = run_in_child(child_key_not_exported); + signMech = CKM_VENDOR_DEFINED; + (void)snprintf(msg, sizeof(msg), + "%s keeps the policy it was stored under", name); + CHECK_TRUE(res == CHILD_PASS, msg); + cleanup_test_files(); +} + +static void test_stored_policy_is_authenticated(void) +{ + printf("\n--- stored key policy is authenticated ---\n"); + check_stored_policy("AES key", create_aes_key, CKO_SECRET_KEY, CKA_VALUE, + aesKeyValue, sizeof(aesKeyValue), 1, CKM_VENDOR_DEFINED); +#ifdef HAVE_ECC + check_stored_policy("ECC private key", create_ecc_private_key, + CKO_PRIVATE_KEY, CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv), 0, + CKM_ECDSA); +#endif +#ifndef NO_RSA + check_stored_policy("RSA private key", create_rsa_private_key, + CKO_PRIVATE_KEY, CKA_PRIVATE_EXPONENT, rsa_2048_priv_exp, + sizeof(rsa_2048_priv_exp), 0, CKM_RSA_PKCS); +#endif +#ifndef NO_DH + check_stored_policy("DH private key", create_dh_private_key, + CKO_PRIVATE_KEY, CKA_VALUE, dh_2048_priv, sizeof(dh_2048_priv), 0, + CKM_VENDOR_DEFINED); +#endif +#ifdef WOLFPKCS11_MLDSA + check_stored_policy("ML-DSA private key", create_mldsa_key_pair, + CKO_PRIVATE_KEY, CKA_VALUE, NULL, 0, 0, CKM_ML_DSA); +#endif +} + +/* Tighten the policy of an exportable key while logged in. */ +static CK_RV create_and_restrict_aes_key(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_DECRYPT, &ckFalse, sizeof(ckFalse) }, + }; + + rv = create_aes_key_ex(session, CK_TRUE, CK_FALSE, CK_TRUE); + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, obj, tmpl, + sizeof(tmpl) / sizeof(*tmpl)); + } + return rv; +} + +/* Restrict the key while logged in, then persist the token by adding another + * object and stop without finalizing. */ +static int child_restrict_and_store(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE data = CK_INVALID_HANDLE; + CK_OBJECT_CLASS dataClass = CKO_DATA; + byte value[] = "stored"; + CK_ATTRIBUTE restrictTmpl[] = { + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE dataTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, value, sizeof(value) - 1 }, + }; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv == CKR_OK) + rv = funcList->C_SetAttributeValue(session, obj, restrictTmpl, 2); + if (rv == CKR_OK) + rv = funcList->C_CreateObject(session, dataTmpl, 3, &data); + if (rv != CKR_OK) { + fprintf(stderr, " restrict and store failed: 0x%lx\n", + (unsigned long)rv); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +static CK_RV create_exportable_aes_key(CK_SESSION_HANDLE session) +{ + return create_aes_key_ex(session, CK_TRUE, CK_FALSE, CK_TRUE); +} + +static CK_RV create_public_aes_key(CK_SESSION_HANDLE session) +{ + return create_aes_key_ex(session, CK_FALSE, CK_TRUE, CK_FALSE); +} + +/* Without the user logged in, a policy change cannot be bound to the stored + * key and is refused, while a label change is accepted. */ +static int child_unbound_update_refused(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + char label[] = "renamed"; + CK_ATTRIBUTE policy[] = { + { CKA_DECRYPT, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE name[] = { + { CKA_LABEL, label, sizeof(label) - 1 }, + }; + int res = CHILD_PASS; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 0, &session); + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv != CKR_OK) { + fprintf(stderr, " public key not found: 0x%lx\n", (unsigned long)rv); + res = CHILD_FAIL; + } + if (res == CHILD_PASS) { + rv = funcList->C_SetAttributeValue(session, obj, policy, 1); + if (rv != CKR_USER_NOT_LOGGED_IN) { + fprintf(stderr, " policy change returned 0x%lx\n", + (unsigned long)rv); + res = CHILD_FAIL; + } + rv = funcList->C_SetAttributeValue(session, obj, name, 1); + if (rv != CKR_OK) { + fprintf(stderr, " label change returned 0x%lx\n", + (unsigned long)rv); + res = CHILD_FAIL; + } + } + (void)funcList->C_Finalize(NULL); + return res; +} + +static void test_legitimate_updates_round_trip(void) +{ + CK_RV rv; + int res = CHILD_SETUP; + + printf("\n--- legitimate policy updates survive a reload ---\n"); + set_aes_check(); + rv = store_objects(create_and_restrict_aes_key); + CHECK_RV(rv, "restrict AES key policy while logged in", CKR_OK); + if (rv == CKR_OK) { + expDecrypt = CK_FALSE; + res = run_in_child(child_key_intact); + } + CHECK_TRUE(res == CHILD_PASS, "restricted policy is kept after reload"); + res = CHILD_SETUP; + if (rv == CKR_OK && mark_stored_key_exportable() == 0) + res = run_in_child(child_key_not_exported); + CHECK_TRUE(res == CHILD_PASS, + "restricted policy cannot be undone in store"); + cleanup_test_files(); + + res = CHILD_SETUP; + set_aes_check(); + rv = store_objects(create_exportable_aes_key); + CHECK_RV(rv, "store exportable AES key", CKR_OK); + if (rv == CKR_OK) + res = run_in_child(child_restrict_and_store); + CHECK_TRUE(res == CHILD_PASS, "restrict key and store while logged in"); + if (res == CHILD_PASS) + res = run_in_child(child_key_intact); + CHECK_TRUE(res == CHILD_PASS, "policy stored while logged in is kept"); + if (res == CHILD_PASS && mark_stored_key_exportable() == 0) + res = run_in_child(child_key_not_exported); + else + res = CHILD_SETUP; + CHECK_TRUE(res == CHILD_PASS, "policy stored while logged in is bound"); + cleanup_test_files(); + + res = CHILD_SETUP; + set_aes_check(); + rv = store_objects(create_public_aes_key); + CHECK_RV(rv, "store public AES key", CKR_OK); + if (rv == CKR_OK) + res = run_in_child(child_unbound_update_refused); + CHECK_TRUE(res == CHILD_PASS, "policy change needs the user's key"); + res = CHILD_SETUP; + if (rv == CKR_OK) + res = run_in_child(child_key_intact); + CHECK_TRUE(res == CHILD_PASS, "public key usable after refused change"); + cleanup_test_files(); +} + +#if (defined(WOLFPKCS11_PBKDF2) || defined(HAVE_SCRYPT)) && \ + !defined(WOLFSSL_STM32U5_DHUK) && \ + !(defined(HAVE_FIPS) && defined(WOLFPKCS11_NSS)) +#define HAVE_LEGACY_STORE_TEST + +/* Derive the token storage key from the user PIN and the stored token seed, + * the way stores have always been keyed. */ +static int derive_token_key(byte* key, word32 keySz) +{ + static byte data[MAX_FILE_SZ]; + char path[256]; + size_t sz = 0; + size_t off = 32; + byte* seed; + + store_path(path, sizeof(path), "token", -1); + if (read_file(path, data, sizeof(data), &sz) != 0) + return -1; + /* label, SO PIN, SO seed, SO fail count and times */ + if (sz < off + 4) + return -1; + off += 4 + get_be32(data + off) + 16 + 4 + 2 * sizeof(time_t); + /* user PIN, user seed, user fail count and times */ + if (sz < off + 4) + return -1; + off += 4 + get_be32(data + off) + 16 + 4 + 2 * sizeof(time_t); + if (sz < off + 16) + return -1; + seed = data + off; +#ifdef WOLFPKCS11_PBKDF2 + return wc_PBKDF2(key, userPin, (int)sizeof(userPin) - 1, seed, 16, + PBKDF2_ITERATIONS, (int)keySz, WC_SHA256); +#else + return wc_scrypt(key, userPin, (int)sizeof(userPin) - 1, seed, 16, + WP11_HASH_PIN_COST, WP11_HASH_PIN_BLOCKSIZE, + WP11_HASH_PIN_PARALLEL, (int)keySz); +#endif +} + +static int read_stored_iv(byte* iv) +{ + static byte data[MAX_FILE_SZ]; + char path[256]; + size_t sz = 0; + + if (find_store_file("obj", path, sizeof(path)) != 0 || + read_file(path, data, sizeof(data), &sz) != 0 || sz < OBJ_IV_SZ) + return -1; + XMEMCPY(iv, data, OBJ_IV_SZ); + return 0; +} + +/* Replace the stored AES key with an encryption that binds no policy. */ +static int write_unbound_key_record(void) +{ + byte key[32]; + byte iv[OBJ_IV_SZ]; + byte rec[4 + sizeof(aesKeyValue) + TAG_SZ]; + char path[256]; + Aes aes; + int ret; + + ret = derive_token_key(key, sizeof(key)); + if (ret == 0) + ret = read_stored_iv(iv); + if (ret == 0) + ret = find_store_file("symmkey", path, sizeof(path)); + if (ret == 0) + ret = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesGcmSetKey(&aes, key, sizeof(key)); + if (ret == 0) { + ret = wc_AesGcmEncrypt(&aes, rec + 4, aesKeyValue, + sizeof(aesKeyValue), iv, sizeof(iv), + rec + 4 + sizeof(aesKeyValue), TAG_SZ, NULL, 0); + } + wc_AesFree(&aes); + } + if (ret == 0) { + put_be32(rec, (word32)(sizeof(aesKeyValue) + TAG_SZ)); + ret = write_file(path, rec, sizeof(rec)); + } + wc_ForceZero(key, sizeof(key)); + return ret; +} + +/* Returns 1 when the stored AES key record still verifies without a policy. */ +static int stored_key_is_unbound(void) +{ + static byte data[MAX_FILE_SZ]; + byte key[32]; + byte iv[OBJ_IV_SZ]; + byte plain[sizeof(aesKeyValue)]; + char path[256]; + size_t sz = 0; + Aes aes; + int ret; + int unbound = 0; + + ret = derive_token_key(key, sizeof(key)); + if (ret == 0) + ret = read_stored_iv(iv); + if (ret == 0) + ret = find_store_file("symmkey", path, sizeof(path)); + if (ret == 0) + ret = read_file(path, data, sizeof(data), &sz); + if (ret == 0 && (sz != 4 + sizeof(aesKeyValue) + TAG_SZ || + get_be32(data) != sizeof(aesKeyValue) + TAG_SZ)) + ret = -1; + if (ret == 0) + ret = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesGcmSetKey(&aes, key, sizeof(key)); + if (ret == 0) { + ret = wc_AesGcmDecrypt(&aes, plain, data + 4, sizeof(plain), iv, + sizeof(iv), data + 4 + sizeof(plain), TAG_SZ, NULL, 0); + unbound = (ret == 0); + } + wc_AesFree(&aes); + } + wc_ForceZero(key, sizeof(key)); + wc_ForceZero(plain, sizeof(plain)); + return unbound; +} + +static void test_unbound_store_upgraded(void) +{ + CK_RV rv; + int res = CHILD_SETUP; + + printf("\n--- store without bound policy loads and is upgraded ---\n"); + rv = store_objects(create_aes_key); + CHECK_RV(rv, "store AES key", CKR_OK); + if (rv == CKR_OK && write_unbound_key_record() == 0 && + stored_key_is_unbound()) { + set_aes_check(); + res = run_in_child(child_key_intact); + } + CHECK_TRUE(res == CHILD_PASS, "unbound key record still loads"); + CHECK_TRUE(res == CHILD_PASS && !stored_key_is_unbound(), + "key record is rebound to its policy after use"); + if (res == CHILD_PASS) + res = run_in_child(child_key_intact); + CHECK_TRUE(res == CHILD_PASS, "rebound key loads again"); + res = CHILD_SETUP; + if (mark_stored_key_exportable() == 0) + res = run_in_child(child_key_not_exported); + CHECK_TRUE(res == CHILD_PASS, "rebound key keeps its policy"); + cleanup_test_files(); +} +#endif + +static byte otherUserPin[] = "wolfpkcs11-other"; + +/* Store passes that cannot start must leave the PIN and token in effect. */ +static int child_failed_store_keeps_state(void) +{ + CK_RV rv; + CK_RV setRv = CKR_OK; + CK_RV initRv = CKR_OK; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_UTF8CHAR label[32]; + byte out[BLOCK_SZ]; + char lockPath[256]; + + (void)snprintf(lockPath, sizeof(lockPath), "%s/wp11_txn_%016lx_lock", + TEST_DIR, 1UL); + XMEMSET(label, ' ', sizeof(label)); + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + /* A directory at the lock file name makes every store pass fail. */ + (void)unlink(lockPath); + if (rv == CKR_OK && mkdir(lockPath, 0700) != 0) + rv = CKR_GENERAL_ERROR; + if (rv == CKR_OK) { + setRv = funcList->C_SetPIN(session, userPin, sizeof(userPin) - 1, + otherUserPin, sizeof(otherUserPin) - 1); + (void)funcList->C_CloseSession(session); + session = CK_INVALID_HANDLE; + initRv = funcList->C_InitToken(slot, soPin, sizeof(soPin) - 1, label); + } + (void)rmdir(lockPath); + if (rv == CKR_OK && (setRv == CKR_OK || initRv == CKR_OK)) + rv = CKR_GENERAL_ERROR; + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv == CKR_OK) + rv = aes_encrypt_block(session, obj, out); + if (rv == CKR_OK && XMEMCMP(out, refCipher, sizeof(out)) != 0) + rv = CKR_FUNCTION_FAILED; + (void)funcList->C_Finalize(NULL); + if (rv != CKR_OK) { + fprintf(stderr, " old PIN or key lost after failed stores: 0x%lx\n", + (unsigned long)rv); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +static void test_failed_store_keeps_state(void) +{ + CK_RV rv; + + printf("\n--- failed PIN change and token init keep stored state ---\n"); + set_aes_check(); + rv = store_objects(create_aes_key); + CHECK_RV(rv, "store AES key", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(run_in_child(child_failed_store_keeps_state) == CHILD_PASS, + "old PIN and key work after failed stores"); + } + cleanup_test_files(); +} + +#ifdef DEBUG_WOLFPKCS11 +/* Matches WP11_TEST_STORE_EXIT_CODE in wolfpkcs11/internal.h. */ +#define STORE_EXIT_CODE 75 +#define CRASH_MAX_RENAMES 40 + +extern void WP11_Test_StoreExitAfterRenames(int renames); + +static int crashAt = 0; + +/* Restrict the key and store the token, ending after crashAt renames. */ +static int child_restrict_and_crash(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE data = CK_INVALID_HANDLE; + CK_OBJECT_CLASS dataClass = CKO_DATA; + byte value[] = "stored"; + CK_ATTRIBUTE restrictTmpl[] = { + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE dataTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, value, sizeof(value) - 1 }, + }; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv == CKR_OK) + rv = funcList->C_SetAttributeValue(session, obj, restrictTmpl, 2); + if (rv == CKR_OK) { + WP11_Test_StoreExitAfterRenames(crashAt); + rv = funcList->C_CreateObject(session, dataTmpl, 3, &data); + WP11_Test_StoreExitAfterRenames(0); + } + return (rv == CKR_OK) ? CHILD_PASS : CHILD_FAIL; +} + +/* The key loads and encrypts as before, under the old or the new policy. */ +static int child_key_old_or_new(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + byte out[BLOCK_SZ]; + CK_BBOOL sensitive = CK_FALSE; + CK_BBOOL extractable = CK_FALSE; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv == CKR_OK) + rv = aes_encrypt_block(session, obj, out); + if (rv == CKR_OK && XMEMCMP(out, refCipher, sizeof(out)) != 0) + rv = CKR_FUNCTION_FAILED; + if (rv == CKR_OK) + rv = get_bool(session, obj, CKA_SENSITIVE, &sensitive); + if (rv == CKR_OK) + rv = get_bool(session, obj, CKA_EXTRACTABLE, &extractable); + if (rv == CKR_OK && sensitive == extractable) + rv = CKR_GENERAL_ERROR; + (void)funcList->C_Finalize(NULL); + if (rv != CKR_OK) { + fprintf(stderr, " key not usable after crash at rename %d: 0x%lx\n", + crashAt, (unsigned long)rv); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +static void test_store_pass_is_atomic(void) +{ + CK_RV rv; + int res; + int done = 0; + int bad = 0; + + printf("\n--- a store pass cut short at any rename keeps the key ---\n"); + for (crashAt = 1; !done && bad == 0 && crashAt <= CRASH_MAX_RENAMES; + crashAt++) { + set_aes_check(); + rv = store_objects(create_exportable_aes_key); + if (rv != CKR_OK) { + bad++; + break; + } + res = run_in_child(child_restrict_and_crash); + if (res == CHILD_PASS) + done = 1; + else if (res != STORE_EXIT_CODE) + bad++; + if (run_in_child(child_key_old_or_new) != CHILD_PASS) + bad++; + } + CHECK_TRUE(done, "store pass runs to completion without the hook"); + CHECK_TRUE(bad == 0, "key is old or new after a crash at every rename"); + cleanup_test_files(); +} + +static byte newUserPin[] = "wolfpkcs11-new1"; + +/* Restrict the key and change the user PIN, ending after crashAt renames. */ +static int child_rebind_and_change_pin(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE restrictTmpl[] = { + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + }; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv == CKR_OK) + rv = funcList->C_SetAttributeValue(session, obj, restrictTmpl, 2); + if (rv == CKR_OK) { + WP11_Test_StoreExitAfterRenames(crashAt); + rv = funcList->C_SetPIN(session, userPin, sizeof(userPin) - 1, + newUserPin, sizeof(newUserPin) - 1); + WP11_Test_StoreExitAfterRenames(0); + } + return (rv == CKR_OK) ? CHILD_PASS : CHILD_FAIL; +} + +/* The key works after logging in with either the old or the new PIN. */ +static int child_key_under_either_pin(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + byte out[BLOCK_SZ]; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 0, &session); + if (rv == CKR_OK) { + rv = funcList->C_Login(session, CKU_USER, userPin, + sizeof(userPin) - 1); + if (rv == CKR_PIN_INCORRECT) { + rv = funcList->C_Login(session, CKU_USER, newUserPin, + sizeof(newUserPin) - 1); + } + } + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv == CKR_OK) + rv = aes_encrypt_block(session, obj, out); + if (rv == CKR_OK && XMEMCMP(out, refCipher, sizeof(out)) != 0) + rv = CKR_FUNCTION_FAILED; + (void)funcList->C_Finalize(NULL); + if (rv != CKR_OK) { + fprintf(stderr, " key not usable after crash at rename %d: 0x%lx\n", + crashAt, (unsigned long)rv); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +static void test_pin_change_pass_is_atomic(void) +{ + CK_RV rv; + int res; + int done = 0; + int bad = 0; + + printf("\n--- a PIN change cut short at any rename keeps the key ---\n"); + for (crashAt = 1; !done && bad == 0 && crashAt <= CRASH_MAX_RENAMES; + crashAt++) { + set_aes_check(); + rv = store_objects(create_exportable_aes_key); + if (rv != CKR_OK) { + bad++; + break; + } + res = run_in_child(child_rebind_and_change_pin); + if (res == CHILD_PASS) + done = 1; + else if (res != STORE_EXIT_CODE) + bad++; + if (run_in_child(child_key_under_either_pin) != CHILD_PASS) + bad++; + } + CHECK_TRUE(done, "PIN change runs to completion without the hook"); + CHECK_TRUE(bad == 0, "key usable with old or new PIN after every crash"); + cleanup_test_files(); +} +#endif + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); + + printf("=== wolfPKCS11 stored object policy integrity test ===\n"); + test_stored_policy_is_authenticated(); + test_legitimate_updates_round_trip(); + test_failed_store_keeps_state(); +#ifdef DEBUG_WOLFPKCS11 + test_store_pass_is_atomic(); + test_pin_change_pass_is_atomic(); +#endif +#if defined(WOLFPKCS11_MLDSA) || defined(WOLFPKCS11_MLKEM) + test_pq_keys_round_trip(); +#endif +#ifdef HAVE_LEGACY_STORE_TEST + test_unbound_store_upgraded(); +#endif + + return pkcs11_test_summary(); +} + +#else + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + printf("File-backed token storage not available, skipping test\n"); + return 77; +} + +#endif diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index bb45e54b..279755c5 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -426,6 +426,12 @@ WP11_API void WP11_Object_SetFindHook(void (*hook)(void)); WP11_API void WP11_Session_SetAddObjectHook(void (*hook)(void)); WP11_API void WP11_Slot_SetPinCheckHook(void (*hook)(void)); WP11_API void WP11_Test_FailTokenInit(int fail); +#if !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_CUSTOM_STORE) && \ + !defined(WOLFPKCS11_TPM_STORE) && !defined(_WIN32) && !defined(_MSC_VER) +/* Exit status of a process ended by the store rename test hook. */ +#define WP11_TEST_STORE_EXIT_CODE 75 +WP11_API void WP11_Test_StoreExitAfterRenames(int renames); +#endif #if defined(WOLFPKCS11_TPM) && (!defined(NO_RSA) || defined(HAVE_ECC)) WP11_API int WP11_Test_DecodeTpmKey(CK_SLOT_ID slotId, unsigned char* keyData, int keyDataLen); From f662fcd389eb2e2b878eee5fe0074a32c4e37ac8 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:33:15 -0700 Subject: [PATCH 033/204] Take the store lock before publishing the store batch slot --- src/internal.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/internal.c b/src/internal.c index 38e29780..e5f7cb4a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1976,6 +1976,7 @@ static int wolfPKCS11_StoreBatchBegin(int tokenId) HANDLE h; #endif int fd; + WP11_Slot* slot; #ifndef WOLFPKCS11_NO_ENV /* Storage is turned off, so the pass writes nothing. */ @@ -1983,9 +1984,11 @@ static int wolfPKCS11_StoreBatchBegin(int tokenId) if (str != NULL) return 0; #endif - if (WP11_Slot_Get((CK_SLOT_ID)tokenId, &storeBatch.slot) != 0) + if (WP11_Slot_Get((CK_SLOT_ID)tokenId, &slot) != 0) return BAD_FUNC_ARG; - WP11_Lock_LockRW(&storeBatch.slot->storeLock); + /* The batch state is only written while the store lock is held. */ + WP11_Lock_LockRW(&slot->storeLock); + storeBatch.slot = slot; ret = wolfPKCS11_StoreBatchNames(tokenId); if (ret == 0) ret = wolfPKCS11_StoreEnsureDir(storeBatch.dir); @@ -2023,8 +2026,8 @@ static int wolfPKCS11_StoreBatchBegin(int tokenId) } else { (void)wolfPKCS11_StoreBatchLockFile(0); - WP11_Lock_UnlockRW(&storeBatch.slot->storeLock); storeBatch.slot = NULL; + WP11_Lock_UnlockRW(&slot->storeLock); } return ret; @@ -2075,6 +2078,7 @@ static int wolfPKCS11_StoreStageTemp(WP11_FileStoreCtx* ctx) static int wolfPKCS11_StoreBatchEnd(int commit) { int ret = 0; + WP11_Slot* slot; if (storeBatch.active) { storeBatch.active = 0; @@ -2104,9 +2108,10 @@ static int wolfPKCS11_StoreBatchEnd(int commit) } (void)wolfPKCS11_StoreBatchLockFile(0); } - if (storeBatch.slot != NULL) { - WP11_Lock_UnlockRW(&storeBatch.slot->storeLock); + slot = storeBatch.slot; + if (slot != NULL) { storeBatch.slot = NULL; + WP11_Lock_UnlockRW(&slot->storeLock); } return ret; From 6ea98baa2f20354a4428cdf38ed265006cd3378d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:33:16 -0700 Subject: [PATCH 034/204] Skip store batch recovery when token storage is disabled --- src/internal.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/internal.c b/src/internal.c index e5f7cb4a..8386b8d6 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1935,6 +1935,11 @@ static int wolfPKCS11_StoreBatchCheck(int tokenId) XFILE marker; WP11_Slot* slot; +#ifndef WOLFPKCS11_NO_ENV + /* Storage is turned off, so nothing is read and nothing is finished. */ + if (XGETENV("WOLFPKCS11_NO_STORE") != NULL) + return 0; +#endif if (WP11_Slot_Get((CK_SLOT_ID)tokenId, &slot) != 0) return 0; WP11_Lock_LockRW(&slot->storeLock); From 9d1c889219ba5ec5851d363b2dfc2296eafc2bb4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:33:16 -0700 Subject: [PATCH 035/204] Hold the store batch locks until the token load finishes --- src/internal.c | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/internal.c b/src/internal.c index 8386b8d6..148cfa2d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1923,18 +1923,22 @@ static int wolfPKCS11_StoreBatchRecover(void) } /** - * Bring the store for a token to a committed state before it is read. + * Bring the store for a token to a committed state before it is read. On + * return the batch locks are held, so the token and object records read + * until wolfPKCS11_StoreBatchRelease belong to one committed pass. * - * @param [in] tokenId Id of token. + * @param [in] tokenId Id of token. + * @param [out] held Slot whose batch locks are held, or NULL. * @return 0 on success. * @return Other value when an interrupted pass could not be finished. */ -static int wolfPKCS11_StoreBatchCheck(int tokenId) +static int wolfPKCS11_StoreBatchCheck(int tokenId, WP11_Slot** held) { int ret = 0; XFILE marker; WP11_Slot* slot; + *held = NULL; #ifndef WOLFPKCS11_NO_ENV /* Storage is turned off, so nothing is read and nothing is finished. */ if (XGETENV("WOLFPKCS11_NO_STORE") != NULL) @@ -1943,10 +1947,10 @@ static int wolfPKCS11_StoreBatchCheck(int tokenId) if (WP11_Slot_Get((CK_SLOT_ID)tokenId, &slot) != 0) return 0; WP11_Lock_LockRW(&slot->storeLock); + *held = slot; if (wolfPKCS11_StoreBatchNames(tokenId) == 0) { if (wolfPKCS11_StoreBatchLockFile(1) == 0) { ret = wolfPKCS11_StoreBatchRecover(); - (void)wolfPKCS11_StoreBatchLockFile(0); } else { /* A committed pass that cannot be finished must not be read. */ @@ -1957,11 +1961,23 @@ static int wolfPKCS11_StoreBatchCheck(int tokenId) } } } - WP11_Lock_UnlockRW(&slot->storeLock); return ret; } +/** + * Release the batch locks held since wolfPKCS11_StoreBatchCheck. + * + * @param [in] held Slot returned by wolfPKCS11_StoreBatchCheck. + */ +static void wolfPKCS11_StoreBatchRelease(WP11_Slot* held) +{ + if (held != NULL) { + (void)wolfPKCS11_StoreBatchLockFile(0); + WP11_Lock_UnlockRW(&held->storeLock); + } +} + /** * Start a store pass: records closed from now on are staged, not committed. * On success the batch locks are held until wolfPKCS11_StoreBatchEnd. Without @@ -8247,11 +8263,15 @@ static int wp11_Token_Load(WP11_Slot* slot, int tokenId, WP11_Token* token) WP11_Object** current; int objCnt = 0; word32 len; +#ifdef WP11_STORE_BATCH + WP11_Slot* batchHeld = NULL; +#endif ret = 0; #ifdef WP11_STORE_BATCH - /* Finish or drop a store pass that a crash interrupted. */ - ret = wolfPKCS11_StoreBatchCheck(tokenId); + /* Finish or drop a store pass that a crash interrupted, then keep other + * store passes out until every record of the token has been read. */ + ret = wolfPKCS11_StoreBatchCheck(tokenId, &batchHeld); #endif /* Open access to token object. */ if (ret == 0) { @@ -8398,6 +8418,9 @@ static int wp11_Token_Load(WP11_Slot* slot, int tokenId, WP11_Token* token) /* No data to read. */ ret = 0; } +#ifdef WP11_STORE_BATCH + wolfPKCS11_StoreBatchRelease(batchHeld); +#endif return ret; } From c2edb2375b9297ff5f5f589e6673549ea10dda81 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:33:55 -0700 Subject: [PATCH 036/204] Record store batch entries by file name --- src/internal.c | 59 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/src/internal.c b/src/internal.c index 148cfa2d..8519e3c5 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1784,7 +1784,8 @@ static int wolfPKCS11_StoreBatchLockFile(int lock) } /** - * Check that a listed name is a store file directly in the store directory. + * Check that a listed name is the basename of a store file. Lists hold + * basenames so a recovery does not depend on how the directory was spelled. * * @param [in] name Name read from a batch list. * @return 1 when acceptable, 0 otherwise. @@ -1795,11 +1796,9 @@ static int wolfPKCS11_StoreBatchNameOk(const char* name) size_t i; int ok; - ok = len > storeBatch.dirLen + 6 && - XSTRNCMP(name, storeBatch.dir, storeBatch.dirLen) == 0 && - name[storeBatch.dirLen] == '/' && - XSTRNCMP(name + storeBatch.dirLen + 1, "wp11_", 5) == 0; - for (i = storeBatch.dirLen + 1; ok && i < len; i++) { + ok = len > 5 && XSTRNCMP(name, "wp11_", 5) == 0 && + storeBatch.dirLen + 1 + len < WP11_STORE_MAX_PATH; + for (i = 0; ok && i < len; i++) { if (name[i] == '/' || name[i] == '\\') ok = 0; } @@ -1847,7 +1846,10 @@ static int wolfPKCS11_StoreBatchApply(const char* listName, int mode) XFILE list; XFILE staged; size_t len; + size_t i; + const char* base; char line[WP11_STORE_MAX_PATH + 2]; + char finalName[WP11_STORE_MAX_PATH]; char stagedName[WP11_STORE_STAGED_MAX]; list = XFOPEN(listName, "rb"); @@ -1862,9 +1864,24 @@ static int wolfPKCS11_StoreBatchApply(const char* listName, int mode) continue; } line[len - 1] = '\0'; - len = (size_t)XSNPRINTF(stagedName, sizeof(stagedName), "%s.new", - line); - if (!wolfPKCS11_StoreBatchNameOk(line) || len >= sizeof(stagedName)) { + /* A list written with full paths is read by its basenames too. */ + base = line; + for (i = 0; i + 1 < len; i++) { + if (line[i] == '/' || line[i] == '\\') + base = line + i + 1; + } + if (!wolfPKCS11_StoreBatchNameOk(base)) { + if (mode != WP11_BATCH_DISCARD) + ret = READ_ONLY_E; + continue; + } + len = (size_t)XSNPRINTF(finalName, sizeof(finalName), "%s/%s", + storeBatch.dir, base); + if (len < sizeof(finalName)) { + len = (size_t)XSNPRINTF(stagedName, sizeof(stagedName), "%s.new", + finalName); + } + if (len >= sizeof(finalName) || len >= sizeof(stagedName)) { if (mode != WP11_BATCH_DISCARD) ret = READ_ONLY_E; continue; @@ -1873,10 +1890,10 @@ static int wolfPKCS11_StoreBatchApply(const char* listName, int mode) (void)remove(stagedName); } else if (mode == WP11_BATCH_CHECK) { - if (!wolfPKCS11_StoreBatchReplaceable(line)) + if (!wolfPKCS11_StoreBatchReplaceable(finalName)) ret = READ_ONLY_E; } - else if (wolfPKCS11_StoreRename(stagedName, line) != 0) { + else if (wolfPKCS11_StoreRename(stagedName, finalName) != 0) { /* Already renamed by an earlier, interrupted roll forward. */ staged = XFOPEN(stagedName, "rb"); if (staged != XBADFILE) { @@ -2055,7 +2072,8 @@ static int wolfPKCS11_StoreBatchBegin(int tokenId) } /** - * Stage a written record as ".new" and list it in the intent file. + * Stage a written record as ".new" and list its basename in the intent + * file. The record must be in the store directory of the pass. * * @param [in, out] ctx File store context with a written temporary file. * @return 0 on success. @@ -2065,17 +2083,26 @@ static int wolfPKCS11_StoreStageTemp(WP11_FileStoreCtx* ctx) { int ret = 0; int len; + const char* base; char stagedName[WP11_STORE_STAGED_MAX]; - len = XSNPRINTF(stagedName, sizeof(stagedName), "%s.new", - ctx->final_name); - if (len <= 0 || len >= (int)sizeof(stagedName)) + base = ctx->final_name + storeBatch.dirLen + 1; + if (XSTRNCMP(ctx->final_name, storeBatch.dir, storeBatch.dirLen) != 0 || + ctx->final_name[storeBatch.dirLen] != '/' || + !wolfPKCS11_StoreBatchNameOk(base)) { ret = READ_ONLY_E; + } + if (ret == 0) { + len = XSNPRINTF(stagedName, sizeof(stagedName), "%s.new", + ctx->final_name); + if (len <= 0 || len >= (int)sizeof(stagedName)) + ret = READ_ONLY_E; + } if (ret == 0) ret = wolfPKCS11_StoreRename(ctx->temp_name, stagedName); if (ret == 0) { ctx->has_temp = 0; - if (fputs(ctx->final_name, storeBatch.intent) < 0 || + if (fputs(base, storeBatch.intent) < 0 || fputc('\n', storeBatch.intent) == EOF || fflush(storeBatch.intent) != 0) { ret = READ_ONLY_E; From 29812e691aacfc0ea38fe8082299f756c7e5ccbb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:35:04 -0700 Subject: [PATCH 037/204] Sync staged store files before commit --- src/internal.c | 119 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 8 deletions(-) diff --git a/src/internal.c b/src/internal.c index 8519e3c5..e67432e2 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1309,6 +1309,93 @@ static int wolfPKCS11_StoreRename(const char* from, const char* to) return ret; } +/** + * Flush a written store file to stable storage. + * + * @param [in] file Open file written by the store. + * @return 0 on success. + * @return READ_ONLY_E when the data may not have reached the device. + */ +static int wolfPKCS11_StoreSyncFile(XFILE file) +{ + int ret = 0; +#if defined(_WIN32) || defined(_MSC_VER) + intptr_t osHandle; +#endif + + if (XFFLUSH(file) != 0) + ret = READ_ONLY_E; +#if defined(_WIN32) || defined(_MSC_VER) + if (ret == 0) { + osHandle = _get_osfhandle(_fileno(file)); + if (!FlushFileBuffers((HANDLE)osHandle)) + ret = READ_ONLY_E; + } +#else + if (ret == 0 && fsync(fileno(file)) != 0) + ret = READ_ONLY_E; +#endif + + return ret; +} + +/** + * Make the renames done in a store directory durable. Windows has no + * directory flush; its renames are done write-through instead. + * + * @param [in] dir Store directory. + * @return 0 on success. + * @return READ_ONLY_E when the directory could not be flushed. + */ +static int wolfPKCS11_StoreSyncDir(const char* dir) +{ + int ret = 0; +#if !defined(_WIN32) && !defined(_MSC_VER) + int fd; + + fd = open(dir, O_RDONLY); + if (fd < 0) { + ret = READ_ONLY_E; + } + else { + /* Filesystems without directory flushing answer EINVAL. */ + if (fsync(fd) != 0 && errno != EINVAL) + ret = READ_ONLY_E; + close(fd); + } +#else + (void)dir; +#endif + + return ret; +} + +/** + * Make a rename of a store file durable by flushing its directory. + * + * @param [in] name Renamed store file. + * @return 0 on success. + * @return READ_ONLY_E when the directory could not be flushed. + */ +static int wolfPKCS11_StoreSyncParent(const char* name) +{ + size_t len = XSTRLEN(name); + size_t i; + size_t sep = 0; + char dir[WP11_STORE_MAX_PATH]; + + for (i = 0; i < len; i++) { + if (name[i] == '/' || name[i] == '\\') + sep = i; + } + if (sep == 0 || sep >= sizeof(dir)) + return READ_ONLY_E; + XMEMCPY(dir, name, sep); + dir[sep] = '\0'; + + return wolfPKCS11_StoreSyncDir(dir); +} + static int wolfPKCS11_StoreCommitTemp(WP11_FileStoreCtx* ctx) { int ret = 0; @@ -1320,6 +1407,7 @@ static int wolfPKCS11_StoreCommitTemp(WP11_FileStoreCtx* ctx) ret = wolfPKCS11_StoreRename(ctx->temp_name, ctx->final_name); if (ret == 0) { ctx->has_temp = 0; + ret = wolfPKCS11_StoreSyncParent(ctx->final_name); } return ret; @@ -1927,6 +2015,9 @@ static int wolfPKCS11_StoreBatchRecover(void) XFCLOSE(marker); ret = wolfPKCS11_StoreBatchApply(storeBatch.commitName, WP11_BATCH_APPLY); + /* The marker outlives the records it names until they are durable. */ + if (ret == 0) + ret = wolfPKCS11_StoreSyncDir(storeBatch.dir); if (ret == 0) (void)remove(storeBatch.commitName); } @@ -2130,6 +2221,8 @@ static int wolfPKCS11_StoreBatchEnd(int commit) if (storeBatch.active) { storeBatch.active = 0; + if (commit && wolfPKCS11_StoreSyncFile(storeBatch.intent) != 0) + storeBatch.failed = 1; if (XFCLOSE(storeBatch.intent) != 0) storeBatch.failed = 1; storeBatch.intent = XBADFILE; @@ -2139,13 +2232,18 @@ static int wolfPKCS11_StoreBatchEnd(int commit) ret = wolfPKCS11_StoreBatchApply(storeBatch.intentName, WP11_BATCH_CHECK); } + /* The staged records must be durable before the marker can be. */ + if (ret == 0 && commit) + ret = wolfPKCS11_StoreSyncDir(storeBatch.dir); if (ret == 0 && commit) { ret = wolfPKCS11_StoreRename(storeBatch.intentName, storeBatch.commitName); } if (ret == 0 && commit) { + (void)wolfPKCS11_StoreSyncDir(storeBatch.dir); if (wolfPKCS11_StoreBatchApply(storeBatch.commitName, - WP11_BATCH_APPLY) == 0) { + WP11_BATCH_APPLY) == 0 && + wolfPKCS11_StoreSyncDir(storeBatch.dir) == 0) { (void)remove(storeBatch.commitName); } } @@ -2459,17 +2557,22 @@ void wolfPKCS11_Store_Close(void* store) int commitRet = 0; if (ctx->file != XBADFILE && ctx->file != NULL) { - XFCLOSE(ctx->file); + if (ctx->is_write && wolfPKCS11_StoreSyncFile(ctx->file) != 0) + commitRet = BUFFER_E; + if (XFCLOSE(ctx->file) != 0 && ctx->is_write) + commitRet = BUFFER_E; ctx->file = XBADFILE; } if (ctx->is_write && ctx->has_temp) { - #ifdef WP11_STORE_BATCH - if (storeBatch.active) - commitRet = wolfPKCS11_StoreStageTemp(ctx); - else - #endif - commitRet = wolfPKCS11_StoreCommitTemp(ctx); + if (commitRet == 0) { + #ifdef WP11_STORE_BATCH + if (storeBatch.active) + commitRet = wolfPKCS11_StoreStageTemp(ctx); + else + #endif + commitRet = wolfPKCS11_StoreCommitTemp(ctx); + } if (commitRet != 0) { #ifdef WP11_STORE_BATCH if (storeBatch.active) From d6bc0f1df5623fb5e834320d6aa3ed54407c204b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:35:04 -0700 Subject: [PATCH 038/204] Correct the store batch setup comment --- src/internal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index e67432e2..56e6f24d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2088,8 +2088,8 @@ static void wolfPKCS11_StoreBatchRelease(WP11_Slot* held) /** * Start a store pass: records closed from now on are staged, not committed. - * On success the batch locks are held until wolfPKCS11_StoreBatchEnd. Without - * a usable store directory the pass runs unstaged and fails as before. + * On success the batch locks are held until wolfPKCS11_StoreBatchEnd. When + * the pass cannot start, nothing is written and the error is returned. * * @param [in] tokenId Id of token being stored. * @return 0 on success. From a03f88f4874341f0e6787a4f253968992d015f4f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:35:41 -0700 Subject: [PATCH 039/204] Rename WP11_FLAG_UNBOUND to WP11_FLAG_POLICY_EXCLUDED --- src/internal.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/internal.c b/src/internal.c index 56e6f24d..c4ee724d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -4158,11 +4158,12 @@ static int wp11_DecryptData(byte* out, byte* data, int len, byte* key, return ret; } -/* Set by the SO, who never holds the user's key, or by the library itself. */ +/* Flags left out of the bound policy: set by the SO, who never holds the + * user's key, or by the library itself. */ #ifdef WOLFPKCS11_TPM -#define WP11_FLAG_UNBOUND (WP11_FLAG_TRUSTED | WP11_FLAG_TPM) +#define WP11_FLAG_POLICY_EXCLUDED (WP11_FLAG_TRUSTED | WP11_FLAG_TPM) #else -#define WP11_FLAG_UNBOUND WP11_FLAG_TRUSTED +#define WP11_FLAG_POLICY_EXCLUDED WP11_FLAG_TRUSTED #endif #define WP11_POLICY_AAD_VER 1 @@ -4177,7 +4178,7 @@ static int wp11_DecryptData(byte* out, byte* data, int len, byte* key, static void wp11_Object_PolicyAad(WP11_Object* object, byte* aad) { CK_ULONG vals[3]; - word32 flags = object->opFlag & ~(word32)WP11_FLAG_UNBOUND; + word32 flags = object->opFlag & ~(word32)WP11_FLAG_POLICY_EXCLUDED; int idx = 0; int i; int j; @@ -14968,7 +14969,7 @@ static int wp11_Object_PolicyChanged(WP11_Object* object, if (object->onToken && wp11_Object_IsEncrypted(object) && (object->objClass != prevClass || - ((object->opFlag ^ prevFlag) & ~(word32)WP11_FLAG_UNBOUND) != 0)) { + ((object->opFlag ^ prevFlag) & ~(word32)WP11_FLAG_POLICY_EXCLUDED) != 0)) { if (object->encoded || object->undecoded) { object->objClass = prevClass; object->opFlag = prevFlag; From 1a0e866d86f9700a2adabf5974057e558a3f2b5d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:35:42 -0700 Subject: [PATCH 040/204] Hold the token lock across the PIN change store --- src/internal.c | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/internal.c b/src/internal.c index c4ee724d..c4e044ed 100644 --- a/src/internal.c +++ b/src/internal.c @@ -300,6 +300,7 @@ struct WP11_Object { byte encoded:1; /* Key isn't in decoded form */ byte undecoded:1; /* Loaded key data not yet decoded */ byte unbound:1; /* Key data not bound to the policy */ + byte rebound:1; /* Rebound by the store pass under way */ #endif WP11_Session* session; /* Session object belongs to */ @@ -10282,6 +10283,9 @@ int WP11_Slot_SetSOPin(WP11_Slot* slot, char* pin, int pinLen) token->soPinLen = sizeof(token->soPin); token->tokenFlags |= WP11_TOKEN_FLAG_SO_PIN_SET; #ifndef WOLFPKCS11_NO_STORE + /* Lock order is slot, token, then store: no object changes under + * the pass, as in C_CreateObject. */ + WP11_Lock_LockRW(&token->lock); ret = wp11_Token_Store(token, (int)slot->id); #ifdef WP11_STORE_BATCH if (ret != 0 && (oldFlags & WP11_TOKEN_FLAG_SO_PIN_SET)) { @@ -10292,6 +10296,7 @@ int WP11_Slot_SetSOPin(WP11_Slot* slot, char* pin, int pinLen) token->tokenFlags = oldFlags; } #endif + WP11_Lock_UnlockRW(&token->lock); #endif } WP11_Lock_UnlockRW(&slot->lock); @@ -10380,14 +10385,17 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) token->userPinLen = sizeof(token->userPin); token->tokenFlags |= WP11_TOKEN_FLAG_USER_PIN_SET; #ifndef WOLFPKCS11_NO_STORE + /* Lock order is slot, token, then store: no object changes under + * the pass, as in C_CreateObject. */ + WP11_Lock_LockRW(&token->lock); #ifdef WP11_STORE_BATCH - WP11_Lock_LockRO(&token->lock); - for (object = token->object; object != NULL && !rebind; - object = object->next) { - rebind = object->unbound && !object->encoded && - wp11_Object_IsEncrypted(object); + for (object = token->object; object != NULL; object = object->next) { + /* Only decoded key data can be rebound by the pass. */ + object->rebound = object->unbound && !object->encoded && + !object->undecoded && + wp11_Object_IsEncrypted(object); + rebind |= object->rebound; } - WP11_Lock_UnlockRO(&token->lock); #endif ret = wp11_Token_Store(token, (int)slot->id); #ifdef WP11_STORE_BATCH @@ -10400,18 +10408,18 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) token->userPinLen = oldPinLen; token->tokenFlags = oldFlags; token->userPinEmpty = oldPinEmpty; - /* The store may have rebound objects under the discarded key. */ - WP11_Lock_LockRW(&token->lock); - for (object = token->object; rebind && object != NULL; - object = object->next) { - if (wp11_Object_IsEncrypted(object) && !object->encoded && - wp11_Object_Encode(object, 0) != 0) { - WOLFPKCS11_MSG("SetUserPin: failed to re-encrypt object"); - } + } + for (object = token->object; rebind && object != NULL; + object = object->next) { + /* An object the failed pass rebound is under the discarded key. */ + if (ret != 0 && object->rebound && !object->unbound && + wp11_Object_Encode(object, 0) != 0) { + WOLFPKCS11_MSG("SetUserPin: failed to re-encrypt object"); } - WP11_Lock_UnlockRW(&token->lock); + object->rebound = 0; } #endif + WP11_Lock_UnlockRW(&token->lock); #endif } WP11_Lock_UnlockRW(&slot->lock); From cbac9d3b27859b44630b7bd420b0af531cfc8569 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:35:42 -0700 Subject: [PATCH 041/204] Clean up staged store files in the policy test --- tests/object_policy_integrity_test.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/object_policy_integrity_test.c b/tests/object_policy_integrity_test.c index afda742f..c0da2e72 100644 --- a/tests/object_policy_integrity_test.c +++ b/tests/object_policy_integrity_test.c @@ -123,8 +123,18 @@ static void store_path(char* path, size_t sz, const char* kind, int objId) } } +static void remove_record(const char* path) +{ + char staged[280]; + + (void)remove(path); + (void)snprintf(staged, sizeof(staged), "%s.new", path); + (void)remove(staged); +} + static void cleanup_test_files(void) { + static const char* txnKinds[] = { "lock", "intent", "commit" }; char path[256]; size_t k; int i; @@ -132,11 +142,17 @@ static void cleanup_test_files(void) for (k = 0; k < sizeof(storeKinds) / sizeof(*storeKinds); k++) { for (i = 0; i < MAX_OBJ_ID; i++) { store_path(path, sizeof(path), storeKinds[k], i); - (void)remove(path); + remove_record(path); } } store_path(path, sizeof(path), "token", -1); - (void)remove(path); + remove_record(path); + for (k = 0; k < sizeof(txnKinds) / sizeof(*txnKinds); k++) { + (void)snprintf(path, sizeof(path), "%s/wp11_txn_%016lx_%s", TEST_DIR, + 1UL, txnKinds[k]); + if (remove(path) != 0) + (void)rmdir(path); + } } /* Locate the stored file of the given kind for its single object. */ From daf7b1c4a760339d5b08265ad4f4a7c5a9a49ca6 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:35:43 -0700 Subject: [PATCH 042/204] Test store failures with undecoded keys --- tests/object_policy_integrity_test.c | 280 ++++++++++++++++++++++++++- 1 file changed, 275 insertions(+), 5 deletions(-) diff --git a/tests/object_policy_integrity_test.c b/tests/object_policy_integrity_test.c index c0da2e72..fc659127 100644 --- a/tests/object_policy_integrity_test.c +++ b/tests/object_policy_integrity_test.c @@ -1239,6 +1239,14 @@ static void test_unbound_store_upgraded(void) static byte otherUserPin[] = "wolfpkcs11-other"; +/* A directory at the lock file name makes every store pass fail. */ +static int block_store(char* lockPath, size_t sz) +{ + (void)snprintf(lockPath, sz, "%s/wp11_txn_%016lx_lock", TEST_DIR, 1UL); + (void)unlink(lockPath); + return (mkdir(lockPath, 0700) == 0) ? 0 : -1; +} + /* Store passes that cannot start must leave the PIN and token in effect. */ static int child_failed_store_keeps_state(void) { @@ -1252,17 +1260,13 @@ static int child_failed_store_keeps_state(void) byte out[BLOCK_SZ]; char lockPath[256]; - (void)snprintf(lockPath, sizeof(lockPath), "%s/wp11_txn_%016lx_lock", - TEST_DIR, 1UL); XMEMSET(label, ' ', sizeof(label)); if (pkcs11_load() != CKR_OK) return CHILD_SETUP; rv = init_slot(&slot); if (rv == CKR_OK) rv = open_session(slot, 1, &session); - /* A directory at the lock file name makes every store pass fail. */ - (void)unlink(lockPath); - if (rv == CKR_OK && mkdir(lockPath, 0700) != 0) + if (rv == CKR_OK && block_store(lockPath, sizeof(lockPath)) != 0) rv = CKR_GENERAL_ERROR; if (rv == CKR_OK) { setRv = funcList->C_SetPIN(session, userPin, sizeof(userPin) - 1, @@ -1306,6 +1310,269 @@ static void test_failed_store_keeps_state(void) cleanup_test_files(); } +/* Create a public AES key on the token and change its policy so the next + * store pass has to rebind it. The key is only decoded in this process. */ +static CK_RV create_unbound_public_key(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS cls = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_AES; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &cls, sizeof(cls) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aesKeyValue, sizeof(aesKeyValue) }, + }; + CK_ATTRIBUTE policy[] = { + { CKA_DECRYPT, &ckFalse, sizeof(ckFalse) }, + }; + + rv = funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); + if (rv == CKR_OK) + rv = funcList->C_SetAttributeValue(session, obj, policy, 1); + return rv; +} + +/* With the user's key never decoded in this process, a PIN change whose + * store fails must not touch the stored key. */ +static int child_undecoded_keys_survive_failed_pin_change(int asSO) +{ + CK_RV rv; + CK_RV setRv = CKR_OK; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + char lockPath[256]; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 0, &session); + if (rv == CKR_OK && asSO) + rv = funcList->C_Login(session, CKU_SO, soPin, sizeof(soPin) - 1); + if (rv == CKR_OK) + rv = create_unbound_public_key(session); + if (rv == CKR_OK && block_store(lockPath, sizeof(lockPath)) != 0) + rv = CKR_GENERAL_ERROR; + if (rv == CKR_OK && asSO) { + setRv = funcList->C_InitPIN(session, otherUserPin, + sizeof(otherUserPin) - 1); + } + else if (rv == CKR_OK) { + setRv = funcList->C_SetPIN(session, userPin, sizeof(userPin) - 1, + otherUserPin, sizeof(otherUserPin) - 1); + } + (void)rmdir(lockPath); + if (rv == CKR_OK && setRv == CKR_OK) + rv = CKR_GENERAL_ERROR; + (void)funcList->C_Finalize(NULL); + if (rv != CKR_OK) { + fprintf(stderr, " forced PIN change failure not set up: 0x%lx\n", + (unsigned long)rv); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +static int child_public_pin_change_fails(void) +{ + return child_undecoded_keys_survive_failed_pin_change(0); +} + +static int child_so_init_pin_fails(void) +{ + return child_undecoded_keys_survive_failed_pin_change(1); +} + +/* The user's key still works under the PIN that stayed in storage. */ +static int child_user_key_works(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + byte out[BLOCK_SZ]; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv == CKR_OK) + rv = aes_encrypt_block(session, obj, out); + if (rv == CKR_OK && XMEMCMP(out, refCipher, sizeof(out)) != 0) + rv = CKR_FUNCTION_FAILED; + (void)funcList->C_Finalize(NULL); + if (rv != CKR_OK) { + fprintf(stderr, " stored key lost by a failed PIN change: 0x%lx\n", + (unsigned long)rv); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +static void test_failed_store_keeps_undecoded_keys(void) +{ + CK_RV rv; + + printf("\n--- failed PIN change with undecoded keys keeps them ---\n"); + set_aes_check(); + rv = store_objects(create_aes_key); + CHECK_RV(rv, "store AES key", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(run_in_child(child_public_pin_change_fails) == CHILD_PASS, + "public session PIN change store failure is forced"); + CHECK_TRUE(run_in_child(child_user_key_works) == CHILD_PASS, + "key survives a failed public session PIN change"); + } + cleanup_test_files(); + rv = store_objects(create_aes_key); + CHECK_RV(rv, "store AES key", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(run_in_child(child_so_init_pin_fails) == CHILD_PASS, + "SO user PIN init store failure is forced"); + CHECK_TRUE(run_in_child(child_user_key_works) == CHILD_PASS, + "key survives a failed SO user PIN init"); + } + cleanup_test_files(); +} + +/* A token init whose store fails leaves nothing behind, and a retry works. */ +static int child_init_token_retry(void) +{ + CK_RV rv; + CK_RV initRv = CKR_OK; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_TOKEN_INFO info; + char lockPath[256]; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK && block_store(lockPath, sizeof(lockPath)) != 0) + rv = CKR_GENERAL_ERROR; + if (rv == CKR_OK) { + initRv = funcList->C_InitToken(slot, soPin, sizeof(soPin) - 1, + (CK_UTF8CHAR*)"wolfpkcs11 " + " "); + (void)rmdir(lockPath); + if (initRv == CKR_OK) + rv = CKR_GENERAL_ERROR; + } + if (rv == CKR_OK) + rv = funcList->C_GetTokenInfo(slot, &info); + if (rv == CKR_OK && (info.flags & CKF_TOKEN_INITIALIZED) != 0) + rv = CKR_GENERAL_ERROR; + if (rv == CKR_OK) + rv = provision_token(slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + (void)funcList->C_Finalize(NULL); + if (rv != CKR_OK) { + fprintf(stderr, " token init retry failed: 0x%lx\n", + (unsigned long)rv); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +static void test_init_token_failure_on_fresh_store(void) +{ + printf("\n--- failed token init on a fresh store can be retried ---\n"); + cleanup_test_files(); + CHECK_TRUE(run_in_child(child_init_token_retry) == CHILD_PASS, + "token init works after a failed init of a fresh store"); + cleanup_test_files(); +} + +#define ALT_TEST_DIR "store/./object_policy_integrity_test" + +static byte stagedRecord[] = "staged-record"; + +/* Load the token with the store directory spelled differently. */ +static int child_load_from_alt_dir(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + + XSETENV("WOLFPKCS11_TOKEN_PATH", ALT_TEST_DIR, 1); + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) + rv = find_key(session, &obj); + (void)funcList->C_Finalize(NULL); + if (rv != CKR_OK) { + fprintf(stderr, " load from the renamed store dir failed: 0x%lx\n", + (unsigned long)rv); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +/* A committed pass left by a crash is finished by a process that reaches the + * store directory through a different path, whether the marker lists the + * records by basename or by the path the crashed process used. */ +static void test_recovery_ignores_dir_spelling(void) +{ + CK_RV rv; + char path[256]; + char fullPath[256]; + char staged[280]; + char marker[256]; + char lines[600]; + static byte data[MAX_FILE_SZ]; + size_t sz = 0; + int res; + + printf("\n--- committed pass is finished from any store dir path ---\n"); + set_aes_check(); + rv = store_objects(create_aes_key); + CHECK_RV(rv, "store AES key", CKR_OK); + store_path(path, sizeof(path), "data", MAX_OBJ_ID - 1); + store_path(fullPath, sizeof(fullPath), "data", MAX_OBJ_ID - 2); + (void)snprintf(marker, sizeof(marker), "%s/wp11_txn_%016lx_commit", + TEST_DIR, 1UL); + (void)snprintf(lines, sizeof(lines), "wp11_data_%016lx_%016lx\n%s\n", 1UL, + (unsigned long)(MAX_OBJ_ID - 1), fullPath); + if (rv == CKR_OK) { + (void)snprintf(staged, sizeof(staged), "%s.new", path); + CHECK_TRUE(write_file(staged, stagedRecord, sizeof(stagedRecord)) == 0, + "stage a record"); + (void)snprintf(staged, sizeof(staged), "%s.new", fullPath); + CHECK_TRUE(write_file(staged, stagedRecord, sizeof(stagedRecord)) == 0, + "stage a second record"); + CHECK_TRUE(write_file(marker, (const byte*)lines, XSTRLEN(lines)) == 0, + "write a commit marker naming both"); + res = run_in_child(child_load_from_alt_dir); + CHECK_TRUE(res == CHILD_PASS, "token loads through the other path"); + CHECK_TRUE(read_file(path, data, sizeof(data), &sz) == 0 && + sz == sizeof(stagedRecord) && + XMEMCMP(data, stagedRecord, sz) == 0, + "record listed by basename was put in place"); + CHECK_TRUE(read_file(fullPath, data, sizeof(data), &sz) == 0 && + sz == sizeof(stagedRecord) && + XMEMCMP(data, stagedRecord, sz) == 0, + "record listed by full path was put in place"); + (void)snprintf(staged, sizeof(staged), "%s.new", path); + CHECK_TRUE(access(staged, F_OK) != 0, "staged copy is gone"); + CHECK_TRUE(access(marker, F_OK) != 0, "commit marker is gone"); + } + cleanup_test_files(); +} + #ifdef DEBUG_WOLFPKCS11 /* Matches WP11_TEST_STORE_EXIT_CODE in wolfpkcs11/internal.h. */ #define STORE_EXIT_CODE 75 @@ -1527,6 +1794,9 @@ int main(int argc, char* argv[]) test_stored_policy_is_authenticated(); test_legitimate_updates_round_trip(); test_failed_store_keeps_state(); + test_failed_store_keeps_undecoded_keys(); + test_init_token_failure_on_fresh_store(); + test_recovery_ignores_dir_spelling(); #ifdef DEBUG_WOLFPKCS11 test_store_pass_is_atomic(); test_pin_change_pass_is_atomic(); From 30d51ce8413279cce91a97d3822c788246b8a7d3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:35:43 -0700 Subject: [PATCH 043/204] Cache the empty user PIN verdict when loading a token --- src/internal.c | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/internal.c b/src/internal.c index c4e044ed..aae20d98 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8377,7 +8377,35 @@ static int wp11_token_write_seed(void* storage, WP11_Token* token) } /** - * Load a token from storage. + * Check for an empty user PIN while loading, when the caller alone can reach + * the token, and cache the verdict as WP11_Slot_Has_Empty_Pin would. + * + * @param [in] slot Slot object. + * @param [in, out] token Token being loaded. + * @return 1 when the user PIN is set and empty, 0 otherwise. + */ +static int wp11_Token_HasEmptyPin(WP11_Slot* slot, WP11_Token* token) +{ + int ret = 0; + byte hash[PIN_HASH_SZ]; + + if ((token->tokenFlags & WP11_TOKEN_FLAG_USER_PIN_SET) != 0) { + if (HashPIN((char*)"", 0, token->userPinSeed, + sizeof(token->userPinSeed), hash, sizeof(hash), slot) == 0) { + ret = WP11_ConstantCompare(hash, token->userPin, + token->userPinLen) != 0; + } + token->userPinEmpty = ret ? 1 : 2; + wc_ForceZero(hash, sizeof(hash)); + } + + return ret; +} + +/** + * Load a token from storage. Runs without the slot lock from library + * initialization and under it from a token reset; neither its callees nor + * this function take the slot lock. * * @param [in, out] object Token object. * @param [in] tokenId Id of token this key belongs to. @@ -8529,7 +8557,7 @@ static int wp11_Token_Load(WP11_Slot* slot, int tokenId, WP11_Token* token) } /* If there is no pin, there is no login, so decode now */ - if (WP11_Slot_Has_Empty_Pin(slot) && (ret == 0)) { + if (ret == 0 && wp11_Token_HasEmptyPin(slot, token)) { #ifndef WOLFPKCS11_NO_STORE /* Derive token->key from empty PIN + seed before decoding */ ret = HashPIN((char*)"", 0, token->seed, sizeof(token->seed), @@ -9483,17 +9511,18 @@ int WP11_Slot_TokenReset(WP11_Slot* slot, char* pin, int pinLen, char* label) #ifdef WP11_STORE_BATCH /* Only a staged store pass is known to leave storage unchanged. */ if (ret != 0) { - /* Reset not stored: restore the token that is still in storage. */ + /* Reset not stored: restore the token that is still in storage, + * under the lock so no session sees it half loaded. */ XMEMSET(emptyLabel, 0, sizeof(emptyLabel)); WP11_Lock_LockRW(&slot->lock); wp11_Token_Final(token); - if (wp11_Token_Init(token, emptyLabel) == 0) { - WP11_Lock_UnlockRW(&slot->lock); - (void)wp11_Slot_Load(slot, (int)slot->id); - } - else { - WP11_Lock_UnlockRW(&slot->lock); + if (wp11_Token_Init(token, emptyLabel) == 0 && + wp11_Slot_Load(slot, (int)slot->id) != 0) { + /* Storage still holds the token; leave a blank one here. */ + wp11_Token_Final(token); + (void)wp11_Token_Init(token, emptyLabel); } + WP11_Lock_UnlockRW(&slot->lock); } #endif } From 3077146e316dbdb1b14dd8e6e60528c837129359 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:35:44 -0700 Subject: [PATCH 044/204] Re-encrypt only decoded objects on PIN change --- src/internal.c | 29 ++++++----- tests/object_policy_integrity_test.c | 76 ++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 13 deletions(-) diff --git a/src/internal.c b/src/internal.c index aae20d98..9c346229 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10365,8 +10365,9 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) int oldPinLen; int oldFlags; byte oldPinEmpty; +#endif +#ifndef WOLFPKCS11_NO_STORE WP11_Object* object; - int rebind = 0; #endif WP11_Lock_LockRW(&slot->lock); @@ -10417,16 +10418,18 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) /* Lock order is slot, token, then store: no object changes under * the pass, as in C_CreateObject. */ WP11_Lock_LockRW(&token->lock); - #ifdef WP11_STORE_BATCH - for (object = token->object; object != NULL; object = object->next) { - /* Only decoded key data can be rebound by the pass. */ - object->rebound = object->unbound && !object->encoded && - !object->undecoded && + /* Decoded key data is re-encrypted under the new key now, so the pass + * writes records that open with the new PIN; a logout is not relied + * on. Key data never decoded here cannot be, and is left alone. */ + for (object = token->object; ret == 0 && object != NULL; + object = object->next) { + object->rebound = !object->encoded && !object->undecoded && wp11_Object_IsEncrypted(object); - rebind |= object->rebound; + if (object->rebound) + ret = wp11_Object_Encode(object, 0); } - #endif - ret = wp11_Token_Store(token, (int)slot->id); + if (ret == 0) + ret = wp11_Token_Store(token, (int)slot->id); #ifdef WP11_STORE_BATCH if (ret != 0) { /* Keep the PIN and key that are still in storage in effect. */ @@ -10438,10 +10441,10 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) token->tokenFlags = oldFlags; token->userPinEmpty = oldPinEmpty; } - for (object = token->object; rebind && object != NULL; - object = object->next) { - /* An object the failed pass rebound is under the discarded key. */ - if (ret != 0 && object->rebound && !object->unbound && + for (object = token->object; object != NULL; object = object->next) { + /* Key data re-encrypted for the failed pass goes back under the + * key that stays in effect. */ + if (ret != 0 && object->rebound && wp11_Object_Encode(object, 0) != 0) { WOLFPKCS11_MSG("SetUserPin: failed to re-encrypt object"); } diff --git a/tests/object_policy_integrity_test.c b/tests/object_policy_integrity_test.c index fc659127..dd98ba31 100644 --- a/tests/object_policy_integrity_test.c +++ b/tests/object_policy_integrity_test.c @@ -1444,6 +1444,81 @@ static void test_failed_store_keeps_undecoded_keys(void) cleanup_test_files(); } +/* Change the user PIN and end without logging out or finalizing. */ +static int child_change_pin_and_exit(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 1, &session); + if (rv == CKR_OK) { + rv = funcList->C_SetPIN(session, userPin, sizeof(userPin) - 1, + otherUserPin, sizeof(otherUserPin) - 1); + } + if (rv != CKR_OK) { + fprintf(stderr, " PIN change failed: 0x%lx\n", (unsigned long)rv); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +/* The key works after logging in with the new PIN. */ +static int child_key_under_new_pin(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + byte out[BLOCK_SZ]; + + if (pkcs11_load() != CKR_OK) + return CHILD_SETUP; + rv = init_slot(&slot); + if (rv == CKR_OK) + rv = open_session(slot, 0, &session); + if (rv == CKR_OK) { + rv = funcList->C_Login(session, CKU_USER, otherUserPin, + sizeof(otherUserPin) - 1); + } + if (rv == CKR_OK) + rv = find_key(session, &obj); + if (rv == CKR_OK) + rv = aes_encrypt_block(session, obj, out); + if (rv == CKR_OK && XMEMCMP(out, refCipher, sizeof(out)) != 0) + rv = CKR_FUNCTION_FAILED; + (void)funcList->C_Finalize(NULL); + if (rv != CKR_OK) { + fprintf(stderr, " key not usable under the new PIN: 0x%lx\n", + (unsigned long)rv); + return CHILD_FAIL; + } + return CHILD_PASS; +} + +/* The store pass of a PIN change must itself leave every key usable under + * the new PIN, without a later logout or finalize rewriting the records. */ +static void test_pin_change_is_complete_when_stored(void) +{ + CK_RV rv; + + printf("\n--- a stored PIN change needs no logout to take effect ---\n"); + set_aes_check(); + rv = store_objects(create_aes_key); + CHECK_RV(rv, "store AES key", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(run_in_child(child_change_pin_and_exit) == CHILD_PASS, + "PIN change stored"); + CHECK_TRUE(run_in_child(child_key_under_new_pin) == CHILD_PASS, + "key usable under the new PIN after an exit"); + } + cleanup_test_files(); +} + /* A token init whose store fails leaves nothing behind, and a retry works. */ static int child_init_token_retry(void) { @@ -1795,6 +1870,7 @@ int main(int argc, char* argv[]) test_legitimate_updates_round_trip(); test_failed_store_keeps_state(); test_failed_store_keeps_undecoded_keys(); + test_pin_change_is_complete_when_stored(); test_init_token_failure_on_fresh_store(); test_recovery_ignores_dir_spelling(); #ifdef DEBUG_WOLFPKCS11 From bdcdd996b1eb02f4eea5485fb2425d7f94583a0f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:35:45 -0700 Subject: [PATCH 045/204] Tidy store batch comments and PIN change guards --- src/internal.c | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/internal.c b/src/internal.c index 9c346229..7b3b5584 100644 --- a/src/internal.c +++ b/src/internal.c @@ -4159,8 +4159,7 @@ static int wp11_DecryptData(byte* out, byte* data, int len, byte* key, return ret; } -/* Flags left out of the bound policy: set by the SO, who never holds the - * user's key, or by the library itself. */ +/* Left out of the bound policy: set by the SO or the library, not the user. */ #ifdef WOLFPKCS11_TPM #define WP11_FLAG_POLICY_EXCLUDED (WP11_FLAG_TRUSTED | WP11_FLAG_TPM) #else @@ -8429,8 +8428,7 @@ static int wp11_Token_Load(WP11_Slot* slot, int tokenId, WP11_Token* token) ret = 0; #ifdef WP11_STORE_BATCH - /* Finish or drop a store pass that a crash interrupted, then keep other - * store passes out until every record of the token has been read. */ + /* Finish or drop an interrupted pass, then keep passes out until read. */ ret = wolfPKCS11_StoreBatchCheck(tokenId, &batchHeld); #endif /* Open access to token object. */ @@ -9511,8 +9509,7 @@ int WP11_Slot_TokenReset(WP11_Slot* slot, char* pin, int pinLen, char* label) #ifdef WP11_STORE_BATCH /* Only a staged store pass is known to leave storage unchanged. */ if (ret != 0) { - /* Reset not stored: restore the token that is still in storage, - * under the lock so no session sees it half loaded. */ + /* Reload under the lock so no session sees a half loaded token. */ XMEMSET(emptyLabel, 0, sizeof(emptyLabel)); WP11_Lock_LockRW(&slot->lock); wp11_Token_Final(token); @@ -10312,8 +10309,7 @@ int WP11_Slot_SetSOPin(WP11_Slot* slot, char* pin, int pinLen) token->soPinLen = sizeof(token->soPin); token->tokenFlags |= WP11_TOKEN_FLAG_SO_PIN_SET; #ifndef WOLFPKCS11_NO_STORE - /* Lock order is slot, token, then store: no object changes under - * the pass, as in C_CreateObject. */ + /* Order is slot, token, storeLock: no object changes under the pass. */ WP11_Lock_LockRW(&token->lock); ret = wp11_Token_Store(token, (int)slot->id); #ifdef WP11_STORE_BATCH @@ -10365,8 +10361,6 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) int oldPinLen; int oldFlags; byte oldPinEmpty; -#endif -#ifndef WOLFPKCS11_NO_STORE WP11_Object* object; #endif @@ -10415,12 +10409,10 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) token->userPinLen = sizeof(token->userPin); token->tokenFlags |= WP11_TOKEN_FLAG_USER_PIN_SET; #ifndef WOLFPKCS11_NO_STORE - /* Lock order is slot, token, then store: no object changes under - * the pass, as in C_CreateObject. */ + /* Order is slot, token, storeLock: no object changes under the pass. */ WP11_Lock_LockRW(&token->lock); - /* Decoded key data is re-encrypted under the new key now, so the pass - * writes records that open with the new PIN; a logout is not relied - * on. Key data never decoded here cannot be, and is left alone. */ + #ifdef WP11_STORE_BATCH + /* A staged pass must write records that open with the new PIN. */ for (object = token->object; ret == 0 && object != NULL; object = object->next) { object->rebound = !object->encoded && !object->undecoded && @@ -10428,6 +10420,7 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) if (object->rebound) ret = wp11_Object_Encode(object, 0); } + #endif if (ret == 0) ret = wp11_Token_Store(token, (int)slot->id); #ifdef WP11_STORE_BATCH @@ -10442,8 +10435,7 @@ int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen) token->userPinEmpty = oldPinEmpty; } for (object = token->object; object != NULL; object = object->next) { - /* Key data re-encrypted for the failed pass goes back under the - * key that stays in effect. */ + /* Key data encrypted for the failed pass returns to the old key. */ if (ret != 0 && object->rebound && wp11_Object_Encode(object, 0) != 0) { WOLFPKCS11_MSG("SetUserPin: failed to re-encrypt object"); @@ -15009,7 +15001,8 @@ static int wp11_Object_PolicyChanged(WP11_Object* object, if (object->onToken && wp11_Object_IsEncrypted(object) && (object->objClass != prevClass || - ((object->opFlag ^ prevFlag) & ~(word32)WP11_FLAG_POLICY_EXCLUDED) != 0)) { + ((object->opFlag ^ prevFlag) & + ~(word32)WP11_FLAG_POLICY_EXCLUDED) != 0)) { if (object->encoded || object->undecoded) { object->objClass = prevClass; object->opFlag = prevFlag; From b62a9e2163b70c4285e0f181e73597fc08f18be9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:55:08 -0700 Subject: [PATCH 046/204] F-5763 - Release the replaced operation when a new one is initialized --- src/internal.c | 85 ++++- tests/include.am | 7 + tests/session_op_lifecycle_test.c | 519 ++++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 1 + 4 files changed, 600 insertions(+), 12 deletions(-) create mode 100644 tests/session_op_lifecycle_test.c diff --git a/src/internal.c b/src/internal.c index 7b3b5584..e70b9f9b 100644 --- a/src/internal.c +++ b/src/internal.c @@ -969,6 +969,7 @@ static int wp11_Slot_AddSession(WP11_Slot* slot, WP11_Session** session) return ret; } +static void wp11_Session_FreeOp(WP11_Session* session); static void wp11_Slot_UnlinkObject(WP11_Slot* slot, WP11_Object* object, WP11_Object** list); static void wp11_Slot_FreeUnlinkedObjects(WP11_Slot* slot); @@ -1008,6 +1009,17 @@ static void wp11_Session_Final(WP11_Session* session) /* Finalize any find. */ WP11_Session_FindFinal(session); + wp11_Session_FreeOp(session); +} + +/** + * Release the resources owned by the session's current operation and mark no + * operation as initialized. + * + * @param session [in] Session object. + */ +static void wp11_Session_FreeOp(WP11_Session* session) +{ #if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) if (session->mechanism == CKM_RSA_PKCS_OAEP && session->params.oaep.label != NULL) { @@ -1036,8 +1048,11 @@ static void wp11_Session_Final(WP11_Session* session) session->params.gcm.aad = NULL; } if (session->params.gcm.enc != NULL) { + wc_ForceZero(session->params.gcm.enc, + (word32)session->params.gcm.encSz); XFREE(session->params.gcm.enc, NULL, DYNAMIC_TYPE_TMP_BUFFER); session->params.gcm.enc = NULL; + session->params.gcm.encSz = 0; } #ifdef WOLFSSL_AESGCM_STREAM if (session->params.gcm.streamInit) { @@ -1097,6 +1112,17 @@ static void wp11_Session_Final(WP11_Session* session) session->init = 0; } +/** + * Abort the active operation on the session, releasing what it owns. + * + * @param session [in] Session object. + */ +void WP11_Session_AbortOp(WP11_Session* session) +{ + if (session->init != 0) + wp11_Session_FreeOp(session); +} + #ifndef WOLFPKCS11_NO_STORE #ifndef WOLFPKCS11_CUSTOM_STORE @@ -11190,6 +11216,8 @@ CK_MECHANISM_TYPE WP11_Session_GetMechanism(WP11_Session* session) void WP11_Session_SetMechanism(WP11_Session* session, CK_MECHANISM_TYPE mechanism) { + /* Committing a new operation ends any operation it replaces. */ + WP11_Session_AbortOp(session); session->mechanism = mechanism; } @@ -11313,14 +11341,22 @@ int WP11_Session_SetOaepParams(WP11_Session* session, CK_MECHANISM_TYPE hashAlg, { int ret; WP11_OaepParams* oaep = &session->params.oaep; + enum wc_HashType hashType; + int mgfType; + + ret = wp11_hash_type(hashAlg, &hashType); + if (ret == 0) + ret = wp11_mgf(mgf, &mgfType); + if (ret != 0) + return ret; + WP11_Session_AbortOp(session); if (session->mechanism == CKM_RSA_PKCS_OAEP && oaep->label != NULL) { XFREE(oaep->label, NULL, DYNAMIC_TYPE_TMP_BUFFER); } XMEMSET(oaep, 0, sizeof(*oaep)); - ret = wp11_hash_type(hashAlg, &oaep->hashType); - if (ret == 0) - ret = wp11_mgf(mgf, &oaep->mgf); + oaep->hashType = hashType; + oaep->mgf = mgfType; if (ret == 0 && label == NULL) { oaep->label = NULL; oaep->labelSz = 0; @@ -11356,13 +11392,19 @@ int WP11_Session_SetPssParams(WP11_Session* session, CK_MECHANISM_TYPE hashAlg, { int ret; WP11_PssParams* pss = &session->params.pss; + enum wc_HashType hashType; + int mgfType; - XMEMSET(pss, 0, sizeof(*pss)); - ret = wp11_hash_type(hashAlg, &pss->hashType); - if (ret == 0) - ret = wp11_mgf(mgf, &pss->mgf); + ret = wp11_hash_type(hashAlg, &hashType); if (ret == 0) + ret = wp11_mgf(mgf, &mgfType); + if (ret == 0) { + WP11_Session_AbortOp(session); + XMEMSET(pss, 0, sizeof(*pss)); + pss->hashType = hashType; + pss->mgf = mgfType; pss->saltLen = sLen; + } return ret; } @@ -11383,7 +11425,8 @@ int WP11_Session_SetMldsaParams(WP11_Session* session, CK_VOID_PTR params, CK_ULONG paramsLen) { int ret = 0; - WP11_MldsaParams* mldsa = &session->params.mldsa; + WP11_MldsaParams newParams; + WP11_MldsaParams* mldsa = &newParams; XMEMSET(mldsa, 0, sizeof(*mldsa)); @@ -11445,6 +11488,11 @@ int WP11_Session_SetMldsaParams(WP11_Session* session, CK_VOID_PTR params, mldsa->ctxSz = 0; } + if (ret == 0) { + WP11_Session_AbortOp(session); + XMEMCPY(&session->params.mldsa, mldsa, sizeof(*mldsa)); + } + return ret; } #endif /* WOLFPKCS11_MLDSA */ @@ -11472,6 +11520,7 @@ int WP11_Session_SetCbcParams(WP11_Session* session, unsigned char* iv, * at allocation, so a prior operation can leave stale multi-part streaming * state here. Reset it before use (as the other Set*Params routines do) so * a fresh CBC operation cannot inherit a bogus partial-block count. */ + WP11_Session_AbortOp(session); cbc->partialSz = 0; cbc->finalReady = 0; XMEMSET(cbc->partial, 0, sizeof(cbc->partial)); @@ -11522,6 +11571,7 @@ int WP11_Session_SetCtrParams(WP11_Session* session, CK_ULONG ulCounterBits, if (ulCounterBits > 128 || ulCounterBits == 0) return BAD_FUNC_ARG; + WP11_Session_AbortOp(session); XMEMSET(ctr, 0, sizeof(*ctr)); ret = wc_AesInit(&ctr->aes, NULL, object->devId); if (ret == 0) { @@ -11549,6 +11599,7 @@ int WP11_Session_SetAesWrapParams(WP11_Session* session, byte* iv, word32 ivLen, WP11_KeyWrapParams *wrap = &session->params.kw; WP11_Data *key; + WP11_Session_AbortOp(session); XMEMSET(wrap, 0, sizeof(*wrap)); ret = wc_AesInit(&wrap->aes, NULL, object->devId); if (ret == 0) { @@ -11604,6 +11655,7 @@ int WP11_Session_SetGcmParams(WP11_Session* session, unsigned char* iv, ret = BAD_FUNC_ARG; if (ret == 0) { + WP11_Session_AbortOp(session); if (session->mechanism == CKM_AES_GCM && gcm->aad != NULL) { XFREE(gcm->aad, NULL, DYNAMIC_TYPE_TMP_BUFFER); } @@ -11664,6 +11716,7 @@ int WP11_Session_SetCcmParams(WP11_Session* session, int dataSz, ret = BAD_FUNC_ARG; if (ret == 0) { + WP11_Session_AbortOp(session); if (session->mechanism == CKM_AES_CCM && ccm->aad != NULL) { XFREE(ccm->aad, NULL, DYNAMIC_TYPE_TMP_BUFFER); } @@ -11699,6 +11752,7 @@ int WP11_Session_SetCtsParams(WP11_Session* session, unsigned char* iv, WP11_CtsParams* cts = &session->params.cts; WP11_Data* key; + WP11_Session_AbortOp(session); /* AES object on session. */ ret = wc_AesInit(&cts->aes, NULL, object->devId); if (ret == 0) { @@ -19621,6 +19675,7 @@ int WP11_Aes_Cmac_Init(WP11_Object* secret, WP11_Session* session, if (sigLen > WC_CMAC_TAG_MAX_SZ || sigLen < WC_CMAC_TAG_MIN_SZ) return BAD_FUNC_ARG; + WP11_Session_AbortOp(session); cmac->sigLen = (byte)sigLen; if (secret->onToken) WP11_Lock_LockRO(secret->lock); @@ -19805,6 +19860,7 @@ int WP11_Digest_Init(CK_MECHANISM_TYPE mechanism, WP11_Session* session) ret = wp11_digest_hash_type(mechanism, &hashType); if (ret == 0) { + WP11_Session_AbortOp(session); digest->hashType = (enum wc_HashType)hashType; ret = wc_HashInit(&digest->hash, (enum wc_HashType)hashType); } @@ -20031,12 +20087,14 @@ int WP11_Hmac_Init(CK_MECHANISM_TYPE mechanism, WP11_Object* secret, WP11_Data* key; ret = wp11_hmac_hash_type(mechanism, &hashType); - if (ret == 0) - hmac->hmacSz = wc_HmacSizeByType(hashType); - if (ret == 0 && digestSize != 0 && hmac->hmacSz != (word32)digestSize) + if (ret == 0 && digestSize != 0 && + (word32)wc_HmacSizeByType(hashType) != (word32)digestSize) ret = BAD_FUNC_ARG; - if (ret == 0) + if (ret == 0) { + WP11_Session_AbortOp(session); + hmac->hmacSz = wc_HmacSizeByType(hashType); ret = wc_HmacInit(&hmac->hmac, NULL, secret->slot->devId); + } if (ret == 0) { if (secret->onToken) WP11_Lock_LockRO(secret->lock); @@ -20187,6 +20245,9 @@ int WP11_TLS_MAC_init(unsigned long hashType, unsigned long macLen, byte server, int ret = 0; WP11_TlsMacParams *mac = &session->params.tlsMac; + if (hashType != CKM_TLS_PRF && MechToMac(hashType) == no_mac) + return BAD_FUNC_ARG; + WP11_Session_AbortOp(session); XMEMSET(mac, 0, sizeof(*mac)); if (hashType == CKM_TLS_PRF) { mac->isTlsPrf = 1; diff --git a/tests/include.am b/tests/include.am index 0a7581e7..7e7ec4e6 100644 --- a/tests/include.am +++ b/tests/include.am @@ -326,6 +326,11 @@ tests_object_policy_integrity_test_SOURCES = \ tests/object_policy_integrity_test.c tests_object_policy_integrity_test_LDADD = +check_PROGRAMS += tests/session_op_lifecycle_test +noinst_PROGRAMS += tests/session_op_lifecycle_test +tests_session_op_lifecycle_test_SOURCES = tests/session_op_lifecycle_test.c +tests_session_op_lifecycle_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -392,6 +397,7 @@ tests_logout_retire_test_LDADD += src/libwolfpkcs11.la tests_login_pin_state_test_LDADD += src/libwolfpkcs11.la tests_object_store_decode_test_LDADD += src/libwolfpkcs11.la tests_object_policy_integrity_test_LDADD += src/libwolfpkcs11.la +tests_session_op_lifecycle_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -450,6 +456,7 @@ tests_logout_retire_test_LDADD += src/libwolfpkcs11.la tests_login_pin_state_test_LDADD += src/libwolfpkcs11.la tests_object_store_decode_test_LDADD += src/libwolfpkcs11.la tests_object_policy_integrity_test_LDADD += src/libwolfpkcs11.la +tests_session_op_lifecycle_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ diff --git a/tests/session_op_lifecycle_test.c b/tests/session_op_lifecycle_test.c new file mode 100644 index 00000000..95e425d7 --- /dev/null +++ b/tests/session_op_lifecycle_test.c @@ -0,0 +1,519 @@ +/* session_op_lifecycle_test.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Every way a session operation ends (completion, error, replacement or + * session close) must release and scrub the state the operation owned. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#define TEST_DIR "./store/session_op_lifecycle_test" + +#if defined(USE_WOLFSSL_MEMORY) && !defined(NO_WOLFSSL_MEMORY) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_DEBUG_MEMORY) + #define TRACK_ALLOCS +#endif + +/* Fixtures and helpers are shared by tests that compile in different builds. */ +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable: 4101 4189 4505) +#elif defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-variable" + #pragma GCC diagnostic ignored "-Wunused-function" +#endif + +static CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; +static CK_KEY_TYPE aesType = CKK_AES; +static CK_KEY_TYPE genericType = CKK_GENERIC_SECRET; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; + +/* Uniform key bytes so the value is recognisable in any key schedule. */ +static byte aesKeyValue[16] = { + 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, + 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3 +}; +static byte macKeyValue[32] = { + 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, + 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, + 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, + 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C +}; +static byte gcmIv[12] = { 0 }; +static byte gcmAad[48] = { 0 }; +static byte plainMarker[32] = { + 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, + 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, + 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, + 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B +}; + +#ifdef TRACK_ALLOCS +/* Allocation hooks: count live blocks and look for a watched byte pattern in + * every block handed back to the allocator. */ +#define ALLOC_HDR_SZ 16 + +static long liveBlocks = 0; +static const byte* watchData = NULL; +static size_t watchLen = 0; +static int watchHit = 0; + +static void track_scan(const byte* p, size_t sz) +{ + size_t i; + + if (watchData == NULL || watchLen == 0 || sz < watchLen) + return; + for (i = 0; i + watchLen <= sz; i++) { + if (XMEMCMP(p + i, watchData, watchLen) == 0) { + watchHit = 1; + return; + } + } +} + +static void* track_malloc(size_t sz) +{ + byte* p = (byte*)malloc(sz + ALLOC_HDR_SZ); + + if (p == NULL) + return NULL; + /* Zeroed so a scan only sees bytes the library wrote. */ + XMEMSET(p, 0, sz + ALLOC_HDR_SZ); + XMEMCPY(p, &sz, sizeof(sz)); + liveBlocks++; + return p + ALLOC_HDR_SZ; +} + +static void track_free(void* ptr) +{ + byte* p; + size_t sz; + + if (ptr == NULL) + return; + p = (byte*)ptr - ALLOC_HDR_SZ; + XMEMCPY(&sz, p, sizeof(sz)); + track_scan((byte*)ptr, sz); + liveBlocks--; + free(p); +} + +static void* track_realloc(void* ptr, size_t sz) +{ + void* n; + size_t oldSz; + + if (ptr == NULL) + return track_malloc(sz); + if (sz == 0) { + track_free(ptr); + return NULL; + } + n = track_malloc(sz); + if (n == NULL) + return NULL; + XMEMCPY(&oldSz, (byte*)ptr - ALLOC_HDR_SZ, sizeof(oldSz)); + XMEMCPY(n, ptr, (oldSz < sz) ? oldSz : sz); + track_free(ptr); + return n; +} + +static void watch_start(const byte* data, size_t len) +{ + watchData = data; + watchLen = len; + watchHit = 0; +} + +static int watch_stop(void) +{ + watchData = NULL; + watchLen = 0; + return watchHit; +} +#endif /* TRACK_ALLOCS */ + +static CK_RV create_secret(CK_SESSION_HANDLE session, CK_KEY_TYPE* type, + byte* value, CK_ULONG valueLen, + CK_OBJECT_HANDLE* key) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, type, sizeof(*type) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, value, valueLen }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), key); +} + +#if !defined(NO_RSA) && (!defined(WC_NO_RSA_OAEP) || defined(WC_RSA_PSS)) +static CK_RV create_rsa_public(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key) +{ + static CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + static CK_KEY_TYPE rsaType = CKK_RSA; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), key); +} +#endif + +#ifdef HAVE_ECC +static CK_RV create_ec_public(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* key) +{ + static CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + static CK_KEY_TYPE ecType = CKK_EC; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_EC_POINT, ecc_p256_pub, sizeof(ecc_p256_pub) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), key); +} +#endif + +static CK_RV init_library(CK_SLOT_ID* slot) +{ + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_ULONG slotCount = 1; + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + if (rv == CKR_OK) + rv = funcList->C_GetSlotList(CK_TRUE, slot, &slotCount); + if (rv == CKR_OK && slotCount == 0) + rv = CKR_TOKEN_NOT_PRESENT; + return rv; +} + +static CK_RV open_with_keys(CK_SLOT_ID slot, CK_SESSION_HANDLE* session, + CK_OBJECT_HANDLE* aesKey, + CK_OBJECT_HANDLE* macKey) +{ + CK_RV rv; + + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, session); + if (rv == CKR_OK) { + rv = create_secret(*session, &aesType, aesKeyValue, + sizeof(aesKeyValue), aesKey); + } + if (rv == CKR_OK) { + rv = create_secret(*session, &genericType, macKeyValue, + sizeof(macKeyValue), macKey); + } + return rv; +} + +#if defined(_MSC_VER) + #pragma warning(pop) +#elif defined(__GNUC__) + #pragma GCC diagnostic pop +#endif + +#if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ + !defined(NO_HMAC) && !defined(NO_SHA256) +/* Starting an operation of another category must release the resources held + * by the operation it replaces. */ +static void test_cross_category_init_releases_state(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + CK_GCM_PARAMS gcmParams; + CK_MECHANISM gcmMech = { CKM_AES_GCM, &gcmParams, sizeof(gcmParams) }; + CK_MECHANISM hmacMech = { CKM_SHA256_HMAC, NULL, 0 }; + CK_MECHANISM sha256Mech = { CKM_SHA256, NULL, 0 }; +#if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) + CK_OBJECT_HANDLE rsaKey = CK_INVALID_HANDLE; + byte label[24] = { 0 }; + CK_RSA_PKCS_OAEP_PARAMS oaepParams = { + CKM_SHA256, CKG_MGF1_SHA256, CKZ_DATA_SPECIFIED, label, sizeof(label) + }; + CK_MECHANISM oaepMech = { CKM_RSA_PKCS_OAEP, &oaepParams, + sizeof(oaepParams) }; +#endif +#ifdef HAVE_ECC + CK_OBJECT_HANDLE ecKey = CK_INVALID_HANDLE; + CK_MECHANISM ecdsaMech = { CKM_ECDSA, NULL, 0 }; +#endif + byte out[64]; + CK_ULONG outLen; + CK_SLOT_ID slot; + long before; + + printf("\n--- replacing an operation releases its state ---\n"); + XMEMSET(&gcmParams, 0, sizeof(gcmParams)); + gcmParams.pIv = gcmIv; + gcmParams.ulIvLen = sizeof(gcmIv); + gcmParams.ulIvBits = sizeof(gcmIv) * 8; + gcmParams.pAAD = gcmAad; + gcmParams.ulAADLen = sizeof(gcmAad); + gcmParams.ulTagBits = 128; + + rv = init_library(&slot); + CHECK_RV(rv, "initialize library", CKR_OK); + if (rv != CKR_OK) + goto out; + before = liveBlocks; + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = funcList->C_EncryptInit(session, &gcmMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-GCM with AAD)", CKR_OK); + rv = funcList->C_VerifyInit(session, &hmacMech, macKey); + CHECK_RV(rv, "C_VerifyInit(HMAC) over active encrypt", CKR_OK); + + rv = funcList->C_DecryptInit(session, &gcmMech, aesKey); + CHECK_RV(rv, "C_DecryptInit(AES-GCM with AAD)", CKR_OK); + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit over active decrypt", CKR_OK); + + rv = funcList->C_SignInit(session, &hmacMech, macKey); + CHECK_RV(rv, "C_SignInit(HMAC) over active digest", CKR_OK); + rv = funcList->C_EncryptInit(session, &gcmMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-GCM) over active sign", CKR_OK); + rv = funcList->C_SignInit(session, &hmacMech, macKey); + CHECK_RV(rv, "C_SignInit(HMAC) over active encrypt", CKR_OK); + + rv = funcList->C_EncryptInit(session, &gcmMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-GCM) over active sign", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_EncryptUpdate(session, plainMarker, sizeof(plainMarker), + out, &outLen); + CHECK_RV(rv, "C_EncryptUpdate(AES-GCM)", CKR_OK); + watch_start(plainMarker, sizeof(plainMarker)); + rv = funcList->C_SignInit(session, &hmacMech, macKey); + CHECK_RV(rv, "C_SignInit(HMAC) over multi-part encrypt", CKR_OK); + CHECK_TRUE(!watch_stop(), + "replaced operation scrubs buffered input before release"); + +#ifdef HAVE_ECC + rv = create_ec_public(session, &ecKey); + CHECK_RV(rv, "C_CreateObject(EC public)", CKR_OK); + rv = funcList->C_EncryptInit(session, &gcmMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-GCM) over active sign", CKR_OK); + rv = funcList->C_VerifyInit(session, &ecdsaMech, ecKey); + CHECK_RV(rv, "C_VerifyInit(ECDSA) over active encrypt", CKR_OK); + rv = funcList->C_DestroyObject(session, ecKey); + CHECK_RV(rv, "C_DestroyObject(EC)", CKR_OK); +#endif + +#if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) + rv = create_rsa_public(session, &rsaKey); + CHECK_RV(rv, "C_CreateObject(RSA public)", CKR_OK); + rv = funcList->C_EncryptInit(session, &oaepMech, rsaKey); + CHECK_RV(rv, "C_EncryptInit(RSA-OAEP with label)", CKR_OK); + rv = funcList->C_DecryptInit(session, &gcmMech, aesKey); + CHECK_RV(rv, "C_DecryptInit(AES-GCM) over active encrypt", CKR_OK); + rv = funcList->C_SignInit(session, &hmacMech, macKey); + CHECK_RV(rv, "C_SignInit(HMAC) over active decrypt", CKR_OK); + rv = funcList->C_DestroyObject(session, rsaKey); + CHECK_RV(rv, "C_DestroyObject(RSA)", CKR_OK); +#endif + + rv = funcList->C_DestroyObject(session, aesKey); + CHECK_RV(rv, "C_DestroyObject(AES)", CKR_OK); + rv = funcList->C_DestroyObject(session, macKey); + CHECK_RV(rv, "C_DestroyObject(HMAC)", CKR_OK); + funcList->C_CloseSession(session); + session = CK_INVALID_HANDLE; + CHECK_TRUE(liveBlocks == before, + "replaced operations leave no allocation behind"); + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + +#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(HAVE_ECC) +/* A request that is rejected before it can start must leave the operation + * already active on the session untouched. */ +static void test_rejected_init_keeps_active_operation(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + CK_MECHANISM cbcMech = { CKM_AES_CBC, NULL, 0 }; + CK_MECHANISM ecdsaMech = { CKM_ECDSA, NULL, 0 }; + CK_MECHANISM badMech = { CKM_VENDOR_DEFINED, NULL, 0 }; +#if !defined(NO_HMAC) && !defined(NO_SHA256) + CK_ULONG badMacLen = 5; + CK_MECHANISM hmacMech = { CKM_SHA256_HMAC, &badMacLen, + sizeof(badMacLen) }; +#endif +#if !defined(NO_RSA) && defined(WC_RSA_PSS) + CK_OBJECT_HANDLE rsaKey = CK_INVALID_HANDLE; + CK_RSA_PKCS_PSS_PARAMS pssParams = { CKM_VENDOR_DEFINED, CKG_MGF1_SHA256, + 32 }; + CK_MECHANISM pssMech = { CKM_RSA_PKCS_PSS, &pssParams, + sizeof(pssParams) }; +#endif + byte iv[16] = { 0 }; + byte out[32]; + CK_ULONG outLen; + CK_SLOT_ID slot; + + printf("\n--- a rejected Init keeps the active operation ---\n"); + cbcMech.pParameter = iv; + cbcMech.ulParameterLen = sizeof(iv); + + rv = init_library(&slot); + CHECK_RV(rv, "initialize library", CKR_OK); + if (rv != CKR_OK) + goto out; + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = funcList->C_EncryptInit(session, &cbcMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-CBC)", CKR_OK); + rv = funcList->C_VerifyInit(session, &ecdsaMech, macKey); + CHECK_RV(rv, "C_VerifyInit(ECDSA) with a secret key", + CKR_KEY_TYPE_INCONSISTENT); + rv = funcList->C_SignInit(session, &badMech, macKey); + CHECK_RV(rv, "C_SignInit(unknown mechanism)", CKR_MECHANISM_INVALID); +#if !defined(NO_HMAC) && !defined(NO_SHA256) + rv = funcList->C_SignInit(session, &hmacMech, macKey); + CHECK_RV(rv, "C_SignInit(HMAC with bad output length)", + CKR_MECHANISM_PARAM_INVALID); +#endif +#if !defined(NO_RSA) && defined(WC_RSA_PSS) + rv = create_rsa_public(session, &rsaKey); + CHECK_RV(rv, "C_CreateObject(RSA public)", CKR_OK); + rv = funcList->C_VerifyInit(session, &pssMech, rsaKey); + CHECK_RV(rv, "C_VerifyInit(RSA-PSS with bad hash)", + CKR_MECHANISM_PARAM_INVALID); +#endif + + outLen = sizeof(out); + rv = funcList->C_EncryptUpdate(session, plainMarker, sizeof(plainMarker), + out, &outLen); + CHECK_RV(rv, "C_EncryptUpdate after rejected Inits", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_EncryptFinal(session, out, &outLen); + CHECK_RV(rv, "C_EncryptFinal after rejected Inits", CKR_OK); + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + +static int run_test(void) +{ + CK_RV rv; + +#ifdef TRACK_ALLOCS + if (wolfSSL_SetAllocators(track_malloc, track_free, track_realloc) != 0) { + CHECK_TRUE(0, "install allocation hooks"); + return -1; + } +#endif + + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv != CKR_OK) + return -1; + +#if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ + !defined(NO_HMAC) && !defined(NO_SHA256) + test_cross_category_init_releases_state(); +#endif +#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(HAVE_ECC) + test_rejected_init_keeps_active_operation(); +#endif + + pkcs11_unload(); + return 0; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + + printf("=== wolfPKCS11 session operation lifecycle test ===\n"); + run_test(); + return pkcs11_test_summary(); +} diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 279755c5..2f4848cf 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -459,6 +459,7 @@ WP11_LOCAL void WP11_Session_FreeData(WP11_Session *session); WP11_LOCAL int WP11_Session_IsHashOpInitialized(WP11_Session* session, int mechanism); WP11_LOCAL enum wc_HashType WP11_Session_ToHashType(WP11_Session* session); WP11_LOCAL void WP11_Session_SetOpInitialized(WP11_Session* session, int init); +WP11_LOCAL void WP11_Session_AbortOp(WP11_Session* session); WP11_LOCAL WP11_Slot* WP11_Session_GetSlot(WP11_Session* session); WP11_LOCAL CK_SLOT_ID WP11_Session_GetSlotId(WP11_Session* session); WP11_LOCAL CK_MECHANISM_TYPE WP11_Session_GetMechanism(WP11_Session* session); From a7b373acc33b348ae93a027b9a49afec8b57a945 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:31:07 -0700 Subject: [PATCH 047/204] F-8664 - Release operation state on terminating error paths --- src/crypto.c | 120 +++++++++++++++--------------- src/internal.c | 2 + tests/session_op_lifecycle_test.c | 88 ++++++++++++++++++++++ 3 files changed, 150 insertions(+), 60 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index a301c4d2..5f628d3b 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -2890,7 +2890,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CBC_ENC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulDataLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -2914,12 +2914,12 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, return CKR_OPERATION_NOT_INITIALIZED; } if (!CK_ULONG_FITS_WORD32(ulDataLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } /* Ensure padded result fits in word32 */ if (ulDataLen > (CK_ULONG)(0xFFFFFFFF - AES_BLOCK_SIZE)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -2967,7 +2967,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_GCM_ENC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulDataLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -2992,7 +2992,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CCM_ENC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulDataLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -3017,7 +3017,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_ECB_ENC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulDataLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -3061,7 +3061,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_KEYWRAP_ENC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulDataLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -3086,7 +3086,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_KEYWRAP_ENC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulDataLen) || ulDataLen == 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -3116,7 +3116,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, (void)encDataLen; (void)ulDataLen; (void)pEncryptedData; - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_MECHANISM_INVALID; } @@ -3211,7 +3211,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, return rv; } if (!CK_ULONG_FITS_WORD32(ulPartLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -3233,7 +3233,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, pEncryptedPart, &encPartLen, session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); rv = CKR_FUNCTION_FAILED; WOLFPKCS11_LEAVE("C_EncryptUpdate", rv); return rv; @@ -3246,7 +3246,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, return CKR_OPERATION_NOT_INITIALIZED; } if (!CK_ULONG_FITS_WORD32(ulPartLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -3262,7 +3262,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, ret = WP11_AesCbcPad_EncryptUpdate(pPart, (int)ulPartLen, pEncryptedPart, &encPartLen, session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulEncryptedPartLen = encPartLen; @@ -3288,7 +3288,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, return CKR_DATA_LEN_RANGE; } if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulEncryptedPartLen = encPartLen; @@ -3311,7 +3311,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, pEncryptedPart, &encPartLen, obj, session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulEncryptedPartLen = encPartLen; @@ -3333,7 +3333,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, if (ret == BUFFER_E) return CKR_BUFFER_TOO_SMALL; if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulEncryptedPartLen = encPartLen; @@ -3345,7 +3345,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, (void)ret; (void)ulPartLen; (void)pEncryptedPart; - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); rv = CKR_MECHANISM_INVALID; WOLFPKCS11_LEAVE("C_EncryptUpdate", rv); return rv; @@ -3441,7 +3441,7 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, ret = WP11_AesCbc_EncryptFinal(session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } break; @@ -3462,7 +3462,7 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, ret = WP11_AesCbcPad_EncryptFinal(pLastEncryptedPart, &encPartLen, session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } break; @@ -3479,7 +3479,7 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, ret = WP11_AesCtr_Final(session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulLastEncryptedPartLen = 0; @@ -3501,7 +3501,7 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, ret = WP11_AesGcm_EncryptFinal(pLastEncryptedPart, &encPartLen, obj, session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulLastEncryptedPartLen = encPartLen; @@ -3523,7 +3523,7 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, if (ret == BUFFER_E) return CKR_BUFFER_TOO_SMALL; if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulLastEncryptedPartLen = encPartLen; @@ -3534,7 +3534,7 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, (void)encPartLen; (void)ret; (void)pLastEncryptedPart; - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_MECHANISM_INVALID; } @@ -3974,7 +3974,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CBC_DEC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -3998,7 +3998,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, return CKR_OPERATION_NOT_INITIALIZED; } if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -4063,7 +4063,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, return CKR_OPERATION_NOT_INITIALIZED; if (ulEncryptedDataLen < (CK_ULONG)WP11_AesGcm_GetTagBits(session) / 8) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_ENCRYPTED_DATA_LEN_RANGE; } decDataLen = (word32)ulEncryptedDataLen - @@ -4088,7 +4088,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, return CKR_OPERATION_NOT_INITIALIZED; if (ulEncryptedDataLen < (CK_ULONG)WP11_AesCcm_GetMacLen(session)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_ENCRYPTED_DATA_LEN_RANGE; } decDataLen = (word32)ulEncryptedDataLen - @@ -4112,7 +4112,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_ECB_DEC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -4161,7 +4161,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, /* AES Key Wrap ciphertext is at least two semiblocks: one data * semiblock plus the 8-byte integrity check value. */ if (ulEncryptedDataLen < 2 * KEYWRAP_BLOCK_SIZE) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_ENCRYPTED_DATA_LEN_RANGE; } decDataLen = (word32)(ulEncryptedDataLen - KEYWRAP_BLOCK_SIZE); @@ -4187,7 +4187,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, * after unwrapping; the upper bound is ciphertext - 8. */ if (ulEncryptedDataLen < 2 * KEYWRAP_BLOCK_SIZE || (ulEncryptedDataLen % KEYWRAP_BLOCK_SIZE) != 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_ENCRYPTED_DATA_LEN_RANGE; } decDataLen = (word32)(ulEncryptedDataLen - KEYWRAP_BLOCK_SIZE); @@ -4220,7 +4220,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, (void)ret; (void)ulEncryptedDataLen; (void)pData; - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_MECHANISM_INVALID; } @@ -4307,7 +4307,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CBC_DEC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulEncryptedPartLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -4325,7 +4325,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, (int)ulEncryptedPartLen, pPart, &decPartLen, session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulPartLen = decPartLen; @@ -4336,7 +4336,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, return CKR_OPERATION_NOT_INITIALIZED; } if (!CK_ULONG_FITS_WORD32(ulEncryptedPartLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -4363,7 +4363,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, return CKR_BUFFER_TOO_SMALL; } if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulPartLen = decPartLen; @@ -4389,7 +4389,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, return CKR_DATA_LEN_RANGE; } if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulPartLen = decPartLen; @@ -4407,7 +4407,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, ret = WP11_AesGcm_DecryptUpdate(pEncryptedPart, (int)ulEncryptedPartLen, session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } break; @@ -4428,7 +4428,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, if (ret == BUFFER_E) return CKR_BUFFER_TOO_SMALL; if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulPartLen = decPartLen; @@ -4440,7 +4440,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, (void)ret; (void)ulEncryptedPartLen; (void)pPart; - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_MECHANISM_INVALID; } @@ -4533,7 +4533,7 @@ static CK_RV wp11_C_DecryptFinal(CK_SESSION_HANDLE hSession, ret = WP11_AesCbc_DecryptFinal(session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } break; @@ -4568,7 +4568,7 @@ static CK_RV wp11_C_DecryptFinal(CK_SESSION_HANDLE hSession, return CKR_BUFFER_TOO_SMALL; } if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulLastPartLen = decPartLen; @@ -4586,7 +4586,7 @@ static CK_RV wp11_C_DecryptFinal(CK_SESSION_HANDLE hSession, ret = WP11_AesCtr_Final(session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulLastPartLen = 0; @@ -4599,7 +4599,7 @@ static CK_RV wp11_C_DecryptFinal(CK_SESSION_HANDLE hSession, if (WP11_AesGcm_EncDataLen(session) < WP11_AesGcm_GetTagBits(session) / 8) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_ENCRYPTED_DATA_LEN_RANGE; } decPartLen = WP11_AesGcm_EncDataLen(session) - @@ -4614,7 +4614,7 @@ static CK_RV wp11_C_DecryptFinal(CK_SESSION_HANDLE hSession, ret = WP11_AesGcm_DecryptFinal(pLastPart, &decPartLen, obj, session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulLastPartLen = decPartLen; @@ -4635,7 +4635,7 @@ static CK_RV wp11_C_DecryptFinal(CK_SESSION_HANDLE hSession, if (ret == BUFFER_E) return CKR_BUFFER_TOO_SMALL; if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } *pulLastPartLen = decPartLen; @@ -4646,7 +4646,7 @@ static CK_RV wp11_C_DecryptFinal(CK_SESSION_HANDLE hSession, (void)decPartLen; (void)ret; (void)pLastPart; - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_MECHANISM_INVALID; } @@ -4835,7 +4835,7 @@ CK_RV C_DigestUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, ret = WP11_Digest_Update(pPart, (word32)ulPartLen, session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } return CKR_OK; @@ -4879,21 +4879,21 @@ static CK_RV wp11_C_DigestKey(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hKey) ret = WP11_Object_Find(session, hKey, &obj); if (ret != 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_OBJECT_HANDLE_INVALID; } ret = WP11_Digest_Key(obj, session); if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } if (ret > 0) { /* Positive return is a CK_RV (e.g. CKR_FUNCTION_NOT_SUPPORTED on * WOLFPKCS11_NO_STORE builds). Pass it through but still terminate * the digest operation so the session is not left active. */ - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return (CK_RV)ret; } return CKR_OK; @@ -5810,7 +5810,7 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, (void)sigLen; (void)ulDataLen; (void)pSignature; - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_MECHANISM_INVALID; } WP11_Session_SetOpInitialized(session, 0); @@ -5941,7 +5941,7 @@ static CK_RV wp11_C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_TLS_MAC_SIGN)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulPartLen)) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -5950,11 +5950,11 @@ static CK_RV wp11_C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, #endif default: (void)ulPartLen; - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_MECHANISM_INVALID; } if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } @@ -6127,7 +6127,7 @@ static CK_RV wp11_C_SignFinal(CK_SESSION_HANDLE hSession, default: (void)sigLen; (void)pSignature; - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_MECHANISM_INVALID; } WP11_Session_SetOpInitialized(session, 0); @@ -6948,7 +6948,7 @@ static CK_RV wp11_C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, default: (void)ulDataLen; (void)ulSignatureLen; - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_MECHANISM_INVALID; } WP11_Session_SetOpInitialized(session, 0); @@ -7081,11 +7081,11 @@ static CK_RV wp11_C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, #endif default: (void)ulPartLen; - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_MECHANISM_INVALID; } if (ret < 0) { - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } @@ -7216,7 +7216,7 @@ static CK_RV wp11_C_VerifyFinal(CK_SESSION_HANDLE hSession, #endif default: (void)ulSignatureLen; - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); return CKR_MECHANISM_INVALID; } WP11_Session_SetOpInitialized(session, 0); @@ -8878,7 +8878,7 @@ static CK_RV wp11_C_WrapKey(CK_SESSION_HANDLE hSession, /* C_WrapKey is single-part: a length query or a short buffer * leaves C_Encrypt's operation active, which would make the * caller's next C_WrapKey fail with CKR_OPERATION_ACTIVE. */ - WP11_Session_SetOpInitialized(session, 0); + WP11_Session_AbortOp(session); if (rv != CKR_OK) goto err_out; } diff --git a/src/internal.c b/src/internal.c index e70b9f9b..65cae9a8 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1108,6 +1108,8 @@ static void wp11_Session_FreeOp(WP11_Session* session) session->params.digest.hashType); session->init &= ~WP11_INIT_DIGEST_MASK; } + /* Older wolfSSL versions do not zeroize the key schedule in wc_AesFree. */ + wc_ForceZero(&session->params, sizeof(session->params)); /* Ensure no stale bits remain after all cleanup. */ session->init = 0; } diff --git a/tests/session_op_lifecycle_test.c b/tests/session_op_lifecycle_test.c index 95e425d7..007b2733 100644 --- a/tests/session_op_lifecycle_test.c +++ b/tests/session_op_lifecycle_test.c @@ -476,6 +476,90 @@ static void test_rejected_init_keeps_active_operation(void) } #endif +#if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + !defined(NO_SHA256) +static int finalize_watched(CK_SESSION_HANDLE session) +{ + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); + return watch_stop(); +} + +/* An operation that ends on an error must release and scrub its keyed and + * buffered state, just as a completed operation does. */ +static void test_failed_operation_scrubs_state(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + byte iv[16] = { 0 }; + CK_MECHANISM cbcMech = { CKM_AES_CBC, NULL, 0 }; + CK_MECHANISM sha256Mech = { CKM_SHA256, NULL, 0 }; + CK_ULONG bigLen = (CK_ULONG)0xFFFFFFFFUL; + byte out[64]; + CK_ULONG outLen; + CK_SLOT_ID slot; + int i; + + printf("\n--- an operation ending on error scrubs its state ---\n"); + cbcMech.pParameter = iv; + cbcMech.ulParameterLen = sizeof(iv); + if (sizeof(CK_ULONG) <= sizeof(word32)) { + printf("SKIP: lengths beyond 32 bits need a 64-bit CK_ULONG\n"); + return; + } + bigLen += 16; + + for (i = 0; i < 2; i++) { + rv = init_library(&slot); + if (rv == CKR_OK) + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + rv = funcList->C_EncryptInit(session, &cbcMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-CBC)", CKR_OK); + outLen = sizeof(out); + /* Watch from the failing call: it may free the state itself. */ + watch_start(aesKeyValue, sizeof(aesKeyValue)); + if (i == 0) { + rv = funcList->C_EncryptUpdate(session, out, bigLen, out, &outLen); + CHECK_RV(rv, "C_EncryptUpdate(oversized)", CKR_DATA_LEN_RANGE); + } + else { + rv = funcList->C_Encrypt(session, out, bigLen, out, &outLen); + CHECK_RV(rv, "C_Encrypt(oversized)", CKR_DATA_LEN_RANGE); + } + CHECK_TRUE(!finalize_watched(session), + "failed AES operation leaves no key schedule behind"); + session = CK_INVALID_HANDLE; + } + + rv = init_library(&slot); + if (rv == CKR_OK) + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA-256)", CKR_OK); + rv = funcList->C_DigestUpdate(session, plainMarker, sizeof(plainMarker)); + CHECK_RV(rv, "C_DigestUpdate", CKR_OK); + watch_start(plainMarker, sizeof(plainMarker)); + rv = funcList->C_DigestKey(session, CK_INVALID_HANDLE); + CHECK_RV(rv, "C_DigestKey(invalid handle)", CKR_OBJECT_HANDLE_INVALID); + CHECK_TRUE(!finalize_watched(session), + "failed digest leaves no buffered input behind"); + session = CK_INVALID_HANDLE; + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + static int run_test(void) { CK_RV rv; @@ -499,6 +583,10 @@ static int run_test(void) #if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(HAVE_ECC) test_rejected_init_keeps_active_operation(); #endif +#if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + !defined(NO_SHA256) + test_failed_operation_scrubs_state(); +#endif pkcs11_unload(); return 0; From 721d8863639fec19faffc5cf5ffb0621207a2408 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:07:02 -0700 Subject: [PATCH 048/204] F-11514 - Free buffered AES-GCM data when GCM parameters are reset --- src/internal.c | 8 ++++ tests/session_op_lifecycle_test.c | 69 +++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/internal.c b/src/internal.c index 65cae9a8..1d1f8cc8 100644 --- a/src/internal.c +++ b/src/internal.c @@ -11661,6 +11661,14 @@ int WP11_Session_SetGcmParams(WP11_Session* session, unsigned char* iv, if (session->mechanism == CKM_AES_GCM && gcm->aad != NULL) { XFREE(gcm->aad, NULL, DYNAMIC_TYPE_TMP_BUFFER); } + if (session->mechanism == CKM_AES_GCM && gcm->enc != NULL) { + wc_ForceZero(gcm->enc, (word32)gcm->encSz); + XFREE(gcm->enc, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } +#ifdef WOLFSSL_AESGCM_STREAM + if (session->mechanism == CKM_AES_GCM && gcm->streamInit) + wc_AesFree(&gcm->aes); +#endif XMEMSET(gcm, 0, sizeof(*gcm)); if (ivSz > 0) XMEMCPY(gcm->iv, iv, ivSz); diff --git a/tests/session_op_lifecycle_test.c b/tests/session_op_lifecycle_test.c index 007b2733..aff52515 100644 --- a/tests/session_op_lifecycle_test.c +++ b/tests/session_op_lifecycle_test.c @@ -560,6 +560,72 @@ static void test_failed_operation_scrubs_state(void) } #endif +#if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) +/* Re-initializing AES-GCM must release, scrubbed, any data a previous GCM + * operation left buffered on the session. */ +static void test_gcm_reinit_releases_buffer(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + CK_GCM_PARAMS gcmParams; + CK_MECHANISM gcmMech = { CKM_AES_GCM, &gcmParams, sizeof(gcmParams) }; + byte out[64]; + CK_ULONG outLen; + CK_SLOT_ID slot; + long before; + int hit; + + printf("\n--- GCM re-init releases buffered data ---\n"); + XMEMSET(&gcmParams, 0, sizeof(gcmParams)); + gcmParams.pIv = gcmIv; + gcmParams.ulIvLen = sizeof(gcmIv); + gcmParams.ulIvBits = sizeof(gcmIv) * 8; + gcmParams.ulTagBits = 128; + + rv = init_library(&slot); + CHECK_RV(rv, "initialize library", CKR_OK); + if (rv != CKR_OK) + goto out; + before = liveBlocks; + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = funcList->C_EncryptInit(session, &gcmMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-GCM)", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_EncryptUpdate(session, plainMarker, sizeof(plainMarker), + out, &outLen); + CHECK_RV(rv, "C_EncryptUpdate(AES-GCM)", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_Encrypt(session, gcmIv, sizeof(gcmIv), out, &outLen); + CHECK_RV(rv, "C_Encrypt(AES-GCM) ends the operation", CKR_OK); + + watch_start(plainMarker, sizeof(plainMarker)); + rv = funcList->C_EncryptInit(session, &gcmMech, aesKey); + hit = watch_stop(); + CHECK_RV(rv, "C_EncryptInit(AES-GCM) again", CKR_OK); + CHECK_TRUE(!hit, "GCM re-init scrubs buffered data before release"); + + rv = funcList->C_DestroyObject(session, aesKey); + CHECK_RV(rv, "C_DestroyObject(AES)", CKR_OK); + rv = funcList->C_DestroyObject(session, macKey); + CHECK_RV(rv, "C_DestroyObject(HMAC)", CKR_OK); + funcList->C_CloseSession(session); + session = CK_INVALID_HANDLE; + CHECK_TRUE(liveBlocks == before, + "GCM re-init leaves no allocation behind"); + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + static int run_test(void) { CK_RV rv; @@ -587,6 +653,9 @@ static int run_test(void) !defined(NO_SHA256) test_failed_operation_scrubs_state(); #endif +#if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) + test_gcm_reinit_releases_buffer(); +#endif pkcs11_unload(); return 0; From 4ee265049cfc347823816172b04d9684d49ff0d5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:20:11 -0700 Subject: [PATCH 049/204] F-11506 - Free AES and HMAC contexts when key setup fails --- src/internal.c | 14 ++++++ tests/session_op_lifecycle_test.c | 71 +++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/src/internal.c b/src/internal.c index 1d1f8cc8..e08f0019 100644 --- a/src/internal.c +++ b/src/internal.c @@ -11515,6 +11515,7 @@ int WP11_Session_SetCbcParams(WP11_Session* session, unsigned char* iv, int enc, WP11_Object* object) { int ret; + int aesInit = 0; WP11_CbcParams* cbc = &session->params.cbc; WP11_Data* key; @@ -11530,6 +11531,8 @@ int WP11_Session_SetCbcParams(WP11_Session* session, unsigned char* iv, /* AES object on session. */ ret = wc_AesInit(&cbc->aes, NULL, object->devId); + if (ret == 0) + aesInit = 1; #ifdef WOLFSSL_STM32U5_DHUK if (ret == 0 && object->dhukIvLen > 0) { ret = wc_Stm32_Aes_SetDHUK_IV(&cbc->aes, object->dhukIv, @@ -11546,6 +11549,9 @@ int WP11_Session_SetCbcParams(WP11_Session* session, unsigned char* iv, WP11_Lock_UnlockRO(object->lock); } + if (ret != 0 && aesInit) + wc_AesFree(&cbc->aes); + return ret; } #endif /* HAVE_AES_CBC */ @@ -11583,6 +11589,8 @@ int WP11_Session_SetCtrParams(WP11_Session* session, CK_ULONG ulCounterBits, ret = wc_AesSetKey(&ctr->aes, key->data, key->len, cb, AES_ENCRYPTION); if (object->onToken) WP11_Lock_UnlockRO(object->lock); + if (ret != 0) + wc_AesFree(&ctr->aes); } if (ret == 0) { XMEMCPY(ctr->counter, cb, sizeof(ctr->counter)); @@ -11612,6 +11620,8 @@ int WP11_Session_SetAesWrapParams(WP11_Session* session, byte* iv, word32 ivLen, enc ? AES_ENCRYPTION : AES_DECRYPTION); if (object->onToken) WP11_Lock_UnlockRO(object->lock); + if (ret != 0) + wc_AesFree(&wrap->aes); } if (ret == 0) { if (iv != NULL) @@ -11773,6 +11783,8 @@ int WP11_Session_SetCtsParams(WP11_Session* session, unsigned char* iv, enc ? AES_ENCRYPTION : AES_DECRYPTION); if (object->onToken) WP11_Lock_UnlockRO(object->lock); + if (ret != 0) + wc_AesFree(&cts->aes); } return ret; @@ -20112,6 +20124,8 @@ int WP11_Hmac_Init(CK_MECHANISM_TYPE mechanism, WP11_Object* secret, ret = wc_HmacSetKey(&hmac->hmac, hashType, key->data, key->len); if (secret->onToken) WP11_Lock_UnlockRO(secret->lock); + if (ret != 0) + wc_HmacFree(&hmac->hmac); } return ret; diff --git a/tests/session_op_lifecycle_test.c b/tests/session_op_lifecycle_test.c index aff52515..2da90e5c 100644 --- a/tests/session_op_lifecycle_test.c +++ b/tests/session_op_lifecycle_test.c @@ -626,6 +626,74 @@ static void test_gcm_reinit_releases_buffer(void) } #endif +#if !defined(NO_AES) && defined(HAVE_AES_CBC) +/* A key that the cipher rejects during setup must not leave a half-built + * context behind, and the session must stay usable. */ +static void test_failed_key_setup_keeps_session_usable(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE oddKey = CK_INVALID_HANDLE; + byte oddValue[20]; + byte iv[16] = { 0 }; + CK_MECHANISM cbcMech = { CKM_AES_CBC, NULL, 0 }; + byte out[32]; + CK_ULONG outLen; + CK_SLOT_ID slot; +#ifdef TRACK_ALLOCS + long before = 0; +#endif + + printf("\n--- a failed key setup leaves the session usable ---\n"); + cbcMech.pParameter = iv; + cbcMech.ulParameterLen = sizeof(iv); + XMEMSET(oddValue, 0x5A, sizeof(oddValue)); + + rv = init_library(&slot); + CHECK_RV(rv, "initialize library", CKR_OK); + if (rv != CKR_OK) + goto out; +#ifdef TRACK_ALLOCS + before = liveBlocks; +#endif + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = create_secret(session, &aesType, oddValue, sizeof(oddValue), &oddKey); + if (rv != CKR_OK) { + printf("SKIP: token rejects AES keys of invalid length\n"); + } + else { + rv = funcList->C_EncryptInit(session, &cbcMech, oddKey); + CHECK_TRUE(rv != CKR_OK, "C_EncryptInit with an unusable key fails"); + rv = funcList->C_EncryptInit(session, &cbcMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-CBC) after failed setup", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_Encrypt(session, iv, sizeof(iv), out, &outLen); + CHECK_RV(rv, "C_Encrypt(AES-CBC) after failed setup", CKR_OK); + funcList->C_DestroyObject(session, oddKey); + } + + funcList->C_DestroyObject(session, aesKey); + funcList->C_DestroyObject(session, macKey); + funcList->C_CloseSession(session); + session = CK_INVALID_HANDLE; +#ifdef TRACK_ALLOCS + CHECK_TRUE(liveBlocks == before, + "failed key setup leaves no allocation behind"); +#endif + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + static int run_test(void) { CK_RV rv; @@ -656,6 +724,9 @@ static int run_test(void) #if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) test_gcm_reinit_releases_buffer(); #endif +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + test_failed_key_setup_keeps_session_usable(); +#endif pkcs11_unload(); return 0; From efde4eb63fb34f80253aae34d958d76d90f7ab09 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:52:29 -0700 Subject: [PATCH 050/204] F-12480 - Drop the session reference to a destroyed AES key read at operation time --- src/internal.c | 38 +++++- tests/session_op_lifecycle_test.c | 199 ++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 5 deletions(-) diff --git a/src/internal.c b/src/internal.c index e08f0019..f6719d2e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10945,6 +10945,20 @@ static int wp11_init_get_op_category(int init) } } +/** + * Return whether the mechanism reads the key object each time data is + * processed rather than copying the key at initialization. + * + * @param mechanism [in] Mechanism of the operation. + * @return 1 when the key object is read during the operation. + * 0 otherwise. + */ +static int wp11_mech_reads_key(CK_MECHANISM_TYPE mechanism) +{ + return mechanism == CKM_AES_GCM || mechanism == CKM_AES_CCM || + mechanism == CKM_AES_ECB; +} + /** * Check whether the session has an active operation of the given category. * Per PKCS#11 spec, only operations of the same type block re-initialization. @@ -10960,6 +10974,9 @@ int WP11_Session_IsOpCategoryActive(WP11_Session* session, int opCategory) if (session->init == 0) return 0; + /* The key of this operation was destroyed so it can no longer run. */ + if (session->curr == NULL && wp11_mech_reads_key(session->mechanism)) + return 0; currentInit = session->init & ~WP11_INIT_DIGEST_MASK; return wp11_init_get_op_category(currentInit) == opCategory; @@ -12147,7 +12164,9 @@ void WP11_Session_RemoveObject(WP11_Session* session, WP11_Object* object) * operation context during their *Init and run to completion without referring * to the object again, so a caller may legitimately destroy the key before * finishing the operation (wolfCrypt's PKCS#11 layer does exactly this for - * multi-part HMAC). Those operations are left untouched. + * multi-part HMAC). Those operations are left untouched. AES-GCM, AES-CCM and + * AES-ECB read the key object when data is processed, so only their reference + * is dropped; the owning session releases the operation state itself. * * Asymmetric operations dereference the key object while running, so for those * key types drop the active reference in every session that holds it and reset @@ -12160,18 +12179,22 @@ void WP11_Slot_ClearActiveObject(WP11_Slot* slot, WP11_Object* object) { WP11_Session* curr; CK_KEY_TYPE keyType; + int symmetric; if (slot == NULL || object == NULL) return; keyType = WP11_Object_GetType(object); - if (keyType == CKK_AES || keyType == CKK_GENERIC_SECRET || - keyType == CKK_HKDF) - return; + symmetric = (keyType == CKK_AES || keyType == CKK_GENERIC_SECRET || + keyType == CKK_HKDF); WP11_Lock_LockRW(&slot->lock); for (curr = slot->session; curr != NULL; curr = curr->next) { - if (curr->curr == object) { + if (curr->curr == object && symmetric) { + if (wp11_mech_reads_key(curr->mechanism)) + curr->curr = NULL; + } + else if (curr->curr == object) { #if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) /* Release the operation-owned RSA-OAEP label before dropping the * operation state. Otherwise a later init for a different mechanism @@ -12199,6 +12222,11 @@ void WP11_Slot_ClearActiveObject(WP11_Slot* slot, WP11_Object* object) */ void WP11_Session_GetObject(WP11_Session* session, WP11_Object** object) { + /* The key was destroyed: release the state its operation still holds. */ + if (session->curr == NULL && wp11_mech_reads_key(session->mechanism) && + (session->init & ~WP11_INIT_DIGEST_MASK) != 0) { + WP11_Session_AbortOp(session); + } *object = session->curr; } diff --git a/tests/session_op_lifecycle_test.c b/tests/session_op_lifecycle_test.c index 2da90e5c..3ed8f8b4 100644 --- a/tests/session_op_lifecycle_test.c +++ b/tests/session_op_lifecycle_test.c @@ -694,6 +694,197 @@ static void test_failed_key_setup_keeps_session_usable(void) } #endif +#if !defined(NO_AES) && (defined(HAVE_AESGCM) || defined(HAVE_AESECB) || \ + defined(HAVE_AESCCM)) +/* Destroying a key must end the operations that still read it, while + * operations that copied the key at Init time may finish. */ +static void test_destroyed_key_ends_dependent_operation(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE tmpKey = CK_INVALID_HANDLE; +#ifdef HAVE_AESGCM + CK_GCM_PARAMS gcmParams; + CK_MECHANISM gcmMech = { CKM_AES_GCM, &gcmParams, sizeof(gcmParams) }; +#endif +#ifdef HAVE_AESECB + CK_MECHANISM ecbMech = { CKM_AES_ECB, NULL, 0 }; +#endif +#ifdef HAVE_AESCCM + CK_CCM_PARAMS ccmParams; + CK_MECHANISM ccmMech = { CKM_AES_CCM, &ccmParams, sizeof(ccmParams) }; +#endif +#ifdef HAVE_AES_CBC + byte iv[16] = { 0 }; + CK_MECHANISM cbcMech = { CKM_AES_CBC, NULL, 0 }; +#endif + byte out[64]; + CK_ULONG outLen; + CK_SLOT_ID slot; + + printf("\n--- destroying a key ends operations that read it ---\n"); +#ifdef HAVE_AESGCM + XMEMSET(&gcmParams, 0, sizeof(gcmParams)); + gcmParams.pIv = gcmIv; + gcmParams.ulIvLen = sizeof(gcmIv); + gcmParams.ulIvBits = sizeof(gcmIv) * 8; + gcmParams.ulTagBits = 128; +#endif +#ifdef HAVE_AESCCM + XMEMSET(&ccmParams, 0, sizeof(ccmParams)); + ccmParams.ulDataLen = 16; + ccmParams.pIv = gcmIv; + ccmParams.ulIvLen = sizeof(gcmIv); + ccmParams.pAAD = gcmAad; + ccmParams.ulAADLen = sizeof(gcmAad); + ccmParams.ulMacLen = 16; +#endif +#ifdef HAVE_AES_CBC + cbcMech.pParameter = iv; + cbcMech.ulParameterLen = sizeof(iv); +#endif + + rv = init_library(&slot); + if (rv == CKR_OK) + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + +#ifdef HAVE_AESGCM + rv = create_secret(session, &aesType, aesKeyValue, sizeof(aesKeyValue), + &tmpKey); + CHECK_RV(rv, "C_CreateObject(AES)", CKR_OK); + rv = funcList->C_EncryptInit(session, &gcmMech, tmpKey); + CHECK_RV(rv, "C_EncryptInit(AES-GCM)", CKR_OK); + rv = funcList->C_DestroyObject(session, tmpKey); + CHECK_RV(rv, "C_DestroyObject(active AES-GCM key)", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_Encrypt(session, plainMarker, 16, out, &outLen); + CHECK_RV(rv, "C_Encrypt(AES-GCM) after key destroyed", + CKR_OPERATION_NOT_INITIALIZED); + rv = funcList->C_EncryptInit(session, &gcmMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-GCM) with another key", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_Encrypt(session, plainMarker, 16, out, &outLen); + CHECK_RV(rv, "C_Encrypt(AES-GCM) with another key", CKR_OK); +#endif +#ifdef HAVE_AESCCM + rv = create_secret(session, &aesType, aesKeyValue, sizeof(aesKeyValue), + &tmpKey); + CHECK_RV(rv, "C_CreateObject(AES)", CKR_OK); + rv = funcList->C_EncryptInit(session, &ccmMech, tmpKey); + CHECK_RV(rv, "C_EncryptInit(AES-CCM)", CKR_OK); + rv = funcList->C_DestroyObject(session, tmpKey); + CHECK_RV(rv, "C_DestroyObject(active AES-CCM key)", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_Encrypt(session, plainMarker, 16, out, &outLen); + CHECK_RV(rv, "C_Encrypt(AES-CCM) after key destroyed", + CKR_OPERATION_NOT_INITIALIZED); + rv = funcList->C_EncryptInit(session, &ccmMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-CCM) with another key", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_Encrypt(session, plainMarker, 16, out, &outLen); + CHECK_RV(rv, "C_Encrypt(AES-CCM) with another key", CKR_OK); +#endif +#ifdef HAVE_AESECB + rv = create_secret(session, &aesType, aesKeyValue, sizeof(aesKeyValue), + &tmpKey); + CHECK_RV(rv, "C_CreateObject(AES)", CKR_OK); + rv = funcList->C_DecryptInit(session, &ecbMech, tmpKey); + CHECK_RV(rv, "C_DecryptInit(AES-ECB)", CKR_OK); + rv = funcList->C_DestroyObject(session, tmpKey); + CHECK_RV(rv, "C_DestroyObject(active AES-ECB key)", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_Decrypt(session, plainMarker, 16, out, &outLen); + CHECK_RV(rv, "C_Decrypt(AES-ECB) after key destroyed", + CKR_OPERATION_NOT_INITIALIZED); +#endif +#ifdef HAVE_AES_CBC + rv = create_secret(session, &aesType, aesKeyValue, sizeof(aesKeyValue), + &tmpKey); + CHECK_RV(rv, "C_CreateObject(AES)", CKR_OK); + rv = funcList->C_EncryptInit(session, &cbcMech, tmpKey); + CHECK_RV(rv, "C_EncryptInit(AES-CBC)", CKR_OK); + rv = funcList->C_DestroyObject(session, tmpKey); + CHECK_RV(rv, "C_DestroyObject(active AES-CBC key)", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_Encrypt(session, plainMarker, 16, out, &outLen); + CHECK_RV(rv, "C_Encrypt(AES-CBC) with key copied at Init", CKR_OK); +#endif + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + +#if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ + !defined(WOLFSSL_AESGCM_STREAM) +/* Destroying the key of a multi-part AES-GCM operation must release the + * plaintext it buffered once the operation is next used. */ +static void test_destroyed_gcm_key_releases_input(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE tmpKey = CK_INVALID_HANDLE; + CK_GCM_PARAMS gcmParams; + CK_MECHANISM gcmMech = { CKM_AES_GCM, &gcmParams, sizeof(gcmParams) }; + byte out[64]; + CK_ULONG outLen; + CK_SLOT_ID slot; + long before; + int hit; + + printf("\n--- destroying an AES-GCM key releases buffered input ---\n"); + XMEMSET(&gcmParams, 0, sizeof(gcmParams)); + gcmParams.pIv = gcmIv; + gcmParams.ulIvLen = sizeof(gcmIv); + gcmParams.ulIvBits = sizeof(gcmIv) * 8; + gcmParams.ulTagBits = 128; + + rv = init_library(&slot); + if (rv == CKR_OK) + rv = open_with_keys(slot, &session, &aesKey, &macKey); + if (rv == CKR_OK) { + rv = create_secret(session, &aesType, aesKeyValue, sizeof(aesKeyValue), + &tmpKey); + } + if (rv == CKR_OK) + rv = funcList->C_EncryptInit(session, &gcmMech, tmpKey); + if (rv == CKR_OK) { + outLen = sizeof(out); + rv = funcList->C_EncryptUpdate(session, plainMarker, + sizeof(plainMarker), out, &outLen); + } + if (rv == CKR_OK) + rv = funcList->C_DestroyObject(session, tmpKey); + CHECK_RV(rv, "buffer AES-GCM input and destroy its key", CKR_OK); + if (rv != CKR_OK) + goto out; + + before = liveBlocks; + watch_start(plainMarker, sizeof(plainMarker)); + outLen = sizeof(out); + rv = funcList->C_EncryptFinal(session, out, &outLen); + hit = watch_stop(); + CHECK_RV(rv, "C_EncryptFinal(AES-GCM) after key destroyed", + CKR_OPERATION_NOT_INITIALIZED); + CHECK_TRUE(liveBlocks < before, "buffered AES-GCM input is released"); + CHECK_TRUE(!hit, "buffered AES-GCM input is scrubbed"); + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + static int run_test(void) { CK_RV rv; @@ -727,6 +918,14 @@ static int run_test(void) #if !defined(NO_AES) && defined(HAVE_AES_CBC) test_failed_key_setup_keeps_session_usable(); #endif +#if !defined(NO_AES) && (defined(HAVE_AESGCM) || defined(HAVE_AESECB) || \ + defined(HAVE_AESCCM)) + test_destroyed_key_ends_dependent_operation(); +#endif +#if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ + !defined(WOLFSSL_AESGCM_STREAM) + test_destroyed_gcm_key_releases_input(); +#endif pkcs11_unload(); return 0; From cfb889ee609ee401c7c3ab3c18ed48141d83d143 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:04:35 -0700 Subject: [PATCH 051/204] F-4071 - Scrub accumulated MAC input before growing or freeing it --- src/internal.c | 41 ++++++++----- tests/session_op_lifecycle_test.c | 98 +++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 16 deletions(-) diff --git a/src/internal.c b/src/internal.c index f6719d2e..957ff561 100644 --- a/src/internal.c +++ b/src/internal.c @@ -534,6 +534,7 @@ struct WP11_Session { byte* data; /* Held data for one-shot mechs */ word32 dataSz; /* Size of held data */ + word32 dataCap; /* Allocated size of held data */ int devId; WP11_Session* next; /* Next session for slot */ @@ -10986,31 +10987,36 @@ int WP11_Session_UpdateData(WP11_Session *session, byte *data, word32 dataLen) { int ret = 0; byte* tmp; + word32 cap; /* Guard the cumulative word32 sum against silent wrap that would lead to * a tiny allocation followed by an oversized XMEMCPY past it. */ if (dataLen > (word32)0xFFFFFFFFu - session->dataSz) return MEMORY_E; -#ifdef XREALLOC - tmp = (byte*)XREALLOC(session->data, session->dataSz + dataLen, NULL, - DYNAMIC_TYPE_TMP_BUFFER); - if (tmp == NULL) - ret = MEMORY_E; -#else - tmp = (byte*)XMALLOC(session->dataSz + dataLen, NULL, - DYNAMIC_TYPE_TMP_BUFFER); - if (tmp == NULL) - ret = MEMORY_E; - if (ret == 0) { - if (session->data != NULL) - XMEMCPY(tmp, session->data, session->dataSz); - XFREE(session->data, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (session->data == NULL || dataLen > session->dataCap - session->dataSz) { + /* Doubling keeps the copying linear in the total input. */ + cap = session->dataSz + dataLen; + if (session->dataCap <= (word32)0x7FFFFFFFu && + cap < session->dataCap * 2) { + cap = session->dataCap * 2; + } + /* Grow by copying so the old buffer can be scrubbed before freeing. */ + tmp = (byte*)XMALLOC(cap, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) + ret = MEMORY_E; + if (ret == 0) { + if (session->data != NULL) { + XMEMCPY(tmp, session->data, session->dataSz); + wc_ForceZero(session->data, session->dataCap); + } + XFREE(session->data, NULL, DYNAMIC_TYPE_TMP_BUFFER); + session->data = tmp; + session->dataCap = cap; + } } -#endif /* !XREALLOC */ if (ret == 0) { - session->data = tmp; XMEMCPY(session->data + session->dataSz, data, dataLen); session->dataSz += dataLen; } @@ -11026,9 +11032,12 @@ void WP11_Session_GetData(WP11_Session *session, byte** data, word32* dataLen) void WP11_Session_FreeData(WP11_Session *session) { + if (session->data != NULL) + wc_ForceZero(session->data, session->dataCap); XFREE(session->data, NULL, DYNAMIC_TYPE_TMP_BUFFER); session->data = NULL; session->dataSz = 0; + session->dataCap = 0; session->init = 0; } diff --git a/tests/session_op_lifecycle_test.c b/tests/session_op_lifecycle_test.c index 3ed8f8b4..cb269bd5 100644 --- a/tests/session_op_lifecycle_test.c +++ b/tests/session_op_lifecycle_test.c @@ -98,6 +98,7 @@ static byte plainMarker[32] = { #define ALLOC_HDR_SZ 16 static long liveBlocks = 0; +static long allocCount = 0; static const byte* watchData = NULL; static size_t watchLen = 0; static int watchHit = 0; @@ -126,6 +127,7 @@ static void* track_malloc(size_t sz) XMEMSET(p, 0, sz + ALLOC_HDR_SZ); XMEMCPY(p, &sz, sizeof(sz)); liveBlocks++; + allocCount++; return p + ALLOC_HDR_SZ; } @@ -822,6 +824,98 @@ static void test_destroyed_key_ends_dependent_operation(void) } #endif +#if defined(TRACK_ALLOCS) && defined(WOLFSSL_HAVE_PRF) && !defined(NO_SHA256) +static CK_RV tls_mac_sign_init(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE key) +{ + CK_TLS_MAC_PARAMS params; + CK_MECHANISM mech = { CKM_TLS_MAC, ¶ms, sizeof(params) }; + + params.prfHashMechanism = CKM_SHA256; + params.ulMacLength = 12; + params.ulServerOrClient = 1; + return funcList->C_SignInit(session, &mech, key); +} + +/* Input accumulated for a one-shot MAC must be scrubbed whenever its buffer + * is grown or released. */ +static void test_mac_input_scrubbed_on_release(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + byte sig[64]; + CK_ULONG sigLen; + CK_SLOT_ID slot; + int hit; + + printf("\n--- accumulated MAC input is scrubbed ---\n"); + rv = init_library(&slot); + if (rv == CKR_OK) + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = tls_mac_sign_init(session, macKey); + CHECK_RV(rv, "C_SignInit(TLS MAC)", CKR_OK); + rv = funcList->C_SignUpdate(session, plainMarker, sizeof(plainMarker)); + CHECK_RV(rv, "C_SignUpdate(TLS MAC)", CKR_OK); + watch_start(plainMarker, sizeof(plainMarker)); + rv = funcList->C_SignUpdate(session, plainMarker, sizeof(plainMarker)); + CHECK_RV(rv, "C_SignUpdate(TLS MAC) again", CKR_OK); + sigLen = sizeof(sig); + rv = funcList->C_SignFinal(session, sig, &sigLen); + hit = watch_stop(); + CHECK_RV(rv, "C_SignFinal(TLS MAC)", CKR_OK); + CHECK_TRUE(!hit, "MAC input is scrubbed when its buffer is released"); + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} + +#define MAC_SMALL_PARTS 1024 + +/* Many one-byte MAC parts must not reallocate the buffered input each time. */ +static void test_mac_input_grows_linearly(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + byte part = 0x5a; + CK_SLOT_ID slot; + long allocs; + int i; + + printf("\n--- many small MAC parts grow the input buffer linearly ---\n"); + rv = init_library(&slot); + if (rv == CKR_OK) + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = tls_mac_sign_init(session, macKey); + CHECK_RV(rv, "C_SignInit(TLS MAC)", CKR_OK); + allocs = allocCount; + for (i = 0; rv == CKR_OK && i < MAC_SMALL_PARTS; i++) + rv = funcList->C_SignUpdate(session, &part, 1); + allocs = allocCount - allocs; + CHECK_RV(rv, "one-byte C_SignUpdate(TLS MAC) parts", CKR_OK); + if (allocs > 64) + fprintf(stderr, " %ld allocations for %d parts\n", allocs, i); + CHECK_TRUE(allocs <= 64, "buffered MAC input is not reallocated per part"); + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + #if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ !defined(WOLFSSL_AESGCM_STREAM) /* Destroying the key of a multi-part AES-GCM operation must release the @@ -926,6 +1020,10 @@ static int run_test(void) !defined(WOLFSSL_AESGCM_STREAM) test_destroyed_gcm_key_releases_input(); #endif +#if defined(TRACK_ALLOCS) && defined(WOLFSSL_HAVE_PRF) && !defined(NO_SHA256) + test_mac_input_scrubbed_on_release(); + test_mac_input_grows_linearly(); +#endif pkcs11_unload(); return 0; From b82d3eef770dc260e16ae1493c2d9cdd3a4095a3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:12:12 -0700 Subject: [PATCH 052/204] F-8660 - Release accumulated MAC input when the session operation ends --- src/internal.c | 1 + tests/session_op_lifecycle_test.c | 45 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/internal.c b/src/internal.c index 957ff561..b3bcf32e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1109,6 +1109,7 @@ static void wp11_Session_FreeOp(WP11_Session* session) session->params.digest.hashType); session->init &= ~WP11_INIT_DIGEST_MASK; } + WP11_Session_FreeData(session); /* Older wolfSSL versions do not zeroize the key schedule in wc_AesFree. */ wc_ForceZero(&session->params, sizeof(session->params)); /* Ensure no stale bits remain after all cleanup. */ diff --git a/tests/session_op_lifecycle_test.c b/tests/session_op_lifecycle_test.c index cb269bd5..e1669dac 100644 --- a/tests/session_op_lifecycle_test.c +++ b/tests/session_op_lifecycle_test.c @@ -916,6 +916,50 @@ static void test_mac_input_grows_linearly(void) } #endif +#if defined(TRACK_ALLOCS) && defined(WOLFSSL_HAVE_PRF) && !defined(NO_SHA256) +/* Closing a session in the middle of a one-shot MAC must release, scrubbed, + * the input accumulated so far. */ +static void test_mac_input_released_on_close(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + CK_SLOT_ID slot; + long before; + int hit; + + printf("\n--- closing a session releases accumulated MAC input ---\n"); + rv = init_library(&slot); + CHECK_RV(rv, "initialize library", CKR_OK); + if (rv != CKR_OK) + goto out; + before = liveBlocks; + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = tls_mac_sign_init(session, macKey); + CHECK_RV(rv, "C_SignInit(TLS MAC)", CKR_OK); + rv = funcList->C_SignUpdate(session, plainMarker, sizeof(plainMarker)); + CHECK_RV(rv, "C_SignUpdate(TLS MAC)", CKR_OK); + + watch_start(plainMarker, sizeof(plainMarker)); + funcList->C_CloseSession(session); + hit = watch_stop(); + session = CK_INVALID_HANDLE; + CHECK_TRUE(!hit, "closed session scrubs accumulated MAC input"); + CHECK_TRUE(liveBlocks == before, + "closed session leaves no allocation behind"); + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + #if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ !defined(WOLFSSL_AESGCM_STREAM) /* Destroying the key of a multi-part AES-GCM operation must release the @@ -1023,6 +1067,7 @@ static int run_test(void) #if defined(TRACK_ALLOCS) && defined(WOLFSSL_HAVE_PRF) && !defined(NO_SHA256) test_mac_input_scrubbed_on_release(); test_mac_input_grows_linearly(); + test_mac_input_released_on_close(); #endif pkcs11_unload(); From 0cececb29cf6da8cf0b24fabb07462eeb141638e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:20:46 -0700 Subject: [PATCH 053/204] F-8641 - Require an active digest before saving operation state --- src/internal.c | 7 +++- tests/session_op_lifecycle_test.c | 61 +++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index b3bcf32e..20792eec 100644 --- a/src/internal.c +++ b/src/internal.c @@ -20464,7 +20464,10 @@ int WP11_GetOperationState(WP11_Session* session, unsigned char* stateData, wc_Hashes* hashAlg; #endif - *stateDataLen = sizeof(session->mechanism); + if (session->init == 0) + return CKR_OPERATION_NOT_INITIALIZED; + if (!WP11_Session_IsOpInitialized(session, WP11_INIT_DIGEST)) + return CKR_STATE_UNSAVEABLE; switch (session->mechanism) { #ifndef NO_MD5 @@ -20500,7 +20503,7 @@ int WP11_GetOperationState(WP11_Session* session, unsigned char* stateData, default: return CKR_STATE_UNSAVEABLE; } - *stateDataLen += mechSize; + *stateDataLen = sizeof(session->mechanism) + mechSize; if (stateData == NULL) return CKR_OK; diff --git a/tests/session_op_lifecycle_test.c b/tests/session_op_lifecycle_test.c index e1669dac..a88c19ec 100644 --- a/tests/session_op_lifecycle_test.c +++ b/tests/session_op_lifecycle_test.c @@ -264,10 +264,14 @@ static CK_RV open_with_keys(CK_SLOT_ID slot, CK_SESSION_HANDLE* session, rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, session); +#ifndef NO_AES if (rv == CKR_OK) { rv = create_secret(*session, &aesType, aesKeyValue, sizeof(aesKeyValue), aesKey); } +#else + (void)aesKey; +#endif if (rv == CKR_OK) { rv = create_secret(*session, &genericType, macKeyValue, sizeof(macKeyValue), macKey); @@ -960,6 +964,60 @@ static void test_mac_input_released_on_close(void) } #endif +#ifndef NO_SHA256 +/* Operation state is only available while a digest is in progress, and a + * refused request leaves the caller's length untouched. */ +static void test_operation_state_requires_active_digest(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + CK_MECHANISM sha256Mech = { CKM_SHA256, NULL, 0 }; +#ifndef NO_HMAC + CK_MECHANISM hmacMech = { CKM_SHA256_HMAC, NULL, 0 }; +#endif + byte digest[32]; + CK_ULONG digestLen; + CK_ULONG stateLen; + CK_SLOT_ID slot; + + printf("\n--- operation state needs an active digest ---\n"); + rv = init_library(&slot); + if (rv == CKR_OK) + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA-256)", CKR_OK); + digestLen = sizeof(digest); + rv = funcList->C_Digest(session, plainMarker, sizeof(plainMarker), digest, + &digestLen); + CHECK_RV(rv, "C_Digest completes the operation", CKR_OK); + stateLen = 0; + rv = funcList->C_GetOperationState(session, NULL, &stateLen); + CHECK_RV(rv, "C_GetOperationState after digest completed", + CKR_OPERATION_NOT_INITIALIZED); + +#ifndef NO_HMAC + rv = funcList->C_SignInit(session, &hmacMech, macKey); + CHECK_RV(rv, "C_SignInit(HMAC)", CKR_OK); + stateLen = 1234; + rv = funcList->C_GetOperationState(session, NULL, &stateLen); + CHECK_RV(rv, "C_GetOperationState during HMAC", CKR_STATE_UNSAVEABLE); + CHECK_TRUE(stateLen == 1234, + "refused C_GetOperationState leaves the length untouched"); +#endif + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + #if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ !defined(WOLFSSL_AESGCM_STREAM) /* Destroying the key of a multi-part AES-GCM operation must release the @@ -1069,6 +1127,9 @@ static int run_test(void) test_mac_input_grows_linearly(); test_mac_input_released_on_close(); #endif +#ifndef NO_SHA256 + test_operation_state_requires_active_digest(); +#endif pkcs11_unload(); return 0; From 0cbe9259b4840770d2a5ab74df6136170f88d7a5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:36:53 -0700 Subject: [PATCH 054/204] F-5871 - Release the active operation before restoring saved state --- src/internal.c | 1 + tests/session_op_lifecycle_test.c | 77 +++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/internal.c b/src/internal.c index 20792eec..4c00c41c 100644 --- a/src/internal.c +++ b/src/internal.c @@ -20572,6 +20572,7 @@ int WP11_SetOperationState(WP11_Session* session, unsigned char* stateData, if (stateDataLen < sizeof(session->mechanism)) return CKR_SAVED_STATE_INVALID; + WP11_Session_AbortOp(session); XMEMCPY(&session->mechanism, stateData, sizeof(session->mechanism)); switch (session->mechanism) { #ifndef NO_MD5 diff --git a/tests/session_op_lifecycle_test.c b/tests/session_op_lifecycle_test.c index a88c19ec..e584eae5 100644 --- a/tests/session_op_lifecycle_test.c +++ b/tests/session_op_lifecycle_test.c @@ -1018,6 +1018,79 @@ static void test_operation_state_requires_active_digest(void) } #endif +#if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ + !defined(NO_SHA256) +/* Restoring a saved digest state must release the operation it replaces. */ +static void test_restored_state_releases_operation(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + CK_MECHANISM sha256Mech = { CKM_SHA256, NULL, 0 }; + CK_GCM_PARAMS gcmParams; + CK_MECHANISM gcmMech = { CKM_AES_GCM, &gcmParams, sizeof(gcmParams) }; + byte state[1024]; + CK_ULONG stateLen; + byte digest[32]; + CK_ULONG digestLen; + CK_SLOT_ID slot; + long before; + + printf("\n--- restoring a saved state releases the replaced op ---\n"); + XMEMSET(&gcmParams, 0, sizeof(gcmParams)); + gcmParams.pIv = gcmIv; + gcmParams.ulIvLen = sizeof(gcmIv); + gcmParams.ulIvBits = sizeof(gcmIv) * 8; + gcmParams.pAAD = gcmAad; + gcmParams.ulAADLen = sizeof(gcmAad); + gcmParams.ulTagBits = 128; + + rv = init_library(&slot); + CHECK_RV(rv, "initialize library", CKR_OK); + if (rv != CKR_OK) + goto out; + before = liveBlocks; + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA-256)", CKR_OK); + rv = funcList->C_DigestUpdate(session, plainMarker, sizeof(plainMarker)); + CHECK_RV(rv, "C_DigestUpdate", CKR_OK); + stateLen = sizeof(state); + rv = funcList->C_GetOperationState(session, state, &stateLen); + CHECK_RV(rv, "C_GetOperationState", CKR_OK); + digestLen = sizeof(digest); + rv = funcList->C_DigestFinal(session, digest, &digestLen); + CHECK_RV(rv, "C_DigestFinal", CKR_OK); + + rv = funcList->C_EncryptInit(session, &gcmMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-GCM with AAD)", CKR_OK); + rv = funcList->C_SetOperationState(session, state, stateLen, 0, 0); + CHECK_RV(rv, "C_SetOperationState over active encrypt", CKR_OK); + digestLen = sizeof(digest); + rv = funcList->C_DigestFinal(session, digest, &digestLen); + CHECK_RV(rv, "C_DigestFinal of restored state", CKR_OK); + + rv = funcList->C_DestroyObject(session, aesKey); + CHECK_RV(rv, "C_DestroyObject(AES)", CKR_OK); + rv = funcList->C_DestroyObject(session, macKey); + CHECK_RV(rv, "C_DestroyObject(HMAC)", CKR_OK); + funcList->C_CloseSession(session); + session = CK_INVALID_HANDLE; + CHECK_TRUE(liveBlocks == before, + "restored state leaves no allocation behind"); + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + #if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ !defined(WOLFSSL_AESGCM_STREAM) /* Destroying the key of a multi-part AES-GCM operation must release the @@ -1130,6 +1203,10 @@ static int run_test(void) #ifndef NO_SHA256 test_operation_state_requires_active_digest(); #endif +#if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ + !defined(NO_SHA256) + test_restored_state_releases_operation(); +#endif pkcs11_unload(); return 0; From 08eeac70783d6afcaa05dad51490fce21a45ca17 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:44:36 -0700 Subject: [PATCH 055/204] F-11510 - Validate saved operation state before changing the session --- src/internal.c | 18 +++++---- tests/session_op_lifecycle_test.c | 64 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/internal.c b/src/internal.c index 4c00c41c..1bc6a2e2 100644 --- a/src/internal.c +++ b/src/internal.c @@ -20563,18 +20563,18 @@ int WP11_SetOperationState(WP11_Session* session, unsigned char* stateData, unsigned long mechSize = 0; int hashType = WC_HASH_TYPE_NONE; int ret; + CK_MECHANISM_TYPE mechanism; #if defined(LIBWOLFSSL_VERSION_HEX) && LIBWOLFSSL_VERSION_HEX < 0x05007004 wc_HashAlg* hashAlg; #else wc_Hashes* hashAlg; #endif - if (stateDataLen < sizeof(session->mechanism)) + if (stateDataLen < sizeof(mechanism)) return CKR_SAVED_STATE_INVALID; - WP11_Session_AbortOp(session); - XMEMCPY(&session->mechanism, stateData, sizeof(session->mechanism)); - switch (session->mechanism) { + XMEMCPY(&mechanism, stateData, sizeof(mechanism)); + switch (mechanism) { #ifndef NO_MD5 case CKM_MD5: mechSize = sizeof(wc_Md5); @@ -20609,16 +20609,20 @@ int WP11_SetOperationState(WP11_Session* session, unsigned char* stateData, return CKR_SAVED_STATE_INVALID; } - if (stateDataLen < (sizeof(session->mechanism) + mechSize)) + if (stateDataLen < (sizeof(mechanism) + mechSize)) return CKR_SAVED_STATE_INVALID; - stateData += sizeof(session->mechanism); + stateData += sizeof(mechanism); - ret = wp11_digest_hash_type(session->mechanism, &hashType); + ret = wp11_digest_hash_type(mechanism, &hashType); if (ret != CKR_OK) return ret; + /* The saved state is valid: replace the current operation. */ + WP11_Session_AbortOp(session); + session->mechanism = mechanism; + session->params.digest.hashType = (enum wc_HashType)hashType; ret = wc_HashInit(&session->params.digest.hash, (enum wc_HashType)hashType); diff --git a/tests/session_op_lifecycle_test.c b/tests/session_op_lifecycle_test.c index e584eae5..93980053 100644 --- a/tests/session_op_lifecycle_test.c +++ b/tests/session_op_lifecycle_test.c @@ -1091,6 +1091,69 @@ static void test_restored_state_releases_operation(void) } #endif +#ifndef NO_SHA256 +/* A saved state that is rejected must leave the session's digest exactly as + * it was. */ +static void test_rejected_state_keeps_session(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_MECHANISM sha256Mech = { CKM_SHA256, NULL, 0 }; + CK_MECHANISM_TYPE badMech; + byte state[1024]; + byte bogus[1024]; + byte after[1024]; + CK_ULONG stateLen; + CK_ULONG afterLen; + CK_SLOT_ID slot; + + printf("\n--- a rejected saved state keeps the session state ---\n"); + rv = init_library(&slot); + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &session); + } + CHECK_RV(rv, "open session", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA-256)", CKR_OK); + rv = funcList->C_DigestUpdate(session, plainMarker, sizeof(plainMarker)); + CHECK_RV(rv, "C_DigestUpdate", CKR_OK); + stateLen = sizeof(state); + rv = funcList->C_GetOperationState(session, state, &stateLen); + CHECK_RV(rv, "C_GetOperationState", CKR_OK); + if (rv != CKR_OK) + goto out; + + XMEMCPY(bogus, state, stateLen); + badMech = CKM_AES_CBC; + XMEMCPY(bogus, &badMech, sizeof(badMech)); + rv = funcList->C_SetOperationState(session, bogus, stateLen, 0, 0); + CHECK_RV(rv, "C_SetOperationState(unsupported mechanism)", + CKR_SAVED_STATE_INVALID); + badMech = CKM_SHA256; + XMEMCPY(bogus, &badMech, sizeof(badMech)); + rv = funcList->C_SetOperationState(session, bogus, sizeof(badMech) + 4, 0, + 0); + CHECK_RV(rv, "C_SetOperationState(truncated state)", + CKR_SAVED_STATE_INVALID); + + afterLen = sizeof(after); + rv = funcList->C_GetOperationState(session, after, &afterLen); + CHECK_RV(rv, "C_GetOperationState after rejected states", CKR_OK); + CHECK_TRUE(rv == CKR_OK && afterLen == stateLen && + XMEMCMP(after, state, stateLen) == 0, + "rejected saved state leaves the digest state unchanged"); + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + #if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ !defined(WOLFSSL_AESGCM_STREAM) /* Destroying the key of a multi-part AES-GCM operation must release the @@ -1202,6 +1265,7 @@ static int run_test(void) #endif #ifndef NO_SHA256 test_operation_state_requires_active_digest(); + test_rejected_state_keeps_session(); #endif #if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ !defined(NO_SHA256) From fbebd7e7a0f98af79f62faff37a44d6ebe6b8cb5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:51:03 -0700 Subject: [PATCH 056/204] F-5870 - Reject key handles when restoring a keyless operation state --- src/slot.c | 9 ++++-- tests/session_op_lifecycle_test.c | 49 +++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/slot.c b/src/slot.c index 95938e00..dab691a5 100644 --- a/src/slot.c +++ b/src/slot.c @@ -1839,8 +1839,13 @@ CK_RV C_SetOperationState(CK_SESSION_HANDLE hSession, return rv; } - (void)hEncryptionKey; - (void)hAuthenticationKey; + /* Only keyless digest states can be saved, so no key is ever needed. */ + if (hEncryptionKey != CK_INVALID_HANDLE || + hAuthenticationKey != CK_INVALID_HANDLE) { + rv = CKR_KEY_NOT_NEEDED; + WOLFPKCS11_LEAVE("C_SetOperationState", rv); + return rv; + } rv = WP11_SetOperationState(session, pOperationState, ulOperationStateLen); WOLFPKCS11_LEAVE("C_SetOperationState", rv); diff --git a/tests/session_op_lifecycle_test.c b/tests/session_op_lifecycle_test.c index 93980053..9446ddb7 100644 --- a/tests/session_op_lifecycle_test.c +++ b/tests/session_op_lifecycle_test.c @@ -1154,6 +1154,54 @@ static void test_rejected_state_keeps_session(void) } #endif +#ifndef NO_SHA256 +/* Saved digest states need no key, so restoring one with a key handle is + * refused. */ +static void test_restore_with_key_is_refused(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + CK_MECHANISM sha256Mech = { CKM_SHA256, NULL, 0 }; + byte state[1024]; + CK_ULONG stateLen; + CK_SLOT_ID slot; + + printf("\n--- restoring a digest state with a key is refused ---\n"); + rv = init_library(&slot); + if (rv == CKR_OK) + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA-256)", CKR_OK); + rv = funcList->C_DigestUpdate(session, plainMarker, sizeof(plainMarker)); + CHECK_RV(rv, "C_DigestUpdate", CKR_OK); + stateLen = sizeof(state); + rv = funcList->C_GetOperationState(session, state, &stateLen); + CHECK_RV(rv, "C_GetOperationState", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = funcList->C_SetOperationState(session, state, stateLen, macKey, 0); + CHECK_RV(rv, "C_SetOperationState with encryption key", + CKR_KEY_NOT_NEEDED); + rv = funcList->C_SetOperationState(session, state, stateLen, 0, macKey); + CHECK_RV(rv, "C_SetOperationState with authentication key", + CKR_KEY_NOT_NEEDED); + rv = funcList->C_SetOperationState(session, state, stateLen, 0, 0); + CHECK_RV(rv, "C_SetOperationState without keys", CKR_OK); + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + #if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ !defined(WOLFSSL_AESGCM_STREAM) /* Destroying the key of a multi-part AES-GCM operation must release the @@ -1266,6 +1314,7 @@ static int run_test(void) #ifndef NO_SHA256 test_operation_state_requires_active_digest(); test_rejected_state_keeps_session(); + test_restore_with_key_is_refused(); #endif #if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ !defined(NO_SHA256) From 7521a9d221d0eec839d974cc5a7b5047da48ff19 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 15:33:39 -0700 Subject: [PATCH 057/204] F-10394 - Cancel the active operation when Init is called with a NULL mechanism --- src/crypto.c | 77 +++++++++-- src/internal.c | 27 +++- tests/pkcs11mtt.c | 24 ++++ tests/pkcs11test.c | 24 ++++ tests/session_op_lifecycle_test.c | 220 +++++++++++++++++++++++++++++- wolfpkcs11/internal.h | 2 + wolfpkcs11/pkcs11.h | 1 + 7 files changed, 360 insertions(+), 15 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 5f628d3b..47ae7f69 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -2452,6 +2452,35 @@ CK_RV C_FindObjectsFinal(CK_SESSION_HANDLE hSession) } +#ifdef WOLFPKCS11_PKCS11_V3_0 +/** + * Terminate the active operation of a kind, as requested by calling an Init + * function with a NULL mechanism. + * + * @param session [in] Session object. + * @param opCategory [in] Operation category (WP11_OP_*). + * @param recover [in] Whether the kind is verification with recovery. + * @return CKR_OK as releasing an operation cannot fail. + */ +static CK_RV CancelOperation(WP11_Session* session, int opCategory, + int recover) +{ + int recoverActive; + + recoverActive = WP11_Session_IsOpInitialized(session, + WP11_INIT_RSA_PKCS_VERIFY_RECOVER) || + WP11_Session_IsOpInitialized(session, + WP11_INIT_RSA_X_509_VERIFY_RECOVER); + /* Not IsOpCategoryActive: an op whose key was destroyed still holds + * state that must be released. */ + if (WP11_Session_IsOpCategoryInit(session, opCategory) && + recoverActive == recover) { + WP11_Session_AbortOp(session); + } + return CKR_OK; +} +#endif + static CK_RV EncryptInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey, byte skipOpCheck) @@ -2481,7 +2510,11 @@ static CK_RV EncryptInit(CK_SESSION_HANDLE hSession, return rv; } if (pMechanism == NULL) { +#ifdef WOLFPKCS11_PKCS11_V3_0 + rv = CancelOperation(session, WP11_OP_ENCRYPT, 0); +#else rv = CKR_ARGUMENTS_BAD; +#endif WOLFPKCS11_LEAVE("C_EncryptInit", rv); return rv; } @@ -2736,7 +2769,8 @@ static CK_RV EncryptInit(CK_SESSION_HANDLE hSession, * @param hKey [in] Handle to key object. * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SESSION_HANDLE_INVALID when session handle is not valid. - * CKR_ARGUMENTS_BAD when pMechanism is NULL. + * CKR_ARGUMENTS_BAD when pMechanism is NULL; PKCS#11 v3.0 builds + * instead cancel the active operation and return CKR_OK. * CKR_OBJECT_HANDLE_INVALID when key object handle is not valid. * CKR_KEY_TYPE_INCONSISTENT when the key type is not valid for the * mechanism (operation). @@ -3581,8 +3615,13 @@ static CK_RV DecryptInit(CK_SESSION_HANDLE hSession, } if (WP11_Session_Get(hSession, &session) != 0) return CKR_SESSION_HANDLE_INVALID; - if (pMechanism == NULL) + if (pMechanism == NULL) { +#ifdef WOLFPKCS11_PKCS11_V3_0 + return CancelOperation(session, WP11_OP_DECRYPT, 0); +#else return CKR_ARGUMENTS_BAD; +#endif + } ret = WP11_Object_Find(session, hKey, &obj); if (ret != 0) @@ -3823,7 +3862,8 @@ static CK_RV DecryptInit(CK_SESSION_HANDLE hSession, * @param hKey [in] Handle to key object. * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SESSION_HANDLE_INVALID when session handle is not valid. - * CKR_ARGUMENTS_BAD when pMechanism is NULL. + * CKR_ARGUMENTS_BAD when pMechanism is NULL; PKCS#11 v3.0 builds + * instead cancel the active operation and return CKR_OK. * CKR_OBJECT_HANDLE_INVALID when key object handle is not valid. * CKR_KEY_TYPE_INCONSISTENT when the key type is not valid for the * mechanism (operation). @@ -4674,7 +4714,8 @@ CK_RV C_DecryptFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pLastPart, * @param pMechanism [in] Type of operation to perform with parameters. * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SESSION_HANDLE_INVALID when session handle is not valid. - * CKR_ARGUMENTS_BAD when pMechanism is NULL. + * CKR_ARGUMENTS_BAD when pMechanism is NULL; PKCS#11 v3.0 builds + * instead cancel the active operation and return CKR_OK. * CKR_MECHANISM_INVALID when the mechanism is not supported with this * type of operation. */ @@ -4704,7 +4745,11 @@ CK_RV C_DigestInit(CK_SESSION_HANDLE hSession, return rv; } if (pMechanism == NULL) { +#ifdef WOLFPKCS11_PKCS11_V3_0 + rv = CancelOperation(session, WP11_OP_DIGEST, 0); +#else rv = CKR_ARGUMENTS_BAD; +#endif WOLFPKCS11_LEAVE("C_DigestInit", rv); return rv; } @@ -5103,7 +5148,8 @@ static int GetInitValue(CK_MECHANISM_TYPE mechanism) { * @param hKey [in] Handle to key object. * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SESSION_HANDLE_INVALID when session handle is not valid. - * CKR_ARGUMENTS_BAD when pMechanism is NULL. + * CKR_ARGUMENTS_BAD when pMechanism is NULL; PKCS#11 v3.0 builds + * instead cancel the active operation and return CKR_OK. * CKR_OBJECT_HANDLE_INVALID when key object handle is not valid. * CKR_KEY_TYPE_INCONSISTENT when the key type is not valid for the * mechanism (operation). @@ -5138,8 +5184,13 @@ static CK_RV wp11_C_SignInit(CK_SESSION_HANDLE hSession, } if (WP11_Session_Get(hSession, &session) != 0) return CKR_SESSION_HANDLE_INVALID; - if (pMechanism == NULL) + if (pMechanism == NULL) { +#ifdef WOLFPKCS11_PKCS11_V3_0 + return CancelOperation(session, WP11_OP_SIGN, 0); +#else return CKR_ARGUMENTS_BAD; +#endif + } ret = WP11_Object_Find(session, hKey, &obj); #ifdef WOLFSSL_MAXQ10XX_CRYPTO @@ -6282,7 +6333,8 @@ CK_RV C_SignRecover(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, * @param hKey [in] Handle to key object. * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SESSION_HANDLE_INVALID when session handle is not valid. - * CKR_ARGUMENTS_BAD when pMechanism is NULL. + * CKR_ARGUMENTS_BAD when pMechanism is NULL; PKCS#11 v3.0 builds + * instead cancel the active operation and return CKR_OK. * CKR_OBJECT_HANDLE_INVALID when key object handle is not valid. * CKR_KEY_TYPE_INCONSISTENT when the key type is not valid for the * mechanism (operation). @@ -6322,7 +6374,11 @@ static CK_RV wp11_C_VerifyInit(CK_SESSION_HANDLE hSession, return rv; } if (pMechanism == NULL) { +#ifdef WOLFPKCS11_PKCS11_V3_0 + rv = CancelOperation(session, WP11_OP_VERIFY, 0); +#else rv = CKR_ARGUMENTS_BAD; +#endif WOLFPKCS11_LEAVE("C_VerifyInit", rv); return rv; } @@ -7248,7 +7304,8 @@ CK_RV C_VerifyFinal(CK_SESSION_HANDLE hSession, * @param hKey [in] Handle to key object. * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SESSION_HANDLE_INVALID when session handle is not valid. - * CKR_ARGUMENTS_BAD when pMechanism is NULL. + * CKR_ARGUMENTS_BAD when pMechanism is NULL; PKCS#11 v3.0 builds + * instead cancel the active operation and return CKR_OK. * CKR_OBJECT_HANDLE_INVALID when key object handle is not valid. * CKR_MECHANISM_INVALID when the mechanism is not supported with this * type of operation. @@ -7281,7 +7338,11 @@ static CK_RV wp11_C_VerifyRecoverInit(CK_SESSION_HANDLE hSession, return rv; } if (pMechanism == NULL) { +#ifdef WOLFPKCS11_PKCS11_V3_0 + rv = CancelOperation(session, WP11_OP_VERIFY, 1); +#else rv = CKR_ARGUMENTS_BAD; +#endif WOLFPKCS11_LEAVE("C_VerifyRecoverInit", rv); return rv; } diff --git a/src/internal.c b/src/internal.c index 1bc6a2e2..73f4ea7e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10962,26 +10962,41 @@ static int wp11_mech_reads_key(CK_MECHANISM_TYPE mechanism) } /** - * Check whether the session has an active operation of the given category. - * Per PKCS#11 spec, only operations of the same type block re-initialization. + * Check whether the session holds operation state of the given category, even + * when the key the operation reads was destroyed. * * @param session [in] Session object. * @param opCategory [in] Operation category (WP11_OP_*). - * @return 1 when an operation of the same category is active. + * @return 1 when an operation of the category is initialized. * 0 otherwise. */ -int WP11_Session_IsOpCategoryActive(WP11_Session* session, int opCategory) +int WP11_Session_IsOpCategoryInit(WP11_Session* session, int opCategory) { int currentInit; if (session->init == 0) return 0; + + currentInit = session->init & ~WP11_INIT_DIGEST_MASK; + return wp11_init_get_op_category(currentInit) == opCategory; +} + +/** + * Check whether the session has an active operation of the given category. + * Per PKCS#11 spec, only operations of the same type block re-initialization. + * + * @param session [in] Session object. + * @param opCategory [in] Operation category (WP11_OP_*). + * @return 1 when an operation of the same category is active. + * 0 otherwise. + */ +int WP11_Session_IsOpCategoryActive(WP11_Session* session, int opCategory) +{ /* The key of this operation was destroyed so it can no longer run. */ if (session->curr == NULL && wp11_mech_reads_key(session->mechanism)) return 0; - currentInit = session->init & ~WP11_INIT_DIGEST_MASK; - return wp11_init_get_op_category(currentInit) == opCategory; + return WP11_Session_IsOpCategoryInit(session, opCategory); } int WP11_Session_UpdateData(WP11_Session *session, byte *data, word32 dataLen) diff --git a/tests/pkcs11mtt.c b/tests/pkcs11mtt.c index c82c9d00..609a85b0 100644 --- a/tests/pkcs11mtt.c +++ b/tests/pkcs11mtt.c @@ -1054,8 +1054,12 @@ static CK_RV test_encrypt_decrypt(void* args) } if (ret == CKR_OK) { ret = funcList->C_EncryptInit(session, NULL, key); +#ifdef WOLFPKCS11_PKCS11_V3_0 + CHECK_CKR(ret, "AES-CBC Encrypt Init no mechanism cancels"); +#else CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "AES-CBC Encrypt Init no mechanism"); +#endif } if (ret == CKR_OK) { ret = funcList->C_EncryptInit(session, &mech, CK_INVALID_HANDLE); @@ -1124,8 +1128,12 @@ static CK_RV test_encrypt_decrypt(void* args) } if (ret == CKR_OK) { ret = funcList->C_DecryptInit(session, NULL, key); +#ifdef WOLFPKCS11_PKCS11_V3_0 + CHECK_CKR(ret, "AES-CBC Decrypt Init no mechanism cancels"); +#else CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "AES-CBC Decrypt Init no mechanism"); +#endif } if (ret == CKR_OK) { ret = funcList->C_DecryptInit(session, &mech, CK_INVALID_HANDLE); @@ -1231,7 +1239,11 @@ static CK_RV test_digest(void* args) } if (ret == CKR_OK) { ret = funcList->C_DigestInit(session, NULL); +#ifdef WOLFPKCS11_PKCS11_V3_0 + CHECK_CKR(ret, "Digest Init no mechanism cancels"); +#else CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "Digest Init no mechanism"); +#endif } if (ret == CKR_OK) { ret = funcList->C_Digest(CK_INVALID_HANDLE, data, dataSz, hash, @@ -1331,7 +1343,11 @@ static CK_RV test_sign_verify(void* args) } if (ret == CKR_OK) { ret = funcList->C_SignInit(session, NULL, key); +#ifdef WOLFPKCS11_PKCS11_V3_0 + CHECK_CKR(ret, "HMAC Sign Init no mechanism cancels"); +#else CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "HMAC Sign Init no mechanism"); +#endif } if (ret == CKR_OK) { ret = funcList->C_SignInit(session, &mech, CK_INVALID_HANDLE); @@ -1397,7 +1413,11 @@ static CK_RV test_sign_verify(void* args) } if (ret == CKR_OK) { ret = funcList->C_VerifyInit(session, NULL, key); +#ifdef WOLFPKCS11_PKCS11_V3_0 + CHECK_CKR(ret, "HMAC Verify Init no mechanism cancels"); +#else CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "HMAC Verify Init no mechanism"); +#endif } if (ret == CKR_OK) { ret = funcList->C_VerifyInit(session, &mech, CK_INVALID_HANDLE); @@ -1534,8 +1554,12 @@ static CK_RV test_recover(void* args) } if (ret == CKR_OK) { ret = funcList->C_VerifyRecoverInit(session, NULL, key); +#ifdef WOLFPKCS11_PKCS11_V3_0 + CHECK_CKR(ret, "Verify Recover Init no mechanism cancels"); +#else CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "Verify Recover Init no mechanism"); +#endif } if (ret == CKR_OK) { ret = funcList->C_VerifyRecoverInit(session, &mech, CK_INVALID_HANDLE); diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index 924d3935..da6d0dca 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -4809,8 +4809,12 @@ static CK_RV test_encrypt_decrypt(void* args) } if (ret == CKR_OK) { ret = funcList->C_EncryptInit(session, NULL, key); +#ifdef WOLFPKCS11_PKCS11_V3_0 + CHECK_CKR(ret, "AES-CBC Encrypt Init no mechanism cancels"); +#else CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "AES-CBC Encrypt Init no mechanism"); +#endif } if (ret == CKR_OK) { ret = funcList->C_EncryptInit(session, &mech, CK_INVALID_HANDLE); @@ -4879,8 +4883,12 @@ static CK_RV test_encrypt_decrypt(void* args) } if (ret == CKR_OK) { ret = funcList->C_DecryptInit(session, NULL, key); +#ifdef WOLFPKCS11_PKCS11_V3_0 + CHECK_CKR(ret, "AES-CBC Decrypt Init no mechanism cancels"); +#else CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "AES-CBC Decrypt Init no mechanism"); +#endif } if (ret == CKR_OK) { ret = funcList->C_DecryptInit(session, &mech, CK_INVALID_HANDLE); @@ -5353,7 +5361,11 @@ static CK_RV test_digest_fail(void* args) } if (ret == CKR_OK) { ret = funcList->C_DigestInit(session, NULL); +#ifdef WOLFPKCS11_PKCS11_V3_0 + CHECK_CKR(ret, "Digest Init no mechanism cancels"); +#else CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "Digest Init no mechanism"); +#endif } if (ret == CKR_OK) { ret = funcList->C_Digest(CK_INVALID_HANDLE, data, dataSz, hash, @@ -5493,7 +5505,11 @@ static CK_RV test_sign_verify(void* args) } if (ret == CKR_OK) { ret = funcList->C_SignInit(session, NULL, key); +#ifdef WOLFPKCS11_PKCS11_V3_0 + CHECK_CKR(ret, "HMAC Sign Init no mechanism cancels"); +#else CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "HMAC Sign Init no mechanism"); +#endif } if (ret == CKR_OK) { ret = funcList->C_SignInit(session, &mech, CK_INVALID_HANDLE); @@ -5559,7 +5575,11 @@ static CK_RV test_sign_verify(void* args) } if (ret == CKR_OK) { ret = funcList->C_VerifyInit(session, NULL, key); +#ifdef WOLFPKCS11_PKCS11_V3_0 + CHECK_CKR(ret, "HMAC Verify Init no mechanism cancels"); +#else CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "HMAC Verify Init no mechanism"); +#endif } if (ret == CKR_OK) { ret = funcList->C_VerifyInit(session, &mech, CK_INVALID_HANDLE); @@ -5752,8 +5772,12 @@ static CK_RV test_recover(void* args) } if (ret == CKR_OK) { ret = funcList->C_VerifyRecoverInit(session, NULL, key); +#ifdef WOLFPKCS11_PKCS11_V3_0 + CHECK_CKR(ret, "Verify Recover Init no mechanism cancels"); +#else CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "Verify Recover Init no mechanism"); +#endif } if (ret == CKR_OK) { ret = funcList->C_VerifyRecoverInit(session, &mech, CK_INVALID_HANDLE); diff --git a/tests/session_op_lifecycle_test.c b/tests/session_op_lifecycle_test.c index 9446ddb7..80571e58 100644 --- a/tests/session_op_lifecycle_test.c +++ b/tests/session_op_lifecycle_test.c @@ -200,7 +200,7 @@ static CK_RV create_secret(CK_SESSION_HANDLE session, CK_KEY_TYPE* type, sizeof(tmpl) / sizeof(*tmpl), key); } -#if !defined(NO_RSA) && (!defined(WC_NO_RSA_OAEP) || defined(WC_RSA_PSS)) +#ifndef NO_RSA static CK_RV create_rsa_public(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* key) { @@ -211,6 +211,7 @@ static CK_RV create_rsa_public(CK_SESSION_HANDLE session, { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_VERIFY_RECOVER, &ckTrue, sizeof(ckTrue) }, { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, @@ -1202,6 +1203,216 @@ static void test_restore_with_key_is_refused(void) } #endif +#if defined(WOLFPKCS11_PKCS11_V3_0) && !defined(NO_AES) && \ + defined(HAVE_AES_CBC) && !defined(NO_HMAC) && !defined(NO_SHA256) +/* An Init call with a NULL mechanism terminates the active operation of that + * kind only, and a new operation of the same kind can then start. */ +static void test_null_mechanism_cancels_operation(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + byte iv[16] = { 0 }; + CK_MECHANISM cbcMech = { CKM_AES_CBC, NULL, 0 }; + CK_MECHANISM hmacMech = { CKM_SHA256_HMAC, NULL, 0 }; + CK_MECHANISM sha256Mech = { CKM_SHA256, NULL, 0 }; +#ifndef NO_RSA + CK_OBJECT_HANDLE rsaKey = CK_INVALID_HANDLE; + CK_MECHANISM rsaMech = { CKM_RSA_PKCS, NULL, 0 }; +#endif +#if defined(TRACK_ALLOCS) && defined(HAVE_AESGCM) + CK_GCM_PARAMS gcmParams; + CK_MECHANISM gcmMech = { CKM_AES_GCM, &gcmParams, sizeof(gcmParams) }; + long before; +#endif + byte out[64]; + CK_ULONG outLen; + CK_SLOT_ID slot; + + printf("\n--- a NULL mechanism cancels the active operation ---\n"); + cbcMech.pParameter = iv; + cbcMech.ulParameterLen = sizeof(iv); + + rv = init_library(&slot); + if (rv == CKR_OK) + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = funcList->C_EncryptInit(session, &cbcMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-CBC)", CKR_OK); + rv = funcList->C_EncryptInit(session, NULL, aesKey); + CHECK_RV(rv, "C_EncryptInit(NULL) cancels", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_EncryptUpdate(session, plainMarker, 16, out, &outLen); + CHECK_RV(rv, "C_EncryptUpdate after cancel", + CKR_OPERATION_NOT_INITIALIZED); + rv = funcList->C_EncryptInit(session, &cbcMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-CBC) after cancel", CKR_OK); + rv = funcList->C_EncryptInit(session, NULL, aesKey); + CHECK_RV(rv, "C_EncryptInit(NULL) cancels again", CKR_OK); + + rv = funcList->C_DecryptInit(session, &cbcMech, aesKey); + CHECK_RV(rv, "C_DecryptInit(AES-CBC)", CKR_OK); + rv = funcList->C_DecryptInit(session, NULL, aesKey); + CHECK_RV(rv, "C_DecryptInit(NULL) cancels", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_DecryptUpdate(session, plainMarker, 16, out, &outLen); + CHECK_RV(rv, "C_DecryptUpdate after cancel", + CKR_OPERATION_NOT_INITIALIZED); + + rv = funcList->C_SignInit(session, &hmacMech, macKey); + CHECK_RV(rv, "C_SignInit(HMAC)", CKR_OK); + rv = funcList->C_SignInit(session, NULL, macKey); + CHECK_RV(rv, "C_SignInit(NULL) cancels", CKR_OK); + rv = funcList->C_SignUpdate(session, plainMarker, 16); + CHECK_RV(rv, "C_SignUpdate after cancel", CKR_OPERATION_NOT_INITIALIZED); + + rv = funcList->C_VerifyInit(session, &hmacMech, macKey); + CHECK_RV(rv, "C_VerifyInit(HMAC)", CKR_OK); +#ifndef NO_RSA + rv = funcList->C_VerifyRecoverInit(session, NULL, macKey); + CHECK_RV(rv, "C_VerifyRecoverInit(NULL) with a verify active", CKR_OK); + rv = funcList->C_VerifyUpdate(session, plainMarker, 16); + CHECK_RV(rv, "verify survives cancel of verify-recover", CKR_OK); +#endif + rv = funcList->C_VerifyInit(session, NULL, macKey); + CHECK_RV(rv, "C_VerifyInit(NULL) cancels", CKR_OK); + rv = funcList->C_VerifyUpdate(session, plainMarker, 16); + CHECK_RV(rv, "C_VerifyUpdate after cancel", + CKR_OPERATION_NOT_INITIALIZED); + +#ifndef NO_RSA + rv = create_rsa_public(session, &rsaKey); + CHECK_RV(rv, "C_CreateObject(RSA public)", CKR_OK); + rv = funcList->C_VerifyRecoverInit(session, &rsaMech, rsaKey); + CHECK_RV(rv, "C_VerifyRecoverInit(RSA)", CKR_OK); + rv = funcList->C_VerifyInit(session, NULL, rsaKey); + CHECK_RV(rv, "C_VerifyInit(NULL) with a verify-recover active", CKR_OK); + rv = funcList->C_VerifyRecoverInit(session, &rsaMech, rsaKey); + CHECK_RV(rv, "verify-recover survives cancel of verify", + CKR_OPERATION_ACTIVE); + rv = funcList->C_VerifyRecoverInit(session, NULL, rsaKey); + CHECK_RV(rv, "C_VerifyRecoverInit(NULL) cancels", CKR_OK); + rv = funcList->C_VerifyRecoverInit(session, &rsaMech, rsaKey); + CHECK_RV(rv, "C_VerifyRecoverInit(RSA) after cancel", CKR_OK); + rv = funcList->C_VerifyRecoverInit(session, NULL, rsaKey); + CHECK_RV(rv, "C_VerifyRecoverInit(NULL) cancels again", CKR_OK); +#endif + + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA-256)", CKR_OK); + rv = funcList->C_EncryptInit(session, NULL, aesKey); + CHECK_RV(rv, "C_EncryptInit(NULL) with only a digest active", CKR_OK); + rv = funcList->C_DigestUpdate(session, plainMarker, 16); + CHECK_RV(rv, "digest survives cancel of another kind", CKR_OK); + rv = funcList->C_DigestInit(session, NULL); + CHECK_RV(rv, "C_DigestInit(NULL) cancels", CKR_OK); + rv = funcList->C_DigestUpdate(session, plainMarker, 16); + CHECK_RV(rv, "C_DigestUpdate after cancel", + CKR_OPERATION_NOT_INITIALIZED); + +#if defined(TRACK_ALLOCS) && defined(HAVE_AESGCM) + XMEMSET(&gcmParams, 0, sizeof(gcmParams)); + gcmParams.pIv = gcmIv; + gcmParams.ulIvLen = sizeof(gcmIv); + gcmParams.ulIvBits = sizeof(gcmIv) * 8; + gcmParams.pAAD = gcmAad; + gcmParams.ulAADLen = sizeof(gcmAad); + gcmParams.ulTagBits = 128; + before = liveBlocks; + rv = funcList->C_EncryptInit(session, &gcmMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-GCM with AAD)", CKR_OK); + rv = funcList->C_EncryptInit(session, NULL, aesKey); + CHECK_RV(rv, "C_EncryptInit(NULL) cancels AES-GCM", CKR_OK); + CHECK_TRUE(liveBlocks == before, + "cancelled operation leaves no allocation behind"); +#endif + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + +#if defined(WOLFPKCS11_PKCS11_V3_0) && !defined(NO_AES) && defined(HAVE_AESGCM) +/* A NULL mechanism must still cancel, and release, an operation whose key + * was destroyed while the operation held buffered data. */ +static void test_null_mechanism_cancels_after_key_destroyed(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE macKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE tmpKey = CK_INVALID_HANDLE; + CK_GCM_PARAMS gcmParams; + CK_MECHANISM gcmMech = { CKM_AES_GCM, &gcmParams, sizeof(gcmParams) }; + byte out[64]; + CK_ULONG outLen; + CK_SLOT_ID slot; +#ifdef TRACK_ALLOCS + long before; + int hit; +#endif + + printf("\n--- a NULL mechanism cancels an operation with a destroyed key " + "---\n"); + XMEMSET(&gcmParams, 0, sizeof(gcmParams)); + gcmParams.pIv = gcmIv; + gcmParams.ulIvLen = sizeof(gcmIv); + gcmParams.ulIvBits = sizeof(gcmIv) * 8; + gcmParams.ulTagBits = 128; + + rv = init_library(&slot); + if (rv == CKR_OK) + rv = open_with_keys(slot, &session, &aesKey, &macKey); + CHECK_RV(rv, "open session with keys", CKR_OK); + if (rv != CKR_OK) + goto out; + +#ifdef TRACK_ALLOCS + before = liveBlocks; +#endif + rv = create_secret(session, &aesType, aesKeyValue, sizeof(aesKeyValue), + &tmpKey); + CHECK_RV(rv, "C_CreateObject(AES)", CKR_OK); + rv = funcList->C_EncryptInit(session, &gcmMech, tmpKey); + CHECK_RV(rv, "C_EncryptInit(AES-GCM)", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_EncryptUpdate(session, plainMarker, sizeof(plainMarker), + out, &outLen); + CHECK_RV(rv, "C_EncryptUpdate(AES-GCM)", CKR_OK); + rv = funcList->C_DestroyObject(session, tmpKey); + CHECK_RV(rv, "C_DestroyObject(active AES-GCM key)", CKR_OK); + +#ifdef TRACK_ALLOCS + watch_start(plainMarker, sizeof(plainMarker)); +#endif + rv = funcList->C_EncryptInit(session, NULL, 0); + CHECK_RV(rv, "C_EncryptInit(NULL) after key destroyed", CKR_OK); +#ifdef TRACK_ALLOCS + hit = watch_stop(); + CHECK_TRUE(!hit, "cancel scrubs buffered plaintext before release"); + CHECK_TRUE(liveBlocks == before, + "cancel releases the operation of a destroyed key"); +#endif + + rv = funcList->C_EncryptInit(session, &gcmMech, aesKey); + CHECK_RV(rv, "C_EncryptInit(AES-GCM) with another key", CKR_OK); + outLen = sizeof(out); + rv = funcList->C_Encrypt(session, plainMarker, 16, out, &outLen); + CHECK_RV(rv, "C_Encrypt(AES-GCM) with another key", CKR_OK); + +out: + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} +#endif + #if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ !defined(WOLFSSL_AESGCM_STREAM) /* Destroying the key of a multi-part AES-GCM operation must release the @@ -1316,6 +1527,13 @@ static int run_test(void) test_rejected_state_keeps_session(); test_restore_with_key_is_refused(); #endif +#if defined(WOLFPKCS11_PKCS11_V3_0) && !defined(NO_AES) && \ + defined(HAVE_AES_CBC) && !defined(NO_HMAC) && !defined(NO_SHA256) + test_null_mechanism_cancels_operation(); +#endif +#if defined(WOLFPKCS11_PKCS11_V3_0) && !defined(NO_AES) && defined(HAVE_AESGCM) + test_null_mechanism_cancels_after_key_destroyed(); +#endif #if defined(TRACK_ALLOCS) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ !defined(NO_SHA256) test_restored_state_releases_operation(); diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 2f4848cf..6a021b1d 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -451,6 +451,8 @@ WP11_LOCAL int WP11_Session_Get(CK_SESSION_HANDLE sessionHandle, WP11_Session** WP11_LOCAL int WP11_Session_GetState(WP11_Session* session); WP11_LOCAL int WP11_Session_IsRW(WP11_Session* session); WP11_LOCAL int WP11_Session_IsOpInitialized(WP11_Session* session, int init); +WP11_LOCAL int WP11_Session_IsOpCategoryInit(WP11_Session* session, + int opCategory); WP11_LOCAL int WP11_Session_IsOpCategoryActive(WP11_Session* session, int opCategory); WP11_LOCAL int WP11_Session_UpdateData(WP11_Session *session, byte *data, word32 dataLen); diff --git a/wolfpkcs11/pkcs11.h b/wolfpkcs11/pkcs11.h index 3397af80..03bd305e 100644 --- a/wolfpkcs11/pkcs11.h +++ b/wolfpkcs11/pkcs11.h @@ -484,6 +484,7 @@ extern "C" { #define CKR_MUTEX_BAD 0x000001A0UL #define CKR_MUTEX_NOT_LOCKED 0x000001A1UL #define CKR_FUNCTION_REJECTED 0x00000200UL +#define CKR_OPERATION_CANCEL_FAILED 0x00000202UL #define CKR_VENDOR_DEFINED 0x80000000UL From 095bb911f78a0d6f3796d5f6856fe63ad53a240a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 08:40:21 -0700 Subject: [PATCH 058/204] F-2769 - Blank pad fixed-length info fields --- src/internal.c | 16 +++ src/slot.c | 8 +- src/wolfpkcs11.c | 9 ++ tests/include.am | 7 ++ tests/info_mech_table_test.c | 191 +++++++++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 1 + 6 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 tests/info_mech_table_test.c diff --git a/src/internal.c b/src/internal.c index 73f4ea7e..7f60afb1 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10704,6 +10704,22 @@ void WP11_Slot_GetTokenLabel(WP11_Slot* slot, char* label) WP11_Lock_UnlockRO(&slot->lock); } +/** + * Replace NUL bytes in a fixed-length character field with blanks. + * + * @param field [in,out] Character field. + * @param len [in] Length of field in bytes. + */ +void WP11_BlankPad(CK_UTF8CHAR* field, int len) +{ + int i; + + for (i = 0; i < len; i++) { + if (field[i] == '\0') + field[i] = ' '; + } +} + /** * Check if token has been initialized. * diff --git a/src/slot.c b/src/slot.c index dab691a5..5fd5375a 100644 --- a/src/slot.c +++ b/src/slot.c @@ -138,6 +138,8 @@ CK_RV C_GetSlotInfo(CK_SLOT_ID slotID, CK_SLOT_INFO_PTR pInfo) } XMEMCPY(pInfo, &slotInfoTemplate, sizeof(slotInfoTemplate)); + WP11_BlankPad(pInfo->slotDescription, sizeof(pInfo->slotDescription)); + WP11_BlankPad(pInfo->manufacturerID, sizeof(pInfo->manufacturerID)); /* Put in the slot id value as two decimal digits. */ pInfo->slotDescription[SLOT_ID_IDX + 0] = ((slotID / 10) % 10) + '0'; pInfo->slotDescription[SLOT_ID_IDX + 1] = ((slotID ) % 10) + '0'; @@ -164,8 +166,8 @@ static CK_TOKEN_INFO tokenInfoTemplate = { "wolfpkcs11", "wolfpkcs11", { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0' }, /* serialNumber */ CKF_RNG | CKF_CLOCK_ON_TOKEN | CKF_LOGIN_REQUIRED, WP11_SESSION_CNT_MAX, /* ulMaxSessionCount */ @@ -230,6 +232,8 @@ CK_RV C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) } XMEMCPY(pInfo, &tokenInfoTemplate, sizeof(tokenInfoTemplate)); + WP11_BlankPad(pInfo->manufacturerID, sizeof(pInfo->manufacturerID)); + WP11_BlankPad(pInfo->model, sizeof(pInfo->model)); WP11_Slot_GetTokenLabel(slot, (char*)pInfo->label); pInfo->serialNumber[14] = ((slotID / 10) % 10) + '0'; pInfo->serialNumber[15] = ((slotID / 1) % 10) + '0'; diff --git a/src/wolfpkcs11.c b/src/wolfpkcs11.c index bc1e64d0..1c1a48f0 100644 --- a/src/wolfpkcs11.c +++ b/src/wolfpkcs11.c @@ -666,6 +666,9 @@ CK_RV C_GetInfo(CK_INFO_PTR pInfo) } XMEMCPY(pInfo, &wolfpkcs11Info, sizeof(wolfpkcs11Info)); + WP11_BlankPad(pInfo->manufacturerID, sizeof(pInfo->manufacturerID)); + WP11_BlankPad(pInfo->libraryDescription, + sizeof(pInfo->libraryDescription)); ret = CKR_OK; WOLFPKCS11_LEAVE("C_GetInfo", ret); return ret; @@ -680,6 +683,9 @@ CK_RV C_GetInfoV3_0(CK_INFO_PTR pInfo) return CKR_ARGUMENTS_BAD; XMEMCPY(pInfo, &wolfpkcs11Info_3_0, sizeof(wolfpkcs11Info_3_0)); + WP11_BlankPad(pInfo->manufacturerID, sizeof(pInfo->manufacturerID)); + WP11_BlankPad(pInfo->libraryDescription, + sizeof(pInfo->libraryDescription)); return CKR_OK; } @@ -694,6 +700,9 @@ CK_RV C_GetInfoV3_2(CK_INFO_PTR pInfo) return CKR_ARGUMENTS_BAD; XMEMCPY(pInfo, &wolfpkcs11Info_3_2, sizeof(wolfpkcs11Info_3_2)); + WP11_BlankPad(pInfo->manufacturerID, sizeof(pInfo->manufacturerID)); + WP11_BlankPad(pInfo->libraryDescription, + sizeof(pInfo->libraryDescription)); return CKR_OK; } diff --git a/tests/include.am b/tests/include.am index 7e7ec4e6..01ac5c42 100644 --- a/tests/include.am +++ b/tests/include.am @@ -331,6 +331,11 @@ noinst_PROGRAMS += tests/session_op_lifecycle_test tests_session_op_lifecycle_test_SOURCES = tests/session_op_lifecycle_test.c tests_session_op_lifecycle_test_LDADD = +check_PROGRAMS += tests/info_mech_table_test +noinst_PROGRAMS += tests/info_mech_table_test +tests_info_mech_table_test_SOURCES = tests/info_mech_table_test.c +tests_info_mech_table_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -398,6 +403,7 @@ tests_login_pin_state_test_LDADD += src/libwolfpkcs11.la tests_object_store_decode_test_LDADD += src/libwolfpkcs11.la tests_object_policy_integrity_test_LDADD += src/libwolfpkcs11.la tests_session_op_lifecycle_test_LDADD += src/libwolfpkcs11.la +tests_info_mech_table_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -457,6 +463,7 @@ tests_login_pin_state_test_LDADD += src/libwolfpkcs11.la tests_object_store_decode_test_LDADD += src/libwolfpkcs11.la tests_object_policy_integrity_test_LDADD += src/libwolfpkcs11.la tests_session_op_lifecycle_test_LDADD += src/libwolfpkcs11.la +tests_info_mech_table_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ diff --git a/tests/info_mech_table_test.c b/tests/info_mech_table_test.c new file mode 100644 index 00000000..adb64dc6 --- /dev/null +++ b/tests/info_mech_table_test.c @@ -0,0 +1,191 @@ +/* info_mech_table_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Library, slot, token and mechanism information must match the PKCS#11 + * encoding rules and what the token actually implements. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#define TEST_DIR "./store/info_mech_table_test" + +/* Fixed-length character fields are blank padded and never contain NUL. */ +static int blank_padded(const CK_UTF8CHAR* field, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (field[i] == '\0') + return 0; + } + return 1; +} + +static void check_info(CK_FUNCTION_LIST* list, const char* name) +{ + CK_RV rv; + CK_INFO info; + char msg[96]; + + XMEMSET(&info, 0, sizeof(info)); + rv = list->C_GetInfo(&info); + snprintf(msg, sizeof(msg), "C_GetInfo(%s)", name); + CHECK_RV(rv, msg, CKR_OK); + if (rv != CKR_OK) + return; + snprintf(msg, sizeof(msg), "%s CK_INFO.manufacturerID blank padded", name); + CHECK_TRUE(blank_padded(info.manufacturerID, sizeof(info.manufacturerID)), + msg); + snprintf(msg, sizeof(msg), "%s CK_INFO.libraryDescription blank padded", + name); + CHECK_TRUE(blank_padded(info.libraryDescription, + sizeof(info.libraryDescription)), msg); +} + +static void test_info_blank_padding(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SLOT_INFO slotInfo; + CK_TOKEN_INFO tokenInfo; +#ifdef WOLFPKCS11_PKCS11_V3_0 + CK_INTERFACE interfaces[8]; + CK_ULONG count = sizeof(interfaces) / sizeof(interfaces[0]); + CK_ULONG i; + char name[32]; +#ifndef HAVE_PKCS11_STATIC + CK_C_GetInterfaceList getList; +#endif +#endif + + check_info(funcList, "v2"); + +#ifdef WOLFPKCS11_PKCS11_V3_0 +#ifndef HAVE_PKCS11_STATIC + getList = (CK_C_GetInterfaceList)dlsym(dlib, "C_GetInterfaceList"); + CHECK_TRUE(getList != NULL, "C_GetInterfaceList symbol"); + rv = (getList != NULL) ? getList(interfaces, &count) : CKR_GENERAL_ERROR; +#else + rv = C_GetInterfaceList(interfaces, &count); +#endif + CHECK_RV(rv, "C_GetInterfaceList", CKR_OK); + for (i = 0; rv == CKR_OK && i < count; i++) { + snprintf(name, sizeof(name), "interface %lu", (unsigned long)i); + check_info((CK_FUNCTION_LIST*)interfaces[i].pFunctionList, name); + } +#endif + + XMEMSET(&slotInfo, 0, sizeof(slotInfo)); + rv = funcList->C_GetSlotInfo(slot, &slotInfo); + CHECK_RV(rv, "C_GetSlotInfo", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(blank_padded(slotInfo.slotDescription, + sizeof(slotInfo.slotDescription)), + "CK_SLOT_INFO.slotDescription blank padded"); + CHECK_TRUE(blank_padded(slotInfo.manufacturerID, + sizeof(slotInfo.manufacturerID)), + "CK_SLOT_INFO.manufacturerID blank padded"); + } + + XMEMSET(&tokenInfo, 0, sizeof(tokenInfo)); + rv = funcList->C_GetTokenInfo(slot, &tokenInfo); + CHECK_RV(rv, "C_GetTokenInfo", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(blank_padded(tokenInfo.label, sizeof(tokenInfo.label)), + "CK_TOKEN_INFO.label blank padded"); + CHECK_TRUE(blank_padded(tokenInfo.manufacturerID, + sizeof(tokenInfo.manufacturerID)), + "CK_TOKEN_INFO.manufacturerID blank padded"); + CHECK_TRUE(blank_padded(tokenInfo.model, sizeof(tokenInfo.model)), + "CK_TOKEN_INFO.model blank padded"); + CHECK_TRUE(blank_padded(tokenInfo.serialNumber, + sizeof(tokenInfo.serialNumber)), + "CK_TOKEN_INFO.serialNumber has no NUL bytes"); + } +} + +static int run_test(void) +{ + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv != CKR_OK) + return -1; + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + CHECK_RV(rv, "C_Initialize", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + CHECK_RV(rv, "C_GetSlotList", CKR_OK); + } + if (rv == CKR_OK && slotCount == 0) { + CHECK_TRUE(0, "at least one slot"); + rv = CKR_TOKEN_NOT_PRESENT; + } + if (rv == CKR_OK) { + test_info_blank_padding(slotList[0]); + } + funcList->C_Finalize(NULL); + + pkcs11_unload(); + return 0; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + + printf("=== wolfPKCS11 info and mechanism table test ===\n"); + run_test(); + return pkcs11_test_summary(); +} diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 6a021b1d..af005256 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -442,6 +442,7 @@ WP11_LOCAL int WP11_Slot_SetUserPin(WP11_Slot* slot, char* pin, int pinLen); WP11_LOCAL int WP11_Slot_TokenReset(WP11_Slot* slot, char* pin, int pinLen, char* label); WP11_LOCAL void WP11_Slot_GetTokenLabel(WP11_Slot* slot, char* label); +WP11_LOCAL void WP11_BlankPad(CK_UTF8CHAR* field, int len); WP11_LOCAL int WP11_Slot_IsTokenInitialized(WP11_Slot* slot); WP11_LOCAL int WP11_Slot_TokenFailedLogin(WP11_Slot* slot, int login); WP11_LOCAL time_t WP11_Slot_TokenFailedExpire(WP11_Slot* slot, int login); From d88c33c00b3c3375936d37646254eb8cc6038223 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 08:55:03 -0700 Subject: [PATCH 059/204] F-6230 - Report the token clock only when a valid UTC time is available --- src/slot.c | 42 +++++++++++++++++++--------------- tests/info_mech_table_test.c | 44 ++++++++++++++++++++++++++++++++++++ tests/pkcs11test.c | 11 ++++++--- 3 files changed, 76 insertions(+), 21 deletions(-) diff --git a/src/slot.c b/src/slot.c index 5fd5375a..87621d49 100644 --- a/src/slot.c +++ b/src/slot.c @@ -204,6 +204,7 @@ CK_RV C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) #ifndef WOLFPKCS11_NO_TIME time_t now, expire; struct tm nowTM; + struct tm* utc; #endif WP11_Slot* slot; int cnt; @@ -245,28 +246,33 @@ CK_RV C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) #ifndef WOLFPKCS11_NO_TIME now = XTIME(0); XMEMSET(&nowTM, 0, sizeof(nowTM)); - if (XGMTIME(&now, &nowTM) != NULL) { - pInfo->utcTime[ 0] = (((1900 + nowTM.tm_year) / 1000) % 10) + '0'; - pInfo->utcTime[ 1] = (((1900 + nowTM.tm_year) / 100) % 10) + '0'; - pInfo->utcTime[ 2] = (((1900 + nowTM.tm_year) / 10) % 10) + '0'; - pInfo->utcTime[ 3] = (((1900 + nowTM.tm_year) / 1) % 10) + '0'; - pInfo->utcTime[ 4] = (((1 + nowTM.tm_mon) / 10) % 10) + '0'; - pInfo->utcTime[ 5] = (((1 + nowTM.tm_mon) / 1) % 10) + '0'; - pInfo->utcTime[ 6] = ((nowTM.tm_mday / 10) % 10) + '0'; - pInfo->utcTime[ 7] = ((nowTM.tm_mday / 1) % 10) + '0'; - pInfo->utcTime[ 8] = ((nowTM.tm_hour / 10) % 10) + '0'; - pInfo->utcTime[ 9] = ((nowTM.tm_hour / 1) % 10) + '0'; - pInfo->utcTime[10] = ((nowTM.tm_min / 10) % 10) + '0'; - pInfo->utcTime[11] = ((nowTM.tm_min / 1) % 10) + '0'; - pInfo->utcTime[12] = ((nowTM.tm_sec / 10) % 10) + '0'; - pInfo->utcTime[13] = ((nowTM.tm_sec / 1) % 10) + '0'; + /* XGMTIME may map to gmtime(), which ignores nowTM. */ + utc = NULL; + if (now != (time_t)-1) + utc = XGMTIME(&now, &nowTM); + if (utc != NULL) { + pInfo->utcTime[ 0] = (((1900 + utc->tm_year) / 1000) % 10) + '0'; + pInfo->utcTime[ 1] = (((1900 + utc->tm_year) / 100) % 10) + '0'; + pInfo->utcTime[ 2] = (((1900 + utc->tm_year) / 10) % 10) + '0'; + pInfo->utcTime[ 3] = (((1900 + utc->tm_year) / 1) % 10) + '0'; + pInfo->utcTime[ 4] = (((1 + utc->tm_mon) / 10) % 10) + '0'; + pInfo->utcTime[ 5] = (((1 + utc->tm_mon) / 1) % 10) + '0'; + pInfo->utcTime[ 6] = ((utc->tm_mday / 10) % 10) + '0'; + pInfo->utcTime[ 7] = ((utc->tm_mday / 1) % 10) + '0'; + pInfo->utcTime[ 8] = ((utc->tm_hour / 10) % 10) + '0'; + pInfo->utcTime[ 9] = ((utc->tm_hour / 1) % 10) + '0'; + pInfo->utcTime[10] = ((utc->tm_min / 10) % 10) + '0'; + pInfo->utcTime[11] = ((utc->tm_min / 1) % 10) + '0'; + pInfo->utcTime[12] = ((utc->tm_sec / 10) % 10) + '0'; + pInfo->utcTime[13] = ((utc->tm_sec / 1) % 10) + '0'; } else { - /* Set date to all zeros. */ - XMEMCPY(pInfo->utcTime, "00000000000000", 14); + pInfo->flags &= ~(CKF_CLOCK_ON_TOKEN); + XMEMSET(pInfo->utcTime, ' ', sizeof(pInfo->utcTime)); } #else - XMEMCPY(pInfo->utcTime, "00000000000000", 14); + pInfo->flags &= ~(CKF_CLOCK_ON_TOKEN); + XMEMSET(pInfo->utcTime, ' ', sizeof(pInfo->utcTime)); #endif cnt = WP11_Slot_TokenFailedLogin(slot, WP11_LOGIN_SO); diff --git a/tests/info_mech_table_test.c b/tests/info_mech_table_test.c index adb64dc6..3ce45b72 100644 --- a/tests/info_mech_table_test.c +++ b/tests/info_mech_table_test.c @@ -143,6 +143,49 @@ static void test_info_blank_padding(CK_SLOT_ID slot) } } +static int two_digits(const CK_CHAR* p, int* val) +{ + if (p[0] < '0' || p[0] > '9' || p[1] < '0' || p[1] > '9') + return 0; + *val = (p[0] - '0') * 10 + (p[1] - '0'); + return 1; +} + +/* A token that claims a clock must report a valid UTC time. */ +static void test_token_clock(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_TOKEN_INFO tokenInfo; + int century = 0, year = 0, month = 0, day = 0; + int hour = 0, minute = 0, second = 0; + int valid; + + XMEMSET(&tokenInfo, 0, sizeof(tokenInfo)); + rv = funcList->C_GetTokenInfo(slot, &tokenInfo); + CHECK_RV(rv, "C_GetTokenInfo(clock)", CKR_OK); + if (rv != CKR_OK) + return; + + if ((tokenInfo.flags & CKF_CLOCK_ON_TOKEN) == 0) { + CHECK_TRUE(XMEMCMP(tokenInfo.utcTime, " ", + sizeof(tokenInfo.utcTime)) == 0, + "utcTime is blank without a clock"); + return; + } + + valid = two_digits(&tokenInfo.utcTime[0], ¢ury) && + two_digits(&tokenInfo.utcTime[2], &year) && + two_digits(&tokenInfo.utcTime[4], &month) && + two_digits(&tokenInfo.utcTime[6], &day) && + two_digits(&tokenInfo.utcTime[8], &hour) && + two_digits(&tokenInfo.utcTime[10], &minute) && + two_digits(&tokenInfo.utcTime[12], &second); + CHECK_TRUE(valid && century * 100 + year >= 1970 && + month >= 1 && month <= 12 && day >= 1 && day <= 31 && + hour <= 23 && minute <= 59 && second <= 60, + "CKF_CLOCK_ON_TOKEN reports a valid utcTime"); +} + static int run_test(void) { CK_RV rv; @@ -169,6 +212,7 @@ static int run_test(void) } if (rv == CKR_OK) { test_info_blank_padding(slotList[0]); + test_token_clock(slotList[0]); } funcList->C_Finalize(NULL); diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index da6d0dca..96da0f1d 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -61,6 +61,11 @@ #define PKCS11TEST_FUNC_SESS_DECL(func) \ PKCS11TEST_CASE(func, TEST_FLAG_INIT | TEST_FLAG_TOKEN | TEST_FLAG_SESSION) +#ifndef WOLFPKCS11_NO_TIME + #define TOKEN_CLOCK_FLAG CKF_CLOCK_ON_TOKEN +#else + #define TOKEN_CLOCK_FLAG 0 +#endif #ifndef HAVE_PKCS11_STATIC static void* dlib; @@ -468,7 +473,7 @@ static CK_RV test_no_token_init(void* args) * 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; + CK_FLAGS expFlags = CKF_RNG | TOKEN_CLOCK_FLAG; int flags = CKF_SERIAL_SESSION | CKF_RW_SESSION; ret = funcList->C_GetTokenInfo(slot, &tokenInfo); @@ -695,7 +700,7 @@ static CK_RV test_token(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; + CK_FLAGS expFlags = CKF_RNG | TOKEN_CLOCK_FLAG | CKF_TOKEN_INITIALIZED; unsigned char label[32]; int flags = CKF_SERIAL_SESSION | CKF_RW_SESSION; @@ -1002,7 +1007,7 @@ static CK_RV test_login_logout(void* args) CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; CK_RV ret = 0; CK_TOKEN_INFO tokenInfo; - CK_FLAGS expFlags = CKF_RNG | CKF_CLOCK_ON_TOKEN | CKF_LOGIN_REQUIRED | + CK_FLAGS expFlags = CKF_RNG | TOKEN_CLOCK_FLAG | CKF_LOGIN_REQUIRED | CKF_TOKEN_INITIALIZED | CKF_USER_PIN_INITIALIZED; funcList->C_Logout(session); From 7a55334dc232f26bf8cb5940b294d1e82a096cd3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:04:28 -0700 Subject: [PATCH 060/204] F-6053 - Advertise wrap and unwrap only for mechanisms C_WrapKey supports --- src/crypto.c | 42 ++++ src/slot.c | 23 +- tests/info_mech_table_test.c | 398 +++++++++++++++++++++++++++++++++++ 3 files changed, 458 insertions(+), 5 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 47ae7f69..d3f7db62 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -8778,6 +8778,10 @@ static CK_RV wp11_C_WrapKey(CK_SESSION_HANDLE hSession, CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; word32 serialSize = 0; byte* serialBuff = NULL; +#ifndef NO_AES + byte* padBuff; + word32 padSize; +#endif CK_BBOOL getVar; CK_ULONG getVarLen = sizeof(CK_BBOOL); @@ -8910,6 +8914,25 @@ static CK_RV wp11_C_WrapKey(CK_SESSION_HANDLE hSession, rv = CKR_WRAPPING_KEY_TYPE_INCONSISTENT; goto err_out; } + /* AES-ECB zero-pads a secret key value to the block size. */ + if (pMechanism->mechanism == CKM_AES_ECB && + keyClass == CKO_SECRET_KEY && + (serialSize % AES_BLOCK_SIZE) != 0) { + padSize = serialSize + AES_BLOCK_SIZE - + (serialSize % AES_BLOCK_SIZE); + padBuff = (byte*)XMALLOC(padSize, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (padBuff == NULL) { + rv = CKR_HOST_MEMORY; + goto err_out; + } + XMEMCPY(padBuff, serialBuff, serialSize); + XMEMSET(padBuff + serialSize, 0, padSize - serialSize); + wc_ForceZero(serialBuff, serialSize); + XFREE(serialBuff, NULL, DYNAMIC_TYPE_TMP_BUFFER); + serialBuff = padBuff; + serialSize = padSize; + } #ifdef WOLFSSL_STM32U5_DHUK if (WP11_Object_GetDevId(wrappingKey) == @@ -9178,6 +9201,25 @@ static CK_RV wp11_C_UnwrapKey(CK_SESSION_HANDLE hSession, if (rv != CKR_OK) goto err_out; + /* AES-ECB keeps no length: CKA_VALUE_LEN drops secret padding. */ + if (pMechanism->mechanism == CKM_AES_ECB && + keyClass == CKO_SECRET_KEY) { + FindAttributeType(pTemplate, ulAttributeCount, CKA_VALUE_LEN, + &attr); + if (attr == NULL) { + rv = CKR_TEMPLATE_INCOMPLETE; + goto err_out; + } + if (attr->pValue == NULL || + attr->ulValueLen != sizeof(CK_ULONG) || + *(CK_ULONG*)attr->pValue == 0 || + *(CK_ULONG*)attr->pValue > ulUnwrappedLen) { + rv = CKR_TEMPLATE_INCONSISTENT; + goto err_out; + } + ulUnwrappedLen = *(CK_ULONG*)attr->pValue; + } + break; #endif #ifndef NO_RSA diff --git a/src/slot.c b/src/slot.c index 87621d49..9790a6c1 100644 --- a/src/slot.c +++ b/src/slot.c @@ -574,6 +574,14 @@ CK_RV C_GetMechanismList(CK_SLOT_ID slotID, return rv; } +/* Matches the key serialization C_WrapKey compiles in for RSA and secret + * keys; without any, every wrap fails with CKR_KEY_NOT_WRAPPABLE. */ +#if defined(WOLFSSL_STM32U5_DHUK) || !defined(WOLFPKCS11_NO_STORE) + #define WP11_MECH_WRAP_FLAG CKF_WRAP +#else + #define WP11_MECH_WRAP_FLAG 0 +#endif + #ifndef NO_RSA #ifdef WOLFSSL_KEY_GEN /* Info on RSA key generation mechanism. */ @@ -583,13 +591,13 @@ static CK_MECHANISM_INFO rsaKgMechInfo = { #endif /* Info on RSA X.509 mechanism. */ static CK_MECHANISM_INFO rsaX509MechInfo = { - 1024, 4096, CKF_ENCRYPT | CKF_DECRYPT | CKF_SIGN | CKF_VERIFY | CKF_WRAP | - CKF_UNWRAP | CKF_VERIFY_RECOVER + 1024, 4096, CKF_ENCRYPT | CKF_DECRYPT | CKF_SIGN | CKF_VERIFY | + CKF_VERIFY_RECOVER }; /* Info on RSA PKCS#1.5 mechanism. */ static CK_MECHANISM_INFO rsaPkcsMechInfo = { 1024, 4096, CKF_ENCRYPT | CKF_DECRYPT | CKF_SIGN | CKF_VERIFY | - CKF_VERIFY_RECOVER + CKF_VERIFY_RECOVER | WP11_MECH_WRAP_FLAG | CKF_UNWRAP }; #ifndef WC_NO_RSA_OAEP /* Info on RSA PKCS#1 OAEP mechanism. */ @@ -753,7 +761,7 @@ static CK_MECHANISM_INFO aesKeyGenMechInfo = { }; #ifdef HAVE_AES_KEYWRAP static CK_MECHANISM_INFO aesKeyWrapMechInfo = { - 16, 32, CKF_ENCRYPT | CKF_DECRYPT | CKF_WRAP | CKF_UNWRAP + 16, 32, CKF_ENCRYPT | CKF_DECRYPT | WP11_MECH_WRAP_FLAG | CKF_UNWRAP }; #endif #ifdef HAVE_AES_CBC @@ -761,6 +769,9 @@ static CK_MECHANISM_INFO aesKeyWrapMechInfo = { static CK_MECHANISM_INFO aesCbcMechInfo = { 16, 32, CKF_ENCRYPT | CKF_DECRYPT }; +static CK_MECHANISM_INFO aesCbcPadMechInfo = { + 16, 32, CKF_ENCRYPT | CKF_DECRYPT | WP11_MECH_WRAP_FLAG | CKF_UNWRAP +}; static CK_MECHANISM_INFO aesCbcEncryptDataMechInfo = { 1, 32, CKF_DERIVE }; @@ -786,7 +797,7 @@ static CK_MECHANISM_INFO aesCcmMechInfo = { #ifdef HAVE_AESECB /* Info on AES-ECB mechanism. */ static CK_MECHANISM_INFO aesEcbMechInfo = { - 16, 32, CKF_ENCRYPT | CKF_DECRYPT + 16, 32, CKF_ENCRYPT | CKF_DECRYPT | WP11_MECH_WRAP_FLAG | CKF_UNWRAP }; #endif #ifdef HAVE_AESCTS @@ -1083,6 +1094,8 @@ CK_RV C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, #endif #ifdef HAVE_AES_CBC case CKM_AES_CBC_PAD: + XMEMCPY(pInfo, &aesCbcPadMechInfo, sizeof(CK_MECHANISM_INFO)); + break; case CKM_AES_CBC: XMEMCPY(pInfo, &aesCbcMechInfo, sizeof(CK_MECHANISM_INFO)); break; diff --git a/tests/info_mech_table_test.c b/tests/info_mech_table_test.c index 3ce45b72..028e162d 100644 --- a/tests/info_mech_table_test.c +++ b/tests/info_mech_table_test.c @@ -186,12 +186,396 @@ static void test_token_clock(CK_SLOT_ID slot) "CKF_CLOCK_ON_TOKEN reports a valid utcTime"); } +/* C_WrapKey rejects every key before the mechanism check when it cannot + * serialize keys, as in a build without a store. */ +#if defined(WOLFPKCS11_NO_STORE) && !defined(WOLFSSL_STM32U5_DHUK) + #define WRAP_UNSUPPORTED_RV CKR_KEY_NOT_WRAPPABLE +#else + #define WRAP_UNSUPPORTED_RV CKR_MECHANISM_INVALID +#endif + +static CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; +static CK_KEY_TYPE genericType = CKK_GENERIC_SECRET; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; + +typedef struct WrapCase { + CK_MECHANISM_TYPE mech; + int useIv; + int rsa; +} WrapCase; + +/* Wrap and unwrap must succeed exactly for mechanisms that advertise them. */ +static void test_wrap_flags(CK_SLOT_ID slot, CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_MECHANISM_INFO info; + CK_MECHANISM mech; + CK_OBJECT_HANDLE secret = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE wrapKey; + CK_OBJECT_HANDLE unwrapKey; + CK_OBJECT_HANDLE unwrapped; + CK_BYTE iv[16]; + CK_BYTE secretValue[32]; + CK_BYTE aesValue[16]; + CK_BYTE wrapped[512]; + CK_ULONG wrappedLen; + CK_BYTE* unwrapData; + CK_ULONG unwrapLen; + char msg[96]; + size_t i; +#ifndef NO_RSA + CK_OBJECT_HANDLE rsaPub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE rsaPriv = CK_INVALID_HANDLE; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE rsaType = CKK_RSA; + CK_ATTRIBUTE rsaPubTmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_WRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + CK_ATTRIBUTE rsaPrivTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_UNWRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &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) }, + { 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) }, + }; +#endif +#ifndef NO_AES + CK_KEY_TYPE aesType = CKK_AES; + CK_ATTRIBUTE aesTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_WRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_UNWRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aesValue, sizeof(aesValue) }, + }; +#endif + CK_ATTRIBUTE secretTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, secretValue, sizeof(secretValue) }, + }; + CK_ULONG secretLen = sizeof(secretValue); + CK_ATTRIBUTE unwrapTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE_LEN, &secretLen, sizeof(secretLen) }, + }; + static const WrapCase cases[] = { + { CKM_AES_KEY_WRAP, 0, 0 }, + { CKM_AES_KEY_WRAP_PAD, 0, 0 }, + { CKM_AES_CBC, 1, 0 }, + { CKM_AES_CBC_PAD, 1, 0 }, + { CKM_AES_ECB, 0, 0 }, + { CKM_RSA_PKCS, 0, 1 }, + { CKM_RSA_X_509, 0, 1 }, + }; + + XMEMSET(iv, 0x11, sizeof(iv)); + XMEMSET(secretValue, 0x22, sizeof(secretValue)); + XMEMSET(aesValue, 0x33, sizeof(aesValue)); + + rv = funcList->C_CreateObject(session, secretTmpl, + sizeof(secretTmpl) / sizeof(*secretTmpl), + &secret); + CHECK_RV(rv, "C_CreateObject(secret to wrap)", CKR_OK); + if (rv != CKR_OK) + return; +#ifndef NO_AES + rv = funcList->C_CreateObject(session, aesTmpl, + sizeof(aesTmpl) / sizeof(*aesTmpl), &aesKey); + CHECK_RV(rv, "C_CreateObject(AES wrapping key)", CKR_OK); +#endif +#ifndef NO_RSA + rv = funcList->C_CreateObject(session, rsaPubTmpl, + sizeof(rsaPubTmpl) / sizeof(*rsaPubTmpl), + &rsaPub); + CHECK_RV(rv, "C_CreateObject(RSA wrapping key)", CKR_OK); + rv = funcList->C_CreateObject(session, rsaPrivTmpl, + sizeof(rsaPrivTmpl) / sizeof(*rsaPrivTmpl), + &rsaPriv); + CHECK_RV(rv, "C_CreateObject(RSA unwrapping key)", CKR_OK); +#endif + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + rv = funcList->C_GetMechanismInfo(slot, cases[i].mech, &info); + if (rv == CKR_MECHANISM_INVALID) + continue; + snprintf(msg, sizeof(msg), "C_GetMechanismInfo(0x%lx)", + (unsigned long)cases[i].mech); + CHECK_RV(rv, msg, CKR_OK); + if (rv != CKR_OK) + continue; + + wrapKey = aesKey; + unwrapKey = aesKey; +#ifndef NO_RSA + if (cases[i].rsa) { + wrapKey = rsaPub; + unwrapKey = rsaPriv; + } +#endif + if (wrapKey == CK_INVALID_HANDLE || unwrapKey == CK_INVALID_HANDLE) + continue; + + mech.mechanism = cases[i].mech; + mech.pParameter = cases[i].useIv ? iv : NULL; + mech.ulParameterLen = cases[i].useIv ? sizeof(iv) : 0; + + wrappedLen = sizeof(wrapped); + rv = funcList->C_WrapKey(session, &mech, wrapKey, secret, wrapped, + &wrappedLen); + snprintf(msg, sizeof(msg), "C_WrapKey(0x%lx) matches CKF_WRAP", + (unsigned long)cases[i].mech); + CHECK_RV(rv, msg, ((info.flags & CKF_WRAP) != 0) ? + CKR_OK : WRAP_UNSUPPORTED_RV); + + unwrapData = wrapped; + unwrapLen = wrappedLen; + if (rv != CKR_OK) { + XMEMSET(wrapped, 0x44, sizeof(wrapped)); + unwrapLen = cases[i].rsa ? 256 : 48; + } + if (rv != CKR_OK && (info.flags & CKF_UNWRAP) != 0) { + /* Unwrap works without wrap support: encrypt the key directly. */ + wrappedLen = sizeof(wrapped); + rv = funcList->C_EncryptInit(session, &mech, wrapKey); + if (rv == CKR_OK) { + rv = funcList->C_Encrypt(session, secretValue, + sizeof(secretValue), wrapped, + &wrappedLen); + } + snprintf(msg, sizeof(msg), "C_Encrypt(0x%lx) key to unwrap", + (unsigned long)cases[i].mech); + CHECK_RV(rv, msg, CKR_OK); + unwrapLen = wrappedLen; + } + unwrapped = CK_INVALID_HANDLE; + /* AES-ECB needs CKA_VALUE_LEN to know the key length. */ + rv = funcList->C_UnwrapKey(session, &mech, unwrapKey, unwrapData, + unwrapLen, unwrapTmpl, + sizeof(unwrapTmpl) / sizeof(*unwrapTmpl) - + (cases[i].mech == CKM_AES_ECB ? 0 : 1), + &unwrapped); + snprintf(msg, sizeof(msg), "C_UnwrapKey(0x%lx) matches CKF_UNWRAP", + (unsigned long)cases[i].mech); + CHECK_RV(rv, msg, ((info.flags & CKF_UNWRAP) != 0) ? + CKR_OK : CKR_MECHANISM_INVALID); + if (unwrapped != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, unwrapped); + } +} + +#ifndef NO_AES +/* AES-ECB wraps a key that is not block aligned and unwraps it back. */ +static void test_ecb_wrap_short_key(CK_SLOT_ID slot, CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_MECHANISM_INFO info; + CK_MECHANISM mech = { CKM_AES_ECB, NULL, 0 }; + CK_OBJECT_HANDLE secret = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE unwrapped = CK_INVALID_HANDLE; + CK_KEY_TYPE aesType = CKK_AES; + CK_ULONG valueLen = 20; + CK_BYTE secretValue[20]; + CK_BYTE aesValue[16]; + CK_BYTE wrapped[64]; + CK_BYTE value[32]; + CK_ULONG wrappedLen = sizeof(wrapped); + CK_ATTRIBUTE secretTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, secretValue, sizeof(secretValue) }, + }; + CK_ATTRIBUTE aesTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_WRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_UNWRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aesValue, sizeof(aesValue) }, + }; + CK_ATTRIBUTE unwrapTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE_LEN, &valueLen, sizeof(valueLen) }, + }; + CK_ATTRIBUTE valueAttr = { CKA_VALUE, value, sizeof(value) }; + + rv = funcList->C_GetMechanismInfo(slot, CKM_AES_ECB, &info); + if (rv != CKR_OK || (info.flags & CKF_WRAP) == 0) + return; + XMEMSET(secretValue, 0x5c, sizeof(secretValue)); + XMEMSET(aesValue, 0x3a, sizeof(aesValue)); + rv = funcList->C_CreateObject(session, secretTmpl, + sizeof(secretTmpl) / sizeof(*secretTmpl), &secret); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, aesTmpl, + sizeof(aesTmpl) / sizeof(*aesTmpl), &aesKey); + } + CHECK_RV(rv, "create 20 byte secret and AES wrapping key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_WrapKey(session, &mech, aesKey, secret, wrapped, + &wrappedLen); + CHECK_RV(rv, "C_WrapKey(AES-ECB) pads a 20 byte key", CKR_OK); + CHECK_TRUE(rv != CKR_OK || wrappedLen == 32, + "AES-ECB wrapped length is two blocks"); + } + if (rv == CKR_OK) { + rv = funcList->C_UnwrapKey(session, &mech, aesKey, wrapped, + wrappedLen, unwrapTmpl, + sizeof(unwrapTmpl) / sizeof(*unwrapTmpl), + &unwrapped); + CHECK_RV(rv, "C_UnwrapKey(AES-ECB) with CKA_VALUE_LEN", CKR_OK); + } + if (rv == CKR_OK) { + CK_OBJECT_HANDLE noLen = CK_INVALID_HANDLE; + + rv = funcList->C_UnwrapKey(session, &mech, aesKey, wrapped, + wrappedLen, unwrapTmpl, + sizeof(unwrapTmpl) / sizeof(*unwrapTmpl) - 1, + &noLen); + CHECK_RV(rv, "C_UnwrapKey(AES-ECB) needs CKA_VALUE_LEN", + CKR_TEMPLATE_INCOMPLETE); + rv = CKR_OK; + } + if (rv == CKR_OK) { + rv = funcList->C_GetAttributeValue(session, unwrapped, &valueAttr, 1); + CHECK_TRUE(rv == CKR_OK && valueAttr.ulValueLen == sizeof(secretValue) + && XMEMCMP(value, secretValue, sizeof(secretValue)) == 0, + "AES-ECB unwrapped key has its original value"); + } + if (unwrapped != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, unwrapped); + if (aesKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, aesKey); + if (secret != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, secret); +} +#endif + +#if !defined(NO_RSA) && !defined(NO_AES) && !defined(WOLFPKCS11_NO_STORE) +/* An RSA key AES-ECB wraps must unwrap without CKA_VALUE_LEN. */ +static void test_ecb_wrap_rsa_key(CK_SLOT_ID slot, CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_MECHANISM_INFO info; + CK_MECHANISM mech = { CKM_AES_ECB, NULL, 0 }; + CK_OBJECT_HANDLE rsaPriv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE unwrapped = CK_INVALID_HANDLE; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE rsaType = CKK_RSA; + CK_KEY_TYPE aesType = CKK_AES; + CK_BYTE aesValue[16]; + CK_BYTE wrapped[2048]; + CK_ULONG wrappedLen = sizeof(wrapped); + CK_ATTRIBUTE rsaPrivTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &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) }, + { 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 aesTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_WRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_UNWRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aesValue, sizeof(aesValue) }, + }; + CK_ATTRIBUTE unwrapTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + }; + + rv = funcList->C_GetMechanismInfo(slot, CKM_AES_ECB, &info); + if (rv != CKR_OK || (info.flags & CKF_WRAP) == 0) + return; + XMEMSET(aesValue, 0x4b, sizeof(aesValue)); + rv = funcList->C_CreateObject(session, rsaPrivTmpl, + sizeof(rsaPrivTmpl) / sizeof(*rsaPrivTmpl), &rsaPriv); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, aesTmpl, + sizeof(aesTmpl) / sizeof(*aesTmpl), &aesKey); + } + CHECK_RV(rv, "create RSA private key and AES wrapping key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_WrapKey(session, &mech, aesKey, rsaPriv, wrapped, + &wrappedLen); + } + if (rv == CKR_OK) { + rv = funcList->C_UnwrapKey(session, &mech, aesKey, wrapped, + wrappedLen, unwrapTmpl, + sizeof(unwrapTmpl) / sizeof(*unwrapTmpl), + &unwrapped); + CHECK_RV(rv, "C_UnwrapKey(AES-ECB) of the RSA key it wrapped", CKR_OK); + } + else { + CHECK_TRUE(rsaPriv != CK_INVALID_HANDLE && aesKey != CK_INVALID_HANDLE, + "C_WrapKey(AES-ECB) refuses an unaligned RSA key"); + } + if (unwrapped != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, unwrapped); + if (aesKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, aesKey); + if (rsaPriv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, rsaPriv); +} +#endif + static int run_test(void) { CK_RV rv; CK_C_INITIALIZE_ARGS args; CK_SLOT_ID slotList[16]; CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; rv = pkcs11_load(); CHECK_RV(rv, "load library", CKR_OK); @@ -213,6 +597,20 @@ static int run_test(void) if (rv == CKR_OK) { test_info_blank_padding(slotList[0]); test_token_clock(slotList[0]); + rv = funcList->C_OpenSession(slotList[0], + CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + CHECK_RV(rv, "C_OpenSession", CKR_OK); + } + if (rv == CKR_OK) { + test_wrap_flags(slotList[0], session); +#ifndef NO_AES + test_ecb_wrap_short_key(slotList[0], session); +#endif +#if !defined(NO_RSA) && !defined(NO_AES) && !defined(WOLFPKCS11_NO_STORE) + test_ecb_wrap_rsa_key(slotList[0], session); +#endif + funcList->C_CloseSession(session); } funcList->C_Finalize(NULL); From 641561a4e46dc2a0f96c3e7981f904472aa451a3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:14:17 -0700 Subject: [PATCH 061/204] F-12477 - Report generic secret key generation sizes in bits --- src/internal.c | 11 --------- src/slot.c | 3 ++- tests/info_mech_table_test.c | 45 ++++++++++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 11 +++++++++ 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/internal.c b/src/internal.c index 7f60afb1..a6306503 100644 --- a/src/internal.c +++ b/src/internal.c @@ -127,17 +127,6 @@ #define WP11_MAX_DH_KEY_SZ (4096/8) #endif -/* Maximum size of storage for generated/derived symmetric key. */ -#ifdef WOLFPKCS11_NSS -#define WP11_MAX_SYM_KEY_SZ (2048) -#elif !defined(NO_DH) -#define WP11_MAX_SYM_KEY_SZ (4096/8) -#elif defined(HAVE_ECC) -#define WP11_MAX_SYM_KEY_SZ ((521+7)/8) -#else -#define WP11_MAX_SYM_KEY_SZ 64 -#endif - #ifndef WP11_MAX_CERT_SZ #define WP11_MAX_CERT_SZ 4096 #endif diff --git a/src/slot.c b/src/slot.c index 9790a6c1..38497006 100644 --- a/src/slot.c +++ b/src/slot.c @@ -893,8 +893,9 @@ static CK_MECHANISM_INFO sha3MechInfo = { }; #endif #endif +/* Key sizes in bits. */ static CK_MECHANISM_INFO genSecKeyGenMechInfo = { - 1, 32, CKF_GENERATE + 8, WP11_MAX_SYM_KEY_SZ * 8, CKF_GENERATE }; static CK_MECHANISM_INFO pkcs5Pbkdf2MechInfo = { diff --git a/tests/info_mech_table_test.c b/tests/info_mech_table_test.c index 028e162d..81a9b4e0 100644 --- a/tests/info_mech_table_test.c +++ b/tests/info_mech_table_test.c @@ -199,6 +199,50 @@ static CK_KEY_TYPE genericType = CKK_GENERIC_SECRET; static CK_BBOOL ckTrue = CK_TRUE; static CK_BBOOL ckFalse = CK_FALSE; +static CK_RV generate_generic(CK_SESSION_HANDLE session, CK_ULONG len) +{ + CK_RV rv; + CK_MECHANISM mech = { CKM_GENERIC_SECRET_KEY_GEN, NULL, 0 }; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE_LEN, &len, sizeof(len) }, + }; + + rv = funcList->C_GenerateKey(session, &mech, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &key); + if (key != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, key); + return rv; +} + +/* Generic secret key sizes are reported in bits and match what generates. */ +static void test_generic_secret_size(CK_SLOT_ID slot, + CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_MECHANISM_INFO info; + + rv = funcList->C_GetMechanismInfo(slot, CKM_GENERIC_SECRET_KEY_GEN, &info); + CHECK_RV(rv, "C_GetMechanismInfo(CKM_GENERIC_SECRET_KEY_GEN)", CKR_OK); + if (rv != CKR_OK) + return; + + CHECK_TRUE(info.ulMinKeySize >= 8 && info.ulMinKeySize % 8 == 0 && + info.ulMaxKeySize % 8 == 0 && + info.ulMaxKeySize >= info.ulMinKeySize, + "generic secret key sizes are whole bytes in bits"); + rv = generate_generic(session, info.ulMinKeySize / 8); + CHECK_RV(rv, "generate generic secret of minimum size", CKR_OK); + rv = generate_generic(session, info.ulMaxKeySize / 8); + CHECK_RV(rv, "generate generic secret of maximum size", CKR_OK); + rv = generate_generic(session, info.ulMaxKeySize / 8 + 1); + CHECK_TRUE(rv != CKR_OK, + "generic secret above maximum size is rejected"); +} + typedef struct WrapCase { CK_MECHANISM_TYPE mech; int useIv; @@ -603,6 +647,7 @@ static int run_test(void) CHECK_RV(rv, "C_OpenSession", CKR_OK); } if (rv == CKR_OK) { + test_generic_secret_size(slotList[0], session); test_wrap_flags(slotList[0], session); #ifndef NO_AES test_ecb_wrap_short_key(slotList[0], session); diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index af005256..d799b130 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -348,6 +348,17 @@ C_EXTRA_FLAGS="-DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT" #define WP11_MAX_PIN_LEN 32 #endif +/* Maximum size of storage for generated/derived symmetric key. */ +#ifdef WOLFPKCS11_NSS +#define WP11_MAX_SYM_KEY_SZ (2048) +#elif !defined(NO_DH) +#define WP11_MAX_SYM_KEY_SZ (4096/8) +#elif defined(HAVE_ECC) +#define WP11_MAX_SYM_KEY_SZ ((521+7)/8) +#else +#define WP11_MAX_SYM_KEY_SZ 64 +#endif + /* Login failure constants. */ #ifndef WP11_MAX_LOGIN_FAILS_SO #define WP11_MAX_LOGIN_FAILS_SO 3 From 8761dd3cf880cd0332fbcdbb3d3f91e92e91e314 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:26:09 -0700 Subject: [PATCH 062/204] F-13248 - Advertise PBKDF2 and PKCS#12 PBE key generation only with HMAC --- src/slot.c | 12 ++++++++++ tests/info_mech_table_test.c | 45 ++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/slot.c b/src/slot.c index 38497006..b7c20331 100644 --- a/src/slot.c +++ b/src/slot.c @@ -486,16 +486,20 @@ static CK_MECHANISM_TYPE mechanismList[] = { #ifdef WOLFPKCS11_NSS /* NSS uses this as a target-key marker when unwrapping TLS secrets. */ CKM_SSL3_MASTER_KEY_DERIVE, +#ifndef NO_HMAC CKM_NSS_PKCS12_PBE_SHA224_HMAC_KEY_GEN, CKM_NSS_PKCS12_PBE_SHA256_HMAC_KEY_GEN, CKM_NSS_PKCS12_PBE_SHA384_HMAC_KEY_GEN, CKM_NSS_PKCS12_PBE_SHA512_HMAC_KEY_GEN, #endif +#endif #ifdef WOLFSSL_HAVE_PRF CKM_TLS_MAC, #endif CKM_GENERIC_SECRET_KEY_GEN, +#ifndef NO_HMAC CKM_PKCS5_PBKD2 +#endif }; /* Count of mechanisms in list. */ @@ -730,6 +734,7 @@ static CK_MECHANISM_INFO nssTls12MasterKeyDeriveDhInfo = { static CK_MECHANISM_INFO nssTls12MasterKeyDeriveInfo = { 48, 128, CKF_DERIVE }; +#ifndef NO_HMAC static CK_MECHANISM_INFO nssPkcs12PbeSha224HmacKeyGenMechInfo = { 224, 224, CKF_GENERATE }; @@ -742,6 +747,7 @@ static CK_MECHANISM_INFO nssPkcs12PbeSha384HmacKeyGenMechInfo = { static CK_MECHANISM_INFO nssPkcs12PbeSha512HmacKeyGenMechInfo = { 512, 512, CKF_GENERATE }; +#endif /* NSS requires this mechanism identifier when selecting the slot used to * unwrap cached TLS secrets. C_DeriveKey does not implement the mechanism, so * do not advertise CKF_DERIVE. */ @@ -898,9 +904,11 @@ static CK_MECHANISM_INFO genSecKeyGenMechInfo = { 8, WP11_MAX_SYM_KEY_SZ * 8, CKF_GENERATE }; +#ifndef NO_HMAC static CK_MECHANISM_INFO pkcs5Pbkdf2MechInfo = { 1, 256, CKF_GENERATE }; +#endif /** * Get information on a mechanism. @@ -1245,6 +1253,7 @@ CK_RV C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, XMEMCPY(pInfo, &nssTls12MasterKeyDeriveDhInfo, sizeof(CK_MECHANISM_INFO)); break; +#ifndef NO_HMAC case CKM_NSS_PKCS12_PBE_SHA224_HMAC_KEY_GEN: XMEMCPY(pInfo, &nssPkcs12PbeSha224HmacKeyGenMechInfo, sizeof(CK_MECHANISM_INFO)); @@ -1261,6 +1270,7 @@ CK_RV C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, XMEMCPY(pInfo, &nssPkcs12PbeSha512HmacKeyGenMechInfo, sizeof(CK_MECHANISM_INFO)); break; +#endif case CKM_SSL3_MASTER_KEY_DERIVE: XMEMCPY(pInfo, &ssl3MasterKeyTargetInfo, sizeof(CK_MECHANISM_INFO)); @@ -1277,10 +1287,12 @@ CK_RV C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, XMEMCPY(pInfo, &genSecKeyGenMechInfo, sizeof(CK_MECHANISM_INFO)); break; +#ifndef NO_HMAC case CKM_PKCS5_PBKD2: XMEMCPY(pInfo, &pkcs5Pbkdf2MechInfo, sizeof(CK_MECHANISM_INFO)); break; +#endif default: return CKR_MECHANISM_INVALID; } diff --git a/tests/info_mech_table_test.c b/tests/info_mech_table_test.c index 81a9b4e0..73549bbe 100644 --- a/tests/info_mech_table_test.c +++ b/tests/info_mech_table_test.c @@ -243,6 +243,50 @@ static void test_generic_secret_size(CK_SLOT_ID slot, "generic secret above maximum size is rejected"); } +/* Every listed key generation mechanism must be known to C_GenerateKey. */ +static void test_listed_keygen_supported(CK_SLOT_ID slot, + CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_MECHANISM_TYPE mechs[256]; + CK_ULONG count = sizeof(mechs) / sizeof(mechs[0]); + CK_ULONG i; + CK_MECHANISM_INFO info; + CK_MECHANISM mech; + CK_OBJECT_HANDLE key; + char msg[96]; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + }; + + rv = funcList->C_GetMechanismList(slot, mechs, &count); + CHECK_RV(rv, "C_GetMechanismList", CKR_OK); + if (rv != CKR_OK) + return; + + for (i = 0; i < count; i++) { + rv = funcList->C_GetMechanismInfo(slot, mechs[i], &info); + snprintf(msg, sizeof(msg), "C_GetMechanismInfo(listed 0x%lx)", + (unsigned long)mechs[i]); + CHECK_RV(rv, msg, CKR_OK); + if (rv != CKR_OK || (info.flags & CKF_GENERATE) == 0) + continue; + + mech.mechanism = mechs[i]; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + key = CK_INVALID_HANDLE; + rv = funcList->C_GenerateKey(session, &mech, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &key); + if (key != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, key); + snprintf(msg, sizeof(msg), "C_GenerateKey accepts listed 0x%lx", + (unsigned long)mechs[i]); + CHECK_TRUE(rv != CKR_MECHANISM_INVALID, msg); + } +} + typedef struct WrapCase { CK_MECHANISM_TYPE mech; int useIv; @@ -648,6 +692,7 @@ static int run_test(void) } if (rv == CKR_OK) { test_generic_secret_size(slotList[0], session); + test_listed_keygen_supported(slotList[0], session); test_wrap_flags(slotList[0], session); #ifndef NO_AES test_ecb_wrap_short_key(slotList[0], session); From d80f979c15fc36f277427ff0baeaba8fce611049 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:34:25 -0700 Subject: [PATCH 063/204] F-8634 - Report C_OpenSession failures instead of returning CKR_OK --- src/slot.c | 12 ++++++ tests/info_mech_table_test.c | 76 ++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/src/slot.c b/src/slot.c index b7c20331..cbd2c8a6 100644 --- a/src/slot.c +++ b/src/slot.c @@ -1595,6 +1595,8 @@ CK_RV C_SetPIN(CK_SESSION_HANDLE hSession, CK_UTF8CHAR_PTR pOldPin, * CKR_SESSION_READ_WRITE_SO_EXISTS when there is an existing open * Security Officer session. * CKR_SESSION_COUNT when no more sessions can be opened on token. + * CKR_HOST_MEMORY when allocating the session fails. + * CKR_FUNCTION_FAILED when opening the session fails otherwise. * CKR_OK on success. */ CK_RV C_OpenSession(CK_SLOT_ID slotID, CK_FLAGS flags, @@ -1644,6 +1646,16 @@ CK_RV C_OpenSession(CK_SLOT_ID slotID, CK_FLAGS flags, WOLFPKCS11_LEAVE("C_OpenSession", rv); return rv; } + if (ret == MEMORY_E) { + rv = CKR_HOST_MEMORY; + WOLFPKCS11_LEAVE("C_OpenSession", rv); + return rv; + } + if (ret != 0) { + rv = CKR_FUNCTION_FAILED; + WOLFPKCS11_LEAVE("C_OpenSession", rv); + return rv; + } rv = CKR_OK; WOLFPKCS11_LEAVE("C_OpenSession", rv); diff --git a/tests/info_mech_table_test.c b/tests/info_mech_table_test.c index 73549bbe..3eea94f6 100644 --- a/tests/info_mech_table_test.c +++ b/tests/info_mech_table_test.c @@ -27,6 +27,7 @@ #endif #include +#include #include #ifndef WOLFSSL_USER_SETTINGS @@ -34,6 +35,7 @@ #endif #include #include +#include #ifndef WOLFPKCS11_USER_SETTINGS #include @@ -49,6 +51,34 @@ #define TEST_DIR "./store/info_mech_table_test" +#if defined(USE_WOLFSSL_MEMORY) && !defined(NO_WOLFSSL_MEMORY) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_DEBUG_MEMORY) + #define ALLOC_HOOKS +#endif + +#ifdef ALLOC_HOOKS +static int failAllocs = 0; + +static void* hook_malloc(size_t sz) +{ + if (failAllocs) + return NULL; + return malloc(sz); +} + +static void hook_free(void* ptr) +{ + free(ptr); +} + +static void* hook_realloc(void* ptr, size_t sz) +{ + if (failAllocs) + return NULL; + return realloc(ptr, sz); +} +#endif + /* Fixed-length character fields are blank padded and never contain NUL. */ static int blank_padded(const CK_UTF8CHAR* field, size_t len) { @@ -287,6 +317,43 @@ static void test_listed_keygen_supported(CK_SLOT_ID slot, } } +#ifdef ALLOC_HOOKS +/* A session is reported open only when a usable handle was returned. */ +static void test_open_session_alloc_failure(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE handles[8]; + CK_SESSION_INFO sessInfo; + int opened = 0; + int failed = 0; + int i; + + for (i = 0; i < (int)(sizeof(handles) / sizeof(handles[0])); i++) { + handles[i] = CK_INVALID_HANDLE; + failAllocs = 1; + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &handles[i]); + failAllocs = 0; + if (rv != CKR_OK) { + CHECK_RV(rv, "C_OpenSession without memory", CKR_HOST_MEMORY); + failed = 1; + break; + } + opened++; + rv = funcList->C_GetSessionInfo(handles[i], &sessInfo); + CHECK_RV(rv, "C_OpenSession returns a usable handle", CKR_OK); + if (rv != CKR_OK) + break; + } + CHECK_TRUE(failed, "C_OpenSession reports allocation failure"); + + for (i = 0; i < opened; i++) { + if (handles[i] != CK_INVALID_HANDLE) + funcList->C_CloseSession(handles[i]); + } +} +#endif + typedef struct WrapCase { CK_MECHANISM_TYPE mech; int useIv; @@ -693,6 +760,9 @@ static int run_test(void) if (rv == CKR_OK) { test_generic_secret_size(slotList[0], session); test_listed_keygen_supported(slotList[0], session); +#ifdef ALLOC_HOOKS + test_open_session_alloc_failure(slotList[0]); +#endif test_wrap_flags(slotList[0], session); #ifndef NO_AES test_ecb_wrap_short_key(slotList[0], session); @@ -718,6 +788,12 @@ int main(int argc, char* argv[]) #endif printf("=== wolfPKCS11 info and mechanism table test ===\n"); +#ifdef ALLOC_HOOKS + if (wolfSSL_SetAllocators(hook_malloc, hook_free, hook_realloc) != 0) { + CHECK_TRUE(0, "install allocation hooks"); + return pkcs11_test_summary(); + } +#endif run_test(); return pkcs11_test_summary(); } From 817a9f491680e5eac3898a9ba181c682907015dd Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:38:53 -0700 Subject: [PATCH 064/204] F-10370 - Document how C_Initialize uses pInitArgs --- src/wolfpkcs11.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/wolfpkcs11.c b/src/wolfpkcs11.c index 1c1a48f0..7170625a 100644 --- a/src/wolfpkcs11.c +++ b/src/wolfpkcs11.c @@ -522,8 +522,12 @@ CK_RV C_GetInterface(CK_UTF8CHAR_PTR pInterfaceName, CK_VERSION_PTR pVersion, /** * Initialize the Crypto-Ki library. * - * @param pInitArgs [out] Ignored. - * @return CKR_FUNCTION_FAILED when initializing fails. + * @param pInitArgs [in] Optional CK_C_INITIALIZE_ARGS. The mutex callbacks + * must be all set or all NULL and pReserved must be + * NULL. NSS builds read the configdir from + * LibraryParameters. The callbacks are not used. + * @return CKR_ARGUMENTS_BAD when pInitArgs is not valid. + * CKR_FUNCTION_FAILED when initializing fails. * CKR_OK on success. */ CK_RV C_Initialize(CK_VOID_PTR pInitArgs) From f2072e6fb2d241491a5fb5a086f80bad60c51c25 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:43:24 -0700 Subject: [PATCH 065/204] F-10371 - Remove the wrong NULL pulCount return from C_GetMechanismList docs --- src/slot.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/slot.c b/src/slot.c index cbd2c8a6..b39d3da2 100644 --- a/src/slot.c +++ b/src/slot.c @@ -518,7 +518,6 @@ static int mechanismCnt = ((int)(sizeof(mechanismList)/sizeof(*mechanismList))); * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SLOT_ID_INVALID when no slot with id can be found. * CKR_ARGUMENTS_BAD when pulCount is NULL. - * CKR_BUFFER_TOO_SMALL when pulCount is NULL. * CKR_BUFFER_TOO_SMALL when there are more mechanisms than entries in * array. * CKR_OK on success. From 4eb5b0a59d28c81718258b7e6e6c271c56b33e10 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:49:41 -0700 Subject: [PATCH 066/204] F-10372 - Document CKR_PIN_LEN_RANGE for C_InitToken PIN length --- src/slot.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slot.c b/src/slot.c index b39d3da2..392ae07b 100644 --- a/src/slot.c +++ b/src/slot.c @@ -1309,8 +1309,8 @@ CK_RV C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SLOT_ID_INVALID when no slot with id can be found. * CKR_ARGUMENTS_BAD when pPin or pLabel is NULL. - * CKR_PIN_INCORRECT when length of PIN is not valid or PIN does not - * match initialized PIN. + * CKR_PIN_LEN_RANGE when length of PIN is not valid. + * CKR_PIN_INCORRECT when PIN does not match initialized PIN. * CKR_SESSION_EXISTS when a session is open on the token. * CKR_FUNCTION_FAILED when resetting token fails. * CKR_OK on success. From 28b5ada7a345fbc7cf279048f4d200b4750a5c28 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:54:18 -0700 Subject: [PATCH 067/204] F-10373 - Document CKR_PIN_LEN_RANGE for C_InitPIN PIN length --- src/slot.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slot.c b/src/slot.c index 392ae07b..cc5210a2 100644 --- a/src/slot.c +++ b/src/slot.c @@ -1398,7 +1398,7 @@ CK_RV C_InitToken(CK_SLOT_ID slotID, CK_UTF8CHAR_PTR pPin, * CKR_SESSION_HANDLE_INVALID when session handle is not valid. * CKR_ARGUMENTS_BAD when pPin is NULL. * CKR_USER_NOT_LOGGED_IN when not logged in as Security Officer. - * CKR_PIN_INCORRECT when length of PIN is not valid. + * CKR_PIN_LEN_RANGE when length of PIN is not valid. * CKR_FUNCTION_FAILED when setting User PIN fails. * CKR_OK on success. */ From 41e17247c0a0ae3d666c131a2cffb6779506e579 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:59:03 -0700 Subject: [PATCH 068/204] F-10374 - Document CKR_PIN_LEN_RANGE for the C_SetPIN new PIN length --- src/slot.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slot.c b/src/slot.c index cc5210a2..4544d84f 100644 --- a/src/slot.c +++ b/src/slot.c @@ -1468,8 +1468,9 @@ CK_RV C_InitPIN(CK_SESSION_HANDLE hSession, CK_UTF8CHAR_PTR pPin, * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SESSION_HANDLE_INVALID when session handle is not valid. * CKR_ARGUMENTS_BAD when pOldPin or pNewPin is NULL. - * CKR_PIN_INCORRECT when length of old or new PIN is not valid or - * old PIN does not verify. + * CKR_PIN_INCORRECT when length of old PIN is not valid or old PIN + * does not verify. + * CKR_PIN_LEN_RANGE when length of new PIN is not valid. * CKR_SESSION_READ_ONLY when session not read/write. * CKR_USER_PIN_NOT_INITIALIZED when no previous PIN set for user. * CKR_FUNCTION_FAILED when setting user PIN fails. From a3f7d032eae2981370f602e56fb883ac235d3b4e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 10:03:10 -0700 Subject: [PATCH 069/204] F-9413 - Correct the WP11_Slot_UserLogin return code docs --- src/internal.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index a6306503..adf76518 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10162,7 +10162,8 @@ void WP11_Slot_ObjectCallLeave(WP11_ObjectCalls* calls) * @param slot [in] Slot object. * @param pin [in] PIN to use to login. * @param pinLen [in] Length of PIN. - * @return READ_ONLY_E when there is a read-only session open. + * @return LOGGED_IN_E when the user is already logged in. + * LOGGED_IN_ANOTHER_E when the SO is logged in. * PIN_NOT_SET_E when the token is not initialized. * PIN_INVALID_E when the PIN is not correct. * Other -ve value when hashing PIN fails. From 83507258e6b98005083aa6c9d25bc516f8ca7c34 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:36:02 -0700 Subject: [PATCH 070/204] Require CKA_VALUE_LEN on ECB unwrap only when ambiguous --- src/crypto.c | 12 +-- tests/info_mech_table_test.c | 150 +++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 5 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index d3f7db62..19361469 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9201,23 +9201,25 @@ static CK_RV wp11_C_UnwrapKey(CK_SESSION_HANDLE hSession, if (rv != CKR_OK) goto err_out; - /* AES-ECB keeps no length: CKA_VALUE_LEN drops secret padding. */ + /* ECB zero pads; a 32 byte AES value may be a padded AES-192 key. */ if (pMechanism->mechanism == CKM_AES_ECB && keyClass == CKO_SECRET_KEY) { FindAttributeType(pTemplate, ulAttributeCount, CKA_VALUE_LEN, &attr); - if (attr == NULL) { + if (attr == NULL && (keyType != CKK_AES || + ulUnwrappedLen != AES_128_KEY_SIZE)) { rv = CKR_TEMPLATE_INCOMPLETE; goto err_out; } - if (attr->pValue == NULL || + if (attr != NULL && (attr->pValue == NULL || attr->ulValueLen != sizeof(CK_ULONG) || *(CK_ULONG*)attr->pValue == 0 || - *(CK_ULONG*)attr->pValue > ulUnwrappedLen) { + *(CK_ULONG*)attr->pValue > ulUnwrappedLen)) { rv = CKR_TEMPLATE_INCONSISTENT; goto err_out; } - ulUnwrappedLen = *(CK_ULONG*)attr->pValue; + if (attr != NULL) + ulUnwrappedLen = *(CK_ULONG*)attr->pValue; } break; diff --git a/tests/info_mech_table_test.c b/tests/info_mech_table_test.c index 3eea94f6..d1c7505c 100644 --- a/tests/info_mech_table_test.c +++ b/tests/info_mech_table_test.c @@ -640,6 +640,154 @@ static void test_ecb_wrap_short_key(CK_SLOT_ID slot, CK_SESSION_HANDLE session) if (secret != CK_INVALID_HANDLE) funcList->C_DestroyObject(session, secret); } + +/* An AES-128 key unwraps under AES-ECB without CKA_VALUE_LEN, a two block + * value needs it since it may be a padded AES-192 key, and a CKA_VALUE_LEN + * that is zero or longer than the decrypted data is rejected. */ +static void test_ecb_unwrap_aes_key(CK_SLOT_ID slot, CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_MECHANISM_INFO info; + CK_MECHANISM mech = { CKM_AES_ECB, NULL, 0 }; + CK_OBJECT_HANDLE inner = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE unwrapped = CK_INVALID_HANDLE; + CK_KEY_TYPE aesType = CKK_AES; + CK_ULONG valueLen = 0; + CK_BYTE innerValue[16]; + CK_BYTE aes192Value[24]; + CK_BYTE aesValue[16]; + CK_BYTE wrapped[64]; + CK_BYTE value[32]; + CK_ULONG wrappedLen = sizeof(wrapped); + CK_ATTRIBUTE innerTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, innerValue, sizeof(innerValue) }, + }; + CK_ATTRIBUTE aesTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_WRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_UNWRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aesValue, sizeof(aesValue) }, + }; + CK_ATTRIBUTE unwrapTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE_LEN, &valueLen, sizeof(valueLen) }, + }; + CK_ULONG unwrapCnt = sizeof(unwrapTmpl) / sizeof(*unwrapTmpl); + CK_ATTRIBUTE valueAttr = { CKA_VALUE, value, sizeof(value) }; + + rv = funcList->C_GetMechanismInfo(slot, CKM_AES_ECB, &info); + if (rv != CKR_OK || (info.flags & CKF_WRAP) == 0) + return; + XMEMSET(innerValue, 0x6d, sizeof(innerValue)); + XMEMSET(aes192Value, 0x7b, sizeof(aes192Value)); + XMEMSET(aesValue, 0x2e, sizeof(aesValue)); + rv = funcList->C_CreateObject(session, innerTmpl, + sizeof(innerTmpl) / sizeof(*innerTmpl), &inner); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, aesTmpl, + sizeof(aesTmpl) / sizeof(*aesTmpl), &aesKey); + } + CHECK_RV(rv, "create AES-128 key and AES wrapping key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_WrapKey(session, &mech, aesKey, inner, wrapped, + &wrappedLen); + CHECK_RV(rv, "C_WrapKey(AES-ECB) of an AES-128 key", CKR_OK); + CHECK_TRUE(rv != CKR_OK || wrappedLen == sizeof(innerValue), + "AES-ECB wrapped AES-128 key is one block"); + } + if (rv == CKR_OK) { + rv = funcList->C_UnwrapKey(session, &mech, aesKey, wrapped, + wrappedLen, unwrapTmpl, unwrapCnt - 1, + &unwrapped); + CHECK_RV(rv, "C_UnwrapKey(AES-ECB) of an AES key without CKA_VALUE_LEN", + CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_GetAttributeValue(session, unwrapped, &valueAttr, 1); + CHECK_TRUE(rv == CKR_OK && valueAttr.ulValueLen == sizeof(innerValue) + && XMEMCMP(value, innerValue, sizeof(innerValue)) == 0, + "AES-ECB unwrapped AES key keeps the full value"); + funcList->C_DestroyObject(session, unwrapped); + unwrapped = CK_INVALID_HANDLE; + } + if (rv == CKR_OK) { + valueLen = 0; + rv = funcList->C_UnwrapKey(session, &mech, aesKey, wrapped, + wrappedLen, unwrapTmpl, unwrapCnt, + &unwrapped); + CHECK_RV(rv, "C_UnwrapKey(AES-ECB) rejects a zero CKA_VALUE_LEN", + CKR_TEMPLATE_INCONSISTENT); + valueLen = wrappedLen + 1; + rv = funcList->C_UnwrapKey(session, &mech, aesKey, wrapped, + wrappedLen, unwrapTmpl, unwrapCnt, + &unwrapped); + CHECK_RV(rv, "C_UnwrapKey(AES-ECB) rejects an oversized CKA_VALUE_LEN", + CKR_TEMPLATE_INCONSISTENT); + valueLen = wrappedLen; + rv = funcList->C_UnwrapKey(session, &mech, aesKey, wrapped, + wrappedLen, unwrapTmpl, unwrapCnt, + &unwrapped); + CHECK_RV(rv, "C_UnwrapKey(AES-ECB) of an AES key with CKA_VALUE_LEN", + CKR_OK); + funcList->C_DestroyObject(session, unwrapped); + unwrapped = CK_INVALID_HANDLE; + } + if (rv == CKR_OK) { + funcList->C_DestroyObject(session, inner); + inner = CK_INVALID_HANDLE; + innerTmpl[5].pValue = aes192Value; + innerTmpl[5].ulValueLen = sizeof(aes192Value); + rv = funcList->C_CreateObject(session, innerTmpl, + sizeof(innerTmpl) / sizeof(*innerTmpl), &inner); + CHECK_RV(rv, "create AES-192 key", CKR_OK); + } + if (rv == CKR_OK) { + wrappedLen = sizeof(wrapped); + rv = funcList->C_WrapKey(session, &mech, aesKey, inner, wrapped, + &wrappedLen); + CHECK_RV(rv, "C_WrapKey(AES-ECB) pads an AES-192 key", CKR_OK); + CHECK_TRUE(rv != CKR_OK || wrappedLen == 32, + "AES-ECB wrapped AES-192 key is two blocks"); + } + if (rv == CKR_OK) { + rv = funcList->C_UnwrapKey(session, &mech, aesKey, wrapped, + wrappedLen, unwrapTmpl, unwrapCnt - 1, + &unwrapped); + CHECK_RV(rv, "C_UnwrapKey(AES-ECB) of two blocks needs CKA_VALUE_LEN", + CKR_TEMPLATE_INCOMPLETE); + valueLen = sizeof(aes192Value); + rv = funcList->C_UnwrapKey(session, &mech, aesKey, wrapped, + wrappedLen, unwrapTmpl, unwrapCnt, + &unwrapped); + CHECK_RV(rv, "C_UnwrapKey(AES-ECB) of an AES-192 key with CKA_VALUE_LEN", + CKR_OK); + } + if (rv == CKR_OK) { + valueAttr.ulValueLen = sizeof(value); + rv = funcList->C_GetAttributeValue(session, unwrapped, &valueAttr, 1); + CHECK_TRUE(rv == CKR_OK && valueAttr.ulValueLen == sizeof(aes192Value) + && XMEMCMP(value, aes192Value, sizeof(aes192Value)) == 0, + "AES-ECB unwrapped AES-192 key has its original value"); + } + if (unwrapped != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, unwrapped); + if (aesKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, aesKey); + if (inner != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, inner); +} #endif #if !defined(NO_RSA) && !defined(NO_AES) && !defined(WOLFPKCS11_NO_STORE) @@ -649,6 +797,7 @@ static void test_ecb_wrap_rsa_key(CK_SLOT_ID slot, CK_SESSION_HANDLE session) CK_RV rv; CK_MECHANISM_INFO info; CK_MECHANISM mech = { CKM_AES_ECB, NULL, 0 }; + CK_MECHANISM cbcMech = { CKM_AES_CBC_PAD, NULL, 0 }; CK_OBJECT_HANDLE rsaPriv = CK_INVALID_HANDLE; CK_OBJECT_HANDLE aesKey = CK_INVALID_HANDLE; CK_OBJECT_HANDLE unwrapped = CK_INVALID_HANDLE; @@ -766,6 +915,7 @@ static int run_test(void) test_wrap_flags(slotList[0], session); #ifndef NO_AES test_ecb_wrap_short_key(slotList[0], session); + test_ecb_unwrap_aes_key(slotList[0], session); #endif #if !defined(NO_RSA) && !defined(NO_AES) && !defined(WOLFPKCS11_NO_STORE) test_ecb_wrap_rsa_key(slotList[0], session); From 6bbb8a8b8d4f449070171812e59ed59d4ba6390f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:36:02 -0700 Subject: [PATCH 071/204] Return CKR_KEY_SIZE_RANGE for unaligned ECB wraps --- src/crypto.c | 5 +++ tests/info_mech_table_test.c | 67 +++++++++++++++++++++++++++++++----- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 19361469..88a17bdf 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -8963,6 +8963,11 @@ static CK_RV wp11_C_WrapKey(CK_SESSION_HANDLE hSession, * leaves C_Encrypt's operation active, which would make the * caller's next C_WrapKey fail with CKR_OPERATION_ACTIVE. */ WP11_Session_AbortOp(session); + /* Only a block aligned key encoding can be ECB wrapped. */ + if (rv == CKR_DATA_LEN_RANGE && + pMechanism->mechanism == CKM_AES_ECB) { + rv = CKR_KEY_SIZE_RANGE; + } if (rv != CKR_OK) goto err_out; } diff --git a/tests/info_mech_table_test.c b/tests/info_mech_table_test.c index d1c7505c..bf05f17d 100644 --- a/tests/info_mech_table_test.c +++ b/tests/info_mech_table_test.c @@ -790,8 +790,10 @@ static void test_ecb_unwrap_aes_key(CK_SLOT_ID slot, CK_SESSION_HANDLE session) } #endif -#if !defined(NO_RSA) && !defined(NO_AES) && !defined(WOLFPKCS11_NO_STORE) -/* An RSA key AES-ECB wraps must unwrap without CKA_VALUE_LEN. */ +#if !defined(NO_RSA) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + !defined(WOLFPKCS11_NO_STORE) +/* AES-ECB refuses an RSA key whose encoding is not block aligned, and the + * zero padded encoding unwraps under AES-ECB without CKA_VALUE_LEN. */ static void test_ecb_wrap_rsa_key(CK_SLOT_ID slot, CK_SESSION_HANDLE session) { CK_RV rv; @@ -805,8 +807,14 @@ static void test_ecb_wrap_rsa_key(CK_SLOT_ID slot, CK_SESSION_HANDLE session) CK_KEY_TYPE rsaType = CKK_RSA; CK_KEY_TYPE aesType = CKK_AES; CK_BYTE aesValue[16]; + CK_BYTE iv[16]; CK_BYTE wrapped[2048]; + CK_BYTE encoded[2048]; + CK_BYTE modulus[sizeof(rsa_2048_modulus)]; CK_ULONG wrappedLen = sizeof(wrapped); + CK_ULONG encodedLen = sizeof(encoded); + CK_ULONG ecbLen = sizeof(encoded); + CK_ATTRIBUTE modAttr = { CKA_MODULUS, modulus, sizeof(modulus) }; CK_ATTRIBUTE rsaPrivTmpl[] = { { CKA_CLASS, &privClass, sizeof(privClass) }, { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, @@ -842,6 +850,9 @@ static void test_ecb_wrap_rsa_key(CK_SLOT_ID slot, CK_SESSION_HANDLE session) if (rv != CKR_OK || (info.flags & CKF_WRAP) == 0) return; XMEMSET(aesValue, 0x4b, sizeof(aesValue)); + XMEMSET(iv, 0x1c, sizeof(iv)); + cbcMech.pParameter = iv; + cbcMech.ulParameterLen = sizeof(iv); rv = funcList->C_CreateObject(session, rsaPrivTmpl, sizeof(rsaPrivTmpl) / sizeof(*rsaPrivTmpl), &rsaPriv); if (rv == CKR_OK) { @@ -849,20 +860,59 @@ static void test_ecb_wrap_rsa_key(CK_SLOT_ID slot, CK_SESSION_HANDLE session) sizeof(aesTmpl) / sizeof(*aesTmpl), &aesKey); } CHECK_RV(rv, "create RSA private key and AES wrapping key", CKR_OK); + /* Recover the key encoding through AES-CBC-PAD; it is zero padded below. */ if (rv == CKR_OK) { - rv = funcList->C_WrapKey(session, &mech, aesKey, rsaPriv, wrapped, + rv = funcList->C_WrapKey(session, &cbcMech, aesKey, rsaPriv, wrapped, &wrappedLen); + if (rv == CKR_FUNCTION_FAILED) { + printf("Skipping AES-ECB RSA wrap: RSA key export unsupported\n"); + rv = CKR_GENERAL_ERROR; + } + else { + CHECK_RV(rv, "C_WrapKey(AES-CBC-PAD) of the RSA key", CKR_OK); + } + } + if (rv == CKR_OK) { + rv = funcList->C_WrapKey(session, &mech, aesKey, rsaPriv, encoded, + &ecbLen); + CHECK_RV(rv, "C_WrapKey(AES-ECB) refuses an unaligned RSA key", + CKR_KEY_SIZE_RANGE); + rv = (rv == CKR_KEY_SIZE_RANGE) ? CKR_OK : CKR_GENERAL_ERROR; + } + if (rv == CKR_OK) { + rv = funcList->C_DecryptInit(session, &cbcMech, aesKey); + if (rv == CKR_OK) { + rv = funcList->C_Decrypt(session, wrapped, wrappedLen, encoded, + &encodedLen); + } + CHECK_RV(rv, "C_Decrypt(AES-CBC-PAD) of the wrapped RSA key", CKR_OK); + CHECK_TRUE(rv != CKR_OK || (encodedLen % 16) != 0, + "RSA key encoding is not block aligned"); + } + if (rv == CKR_OK) { + XMEMSET(encoded + encodedLen, 0, 16 - (encodedLen % 16)); + encodedLen += 16 - (encodedLen % 16); + wrappedLen = sizeof(wrapped); + rv = funcList->C_EncryptInit(session, &mech, aesKey); + if (rv == CKR_OK) { + rv = funcList->C_Encrypt(session, encoded, encodedLen, wrapped, + &wrappedLen); + } + CHECK_RV(rv, "C_Encrypt(AES-ECB) of the padded RSA key", CKR_OK); } if (rv == CKR_OK) { rv = funcList->C_UnwrapKey(session, &mech, aesKey, wrapped, wrappedLen, unwrapTmpl, sizeof(unwrapTmpl) / sizeof(*unwrapTmpl), &unwrapped); - CHECK_RV(rv, "C_UnwrapKey(AES-ECB) of the RSA key it wrapped", CKR_OK); + CHECK_RV(rv, "C_UnwrapKey(AES-ECB) of an RSA key without CKA_VALUE_LEN", + CKR_OK); } - else { - CHECK_TRUE(rsaPriv != CK_INVALID_HANDLE && aesKey != CK_INVALID_HANDLE, - "C_WrapKey(AES-ECB) refuses an unaligned RSA key"); + if (rv == CKR_OK) { + rv = funcList->C_GetAttributeValue(session, unwrapped, &modAttr, 1); + CHECK_TRUE(rv == CKR_OK && modAttr.ulValueLen == sizeof(modulus) && + XMEMCMP(modulus, rsa_2048_modulus, sizeof(modulus)) == 0, + "AES-ECB unwrapped RSA key has the original modulus"); } if (unwrapped != CK_INVALID_HANDLE) funcList->C_DestroyObject(session, unwrapped); @@ -917,7 +967,8 @@ static int run_test(void) test_ecb_wrap_short_key(slotList[0], session); test_ecb_unwrap_aes_key(slotList[0], session); #endif -#if !defined(NO_RSA) && !defined(NO_AES) && !defined(WOLFPKCS11_NO_STORE) +#if !defined(NO_RSA) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + !defined(WOLFPKCS11_NO_STORE) test_ecb_wrap_rsa_key(slotList[0], session); #endif funcList->C_CloseSession(session); From 2622df992ff085b2edc85da828eab2e7cab97f60 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:34:10 -0700 Subject: [PATCH 072/204] F-10378 - Fix file store path lookup build with WOLFPKCS11_NO_ENV --- .github/workflows/unit-test.yml | 5 +++++ src/internal.c | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 8cfcb303..505a9a49 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -50,6 +50,11 @@ jobs: uses: ./.github/workflows/build-workflow.yml with: config: CFLAGS="-DWOLFPKCS11_NO_STORE" + no_env: + uses: ./.github/workflows/build-workflow.yml + with: + config: C_EXTRA_FLAGS="-DWOLFPKCS11_NO_ENV -DWOLFPKCS11_DEFAULT_TOKEN_PATH=./store/default" + check: mkdir -p store && ./tests/pkcs11test no_aesgcm: uses: ./.github/workflows/build-workflow.yml with: diff --git a/src/internal.c b/src/internal.c index adf76518..50c754dc 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1572,14 +1572,14 @@ static int wolfPKCS11_Store_Name(int type, CK_ULONG id1, CK_ULONG id2, char* nam int nameLen) { int ret = 0; -#ifndef WOLFPKCS11_NO_ENV const char* str = NULL; -#endif /* Reserve enough space in the final filename for suffixes such as * "/wp11_rsakey_priv_%016lx_%016lx" (47 chars worst-case). */ enum { WP11_STORE_SUFFIX_RESERVE = 48 }; +#ifndef WOLFPKCS11_NO_ENV char homePath[256]; +#endif #ifdef WP11_HAVE_STORE_DIR_LOCK char storeDirCopy[WP11_STORE_MAX_PATH]; #endif @@ -1619,6 +1619,7 @@ static int wolfPKCS11_Store_Name(int type, CK_ULONG id1, CK_ULONG id2, char* nam } #endif +#ifndef WOLFPKCS11_NO_ENV if (str == NULL) { const char* homeDir = NULL; @@ -1642,6 +1643,7 @@ static int wolfPKCS11_Store_Name(int type, CK_ULONG id1, CK_ULONG id2, char* nam } #endif } +#endif #ifdef WOLFPKCS11_DEFAULT_TOKEN_PATH if (str == NULL) { From 43880a6f29b3c05ebf71d043a95acd8d2436f99a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:23:17 -0700 Subject: [PATCH 073/204] F-10379 - Keep token object count intact when an object store fails --- src/crypto.c | 2 + src/internal.c | 11 +- tests/include.am | 6 + tests/token_store_test.c | 309 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 322 insertions(+), 6 deletions(-) create mode 100644 tests/token_store_test.c diff --git a/src/crypto.c b/src/crypto.c index 88a17bdf..0e0fe05c 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9987,6 +9987,8 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, if (ret == 0) { rv = AddObject(session, obj, pTemplate, ulAttributeCount, phKey); + if (rv != CKR_OK) + WP11_Object_Free(obj); } } else { diff --git a/src/internal.c b/src/internal.c index 50c754dc..29dd3e41 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8645,12 +8645,6 @@ static int wp11_Token_StorePass(WP11_Token* token, int tokenId) #ifdef DEBUG_WOLFPKCS11 printf("Failed to store object %d, token %d, ret: %d\n", i, tokenId, ret); - #endif - token->objCnt = i; /* mark number of objects actually stored */ - #ifdef WOLFPKCS11_TPM_STORE - if ((ret & RC_MAX_FM0) == TPM_RC_NV_SPACE) { - ret = 0; /* allow this error and continue */ - } #endif break; } @@ -11956,6 +11950,11 @@ int WP11_Session_AddObject(WP11_Session* session, int onToken, object->handle = CK_INVALID_HANDLE; object->next = NULL; object->lock = NULL; + #ifdef WOLFPKCS11_TPM_STORE + /* Best effort: the failed store may have recreated the token + * NV index empty. The original error is returned. */ + (void)wp11_Slot_Store(session->slot, (int)session->slotId); + #endif } } #endif diff --git a/tests/include.am b/tests/include.am index 01ac5c42..ce7dc956 100644 --- a/tests/include.am +++ b/tests/include.am @@ -264,6 +264,10 @@ check_PROGRAMS += tests/token_reset_fail_test noinst_PROGRAMS += tests/token_reset_fail_test tests_token_reset_fail_test_SOURCES = tests/token_reset_fail_test.c tests_token_reset_fail_test_LDADD = +check_PROGRAMS += tests/token_store_test +noinst_PROGRAMS += tests/token_store_test +tests_token_store_test_SOURCES = tests/token_store_test.c +tests_token_store_test_LDADD = check_PROGRAMS += tests/nss_email_store_test noinst_PROGRAMS += tests/nss_email_store_test @@ -390,6 +394,7 @@ tests_hbs_persistence_test_LDADD += src/libwolfpkcs11.la tests_hkdf_persistence_test_LDADD += src/libwolfpkcs11.la tests_unique_id_test_LDADD += src/libwolfpkcs11.la tests_token_reset_fail_test_LDADD += src/libwolfpkcs11.la +tests_token_store_test_LDADD += src/libwolfpkcs11.la tests_nss_email_store_test_LDADD += src/libwolfpkcs11.la tests_generate_key_class_test_LDADD += src/libwolfpkcs11.la tests_generate_keypair_class_test_LDADD += src/libwolfpkcs11.la @@ -450,6 +455,7 @@ tests_hbs_persistence_test_LDADD += src/libwolfpkcs11.la tests_hkdf_persistence_test_LDADD += src/libwolfpkcs11.la tests_unique_id_test_LDADD += src/libwolfpkcs11.la tests_token_reset_fail_test_LDADD += src/libwolfpkcs11.la +tests_token_store_test_LDADD += src/libwolfpkcs11.la tests_nss_email_store_test_LDADD += src/libwolfpkcs11.la tests_generate_key_class_test_LDADD += src/libwolfpkcs11.la tests_generate_keypair_class_test_LDADD += src/libwolfpkcs11.la diff --git a/tests/token_store_test.c b/tests/token_store_test.c new file mode 100644 index 00000000..f6d005dd --- /dev/null +++ b/tests/token_store_test.c @@ -0,0 +1,309 @@ +/* token_store_test.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Token persistence must stay consistent with the in-memory token: every + * token object is persisted and reloaded, and a failure to persist token + * state is reported to the caller instead of being reported as success. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#if !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_TPM_STORE) && \ + !defined(WOLFPKCS11_CUSTOM_STORE) && !defined(WOLFPKCS11_NO_ENV) && \ + !defined(WOLFPKCS11_USER_ENV) && !defined(_WIN32) + +#include +#include +#include + +#define TEST_DIR "./store/token_store_test" + +static const char* soPin = "password123456"; +static const char* userPin = "wolfpkcs11-test"; +static const char tokenLabel[] = "token-store-test"; +static CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; +static CK_KEY_TYPE genericType = CKK_GENERIC_SECRET; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; +static byte keyData[16] = { 0 }; + +static void clear_store_dir(void) +{ + DIR* dir; + struct dirent* ent; + char path[512]; + + (void)mkdir("./store", 0700); + (void)chmod(TEST_DIR, 0700); + dir = opendir(TEST_DIR); + if (dir == NULL) { + (void)mkdir(TEST_DIR, 0700); + return; + } + while ((ent = readdir(dir)) != NULL) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; + snprintf(path, sizeof(path), "%s/%s", TEST_DIR, ent->d_name); + if (remove(path) != 0) + (void)rmdir(path); + } + closedir(dir); +} + +static CK_RV lib_init(void) +{ + CK_C_INITIALIZE_ARGS args; + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + return funcList->C_Initialize(&args); +} + +static CK_RV first_slot(CK_SLOT_ID* slot) +{ + CK_RV rv; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv == CKR_OK && slotCount == 0) + rv = CKR_TOKEN_NOT_PRESENT; + if (rv == CKR_OK) + *slot = slotList[0]; + return rv; +} + +/* Initialize the library and provision a fresh token with a user PIN. */ +static CK_RV token_setup(CK_SLOT_ID* slot) +{ + CK_RV rv; + CK_SESSION_HANDLE soSession = CK_INVALID_HANDLE; + unsigned char label[32]; + + clear_store_dir(); + rv = lib_init(); + if (rv == CKR_OK) + rv = first_slot(slot); + if (rv == CKR_OK) { + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenLabel, XSTRLEN(tokenLabel)); + rv = funcList->C_InitToken(*slot, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin), label); + } + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(*slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &soSession); + } + if (rv == CKR_OK) { + rv = funcList->C_Login(soSession, CKU_SO, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin)); + } + if (rv == CKR_OK) { + rv = funcList->C_InitPIN(soSession, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + } + if (soSession != CK_INVALID_HANDLE) { + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + } + return rv; +} + +static CK_RV user_session(CK_SLOT_ID slot, CK_SESSION_HANDLE* session) +{ + CK_RV rv; + + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, session); + if (rv == CKR_OK) { + rv = funcList->C_Login(*session, CKU_USER, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + } + return rv; +} + +static void close_session(CK_SESSION_HANDLE session) +{ + if (session != CK_INVALID_HANDLE) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } +} + +static CK_RV create_token_secret(CK_SESSION_HANDLE session, const char* label, + CK_OBJECT_HANDLE* obj) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_LABEL, NULL, 0 }, + }; + + tmpl[5].pValue = (CK_VOID_PTR)label; + tmpl[5].ulValueLen = (CK_ULONG)XSTRLEN(label); + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), obj); +} + +static CK_ULONG count_label(CK_SESSION_HANDLE session, const char* label) +{ + CK_RV rv; + CK_OBJECT_HANDLE found[8]; + CK_ULONG foundCnt = 0; + CK_ATTRIBUTE tmpl[] = { + { CKA_LABEL, NULL, 0 }, + }; + + tmpl[0].pValue = (CK_VOID_PTR)label; + tmpl[0].ulValueLen = (CK_ULONG)XSTRLEN(label); + rv = funcList->C_FindObjectsInit(session, tmpl, 1); + if (rv == CKR_OK) { + rv = funcList->C_FindObjects(session, found, 8, &foundCnt); + funcList->C_FindObjectsFinal(session); + } + return (rv == CKR_OK) ? foundCnt : 0; +} + +/* Reload the token from storage and open a user session on it. */ +static CK_RV reload(CK_SLOT_ID slot, CK_SESSION_HANDLE* session) +{ + CK_RV rv; + + funcList->C_Finalize(NULL); + rv = lib_init(); + if (rv == CKR_OK) + rv = user_session(slot, session); + return rv; +} + +/* Token objects keep being persisted after storage is paused and resumed. */ +static void test_objects_persist_across_storage_pause(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + + printf("\n--- token objects persist across a storage pause ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "pause-first", &obj); + CHECK_RV(rv, "create first token object", CKR_OK); + } + if (rv == CKR_OK) { + XSETENV("WOLFPKCS11_NO_STORE", "1", 1); + rv = create_token_secret(session, "pause-second", &obj); + unsetenv("WOLFPKCS11_NO_STORE"); + CHECK_RV(rv, "create token object while storage is paused", CKR_OK); + } + if (rv == CKR_OK) { + rv = create_token_secret(session, "pause-third", &obj); + CHECK_RV(rv, "create token object after storage resumes", CKR_OK); + } + close_session(session); + session = CK_INVALID_HANDLE; + + if (rv == CKR_OK) { + rv = reload(slot, &session); + CHECK_RV(rv, "reload token", CKR_OK); + } + if (rv == CKR_OK) { + CHECK_TRUE(count_label(session, "pause-first") == 1, + "object stored before the pause is reloaded"); + CHECK_TRUE(count_label(session, "pause-second") == 1, + "object created during the pause is reloaded"); + CHECK_TRUE(count_label(session, "pause-third") == 1, + "object created after the pause is reloaded"); + } + close_session(session); + funcList->C_Finalize(NULL); +} + +static int run_tests(void) +{ + CK_RV rv; + + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv != CKR_OK) + return -1; + + test_objects_persist_across_storage_pause(); + + clear_store_dir(); + pkcs11_unload(); + return 0; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); + unsetenv("WOLFPKCS11_NO_STORE"); + + printf("=== wolfPKCS11 token store test ===\n"); + run_tests(); + return pkcs11_test_summary(); +} + +#else + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + printf("Token store test requires the file-backed store, skipping\n"); + return 77; +} + +#endif From f3292d80ea5576a33d733498ec8124cbdac2a537 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:21:42 -0700 Subject: [PATCH 074/204] F-10386 - Report token store commit failures to the caller --- src/internal.c | 101 +++++++++++++++------- tests/token_store_test.c | 179 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+), 29 deletions(-) diff --git a/src/internal.c b/src/internal.c index 29dd3e41..c186ef70 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2557,10 +2557,14 @@ int wolfPKCS11_Store_Open(int type, CK_ULONG id1, CK_ULONG id2, int read, * Closes access to location being read or written. * Any dynamic memory associated with the store is freed here. * - * @param [in] store Context for operation. + * @param [in] store Context for operation. + * @param [in] commit 1 to commit written data and 0 to discard it. + * @return 0 on success. + * @return Other value when written data could not be committed. */ -void wolfPKCS11_Store_Close(void* store) +static int wolfPKCS11_Store_CloseCommit(void* store, int commit) { + int ret = 0; #ifdef WOLFPKCS11_TPM_STORE WP11_TpmStore* tpmStore = (WP11_TpmStore*)store; #else @@ -2574,27 +2578,32 @@ void wolfPKCS11_Store_Close(void* store) #ifdef WOLFPKCS11_TPM_STORE /* nothing to do for TPM */ (void)tpmStore; + (void)commit; #else if (ctx != NULL) { int commitRet = 0; if (ctx->file != XBADFILE && ctx->file != NULL) { - if (ctx->is_write && wolfPKCS11_StoreSyncFile(ctx->file) != 0) - commitRet = BUFFER_E; - if (XFCLOSE(ctx->file) != 0 && ctx->is_write) - commitRet = BUFFER_E; + if (ctx->is_write && commit && + wolfPKCS11_StoreSyncFile(ctx->file) != 0) { + commit = 0; + ret = BUFFER_E; + } + if (XFCLOSE(ctx->file) != 0 && ctx->is_write) { + commit = 0; + ret = BUFFER_E; + } ctx->file = XBADFILE; } - if (ctx->is_write && ctx->has_temp) { - if (commitRet == 0) { - #ifdef WP11_STORE_BATCH - if (storeBatch.active) - commitRet = wolfPKCS11_StoreStageTemp(ctx); - else - #endif - commitRet = wolfPKCS11_StoreCommitTemp(ctx); - } + if (ctx->is_write && ctx->has_temp && commit) { + #ifdef WP11_STORE_BATCH + if (storeBatch.active) + commitRet = wolfPKCS11_StoreStageTemp(ctx); + else + #endif + commitRet = wolfPKCS11_StoreCommitTemp(ctx); + ret = commitRet; if (commitRet != 0) { #ifdef WP11_STORE_BATCH if (storeBatch.active) @@ -2615,6 +2624,18 @@ void wolfPKCS11_Store_Close(void* store) XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); } #endif + return ret; +} + +/** + * Closes access to location being read or written. + * Any dynamic memory associated with the store is freed here. + * + * @param [in] store Context for operation. + */ +void wolfPKCS11_Store_Close(void* store) +{ + (void)wolfPKCS11_Store_CloseCommit(store, 1); } /** @@ -2799,6 +2820,28 @@ static void wp11_storage_close(void* storage) wolfPKCS11_Store_Close(storage); } +/* + * Closes access to location being written, committing the data only when + * writing succeeded. + * + * @param [in] storage Context for operation. + * @param [in] ret Result of writing the data. + * @return ret when writing failed. + * @return Result of committing the data otherwise. + */ +static int wp11_storage_close_write(void* storage, int ret) +{ +#ifdef WOLFPKCS11_CUSTOM_STORE + wolfPKCS11_Store_Close(storage); +#else + int closeRet = wolfPKCS11_Store_CloseCommit(storage, ret == 0); + + if (ret == 0) + ret = closeRet; +#endif + return ret; +} + /** * Get the number of bytes left to read, when the store can tell. * @@ -4765,7 +4808,7 @@ static int wp11_Object_Store_Cert(WP11_Object* object, int tokenId, int objId) /* Write cert to storage. */ ret = wp11_storage_write_array(storage, object->data.cert.data, object->data.cert.len); - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); } #ifdef WOLFSSL_MAXQ10XX_CRYPTO @@ -4804,7 +4847,7 @@ static int wp11_Object_Store_Trust(WP11_Object* object, int tokenId, int objId) /* Write trust to storage. */ ret = wp11_storage_write_array(storage, (unsigned char*)&object->data.trust, sizeof(WP11_Trust)); - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); } return ret; @@ -5035,7 +5078,7 @@ static int wp11_Object_Store_Data(WP11_Object* object, int tokenId, int objId) object->data.genericData.objectIdLen); } - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); return ret; } @@ -5384,7 +5427,7 @@ static int wp11_Object_Store_RsaKey(WP11_Object* object, int tokenId, int objId) ret = wp11_storage_write_array(storage, object->keyData, object->keyDataLen); - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); } return ret; @@ -5609,7 +5652,7 @@ static int wp11_Object_Store_EccKey(WP11_Object* object, int tokenId, int objId) ret = wp11_storage_write_array(storage, object->keyData, object->keyDataLen); - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); } return ret; @@ -5854,7 +5897,7 @@ static int wp11_Object_Store_MldsaKey(WP11_Object* object, int tokenId, ret = wp11_storage_write_array(storage, object->keyData, object->keyDataLen); - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); } return ret; @@ -6197,7 +6240,7 @@ static int wp11_Object_Store_DhKey(WP11_Object* object, int tokenId, int objId) ret = wp11_storage_write_array(storage, der, len); } - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); } if (der != NULL) { XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -6418,7 +6461,7 @@ static int wp11_Object_Store_MlKemKey(WP11_Object* object, int tokenId, if (ret == 0) { ret = wp11_storage_write_array(storage, object->keyData, object->keyDataLen); - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); } return ret; @@ -6594,7 +6637,7 @@ static int wp11_Object_Store_HssKey(WP11_Object* object, int tokenId, int objId) if (ret == 0) { ret = wp11_storage_write_array(storage, object->keyData, object->keyDataLen); - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); } return ret; } @@ -7034,7 +7077,7 @@ static int wp11_Object_Store_XmssKey(WP11_Object* object, int tokenId, if (ret == 0) { ret = wp11_storage_write_array(storage, object->keyData, object->keyDataLen); - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); } return ret; } @@ -7360,7 +7403,7 @@ static int wp11_Object_Store_SymmKey(WP11_Object* object, int tokenId, ret = wp11_storage_write_array(storage, object->keyData, object->keyDataLen); - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); } return ret; @@ -7663,7 +7706,7 @@ static int wp11_Object_Store_Object(WP11_Object* object, int tokenId, int objId) } #endif - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); } return ret; } @@ -8632,7 +8675,7 @@ static int wp11_Token_StorePass(WP11_Token* token, int tokenId) ret = wp11_storage_open(WOLFPKCS11_STORE_TOKEN, tokenId, 0, variableSz, &storage); if (ret == 0) { - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); storage = NULL; } @@ -8732,7 +8775,7 @@ static int wp11_Token_StorePass(WP11_Token* token, int tokenId) ret = wp11_storage_write_int(storage, token->nextObjId); } - wp11_storage_close(storage); + ret = wp11_storage_close_write(storage, ret); } else if (ret == NOT_AVAILABLE_E) { /* Not writing. */ diff --git a/tests/token_store_test.c b/tests/token_store_test.c index f6d005dd..48a37c5e 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -209,6 +209,14 @@ static CK_ULONG count_label(CK_SESSION_HANDLE session, const char* label) return (rv == CKR_OK) ? foundCnt : 0; } +/* Path of the stored record of a token object. */ +static void object_record_path(CK_SLOT_ID slot, int objId, char* path, + size_t pathSz) +{ + snprintf(path, pathSz, "%s/wp11_obj_%016lx_%016lx", TEST_DIR, + (unsigned long)slot, (unsigned long)objId); +} + /* Reload the token from storage and open a user session on it. */ static CK_RV reload(CK_SLOT_ID slot, CK_SESSION_HANDLE* session) { @@ -267,6 +275,173 @@ static void test_objects_persist_across_storage_pause(void) funcList->C_Finalize(NULL); } +/* A token object whose record cannot be committed is not reported created. */ +static void test_create_reports_commit_failure(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + char path[512]; + int blocked = 0; + + printf("\n--- token object creation reports a commit failure ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "commit-first", &obj); + CHECK_RV(rv, "create first token object", CKR_OK); + } + if (rv == CKR_OK) { + object_record_path(slot, 1, path, sizeof(path)); + blocked = (mkdir(path, 0700) == 0); + CHECK_TRUE(blocked, "occupy the next object record path"); + } + if (blocked) { + rv = create_token_secret(session, "commit-second", &obj); + CHECK_TRUE(rv != CKR_OK, + "create fails when the object cannot be committed"); + CHECK_TRUE(count_label(session, "commit-second") == 0, + "uncommitted object is not in the token"); + CHECK_TRUE(count_label(session, "commit-first") == 1, + "committed object is still in the token"); + (void)rmdir(path); + } + close_session(session); + funcList->C_Finalize(NULL); +} + +/* A PIN change that cannot be stored leaves the stored PIN in effect. */ +static void test_pin_change_store_failure(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + static const char* newPin = "wolfpkcs11-next"; + + if (geteuid() == 0) { + printf("\nSkipping PIN store failure test when run as root\n"); + return; + } + printf("\n--- PIN change that cannot be stored is not applied ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK && chmod(TEST_DIR, 0500) == 0) { + rv = funcList->C_SetPIN(session, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin), + (CK_UTF8CHAR_PTR)newPin, + (CK_ULONG)XSTRLEN(newPin)); + (void)chmod(TEST_DIR, 0700); + CHECK_TRUE(rv != CKR_OK, "PIN change reports the store failure"); + funcList->C_Logout(session); + rv = funcList->C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + CHECK_RV(rv, "stored PIN still logs in", CKR_OK); + } + close_session(session); + funcList->C_Finalize(NULL); +} + +/* A first user PIN that cannot be stored does not log in. */ +static void test_init_pin_store_failure(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE soSession = CK_INVALID_HANDLE; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + unsigned char label[32]; + + if (geteuid() == 0) { + printf("\nSkipping user PIN store failure test when run as root\n"); + return; + } + printf("\n--- user PIN that cannot be stored is not applied ---\n"); + clear_store_dir(); + rv = lib_init(); + if (rv == CKR_OK) + rv = first_slot(&slot); + if (rv == CKR_OK) { + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenLabel, XSTRLEN(tokenLabel)); + rv = funcList->C_InitToken(slot, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin), label); + } + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &soSession); + } + if (rv == CKR_OK) { + rv = funcList->C_Login(soSession, CKU_SO, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin)); + } + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK && chmod(TEST_DIR, 0500) == 0) { + rv = funcList->C_InitPIN(soSession, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + (void)chmod(TEST_DIR, 0700); + CHECK_TRUE(rv != CKR_OK, "user PIN set reports the store failure"); + close_session(soSession); + soSession = CK_INVALID_HANDLE; + rv = user_session(slot, &session); + CHECK_TRUE(rv != CKR_OK, "unstored user PIN does not log in"); + } + close_session(soSession); + if (session != CK_INVALID_HANDLE) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); +} + +/* A token reset that cannot be stored leaves the stored token in effect. */ +static void test_token_reset_store_failure(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + unsigned char label[32]; + static const char* wrongPin = "not-the-so-pin"; + + if (geteuid() == 0) { + printf("\nSkipping token reset store failure test when run as root\n"); + return; + } + printf("\n--- token reset that cannot be stored is not applied ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "reset-keep", &obj); + CHECK_RV(rv, "create token object", CKR_OK); + } + close_session(session); + session = CK_INVALID_HANDLE; + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenLabel, XSTRLEN(tokenLabel)); + if (rv == CKR_OK && chmod(TEST_DIR, 0500) == 0) { + rv = funcList->C_InitToken(slot, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin), label); + (void)chmod(TEST_DIR, 0700); + CHECK_TRUE(rv != CKR_OK, "token reset reports the store failure"); + rv = funcList->C_InitToken(slot, (CK_UTF8CHAR_PTR)wrongPin, + (CK_ULONG)XSTRLEN(wrongPin), label); + CHECK_TRUE(rv != CKR_OK, "token reset still requires the SO PIN"); + rv = user_session(slot, &session); + CHECK_RV(rv, "stored user PIN still logs in", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(count_label(session, "reset-keep") == 1, + "stored token object is still present"); + } + } + close_session(session); + funcList->C_Finalize(NULL); +} + static int run_tests(void) { CK_RV rv; @@ -277,6 +452,10 @@ static int run_tests(void) return -1; test_objects_persist_across_storage_pause(); + test_create_reports_commit_failure(); + test_pin_change_store_failure(); + test_init_pin_store_failure(); + test_token_reset_store_failure(); clear_store_dir(); pkcs11_unload(); From 33e9254fd8557c28244227ce7e82ea6b7b959592 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:29:54 -0700 Subject: [PATCH 075/204] F-10384 - Keep the stored token record until the new one is committed --- src/internal.c | 6 ++-- tests/token_store_test.c | 62 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index c186ef70..af1c51e9 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8659,7 +8659,7 @@ static int wp11_Token_Load(WP11_Slot* slot, int tokenId, WP11_Token* token) */ static int wp11_Token_StorePass(WP11_Token* token, int tokenId) { - int ret; + int ret = 0; int i; void* storage = NULL; WP11_Object* object; @@ -8669,7 +8669,8 @@ static int wp11_Token_StorePass(WP11_Token* token, int tokenId) printf("wp11_Token_Store: tokenId %d, objCnt %d\n", tokenId, token->objCnt); #endif - /* Reserve space for token object */ +#ifdef WOLFPKCS11_TPM_STORE + /* Reserve NV space for token object before the objects use it. */ variableSz = token->userPinLen + token->soPinLen + (token->objCnt * FIELD_SIZE(WP11_Object, type)); ret = wp11_storage_open(WOLFPKCS11_STORE_TOKEN, tokenId, 0, variableSz, @@ -8678,6 +8679,7 @@ static int wp11_Token_StorePass(WP11_Token* token, int tokenId) ret = wp11_storage_close_write(storage, ret); storage = NULL; } +#endif /* Store the objects */ object = token->object; diff --git a/tests/token_store_test.c b/tests/token_store_test.c index 48a37c5e..70f31329 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -217,6 +217,20 @@ static void object_record_path(CK_SLOT_ID slot, int objId, char* path, (unsigned long)slot, (unsigned long)objId); } +/* Read a stored file into buf, returning its length or -1 on error. */ +static long read_store_file(const char* path, byte* buf, size_t bufSz) +{ + FILE* f; + long len; + + f = fopen(path, "rb"); + if (f == NULL) + return -1; + len = (long)fread(buf, 1, bufSz, f); + fclose(f); + return len; +} + /* Reload the token from storage and open a user session on it. */ static CK_RV reload(CK_SLOT_ID slot, CK_SESSION_HANDLE* session) { @@ -313,6 +327,53 @@ static void test_create_reports_commit_failure(void) funcList->C_Finalize(NULL); } +/* A failed token object store leaves the stored token record unchanged. */ +static void test_token_record_kept_on_failed_store(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + char path[512]; + char tokenPath[512]; + byte before[1024]; + byte after[1024]; + long beforeLen = -1; + long afterLen = -1; + int blocked = 0; + + printf("\n--- token record is kept when an object store fails ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "record-first", &obj); + CHECK_RV(rv, "create first token object", CKR_OK); + } + if (rv == CKR_OK) { + snprintf(tokenPath, sizeof(tokenPath), "%s/wp11_token_%016lx", + TEST_DIR, (unsigned long)slot); + beforeLen = read_store_file(tokenPath, before, sizeof(before)); + CHECK_TRUE(beforeLen > 0 && beforeLen < (long)sizeof(before), + "token record is stored"); + object_record_path(slot, 1, path, sizeof(path)); + blocked = (beforeLen > 0) && (mkdir(path, 0700) == 0); + } + if (blocked) { + rv = create_token_secret(session, "record-second", &obj); + CHECK_TRUE(rv != CKR_OK, + "create fails when the object cannot be committed"); + afterLen = read_store_file(tokenPath, after, sizeof(after)); + CHECK_TRUE(afterLen == beforeLen && + XMEMCMP(before, after, (size_t)beforeLen) == 0, + "token record is unchanged by the failed store"); + (void)rmdir(path); + } + close_session(session); + funcList->C_Finalize(NULL); +} + /* A PIN change that cannot be stored leaves the stored PIN in effect. */ static void test_pin_change_store_failure(void) { @@ -453,6 +514,7 @@ static int run_tests(void) test_objects_persist_across_storage_pause(); test_create_reports_commit_failure(); + test_token_record_kept_on_failed_store(); test_pin_change_store_failure(); test_init_pin_store_failure(); test_token_reset_store_failure(); From d2444eee2cd0af6e9ec9a7fd87f013f583eab559 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:39:40 -0700 Subject: [PATCH 076/204] F-11505 - Report token store failures from C_Finalize --- src/internal.c | 20 +++++++++++++------ src/wolfpkcs11.c | 7 +++---- tests/token_store_test.c | 42 ++++++++++++++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 2 +- 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/internal.c b/src/internal.c index af1c51e9..50486527 100644 --- a/src/internal.c +++ b/src/internal.c @@ -9113,9 +9113,14 @@ int WP11_Library_Init(void) /** * Finalize the globals for the library. * Multiple finalizations allowed. + * + * @return 0 on success. + * @return BAD_MUTEX_E when locking fails. + * @return Other value when storing a token fails. Cleanup is still done. */ -void WP11_Library_Final(void) +int WP11_Library_Final(void) { + int ret = 0; int i; int cnt; @@ -9124,7 +9129,7 @@ void WP11_Library_Final(void) * WP11_Lock_Free so a racing WP11_Library_IsInitialized can't observe * count>0, then call WP11_Lock_LockRO on a freed globalLock. */ if (wc_LockMutex(&libraryInitLock) != 0) - return; + return BAD_MUTEX_E; #endif WP11_Lock_LockRW(&globalLock); @@ -9134,13 +9139,14 @@ void WP11_Library_Final(void) #ifndef WOLFPKCS11_NO_STORE /* Store the slots. */ for (i = 0; i < slotCnt; i++) { - int ret = wp11_Slot_Store(&slotList[i], i + 1); + int storeRet = wp11_Slot_Store(&slotList[i], i + 1); #ifdef DEBUG_WOLFPKCS11 - if (ret != 0) { - printf("Failed to store slot %d, ret: %d\n", i + 1, ret); + if (storeRet != 0) { + printf("Failed to store slot %d, ret: %d\n", i + 1, storeRet); } #endif - (void)ret; /* store failure cannot be returned, so log and ignore */ + if (ret == 0) + ret = storeRet; } #if !defined (WOLFPKCS11_CUSTOM_STORE) && defined(WOLFPKCS11_NSS) /* Serialize the free against a concurrent WP11_SetStoreDir / @@ -9173,6 +9179,8 @@ void WP11_Library_Final(void) #ifdef WP11_HAVE_LIBRARY_INIT_LOCK wc_UnLockMutex(&libraryInitLock); #endif + + return ret; } /** diff --git a/src/wolfpkcs11.c b/src/wolfpkcs11.c index 7170625a..1f887113 100644 --- a/src/wolfpkcs11.c +++ b/src/wolfpkcs11.c @@ -590,7 +590,8 @@ CK_RV C_Initialize(CK_VOID_PTR pInitArgs) * Finalize the Crypto-Ki library. * * @param pReserved [out] Ignored. - * @return CKR_OK on success. + * @return CKR_FUNCTION_FAILED when token state could not be stored. + * CKR_OK on success. */ CK_RV C_Finalize(CK_VOID_PTR pReserved) { @@ -608,9 +609,7 @@ CK_RV C_Finalize(CK_VOID_PTR pReserved) return ret; } - WP11_Library_Final(); - - ret = CKR_OK; + ret = WP11_Library_Final() == 0 ? CKR_OK : CKR_FUNCTION_FAILED; WOLFPKCS11_LEAVE("C_Finalize", ret); return ret; } diff --git a/tests/token_store_test.c b/tests/token_store_test.c index 70f31329..52ab8708 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -503,6 +503,47 @@ static void test_token_reset_store_failure(void) funcList->C_Finalize(NULL); } +/* C_Finalize reports a token that could not be stored. */ +static void test_finalize_reports_store_failure(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + + if (geteuid() == 0) { + printf("\nSkipping finalize store failure test when run as root\n"); + return; + } + printf("\n--- finalize reports a token store failure ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "final-first", &obj); + CHECK_RV(rv, "create token object", CKR_OK); + } + close_session(session); + session = CK_INVALID_HANDLE; + if (rv == CKR_OK && chmod(TEST_DIR, 0500) == 0) { + rv = funcList->C_Finalize(NULL); + (void)chmod(TEST_DIR, 0700); + CHECK_RV(rv, "finalize reports the store failure", + CKR_FUNCTION_FAILED); + rv = lib_init(); + CHECK_RV(rv, "library initializes after the failed finalize", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + CHECK_TRUE(count_label(session, "final-first") == 1, + "previously stored object is still present"); + } + } + close_session(session); + funcList->C_Finalize(NULL); +} + static int run_tests(void) { CK_RV rv; @@ -518,6 +559,7 @@ static int run_tests(void) test_pin_change_store_failure(); test_init_pin_store_failure(); test_token_reset_store_failure(); + test_finalize_reports_store_failure(); clear_store_dir(); pkcs11_unload(); diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index d799b130..3243db27 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -399,7 +399,7 @@ typedef struct WP11_ObjectCalls WP11_ObjectCalls; WP11_LOCAL int WP11_Library_Init(void); -WP11_LOCAL void WP11_Library_Final(void); +WP11_LOCAL int WP11_Library_Final(void); WP11_LOCAL int WP11_Library_IsInitialized(void); WP11_LOCAL int WP11_SlotIdValid(CK_SLOT_ID slotId); From f4e5f07a9e029236005256dd7d93fe8edfe5b524 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:53:16 -0700 Subject: [PATCH 077/204] F-8645 - Undo partial library initialization on failure --- src/internal.c | 24 ++++++++++++- tests/token_store_test.c | 73 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 50486527..d8804383 100644 --- a/src/internal.c +++ b/src/internal.c @@ -9054,6 +9054,10 @@ int WP11_Library_Init(void) { int ret = 0; int i; + int lockInit = 0; + int cryptInit = 0; + int rngInit = 0; + int slotInitCnt = 0; #ifdef WP11_HAVE_LIBRARY_INIT_LOCK /* Serialize the entire init sequence: both the libraryInitCount==0 @@ -9067,8 +9071,10 @@ int WP11_Library_Init(void) if (libraryInitCount == 0) { ret = WP11_Lock_Init(&globalLock); if (ret == 0) { - + lockInit = 1; ret = wolfCrypt_Init(); + if (ret == 0) + cryptInit = 1; #ifdef WC_RNG_SEED_CB if (ret == 0) { ret = wc_SetSeed_Cb(wc_GenerateSeed); @@ -9085,17 +9091,33 @@ int WP11_Library_Init(void) #else ret = wc_InitRng(&globalRandom); #endif + if (ret == 0) + rngInit = 1; } } for (i = 0; (ret == 0) && (i < slotCnt); i++) { ret = wp11_Slot_Init(&slotList[i], i + 1); + if (ret == 0) + slotInitCnt++; } #ifndef WOLFPKCS11_NO_STORE for (i = 0; (ret == 0) && (i < slotCnt); i++) { ret = wp11_Slot_Load(&slotList[i], i + 1); } #endif + if (ret != 0) { + /* Undo partial initialization so a retry starts clean. */ + for (i = 0; i < slotInitCnt; i++) + wp11_Slot_Final(&slotList[i]); + if (rngInit) + wc_FreeRng(&globalRandom); + if (cryptInit) + wolfCrypt_Cleanup(); + if (lockInit) { + WP11_Lock_Free(&globalLock); + } + } } if (ret == 0) { WP11_Lock_LockRW(&globalLock); diff --git a/tests/token_store_test.c b/tests/token_store_test.c index 52ab8708..a2e1b70f 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -231,6 +231,21 @@ static long read_store_file(const char* path, byte* buf, size_t bufSz) return len; } +/* Replace a stored file with len bytes from buf. */ +static int write_store_file(const char* path, const byte* buf, size_t len) +{ + FILE* f; + size_t written; + + f = fopen(path, "wb"); + if (f == NULL) + return -1; + written = fwrite(buf, 1, len, f); + if (fclose(f) != 0 || written != len) + return -1; + return 0; +} + /* Reload the token from storage and open a user session on it. */ static CK_RV reload(CK_SLOT_ID slot, CK_SESSION_HANDLE* session) { @@ -544,6 +559,63 @@ static void test_finalize_reports_store_failure(void) funcList->C_Finalize(NULL); } +/* The library initializes once an unreadable token record is repaired. */ +static void test_init_retry_after_load_failure(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + char tokenPath[512]; + byte record[1024]; + long recordLen = -1; + + printf("\n--- library initializes after a failed initialization ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "retry-keep", &obj); + CHECK_RV(rv, "create token object", CKR_OK); + } + close_session(session); + session = CK_INVALID_HANDLE; + funcList->C_Finalize(NULL); + + if (rv == CKR_OK) { + snprintf(tokenPath, sizeof(tokenPath), "%s/wp11_token_%016lx", + TEST_DIR, (unsigned long)slot); + recordLen = read_store_file(tokenPath, record, sizeof(record)); + CHECK_TRUE(recordLen > 0 && recordLen < (long)sizeof(record), + "token record is stored"); + } + if (recordLen > 0) { + CHECK_TRUE(write_store_file(tokenPath, record, 4) == 0, + "truncate the token record"); + rv = lib_init(); + CHECK_TRUE(rv != CKR_OK, "initialize fails on a truncated record"); + if (rv == CKR_OK) + funcList->C_Finalize(NULL); + rv = write_store_file(tokenPath, record, (size_t)recordLen) == 0 ? + CKR_OK : CKR_GENERAL_ERROR; + if (rv == CKR_OK) + rv = lib_init(); + CHECK_RV(rv, "initialize succeeds once the record is restored", + CKR_OK); + if (rv == CKR_OK) { + rv = user_session(slot, &session); + CHECK_RV(rv, "user logs in after the retry", CKR_OK); + } + if (rv == CKR_OK) { + CHECK_TRUE(count_label(session, "retry-keep") == 1, + "stored object is loaded after the retry"); + } + close_session(session); + funcList->C_Finalize(NULL); + } +} + static int run_tests(void) { CK_RV rv; @@ -560,6 +632,7 @@ static int run_tests(void) test_init_pin_store_failure(); test_token_reset_store_failure(); test_finalize_reports_store_failure(); + test_init_retry_after_load_failure(); clear_store_dir(); pkcs11_unload(); From dc93df4e6efe682fe6397090fde2aa3d3b670d39 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:05:58 -0700 Subject: [PATCH 078/204] F-10385 - Handle a failed object allocation without freeing NULL --- src/internal.c | 5 ++- tests/token_store_test.c | 69 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index d8804383..dc0b6d5c 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3394,7 +3394,7 @@ static int wp11_Object_New(WP11_Slot* slot, CK_KEY_TYPE type, /* Type-specific allocation will be done later when object class is known */ - if (ret != 0) { + if (ret != 0 && obj != NULL) { WP11_Object_Free(obj); obj = NULL; } @@ -12596,6 +12596,9 @@ int WP11_Session_IsFindActive(WP11_Session* session) void WP11_Object_Free(WP11_Object* object) { int certFreed = 0; + + if (object == NULL) + return; #ifdef WOLFPKCS11_TPM if (object->tpmKey != NULL) { wolfTPM2_UnloadHandle(&object->slot->tpmDev, &object->tpmKey->handle); diff --git a/tests/token_store_test.c b/tests/token_store_test.c index a2e1b70f..57279fa3 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -68,6 +68,31 @@ static CK_BBOOL ckTrue = CK_TRUE; static CK_BBOOL ckFalse = CK_FALSE; static byte keyData[16] = { 0 }; +#if !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_DEBUG_MEMORY) && \ + !defined(NO_WOLFSSL_MEMORY) +#define TEST_ALLOC_FAILURE +static int failAllocs = 0; + +static void* test_malloc(size_t n) +{ + if (failAllocs) + return NULL; + return malloc(n); +} + +static void test_free(void* p) +{ + free(p); +} + +static void* test_realloc(void* p, size_t n) +{ + if (failAllocs) + return NULL; + return realloc(p, n); +} +#endif + static void clear_store_dir(void) { DIR* dir; @@ -616,6 +641,41 @@ static void test_init_retry_after_load_failure(void) } } +#ifdef TEST_ALLOC_FAILURE +/* Object creation fails cleanly when memory cannot be allocated. */ +static void test_create_object_out_of_memory(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + + printf("\n--- object creation fails cleanly without memory ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + failAllocs = 1; + rv = funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); + failAllocs = 0; + CHECK_TRUE(rv != CKR_OK, "create fails when allocation fails"); + rv = funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); + CHECK_RV(rv, "create succeeds once memory is available", CKR_OK); + } + close_session(session); + funcList->C_Finalize(NULL); +} +#endif + static int run_tests(void) { CK_RV rv; @@ -633,6 +693,9 @@ static int run_tests(void) test_token_reset_store_failure(); test_finalize_reports_store_failure(); test_init_retry_after_load_failure(); +#ifdef TEST_ALLOC_FAILURE + test_create_object_out_of_memory(); +#endif clear_store_dir(); pkcs11_unload(); @@ -648,6 +711,12 @@ int main(int argc, char* argv[]) unsetenv("WOLFPKCS11_NO_STORE"); printf("=== wolfPKCS11 token store test ===\n"); +#ifdef TEST_ALLOC_FAILURE + if (wolfSSL_SetAllocators(test_malloc, test_free, test_realloc) != 0) { + fprintf(stderr, "FAIL: wolfSSL_SetAllocators\n"); + return 1; + } +#endif run_tests(); return pkcs11_test_summary(); } From 76a8d079743ede0855aaa3d948a8cff685d6609c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:19:14 -0700 Subject: [PATCH 079/204] F-13243 - Report token object unstore failures from object destroy --- src/internal.c | 32 ++++++++++++----- tests/token_store_test.c | 75 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/src/internal.c b/src/internal.c index dc0b6d5c..e214bae5 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2327,6 +2327,10 @@ int wolfPKCS11_Store_Remove(int type, CK_ULONG id1, CK_ULONG id2) ret = wolfPKCS11_Store_Name(type, id1, id2, name, sizeof(name)); if (ret > 0 && ret < (int)sizeof(name)) { ret = remove(name); + if (ret < 0 && errno == ENOENT) { + /* Nothing was stored, so nothing is left behind. */ + ret = 0; + } if (ret < 0) { printf("remove(%s) failed: %d\n", name, ret); } @@ -12103,6 +12107,9 @@ int WP11_Session_RemoveObjectByHandle(WP11_Session* session, WP11_Token* token = &session->slot->token; WP11_Session* owner = session; int id = 0; +#ifndef WOLFPKCS11_NO_STORE + int storeRet; +#endif /* The token lock guards every object list on the slot (session lists * included). Take it before touching any list or the object. Token objects @@ -12194,13 +12201,16 @@ int WP11_Session_RemoveObjectByHandle(WP11_Session* session, #ifndef WOLFPKCS11_NO_STORE /* remove any id's with higher value */ - ret = wp11_Object_Unstore(*curr, (int)session->slotId, id); + storeRet = wp11_Object_Unstore(*curr, (int)session->slotId, id); #ifdef DEBUG_WOLFPKCS11 - if (ret != 0) { + if (storeRet != 0) { printf("Failed to unstore slot %d, id: %d, ret: %d, continuing...\n", - (int)session->slotId, id, ret); + (int)session->slotId, id, storeRet); } #endif + /* NOT_AVAILABLE_E: storage is disabled, nothing to remove. */ + if (ret == 0 && storeRet != NOT_AVAILABLE_E) + ret = storeRet; #endif curr = &(*curr)->next; @@ -12209,21 +12219,25 @@ int WP11_Session_RemoveObjectByHandle(WP11_Session* session, } #ifndef WOLFPKCS11_NO_STORE - ret = wp11_Object_Unstore(object, (int)session->slotId, id); + storeRet = wp11_Object_Unstore(object, (int)session->slotId, id); #ifdef DEBUG_WOLFPKCS11 - if (ret != 0) { + if (storeRet != 0) { printf("Failed to unstore slot %d, id: %d, ret: %d\n", - (int)session->slotId, id, ret); + (int)session->slotId, id, storeRet); } #endif + if (ret == 0 && storeRet != NOT_AVAILABLE_E) + ret = storeRet; /* re-store the token */ - ret = wp11_Slot_Store(session->slot, (int)session->slotId); + storeRet = wp11_Slot_Store(session->slot, (int)session->slotId); #ifdef DEBUG_WOLFPKCS11 - if (ret != 0) { + if (storeRet != 0) { printf("Failed to store slot %d, ret: %d\n", - (int)session->slotId, ret); + (int)session->slotId, storeRet); } #endif + if (ret == 0) + ret = storeRet; #endif } else { diff --git a/tests/token_store_test.c b/tests/token_store_test.c index 57279fa3..5072d4fd 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -641,6 +641,79 @@ static void test_init_retry_after_load_failure(void) } } +/* Destroying a token object reports stored data that cannot be removed. */ +static void test_destroy_reports_unstore_failure(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE first; + CK_OBJECT_HANDLE second; + char keyPath[512]; + char innerPath[600]; + int blocked = 0; + + printf("\n--- destroy reports stored data that cannot be removed ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "destroy-first", &first); + CHECK_RV(rv, "create first token object", CKR_OK); + } + if (rv == CKR_OK) { + rv = create_token_secret(session, "destroy-second", &second); + CHECK_RV(rv, "create second token object", CKR_OK); + } + if (rv == CKR_OK) { + snprintf(keyPath, sizeof(keyPath), "%s/wp11_symmkey_%016lx_%016lx", + TEST_DIR, (unsigned long)slot, 1UL); + snprintf(innerPath, sizeof(innerPath), "%s/keep", keyPath); + blocked = remove(keyPath) == 0 && mkdir(keyPath, 0700) == 0 && + write_store_file(innerPath, keyData, sizeof(keyData)) == 0; + CHECK_TRUE(blocked, "make the stored key data unremovable"); + } + if (blocked) { + rv = funcList->C_DestroyObject(session, second); + CHECK_TRUE(rv != CKR_OK, + "destroy reports stored data that was not removed"); + CHECK_TRUE(count_label(session, "destroy-first") == 1, + "other token object is still present"); + (void)remove(innerPath); + (void)rmdir(keyPath); + } + close_session(session); + funcList->C_Finalize(NULL); +} + +/* A token object never written to storage can still be destroyed. */ +static void test_destroy_unstored_object(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + + printf("\n--- destroy of an object created while paused ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + XSETENV("WOLFPKCS11_NO_STORE", "1", 1); + rv = create_token_secret(session, "paused-destroy", &obj); + unsetenv("WOLFPKCS11_NO_STORE"); + CHECK_RV(rv, "create token object while storage is paused", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_DestroyObject(session, obj); + CHECK_RV(rv, "destroy the never stored object", CKR_OK); + } + close_session(session); + funcList->C_Finalize(NULL); +} + #ifdef TEST_ALLOC_FAILURE /* Object creation fails cleanly when memory cannot be allocated. */ static void test_create_object_out_of_memory(void) @@ -693,6 +766,8 @@ static int run_tests(void) test_token_reset_store_failure(); test_finalize_reports_store_failure(); test_init_retry_after_load_failure(); + test_destroy_reports_unstore_failure(); + test_destroy_unstored_object(); #ifdef TEST_ALLOC_FAILURE test_create_object_out_of_memory(); #endif From e15c50167230181287f3656c04f2618e2287b0e9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:24:42 -0700 Subject: [PATCH 080/204] F-2791 - Zeroize token state with wc_ForceZero on finalize --- src/internal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index e214bae5..5a6499bc 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8304,7 +8304,7 @@ static void wp11_Token_Final(WP11_Token* token) Rng_Free(&token->rng); WP11_Lock_Free(&token->rngLock); WP11_Lock_Free(&token->lock); - XMEMSET(token, 0, sizeof(*token)); + wc_ForceZero(token, sizeof(*token)); } #ifndef WOLFPKCS11_NO_STORE From b1c268ab8b900aad2bafa07c42250a2c2460ad17 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:36:19 -0700 Subject: [PATCH 081/204] F-11502 - Propagate token flag read errors during token load --- src/internal.c | 4 +-- tests/token_store_test.c | 54 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index 5a6499bc..7bf62394 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8599,9 +8599,9 @@ static int wp11_Token_Load(WP11_Slot* slot, int tokenId, WP11_Token* token) token->nextObjId = 1; ret = 0; } - else { + else if (ret == 0) { ret = wp11_storage_read_int(storage, &token->nextObjId); - if (ret == BUFFER_E || token->nextObjId == 0) { + if (ret == BUFFER_E || (ret == 0 && token->nextObjId == 0)) { token->nextObjId = 1; ret = 0; } diff --git a/tests/token_store_test.c b/tests/token_store_test.c index 5072d4fd..3a980bed 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -749,6 +749,59 @@ static void test_create_object_out_of_memory(void) } #endif +/* A token record from before the trailing fields were added still loads. */ +static void test_load_record_without_trailing_fields(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + char tokenPath[512]; + byte record[1024]; + long recordLen = -1; + long dropLen; + + printf("\n--- token record without trailing fields loads ---\n"); + for (dropLen = 4; dropLen <= 8; dropLen += 4) { + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "legacy-keep", &obj); + CHECK_RV(rv, "create token object", CKR_OK); + } + close_session(session); + session = CK_INVALID_HANDLE; + funcList->C_Finalize(NULL); + if (rv == CKR_OK) { + snprintf(tokenPath, sizeof(tokenPath), "%s/wp11_token_%016lx", + TEST_DIR, (unsigned long)slot); + recordLen = read_store_file(tokenPath, record, sizeof(record)); + CHECK_TRUE(recordLen > dropLen && recordLen < (long)sizeof(record), + "token record is stored"); + } + if (rv == CKR_OK && recordLen > dropLen) { + CHECK_TRUE(write_store_file(tokenPath, record, + (size_t)(recordLen - dropLen)) == 0, + "drop trailing token record fields"); + rv = lib_init(); + CHECK_RV(rv, "initialize with the shorter record", CKR_OK); + if (rv == CKR_OK) { + rv = user_session(slot, &session); + CHECK_RV(rv, "user logs in", CKR_OK); + } + if (rv == CKR_OK) { + CHECK_TRUE(count_label(session, "legacy-keep") == 1, + "stored object is loaded"); + } + close_session(session); + session = CK_INVALID_HANDLE; + funcList->C_Finalize(NULL); + } + } +} + static int run_tests(void) { CK_RV rv; @@ -768,6 +821,7 @@ static int run_tests(void) test_init_retry_after_load_failure(); test_destroy_reports_unstore_failure(); test_destroy_unstored_object(); + test_load_record_without_trailing_fields(); #ifdef TEST_ALLOC_FAILURE test_create_object_out_of_memory(); #endif From 529d2fd98caa796e32d054e1e0d226fb57529a0c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:37:27 -0700 Subject: [PATCH 082/204] Prefer the token store error in object destroy --- src/internal.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 7bf62394..d4a93935 100644 --- a/src/internal.c +++ b/src/internal.c @@ -12236,7 +12236,8 @@ int WP11_Session_RemoveObjectByHandle(WP11_Session* session, (int)session->slotId, storeRet); } #endif - if (ret == 0) + /* A token record left unwritten outranks a record left behind. */ + if (storeRet != 0) ret = storeRet; #endif } From 7a2d435fe46240e990b43f9553d286b8de61bef0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:37:27 -0700 Subject: [PATCH 083/204] Document both CKR_FUNCTION_FAILED cases of C_Finalize --- src/wolfpkcs11.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wolfpkcs11.c b/src/wolfpkcs11.c index 1f887113..73369a85 100644 --- a/src/wolfpkcs11.c +++ b/src/wolfpkcs11.c @@ -590,7 +590,10 @@ CK_RV C_Initialize(CK_VOID_PTR pInitArgs) * Finalize the Crypto-Ki library. * * @param pReserved [out] Ignored. - * @return CKR_FUNCTION_FAILED when token state could not be stored. + * @return CKR_FUNCTION_FAILED when token state could not be stored, in which + * case the library is finalized all the same, or when the + * initialization lock could not be taken, in which case it stays + * initialized and the call can be retried. * CKR_OK on success. */ CK_RV C_Finalize(CK_VOID_PTR pReserved) From 4ad7289b4894f5776c351465b15403724ebdef05 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:37:49 -0700 Subject: [PATCH 084/204] Test a close failure in wolfPKCS11_Store_CloseCommit --- src/internal.c | 19 +++++++++++++++ tests/token_store_test.c | 50 ++++++++++++++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 1 + 3 files changed, 70 insertions(+) diff --git a/src/internal.c b/src/internal.c index d4a93935..db7b4c02 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1294,6 +1294,7 @@ static void wolfPKCS11_StoreAbortTemp(WP11_FileStoreCtx* ctx) #if defined(DEBUG_WOLFPKCS11) && !defined(_WIN32) && !defined(_MSC_VER) static int wp11_storeExitAfter = 0; +static int wp11_storeCloseFailAfter = -1; /** * Test hook: end the process right after the given number of store renames, @@ -1305,6 +1306,17 @@ WP11_API void WP11_Test_StoreExitAfterRenames(int renames) { wp11_storeExitAfter = renames; } + +/** + * Test hook: make the close of a written store record fail once, after the + * given number of closes succeed. + * + * @param closes [in] Closes to allow first, or -1 to disable the hook. + */ +WP11_API void WP11_Test_StoreCloseFailAfter(int closes) +{ + wp11_storeCloseFailAfter = closes; +} #endif static int wolfPKCS11_StoreRename(const char* from, const char* to) @@ -2593,6 +2605,13 @@ static int wolfPKCS11_Store_CloseCommit(void* store, int commit) commit = 0; ret = BUFFER_E; } + #if defined(DEBUG_WOLFPKCS11) && !defined(_WIN32) && !defined(_MSC_VER) + if (ctx->is_write && wp11_storeCloseFailAfter >= 0 && + wp11_storeCloseFailAfter-- == 0) { + /* Test hook: with the descriptor gone the close fails. */ + (void)close(fileno(ctx->file)); + } + #endif if (XFCLOSE(ctx->file) != 0 && ctx->is_write) { commit = 0; ret = BUFFER_E; diff --git a/tests/token_store_test.c b/tests/token_store_test.c index 3a980bed..240f8913 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -367,6 +367,53 @@ static void test_create_reports_commit_failure(void) funcList->C_Finalize(NULL); } +#ifdef DEBUG_WOLFPKCS11 +/* Debug-only hook exported by libwolfpkcs11. */ +extern void WP11_Test_StoreCloseFailAfter(int closes); + +/* A token object whose record cannot be closed is not reported created. */ +static void test_create_reports_close_failure(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + + printf("\n--- token object creation reports a close failure ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "close-first", &obj); + CHECK_RV(rv, "create first token object", CKR_OK); + } + if (rv == CKR_OK) { + WP11_Test_StoreCloseFailAfter(0); + rv = create_token_secret(session, "close-second", &obj); + WP11_Test_StoreCloseFailAfter(-1); + CHECK_TRUE(rv != CKR_OK, + "create fails when the record cannot be closed"); + CHECK_TRUE(count_label(session, "close-second") == 0, + "unclosed object is not in the token"); + close_session(session); + session = CK_INVALID_HANDLE; + rv = reload(slot, &session); + CHECK_RV(rv, "reload token", CKR_OK); + } + if (rv == CKR_OK) { + CHECK_TRUE(count_label(session, "close-first") == 1, + "stored object is reloaded"); + CHECK_TRUE(count_label(session, "close-second") == 0, + "unclosed object was not stored"); + rv = create_token_secret(session, "close-third", &obj); + CHECK_RV(rv, "create succeeds once records close", CKR_OK); + } + close_session(session); + funcList->C_Finalize(NULL); +} +#endif + /* A failed token object store leaves the stored token record unchanged. */ static void test_token_record_kept_on_failed_store(void) { @@ -813,6 +860,9 @@ static int run_tests(void) test_objects_persist_across_storage_pause(); test_create_reports_commit_failure(); +#endif +#ifdef DEBUG_WOLFPKCS11 + test_create_reports_close_failure(); test_token_record_kept_on_failed_store(); test_pin_change_store_failure(); test_init_pin_store_failure(); diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 3243db27..db85b177 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -442,6 +442,7 @@ WP11_API void WP11_Test_FailTokenInit(int fail); /* Exit status of a process ended by the store rename test hook. */ #define WP11_TEST_STORE_EXIT_CODE 75 WP11_API void WP11_Test_StoreExitAfterRenames(int renames); +WP11_API void WP11_Test_StoreCloseFailAfter(int closes); #endif #if defined(WOLFPKCS11_TPM) && (!defined(NO_RSA) || defined(HAVE_ECC)) WP11_API int WP11_Test_DecodeTpmKey(CK_SLOT_ID slotId, unsigned char* keyData, From 0c96a8d55a20c6bf5c24217a27a38a5b1bed560e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:37:57 -0700 Subject: [PATCH 085/204] Test C_DeriveKey reporting a token store failure --- tests/token_store_test.c | 95 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/tests/token_store_test.c b/tests/token_store_test.c index 240f8913..4f2e50f2 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -367,6 +367,98 @@ static void test_create_reports_commit_failure(void) funcList->C_Finalize(NULL); } +#if !defined(NO_AES) && defined(HAVE_AES_CBC) +static CK_KEY_TYPE aesType = CKK_AES; +static byte deriveBaseKey[16] = { + 0x4b, 0x1d, 0x8e, 0x3f, 0xa2, 0x57, 0xc0, 0x69, + 0x9d, 0x24, 0x6b, 0xf8, 0x13, 0xe5, 0x70, 0xac +}; +static byte deriveData[16] = { 0 }; + +/* Derive a token key from an AES base key with CKM_AES_CBC_ENCRYPT_DATA. */ +static CK_RV derive_token_secret(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE base, const char* label, + CK_OBJECT_HANDLE* obj) +{ + CK_MECHANISM mech; + CK_AES_CBC_ENCRYPT_DATA_PARAMS params; + CK_ULONG valueLen = sizeof(deriveData); + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_VALUE_LEN, &valueLen, sizeof(valueLen) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_LABEL, NULL, 0 }, + }; + + XMEMSET(¶ms, 0, sizeof(params)); + params.pData = deriveData; + params.length = sizeof(deriveData); + mech.mechanism = CKM_AES_CBC_ENCRYPT_DATA; + mech.pParameter = ¶ms; + mech.ulParameterLen = sizeof(params); + tmpl[5].pValue = (CK_VOID_PTR)label; + tmpl[5].ulValueLen = (CK_ULONG)XSTRLEN(label); + return funcList->C_DeriveKey(session, &mech, base, tmpl, + sizeof(tmpl) / sizeof(*tmpl), obj); +} + +/* A derived token key whose record cannot be committed is not reported + * created, and deriving works again once it can be. */ +static void test_derive_reports_commit_failure(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + char path[512]; + int blocked = 0; + CK_ATTRIBUTE baseTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_VALUE, deriveBaseKey, sizeof(deriveBaseKey) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_LABEL, (CK_VOID_PTR)"derive-base", 11 }, + }; + + printf("\n--- derived token key reports a commit failure ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, baseTmpl, + sizeof(baseTmpl) / sizeof(*baseTmpl), + &base); + CHECK_RV(rv, "create base token key", CKR_OK); + } + if (rv == CKR_OK) { + object_record_path(slot, 1, path, sizeof(path)); + blocked = (mkdir(path, 0700) == 0); + CHECK_TRUE(blocked, "occupy the next object record path"); + } + if (blocked) { + rv = derive_token_secret(session, base, "derive-token", &derived); + CHECK_TRUE(rv != CKR_OK, + "derive fails when the key cannot be committed"); + CHECK_TRUE(count_label(session, "derive-token") == 0, + "uncommitted derived key is not in the token"); + CHECK_TRUE(count_label(session, "derive-base") == 1, + "base key is still in the token"); + (void)rmdir(path); + rv = derive_token_secret(session, base, "derive-token", &derived); + CHECK_RV(rv, "derive succeeds once the key can be committed", CKR_OK); + CHECK_TRUE(count_label(session, "derive-token") == 1, + "committed derived key is in the token"); + } + close_session(session); + funcList->C_Finalize(NULL); +} +#endif + #ifdef DEBUG_WOLFPKCS11 /* Debug-only hook exported by libwolfpkcs11. */ extern void WP11_Test_StoreCloseFailAfter(int closes); @@ -860,9 +952,12 @@ static int run_tests(void) test_objects_persist_across_storage_pause(); test_create_reports_commit_failure(); +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + test_derive_reports_commit_failure(); #endif #ifdef DEBUG_WOLFPKCS11 test_create_reports_close_failure(); +#endif test_token_record_kept_on_failed_store(); test_pin_change_store_failure(); test_init_pin_store_failure(); From bff88cb59f3583873f373b9049bfbee2f97076c4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:37:58 -0700 Subject: [PATCH 086/204] Test library initialization rollback when allocation fails --- tests/token_store_test.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/token_store_test.c b/tests/token_store_test.c index 4f2e50f2..2a0954ff 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -854,6 +854,34 @@ static void test_destroy_unstored_object(void) } #ifdef TEST_ALLOC_FAILURE +/* A library initialization that fails part way is undone and retried. */ +static void test_init_out_of_memory(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + + printf("\n--- library initializes after running out of memory ---\n"); + clear_store_dir(); + failAllocs = 1; + rv = lib_init(); + failAllocs = 0; + CHECK_TRUE(rv != CKR_OK, "initialize fails when allocation fails"); + if (rv == CKR_OK) + funcList->C_Finalize(NULL); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup once memory is available", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "init-retry", &obj); + CHECK_RV(rv, "create token object after the retry", CKR_OK); + } + close_session(session); + funcList->C_Finalize(NULL); +} + /* Object creation fails cleanly when memory cannot be allocated. */ static void test_create_object_out_of_memory(void) { @@ -968,6 +996,7 @@ static int run_tests(void) test_destroy_unstored_object(); test_load_record_without_trailing_fields(); #ifdef TEST_ALLOC_FAILURE + test_init_out_of_memory(); test_create_object_out_of_memory(); #endif From 036219e52bcc55457dcc90a3e2beb72447f91ea1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:24:44 -0700 Subject: [PATCH 087/204] Skip store failure tests when the directory stays writable --- tests/token_store_test.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/tests/token_store_test.c b/tests/token_store_test.c index 2a0954ff..ebb2724c 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -553,6 +553,26 @@ static void test_token_record_kept_on_failed_store(void) funcList->C_Finalize(NULL); } +/* Some platforms, such as MSYS2, ignore a read-only mode on a directory. */ +static int block_store_dir(void) +{ + FILE* probe; + char path[256]; + + if (chmod(TEST_DIR, 0500) != 0) + return 0; + (void)snprintf(path, sizeof(path), "%s/wp11_probe", TEST_DIR); + probe = fopen(path, "wb"); + if (probe != NULL) { + fclose(probe); + (void)remove(path); + (void)chmod(TEST_DIR, 0700); + printf("Skipping: store directory cannot be made read-only\n"); + return 0; + } + return 1; +} + /* A PIN change that cannot be stored leaves the stored PIN in effect. */ static void test_pin_change_store_failure(void) { @@ -570,7 +590,7 @@ static void test_pin_change_store_failure(void) CHECK_RV(rv, "token setup", CKR_OK); if (rv == CKR_OK) rv = user_session(slot, &session); - if (rv == CKR_OK && chmod(TEST_DIR, 0500) == 0) { + if (rv == CKR_OK && block_store_dir()) { rv = funcList->C_SetPIN(session, (CK_UTF8CHAR_PTR)userPin, (CK_ULONG)XSTRLEN(userPin), (CK_UTF8CHAR_PTR)newPin, @@ -620,7 +640,7 @@ static void test_init_pin_store_failure(void) (CK_ULONG)XSTRLEN(soPin)); } CHECK_RV(rv, "token setup", CKR_OK); - if (rv == CKR_OK && chmod(TEST_DIR, 0500) == 0) { + if (rv == CKR_OK && block_store_dir()) { rv = funcList->C_InitPIN(soSession, (CK_UTF8CHAR_PTR)userPin, (CK_ULONG)XSTRLEN(userPin)); (void)chmod(TEST_DIR, 0700); @@ -663,7 +683,7 @@ static void test_token_reset_store_failure(void) session = CK_INVALID_HANDLE; XMEMSET(label, ' ', sizeof(label)); XMEMCPY(label, tokenLabel, XSTRLEN(tokenLabel)); - if (rv == CKR_OK && chmod(TEST_DIR, 0500) == 0) { + if (rv == CKR_OK && block_store_dir()) { rv = funcList->C_InitToken(slot, (CK_UTF8CHAR_PTR)soPin, (CK_ULONG)XSTRLEN(soPin), label); (void)chmod(TEST_DIR, 0700); @@ -705,7 +725,7 @@ static void test_finalize_reports_store_failure(void) } close_session(session); session = CK_INVALID_HANDLE; - if (rv == CKR_OK && chmod(TEST_DIR, 0500) == 0) { + if (rv == CKR_OK && block_store_dir()) { rv = funcList->C_Finalize(NULL); (void)chmod(TEST_DIR, 0700); CHECK_RV(rv, "finalize reports the store failure", From c9109764901068954f9a7cc08175ca1fe848c7ec Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 15:44:20 -0700 Subject: [PATCH 088/204] F-10386 - Stage TPM store records in memory until commit --- .github/workflows/unit-test.yml | 5 + CMakeLists.txt | 3 +- cmake/options.h.in | 4 +- configure.ac | 2 +- src/internal.c | 199 +++++++++++++++++++------ tests/concurrent_destroy_object_test.c | 3 +- tests/login_pin_state_test.c | 30 ++-- tests/pkcs11mtt.c | 3 +- tests/token_store_test.c | 137 ++++++++++++++++- wolfpkcs11/internal.h | 3 + 10 files changed, 321 insertions(+), 68 deletions(-) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 505a9a49..19df9f9c 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -21,6 +21,11 @@ jobs: with: config: --enable-singlethreaded --enable-wolftpm --disable-dh C_EXTRA_FLAGS="-DWOLFPKCS11_TPM_STORE" check: ./tests/pkcs11str && ./tests/pkcs11test && ./tests/rsa_session_persistence_test + tpm_debug: + uses: ./.github/workflows/build-workflow.yml + with: + config: --enable-singlethreaded --enable-wolftpm --disable-dh --enable-debug C_EXTRA_FLAGS="-DWOLFPKCS11_TPM_STORE" + check: ./tests/token_store_test no_rsa: uses: ./.github/workflows/build-workflow.yml diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d6a09a6..3989eaad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,8 +158,7 @@ if(NOT WOLFPKCS11_SINGLE_THREADED) "-D_POSIX_THREADS") endif() else() - set(SINGLE_THREADED 1) - list(APPEND WOLFPKCS11_DEFINITIONS "-DSINGLE_THREADED") + list(APPEND WOLFPKCS11_DEFINITIONS "-DWOLFPKCS11_SINGLE_THREADED") endif() diff --git a/cmake/options.h.in b/cmake/options.h.in index 594bcdb3..14e5d04f 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -38,8 +38,8 @@ extern "C" { #endif #undef DEBUG_WOLFPKCS11 #cmakedefine DEBUG_WOLFPKCS11 -#undef SINGLE_THREADED -#cmakedefine SINGLE_THREADED +#undef WOLFPKCS11_SINGLE_THREADED +#cmakedefine WOLFPKCS11_SINGLE_THREADED #undef NO_RSA #cmakedefine NO_RSA #undef WC_NO_RSA_OAEP diff --git a/configure.ac b/configure.ac index 2ef69ffa..24ea49bc 100644 --- a/configure.ac +++ b/configure.ac @@ -127,7 +127,7 @@ AS_IF([ test "x$ENABLED_SINGLETHREADED" = "xno" ],[ ]) ]) -AS_IF([ test "x$ENABLED_SINGLETHREADED" = "xyes" ],[ AM_CFLAGS="-DSINGLE_THREADED $AM_CFLAGS" ]) +AS_IF([ test "x$ENABLED_SINGLETHREADED" = "xyes" ],[ AM_CFLAGS="-DWOLFPKCS11_SINGLE_THREADED $AM_CFLAGS" ]) HAVE_PTHREAD=0 AS_IF([test "x$ax_pthread_ok" = "xyes"],[HAVE_PTHREAD=1]) diff --git a/src/internal.c b/src/internal.c index db7b4c02..22b4a40c 100644 --- a/src/internal.c +++ b/src/internal.c @@ -166,7 +166,12 @@ /* Determine object id from object handle. */ #define OBJ_HANDLE_OBJ_ID(h) ((h) & 0xfffffff) -#ifdef SINGLE_THREADED +/* SINGLE_THREADED alters wolfSSL struct layouts; only wolfSSL sets it. */ +#if defined(SINGLE_THREADED) || defined(WOLFPKCS11_SINGLE_THREADED) + #define WP11_NO_LOCKING +#endif + +#ifdef WP11_NO_LOCKING /* Disable locking. */ typedef int WP11_Lock; @@ -652,7 +657,7 @@ static int libraryInitCount = 0; /* Lock for globals including global random. */ static WP11_Lock globalLock; -#if !defined(SINGLE_THREADED) && defined(WOLFSSL_MUTEX_INITIALIZER) && \ +#if !defined(WP11_NO_LOCKING) && defined(WOLFSSL_MUTEX_INITIALIZER) && \ defined(WOLFSSL_MUTEX_INITIALIZER_CLAUSE) /* Permanently-live mutex that serializes WP11_Library_Init, * WP11_Library_Final, and WP11_Library_IsInitialized. Needed because @@ -672,7 +677,7 @@ static wolfSSL_Mutex libraryInitLock #define WP11_HAVE_LIBRARY_INIT_LOCK #endif -#if !defined(SINGLE_THREADED) && defined(WOLFSSL_MUTEX_INITIALIZER) && \ +#if !defined(WP11_NO_LOCKING) && defined(WOLFSSL_MUTEX_INITIALIZER) && \ defined(WOLFSSL_MUTEX_INITIALIZER_CLAUSE) && \ !defined(WOLFPKCS11_TPM_STORE) && defined(WOLFPKCS11_NSS) /* Permanently-live leaf mutex serializing the module-global storeDir, which @@ -687,7 +692,7 @@ static wolfSSL_Mutex storeDirLock #endif -#ifndef SINGLE_THREADED +#ifndef WP11_NO_LOCKING /** * Initialize a lock. * @@ -1144,6 +1149,10 @@ typedef struct WP11_TpmStore { WOLFTPM2_DEV* dev; WOLFTPM2_NV nv; word32 offset; + byte* buf; /* write record staged until commit, follows context */ + word32 bufSz; + word32 nvIndex; + int writeFailed; } WP11_TpmStore; static WP11_TpmStore tpmStores[1]; /* maximum of 1 open store */ @@ -2375,9 +2384,7 @@ int wolfPKCS11_Store_OpenSz(int type, CK_ULONG id1, CK_ULONG id2, int read, WP11_Slot* slot = &slotList[0]; WP11_TpmStore* tpmStore = &tpmStores[0]; word32 nvIndex; - word32 nvAttributes; int maxSz; - WOLFTPM2_HANDLE parent; #else WP11_FileStoreCtx* ctx = NULL; char name[WP11_STORE_MAX_PATH] = "\0"; @@ -2396,7 +2403,6 @@ int wolfPKCS11_Store_OpenSz(int type, CK_ULONG id1, CK_ULONG id2, int read, #endif #ifdef WOLFPKCS11_TPM_STORE - XMEMSET(&parent, 0, sizeof(parent)); XMEMSET(tpmStore, 0, sizeof(*tpmStore)); tpmStore->dev = &slot->tpmDev; @@ -2407,39 +2413,26 @@ int wolfPKCS11_Store_OpenSz(int type, CK_ULONG id1, CK_ULONG id2, int read, if (maxSz <= 0) { ret = NOT_AVAILABLE_E; } - if (ret == 0) { - TPMS_NV_PUBLIC nvPublic; - XMEMSET(&nvPublic, 0, sizeof(nvPublic)); - - /* Get NV attributes */ - parent.hndl = WOLFPKCS11_TPM_AUTH_TYPE; - (void)wolfTPM2_GetNvAttributesTemplate(parent.hndl, &nvAttributes); - - /* If write check if handle is large enough */ - if (!read) { - ret = wolfTPM2_NVReadPublic(tpmStore->dev, nvIndex, &nvPublic); - if (ret == 0 && nvPublic.dataSize < maxSz) { - /* NV is not large enough, delete and re-create */ - printf("Expanding TPM NV Handle 0x%x: %d -> %d\n", - nvIndex, nvPublic.dataSize, maxSz); - - ret = wolfTPM2_NVDeleteAuth(tpmStore->dev, &parent, nvIndex); - if (ret != 0) { - printf("Error %d (%s) removing NV handle 0x%x: \n", - ret, wolfTPM2_GetRCString(ret), nvIndex); - } - } - } + if (ret == 0 && read) { /* Try and open handle */ ret = wolfTPM2_NVOpen(tpmStore->dev, &tpmStore->nv, nvIndex, NULL, 0); if (ret != 0) { - if (!read) { - ret = wolfTPM2_NVCreateAuth(tpmStore->dev, &parent, - &tpmStore->nv, nvIndex, nvAttributes, maxSz, NULL, 0); - } - else { - ret = NOT_AVAILABLE_E; /* read for handle that doesn't exist */ - } + ret = NOT_AVAILABLE_E; /* read for handle that doesn't exist */ + } + } + else if (ret == 0) { + /* Stage the record in its own context, NV is only touched on commit */ + tpmStore = (WP11_TpmStore*)XMALLOC(sizeof(*tpmStore) + (size_t)maxSz, + NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (tpmStore == NULL) { + ret = MEMORY_E; + } + else { + XMEMSET(tpmStore, 0, sizeof(*tpmStore)); + tpmStore->dev = &slot->tpmDev; + tpmStore->buf = (byte*)(tpmStore + 1); + tpmStore->bufSz = (word32)maxSz; + tpmStore->nvIndex = nvIndex; } } if (ret == 0) { @@ -2569,6 +2562,83 @@ int wolfPKCS11_Store_Open(int type, CK_ULONG id1, CK_ULONG id2, int read, return wolfPKCS11_Store_OpenSz(type, id1, id2, read, 0, store); } +#ifdef WOLFPKCS11_TPM_STORE +/** + * Writes the staged record to its NV index, growing the index when needed. + * + * @param [in] tpmStore TPM store context holding the staged record. + * @return 0 on success. + * @return Other value when the record could not be written. + */ +static int wolfPKCS11_Store_TpmCommit(WP11_TpmStore* tpmStore) +{ + int ret; + int create = 0; + word32 nvAttributes = 0; + word32 readSz; + WOLFTPM2_HANDLE parent; + TPMS_NV_PUBLIC nvPublic; + + XMEMSET(&parent, 0, sizeof(parent)); + XMEMSET(&nvPublic, 0, sizeof(nvPublic)); + parent.hndl = WOLFPKCS11_TPM_AUTH_TYPE; + + ret = wolfTPM2_GetNvAttributesTemplate(parent.hndl, &nvAttributes); + if (ret == 0 && wolfTPM2_NVReadPublic(tpmStore->dev, tpmStore->nvIndex, + &nvPublic) != 0) { + /* No NV index holds this record yet. */ + create = 1; + } + else if (ret == 0 && nvPublic.dataSize < tpmStore->bufSz) { + /* NV is not large enough, delete and re-create */ + printf("Expanding TPM NV Handle 0x%x: %d -> %d\n", + tpmStore->nvIndex, (int)nvPublic.dataSize, (int)tpmStore->bufSz); + create = 1; + if (tpmStore->offset == 0) { + /* Nothing staged, so carry the stored record into the new index */ + ret = wolfTPM2_NVOpen(tpmStore->dev, &tpmStore->nv, + tpmStore->nvIndex, NULL, 0); + if (ret == 0) { + ret = wolfTPM2_SetAuthHandle(tpmStore->dev, 0, + &tpmStore->nv.handle); + } + if (ret == 0) { + readSz = nvPublic.dataSize; + ret = wolfTPM2_NVReadAuth(tpmStore->dev, &tpmStore->nv, + tpmStore->nvIndex, tpmStore->buf, &readSz, 0); + if (ret == 0) + tpmStore->offset = readSz; + } + if (ret == TPM_RC_NV_UNINITIALIZED) + ret = 0; + } + if (ret == 0) { + ret = wolfTPM2_NVDeleteAuth(tpmStore->dev, &parent, + tpmStore->nvIndex); + if (ret != 0) { + printf("Error %d (%s) removing NV handle 0x%x: \n", + ret, wolfTPM2_GetRCString(ret), tpmStore->nvIndex); + } + } + } + + if (ret == 0 && create) { + ret = wolfTPM2_NVCreateAuth(tpmStore->dev, &parent, &tpmStore->nv, + tpmStore->nvIndex, nvAttributes, tpmStore->bufSz, NULL, 0); + } + else if (ret == 0 && tpmStore->offset > 0) { + ret = wolfTPM2_NVOpen(tpmStore->dev, &tpmStore->nv, tpmStore->nvIndex, + NULL, 0); + } + if (ret == 0 && tpmStore->offset > 0) { + /* wolfTPM splits this into as few maximum sized NV writes as fit */ + ret = wolfTPM2_NVWriteAuth(tpmStore->dev, &tpmStore->nv, + tpmStore->nv.handle.hndl, tpmStore->buf, tpmStore->offset, 0); + } + return ret; +} +#endif + /** * Closes access to location being read or written. * Any dynamic memory associated with the store is freed here. @@ -2583,6 +2653,7 @@ static int wolfPKCS11_Store_CloseCommit(void* store, int commit) int ret = 0; #ifdef WOLFPKCS11_TPM_STORE WP11_TpmStore* tpmStore = (WP11_TpmStore*)store; + size_t ctxSz; #else WP11_FileStoreCtx* ctx = (WP11_FileStoreCtx*)store; #endif @@ -2592,9 +2663,17 @@ static int wolfPKCS11_Store_CloseCommit(void* store, int commit) #endif #ifdef WOLFPKCS11_TPM_STORE - /* nothing to do for TPM */ - (void)tpmStore; - (void)commit; + if (tpmStore != NULL && tpmStore->buf != NULL) { + if (tpmStore->writeFailed) { + ret = BUFFER_E; + } + else if (commit) { + ret = wolfPKCS11_Store_TpmCommit(tpmStore); + } + ctxSz = sizeof(*tpmStore) + tpmStore->bufSz; + wc_ForceZero(tpmStore, ctxSz); + XFREE(tpmStore, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } #else if (ctx != NULL) { int commitRet = 0; @@ -2765,11 +2844,16 @@ int wolfPKCS11_Store_Write(void* store, unsigned char* buffer, int len) #endif #ifdef WOLFPKCS11_TPM_STORE - ret = wolfTPM2_NVWriteAuth(tpmStore->dev, &tpmStore->nv, - tpmStore->nv.handle.hndl, buffer, len, tpmStore->offset); - if (ret == 0) { - tpmStore->offset += len; - ret = len; + if (tpmStore != NULL) { + if (tpmStore->buf == NULL || buffer == NULL || len < 0 || + (word32)len > tpmStore->bufSz - tpmStore->offset) { + tpmStore->writeFailed = 1; + } + else { + XMEMCPY(tpmStore->buf + tpmStore->offset, buffer, (size_t)len); + tpmStore->offset += (word32)len; + ret = len; + } } #else if (ctx != NULL && ctx->file != XBADFILE && ctx->file != NULL) { @@ -2920,6 +3004,24 @@ static int wp11_storage_read(void* storage, unsigned char* buffer, int len) return ret; } +#ifdef DEBUG_WOLFPKCS11 +static int storeWriteFailAfter = -1; + +/** + * Test hook: make storage writes fail after a number of them succeed. + * Exposed only in debug builds so a test can interrupt a record mid-write. + * + * @param writes [in] Writes that succeed before every later write fails, + * or -1 to stop failing writes. + * @return 0 always. + */ +WP11_API int WP11_Test_StoreWriteFailAfter(int writes) +{ + storeWriteFailAfter = writes; + return 0; +} +#endif + /** * Writes a specific number of bytes from buffer. * @@ -2934,6 +3036,13 @@ static int wp11_storage_write(void* storage, unsigned char* buffer, int len) int ret = 0; unsigned char* p = buffer; +#ifdef DEBUG_WOLFPKCS11 + if (storeWriteFailAfter == 0) + return BUFFER_E; + if (storeWriteFailAfter > 0) + storeWriteFailAfter--; +#endif + /* Keep writing until no data written, error or all written. */ while (len > 0) { ret = wolfPKCS11_Store_Write(storage, p, len); diff --git a/tests/concurrent_destroy_object_test.c b/tests/concurrent_destroy_object_test.c index 7edc50c1..8b07a0bb 100644 --- a/tests/concurrent_destroy_object_test.c +++ b/tests/concurrent_destroy_object_test.c @@ -60,7 +60,8 @@ #include "testdata.h" -#if !defined(NO_AES) && !defined(SINGLE_THREADED) +#if !defined(NO_AES) && !defined(SINGLE_THREADED) && \ + !defined(WOLFPKCS11_SINGLE_THREADED) #include diff --git a/tests/login_pin_state_test.c b/tests/login_pin_state_test.c index fd12f0b6..92dc169b 100644 --- a/tests/login_pin_state_test.c +++ b/tests/login_pin_state_test.c @@ -48,7 +48,11 @@ #include "testdata.h" #include "pkcs11_test_util.h" -#ifndef SINGLE_THREADED +#if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_SINGLE_THREADED) + #define LOGIN_TEST_THREADS +#endif + +#ifdef LOGIN_TEST_THREADS #include #include #endif @@ -361,7 +365,7 @@ static void logout_protects_all_objects_test(void) } #endif -#ifndef SINGLE_THREADED +#ifdef LOGIN_TEST_THREADS #define EMPTY_PIN_ROUNDS 8 static pthread_mutex_t infoMutex = PTHREAD_MUTEX_INITIALIZER; @@ -510,7 +514,7 @@ static void set_pin_lockout(CK_USER_TYPE type, const char* pin, int maxFails, } #endif -#if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_TIME) +#if defined(LOGIN_TEST_THREADS) && !defined(WOLFPKCS11_NO_TIME) #define LOCKOUT_THREADS 6 #define LOCKOUT_ROUNDS 4 @@ -636,7 +640,7 @@ static void concurrent_login_lockout_test(void) } #endif -#ifndef SINGLE_THREADED +#ifdef LOGIN_TEST_THREADS #define CLOSE_OPEN_ROUNDS 10 static pthread_mutex_t openMutex = PTHREAD_MUTEX_INITIALIZER; @@ -727,7 +731,7 @@ static void close_last_session_race_test(void) } #endif -#ifndef SINGLE_THREADED +#ifdef LOGIN_TEST_THREADS #define CHURN_ROUNDS 200 typedef struct churn_ctx { @@ -808,7 +812,7 @@ static void find_during_object_churn_test(void) } #endif -#if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_STORE) && \ +#if defined(LOGIN_TEST_THREADS) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_AES) #define FIND_LOGIN_ROUNDS 3 @@ -1043,7 +1047,7 @@ static void logout_drops_private_session_objects_test(void) } #endif -#if defined(DEBUG_WOLFPKCS11) && !defined(SINGLE_THREADED) && \ +#if defined(DEBUG_WOLFPKCS11) && defined(LOGIN_TEST_THREADS) && \ !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_NSS) #define FIND_HOOK_WAIT_MS 200 @@ -1287,33 +1291,33 @@ static int run_test(void) if (rv != CKR_OK) return -1; -#ifndef SINGLE_THREADED +#ifdef LOGIN_TEST_THREADS empty_pin_change_requires_login_test(); #endif #ifdef LOGIN_TEST_FILE_STORE failed_login_leaves_no_token_key_test(); #endif -#ifndef SINGLE_THREADED +#ifdef LOGIN_TEST_THREADS close_last_session_race_test(); #endif #ifndef WOLFPKCS11_NSS logout_drops_private_session_objects_test(); #endif -#if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_TIME) +#if defined(LOGIN_TEST_THREADS) && !defined(WOLFPKCS11_NO_TIME) concurrent_login_lockout_test(); #endif -#ifndef SINGLE_THREADED #if defined(DEBUG_WOLFPKCS11) && defined(LOGIN_TEST_THREADS) && \ !defined(WOLFPKCS11_NO_TIME) guess_in_flight_test(); #endif +#ifdef LOGIN_TEST_THREADS find_during_object_churn_test(); #endif -#if !defined(SINGLE_THREADED) && !defined(WOLFPKCS11_NO_STORE) && \ +#if defined(LOGIN_TEST_THREADS) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_AES) find_during_login_changes_test(); #endif -#if defined(DEBUG_WOLFPKCS11) && !defined(SINGLE_THREADED) && \ +#if defined(DEBUG_WOLFPKCS11) && defined(LOGIN_TEST_THREADS) && \ !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_NSS) find_with_racing_logout_test(); #endif diff --git a/tests/pkcs11mtt.c b/tests/pkcs11mtt.c index 609a85b0..2727a2ec 100644 --- a/tests/pkcs11mtt.c +++ b/tests/pkcs11mtt.c @@ -40,7 +40,8 @@ #include -#if defined(_POSIX_THREADS) && !defined(SINGLE_THREADED) +#if defined(_POSIX_THREADS) && !defined(SINGLE_THREADED) && \ + !defined(WOLFPKCS11_SINGLE_THREADED) #include #define TEST_MULTITHREADED diff --git a/tests/token_store_test.c b/tests/token_store_test.c index ebb2724c..87b8e7f4 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -52,6 +52,14 @@ #if !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_TPM_STORE) && \ !defined(WOLFPKCS11_CUSTOM_STORE) && !defined(WOLFPKCS11_NO_ENV) && \ !defined(WOLFPKCS11_USER_ENV) && !defined(_WIN32) + #define TOKEN_STORE_FILE_TEST +#elif defined(WOLFPKCS11_TPM_STORE) && defined(DEBUG_WOLFPKCS11) && \ + !defined(WOLFPKCS11_NO_ENV) && !defined(WOLFPKCS11_USER_ENV) && \ + !defined(_WIN32) + #define TOKEN_STORE_TPM_TEST +#endif + +#if defined(TOKEN_STORE_FILE_TEST) || defined(TOKEN_STORE_TPM_TEST) #include #include @@ -68,8 +76,8 @@ static CK_BBOOL ckTrue = CK_TRUE; static CK_BBOOL ckFalse = CK_FALSE; static byte keyData[16] = { 0 }; -#if !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_DEBUG_MEMORY) && \ - !defined(NO_WOLFSSL_MEMORY) +#if defined(TOKEN_STORE_FILE_TEST) && !defined(WOLFSSL_STATIC_MEMORY) && \ + !defined(WOLFSSL_DEBUG_MEMORY) && !defined(NO_WOLFSSL_MEMORY) #define TEST_ALLOC_FAILURE static int failAllocs = 0; @@ -93,6 +101,7 @@ static void* test_realloc(void* p, size_t n) } #endif +#ifdef TOKEN_STORE_FILE_TEST static void clear_store_dir(void) { DIR* dir; @@ -115,6 +124,7 @@ static void clear_store_dir(void) } closedir(dir); } +#endif static CK_RV lib_init(void) { @@ -146,7 +156,9 @@ static CK_RV token_setup(CK_SLOT_ID* slot) CK_SESSION_HANDLE soSession = CK_INVALID_HANDLE; unsigned char label[32]; +#ifdef TOKEN_STORE_FILE_TEST clear_store_dir(); +#endif rv = lib_init(); if (rv == CKR_OK) rv = first_slot(slot); @@ -234,6 +246,7 @@ static CK_ULONG count_label(CK_SESSION_HANDLE session, const char* label) return (rv == CKR_OK) ? foundCnt : 0; } +#ifdef TOKEN_STORE_FILE_TEST /* Path of the stored record of a token object. */ static void object_record_path(CK_SLOT_ID slot, int objId, char* path, size_t pathSz) @@ -988,6 +1001,118 @@ static void test_load_record_without_trailing_fields(void) } } } +#endif /* TOKEN_STORE_FILE_TEST */ + +#ifdef TOKEN_STORE_TPM_TEST +/* Debug-only hook exported by libwolfpkcs11. */ +extern int WP11_Test_StoreWriteFailAfter(int writes); + +#define MAX_STORE_WRITES 500 + +/* Reload the token from what storage holds after storing was made to fail. */ +static CK_RV reload_stored(CK_SLOT_ID slot, CK_SESSION_HANDLE* session) +{ + CK_RV rv; + + (void)funcList->C_Finalize(NULL); + (void)WP11_Test_StoreWriteFailAfter(-1); + rv = lib_init(); + if (rv == CKR_OK) + rv = user_session(slot, session); + return rv; +} + +/* An object create that fails at any storage write keeps the stored token. */ +static void test_interrupted_create_keeps_stored_token(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + int writes; + int intact = 1; + + printf("\n--- interrupted object create keeps the stored token ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "tpm-keep", &obj); + CHECK_RV(rv, "create stored token object", CKR_OK); + } + for (writes = 0; intact && rv == CKR_OK && writes < MAX_STORE_WRITES; + writes++) { + (void)WP11_Test_StoreWriteFailAfter(writes); + rv = create_token_secret(session, "tpm-new", &obj); + if (rv == CKR_OK) { + (void)WP11_Test_StoreWriteFailAfter(-1); + break; + } + close_session(session); + session = CK_INVALID_HANDLE; + rv = reload_stored(slot, &session); + if (rv != CKR_OK || count_label(session, "tpm-keep") != 1 || + count_label(session, "tpm-new") != 0) { + printf("Stored token changed by a store failing after %d " + "writes\n", writes); + intact = 0; + } + } + CHECK_TRUE(intact, "stored token is intact after each failed create"); + CHECK_TRUE(rv == CKR_OK && writes < MAX_STORE_WRITES, + "create succeeds once storing works"); + close_session(session); + funcList->C_Finalize(NULL); +} + +/* A PIN change that fails at any storage write keeps the stored PIN. */ +static void test_interrupted_pin_change_keeps_stored_pin(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + int writes; + int intact = 1; + static const char* newPin = "wolfpkcs11-next"; + + printf("\n--- interrupted PIN change keeps the stored PIN ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "tpm-pin-keep", &obj); + CHECK_RV(rv, "create stored token object", CKR_OK); + } + for (writes = 0; intact && rv == CKR_OK && writes < MAX_STORE_WRITES; + writes++) { + (void)WP11_Test_StoreWriteFailAfter(writes); + rv = funcList->C_SetPIN(session, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin), + (CK_UTF8CHAR_PTR)newPin, + (CK_ULONG)XSTRLEN(newPin)); + if (rv == CKR_OK) { + (void)WP11_Test_StoreWriteFailAfter(-1); + break; + } + close_session(session); + session = CK_INVALID_HANDLE; + rv = reload_stored(slot, &session); + if (rv != CKR_OK || count_label(session, "tpm-pin-keep") != 1) { + printf("Stored token changed by a store failing after %d " + "writes\n", writes); + intact = 0; + } + } + CHECK_TRUE(intact, "stored PIN logs in after each failed PIN change"); + CHECK_TRUE(rv == CKR_OK && writes < MAX_STORE_WRITES, + "PIN change succeeds once storing works"); + close_session(session); + funcList->C_Finalize(NULL); +} +#endif /* TOKEN_STORE_TPM_TEST */ static int run_tests(void) { @@ -998,6 +1123,7 @@ static int run_tests(void) if (rv != CKR_OK) return -1; +#ifdef TOKEN_STORE_FILE_TEST test_objects_persist_across_storage_pause(); test_create_reports_commit_failure(); #if !defined(NO_AES) && defined(HAVE_AES_CBC) @@ -1021,6 +1147,10 @@ static int run_tests(void) #endif clear_store_dir(); +#else + test_interrupted_create_keeps_stored_token(); + test_interrupted_pin_change_keeps_stored_pin(); +#endif pkcs11_unload(); return 0; } @@ -1050,7 +1180,8 @@ int main(int argc, char* argv[]) { (void)argc; (void)argv; - printf("Token store test requires the file-backed store, skipping\n"); + printf("Token store test requires the file-backed store or a debug " + "TPM store, skipping\n"); return 77; } diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index db85b177..c52185a0 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -444,6 +444,9 @@ WP11_API void WP11_Test_FailTokenInit(int fail); WP11_API void WP11_Test_StoreExitAfterRenames(int renames); WP11_API void WP11_Test_StoreCloseFailAfter(int closes); #endif +#ifndef WOLFPKCS11_NO_STORE +WP11_API int WP11_Test_StoreWriteFailAfter(int writes); +#endif #if defined(WOLFPKCS11_TPM) && (!defined(NO_RSA) || defined(HAVE_ECC)) WP11_API int WP11_Test_DecodeTpmKey(CK_SLOT_ID slotId, unsigned char* keyData, int keyDataLen); From d812004324bc8142e79bb3e090d7e45c7099e689 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:43:50 -0700 Subject: [PATCH 089/204] F-10380 - Size and index post-quantum key records in the TPM store --- src/internal.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index 22b4a40c..91723580 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1229,6 +1229,12 @@ static int wolfPKCS11_Store_GetMaxSize(int type, int variableSz) case WOLFPKCS11_STORE_DHKEY_PUB: case WOLFPKCS11_STORE_CERT: case WOLFPKCS11_STORE_TRUST: + case WOLFPKCS11_STORE_MLDSAKEY_PRIV: + case WOLFPKCS11_STORE_MLDSAKEY_PUB: + case WOLFPKCS11_STORE_MLKEMKEY_PRIV: + case WOLFPKCS11_STORE_MLKEMKEY_PUB: + case WOLFPKCS11_STORE_HSSKEY_PUB: + case WOLFPKCS11_STORE_XMSSKEY_PUB: maxSz = sizeof(word32) + variableSz; break; @@ -1244,9 +1250,9 @@ static int wolfPKCS11_Store_GetMaxSize(int type, int variableSz) #ifdef WOLFPKCS11_TPM_STORE static word32 wolfPKCS11_Store_Handle(int type, CK_ULONG id1, CK_ULONG id2) { - /* Build unique handle */ + /* Build unique handle. Store types run past 0x0F, so use five bits. */ word32 nvIndex = WOLFPKCS11_TPM_NV_BASE + - ((type & 0x0F) << 16) + + ((type & 0x1F) << 16) + (((word32)id1 & 0xFF) << 8) + ((word32)id2 & 0xFF); return nvIndex; From 9caf10d3792caa2ce15061d61943fe56c351ae6a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:27 -0700 Subject: [PATCH 090/204] Map TPM NV delete errors to failures --- src/internal.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 91723580..e35b0953 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2345,9 +2345,15 @@ int wolfPKCS11_Store_Remove(int type, CK_ULONG id1, CK_ULONG id2) XMEMSET(&parent, 0, sizeof(parent)); parent.hndl = WOLFPKCS11_TPM_AUTH_TYPE; ret = wolfTPM2_NVDeleteAuth(tpmStore->dev, &parent, nvIndex); - if (ret != 0) { + /* TPM_RC_HANDLE carries the handle number: nothing is stored there. */ + if (ret > 0 && (ret & (RC_FMT1 | 0x3F)) == TPM_RC_HANDLE) { + ret = 0; + } + else if (ret != 0) { printf("Error %d (%s) removing NV handle 0x%x: \n", ret, wolfTPM2_GetRCString(ret), nvIndex); + /* Callers take a negative value as failure, not a TPM RC. */ + ret = WC_HW_E; } #else /* remove file */ From 0f9252a4383c60f078af167a460a6c0fb42f494d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:27 -0700 Subject: [PATCH 091/204] Reject TPM records over the NV index limit --- src/internal.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/internal.c b/src/internal.c index e35b0953..eda563fe 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1155,6 +1155,45 @@ typedef struct WP11_TpmStore { int writeFailed; } WP11_TpmStore; static WP11_TpmStore tpmStores[1]; /* maximum of 1 open store */ +static word32 tpmNvIndexMax = 0; /* TPM_PT_NV_INDEX_MAX, 0 when unknown */ + +/** + * Read the largest NV index the TPM allows, so a record that cannot fit in + * one is refused before any NV index is touched. + * + * @return 0 on success. + * @return TPM RC or BAD_FUNC_ARG when the limit could not be read. + */ +static int wolfPKCS11_Store_TpmReadLimits(void) +{ + int rc; + GetCapability_In in; + GetCapability_Out out; + TPML_TAGGED_TPM_PROPERTY* props; + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_TPM_PROPERTIES; + in.property = TPM_PT_NV_INDEX_MAX; + in.propertyCount = 1; + rc = TPM2_GetCapability(&in, &out); + if (rc == TPM_RC_SUCCESS) { + props = &out.capabilityData.data.tpmProperties; + if (out.capabilityData.capability != TPM_CAP_TPM_PROPERTIES || + props->count < 1 || + props->tpmProperty[0].property != TPM_PT_NV_INDEX_MAX || + props->tpmProperty[0].value == 0) { + rc = BAD_FUNC_ARG; + } + else { + tpmNvIndexMax = props->tpmProperty[0].value; + } + } + if (rc != 0) + printf("TPM NV index size limit unknown: %d\n", rc); + + return rc; +} /* Internal function to get maximum size for each store type */ static int wolfPKCS11_Store_GetMaxSize(int type, int variableSz) @@ -2432,6 +2471,12 @@ int wolfPKCS11_Store_OpenSz(int type, CK_ULONG id1, CK_ULONG id2, int read, ret = NOT_AVAILABLE_E; /* read for handle that doesn't exist */ } } + else if (ret == 0 && tpmNvIndexMax != 0 && (word32)maxSz > tpmNvIndexMax) { + /* Too big for one NV index: refuse it before anything is written. */ + printf("Store record of %d bytes exceeds the TPM NV index limit of " + "%u\n", maxSz, (unsigned int)tpmNvIndexMax); + ret = BUFFER_E; + } else if (ret == 0) { /* Stage the record in its own context, NV is only touched on commit */ tpmStore = (WP11_TpmStore*)XMALLOC(sizeof(*tpmStore) + (size_t)maxSz, @@ -9005,6 +9050,10 @@ static int wp11_TpmInit(WP11_Slot* slot) "FIPS 140-2 %d, CC-EAL4 %d\n", caps.mfgStr, caps.mfg, caps.vendorStr, caps.fwVerMajor, caps.fwVerMinor, caps.fwVerVendor, caps.fips140_2, caps.cc_eal4); + #ifdef WOLFPKCS11_TPM_STORE + /* Without the limit, an oversized record fails at commit instead. */ + (void)wolfPKCS11_Store_TpmReadLimits(); + #endif } if (ret == 0) { ret = wolfTPM2_SetCryptoDevCb(&slot->tpmDev, wolfTPM2_CryptoDevCb, From e6c372119545257aed325ccd33cfa6beb6e24121 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:27 -0700 Subject: [PATCH 092/204] Test grown and overflowing TPM store records --- src/internal.c | 18 +++++ tests/token_store_test.c | 151 ++++++++++++++++++++++++++++++++++++++- wolfpkcs11/internal.h | 3 + 3 files changed, 169 insertions(+), 3 deletions(-) diff --git a/src/internal.c b/src/internal.c index eda563fe..80359868 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1156,6 +1156,20 @@ typedef struct WP11_TpmStore { } WP11_TpmStore; static WP11_TpmStore tpmStores[1]; /* maximum of 1 open store */ static word32 tpmNvIndexMax = 0; /* TPM_PT_NV_INDEX_MAX, 0 when unknown */ +#ifdef DEBUG_WOLFPKCS11 +static int tpmStoreShortBy = 0; + +/** + * Test hook: stage every record in a buffer short by the given number of + * bytes, so a write runs past it. + * + * @param bytes [in] Bytes to leave out, or 0 to disable the hook. + */ +WP11_API void WP11_Test_TpmStoreShortBy(int bytes) +{ + tpmStoreShortBy = bytes; +} +#endif /** * Read the largest NV index the TPM allows, so a record that cannot fit in @@ -2478,6 +2492,10 @@ int wolfPKCS11_Store_OpenSz(int type, CK_ULONG id1, CK_ULONG id2, int read, ret = BUFFER_E; } else if (ret == 0) { + #ifdef DEBUG_WOLFPKCS11 + if (tpmStoreShortBy > 0 && maxSz > tpmStoreShortBy) + maxSz -= tpmStoreShortBy; + #endif /* Stage the record in its own context, NV is only touched on commit */ tpmStore = (WP11_TpmStore*)XMALLOC(sizeof(*tpmStore) + (size_t)maxSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/tests/token_store_test.c b/tests/token_store_test.c index 87b8e7f4..087aec22 100644 --- a/tests/token_store_test.c +++ b/tests/token_store_test.c @@ -1004,11 +1004,66 @@ static void test_load_record_without_trailing_fields(void) #endif /* TOKEN_STORE_FILE_TEST */ #ifdef TOKEN_STORE_TPM_TEST -/* Debug-only hook exported by libwolfpkcs11. */ +/* Debug-only hooks exported by libwolfpkcs11. */ extern int WP11_Test_StoreWriteFailAfter(int writes); +extern void WP11_Test_TpmStoreShortBy(int bytes); #define MAX_STORE_WRITES 500 +static byte privateValue[16] = { + 0x5a, 0x0c, 0xe7, 0x31, 0x9b, 0x46, 0xd8, 0x2f, + 0x74, 0xa1, 0x1e, 0xc3, 0x68, 0xf5, 0x0d, 0xb2 +}; + +/* Create a private, readable token secret encrypted under the token key. */ +static CK_RV create_private_token_secret(CK_SESSION_HANDLE session, + const char* label, + CK_OBJECT_HANDLE* obj) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_VALUE, privateValue, sizeof(privateValue) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_LABEL, NULL, 0 }, + }; + + tmpl[7].pValue = (CK_VOID_PTR)label; + tmpl[7].ulValueLen = (CK_ULONG)XSTRLEN(label); + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), obj); +} + +/* The private secret with the label is found and its value decrypts. */ +static int private_secret_usable(CK_SESSION_HANDLE session, const char* label) +{ + CK_RV rv; + CK_OBJECT_HANDLE found[8]; + CK_ULONG foundCnt = 0; + byte value[sizeof(privateValue)]; + CK_ATTRIBUTE valueAttr = { CKA_VALUE, value, sizeof(value) }; + CK_ATTRIBUTE tmpl[] = { + { CKA_LABEL, NULL, 0 }, + }; + + tmpl[0].pValue = (CK_VOID_PTR)label; + tmpl[0].ulValueLen = (CK_ULONG)XSTRLEN(label); + rv = funcList->C_FindObjectsInit(session, tmpl, 1); + if (rv == CKR_OK) { + rv = funcList->C_FindObjects(session, found, 8, &foundCnt); + funcList->C_FindObjectsFinal(session); + } + if (rv == CKR_OK && foundCnt != 1) + rv = CKR_OBJECT_HANDLE_INVALID; + if (rv == CKR_OK) + rv = funcList->C_GetAttributeValue(session, found[0], &valueAttr, 1); + return rv == CKR_OK && valueAttr.ulValueLen == sizeof(privateValue) && + XMEMCMP(value, privateValue, sizeof(privateValue)) == 0; +} + /* Reload the token from what storage holds after storing was made to fail. */ static CK_RV reload_stored(CK_SLOT_ID slot, CK_SESSION_HANDLE* session) { @@ -1086,6 +1141,10 @@ static void test_interrupted_pin_change_keeps_stored_pin(void) rv = create_token_secret(session, "tpm-pin-keep", &obj); CHECK_RV(rv, "create stored token object", CKR_OK); } + if (rv == CKR_OK) { + rv = create_private_token_secret(session, "tpm-pin-private", &obj); + CHECK_RV(rv, "create stored private token object", CKR_OK); + } for (writes = 0; intact && rv == CKR_OK && writes < MAX_STORE_WRITES; writes++) { (void)WP11_Test_StoreWriteFailAfter(writes); @@ -1100,18 +1159,102 @@ static void test_interrupted_pin_change_keeps_stored_pin(void) close_session(session); session = CK_INVALID_HANDLE; rv = reload_stored(slot, &session); - if (rv != CKR_OK || count_label(session, "tpm-pin-keep") != 1) { + /* Records under the token key must still open with the stored PIN. */ + if (rv != CKR_OK || count_label(session, "tpm-pin-keep") != 1 || + !private_secret_usable(session, "tpm-pin-private")) { printf("Stored token changed by a store failing after %d " "writes\n", writes); intact = 0; } } - CHECK_TRUE(intact, "stored PIN logs in after each failed PIN change"); + CHECK_TRUE(intact, "stored PIN and private key work after each failed " + "PIN change"); CHECK_TRUE(rv == CKR_OK && writes < MAX_STORE_WRITES, "PIN change succeeds once storing works"); close_session(session); funcList->C_Finalize(NULL); } + +/* A record that outgrows its NV index is moved to a larger one with its + * data and reloads from there. */ +static void test_grown_record_reloads(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + char longLabel[200]; + CK_ATTRIBUTE labelAttr = { CKA_LABEL, longLabel, sizeof(longLabel) - 1 }; + + printf("\n--- record that outgrows its NV index reloads ---\n"); + XMEMSET(longLabel, 'g', sizeof(longLabel) - 1); + longLabel[sizeof(longLabel) - 1] = '\0'; + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "tpm-grow", &obj); + CHECK_RV(rv, "create stored token object", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, obj, &labelAttr, 1); + CHECK_RV(rv, "grow the object record past its NV index", CKR_OK); + } + close_session(session); + session = CK_INVALID_HANDLE; + if (rv == CKR_OK) { + rv = reload_stored(slot, &session); + CHECK_RV(rv, "reload token", CKR_OK); + } + if (rv == CKR_OK) { + CHECK_TRUE(count_label(session, longLabel) == 1, + "grown record reloads with its new data"); + CHECK_TRUE(count_label(session, "tpm-grow") == 0, + "old record is gone"); + } + close_session(session); + funcList->C_Finalize(NULL); +} + +/* A record written past its staging buffer is reported, not committed. */ +static void test_record_overflow_reports_failure(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj; + + printf("\n--- record overflowing its staging buffer is reported ---\n"); + rv = token_setup(&slot); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) + rv = user_session(slot, &session); + if (rv == CKR_OK) { + rv = create_token_secret(session, "tpm-fit", &obj); + CHECK_RV(rv, "create stored token object", CKR_OK); + } + if (rv == CKR_OK) { + WP11_Test_TpmStoreShortBy(1); + rv = create_token_secret(session, "tpm-overflow", &obj); + WP11_Test_TpmStoreShortBy(0); + CHECK_TRUE(rv != CKR_OK, "create fails when a record overflows"); + close_session(session); + session = CK_INVALID_HANDLE; + rv = reload_stored(slot, &session); + CHECK_RV(rv, "reload token", CKR_OK); + } + if (rv == CKR_OK) { + CHECK_TRUE(count_label(session, "tpm-fit") == 1, + "stored object is still present"); + CHECK_TRUE(count_label(session, "tpm-overflow") == 0, + "overflowing object was not stored"); + rv = create_token_secret(session, "tpm-overflow", &obj); + CHECK_RV(rv, "create succeeds once records fit", CKR_OK); + } + close_session(session); + funcList->C_Finalize(NULL); +} #endif /* TOKEN_STORE_TPM_TEST */ static int run_tests(void) @@ -1150,6 +1293,8 @@ static int run_tests(void) #else test_interrupted_create_keeps_stored_token(); test_interrupted_pin_change_keeps_stored_pin(); + test_grown_record_reloads(); + test_record_overflow_reports_failure(); #endif pkcs11_unload(); return 0; diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index c52185a0..ee4f6dfc 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -447,6 +447,9 @@ WP11_API void WP11_Test_StoreCloseFailAfter(int closes); #ifndef WOLFPKCS11_NO_STORE WP11_API int WP11_Test_StoreWriteFailAfter(int writes); #endif +#if defined(WOLFPKCS11_TPM_STORE) && !defined(WOLFPKCS11_NO_STORE) +WP11_API void WP11_Test_TpmStoreShortBy(int bytes); +#endif #if defined(WOLFPKCS11_TPM) && (!defined(NO_RSA) || defined(HAVE_ECC)) WP11_API int WP11_Test_DecodeTpmKey(CK_SLOT_ID slotId, unsigned char* keyData, int keyDataLen); From 5b07df787842f8c6bd96e60681d29f17853be5f1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:18:31 -0700 Subject: [PATCH 093/204] F-10396 - Reject ECDH shared data when the KDF is CKD_NULL --- src/crypto.c | 2 + tests/ec_derive_test.c | 342 +++++++++++++++++++++++++++++++++++++++++ tests/include.am | 7 + 3 files changed, 351 insertions(+) create mode 100644 tests/ec_derive_test.c diff --git a/src/crypto.c b/src/crypto.c index 0e0fe05c..49f844ae 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9667,6 +9667,8 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, return CKR_MECHANISM_PARAM_INVALID; if (params->kdf != CKD_NULL) return CKR_MECHANISM_PARAM_INVALID; + if (params->pSharedData != NULL || params->ulSharedDataLen != 0) + return CKR_MECHANISM_PARAM_INVALID; keyLen = (word32)(params->ulPublicDataLen / 2); derivedKey = (byte*)XMALLOC(keyLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/tests/ec_derive_test.c b/tests/ec_derive_test.c new file mode 100644 index 00000000..56012c04 --- /dev/null +++ b/tests/ec_derive_test.c @@ -0,0 +1,342 @@ +/* ec_derive_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Tests for C_DeriveKey with CKM_ECDH1_DERIVE: mechanism parameter + * validation and the attributes of the derived key object. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" + +#define TEST_DIR "./store/ec_derive_test" + +static int test_passed = 0; +static int test_failed = 0; +static int test_skipped = 0; + +#ifndef HAVE_PKCS11_STATIC +static void* dlib; +#endif +static CK_FUNCTION_LIST* funcList; +static CK_SLOT_ID slot = 0; +static const char* tokenName = "wolfpkcs11"; +static byte* soPin = (byte*)"password123456"; +static int soPinLen = 14; +static byte* userPin = (byte*)"wolfpkcs11-test"; +static int userPinLen = 15; + +#define CHECK_RV(rv, exp, op) do { \ + if ((rv) != (exp)) { \ + fprintf(stderr, "FAIL: %s: got 0x%lx, expected 0x%lx\n", op, \ + (unsigned long)(rv), (unsigned long)(exp)); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } \ + else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while (0) + +#define CHECK_CKR(rv, op) CHECK_RV(rv, CKR_OK, op) + +#ifdef HAVE_ECC +static CK_OBJECT_CLASS privKeyClass = CKO_PRIVATE_KEY; +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_KEY_TYPE eccKeyType = CKK_EC; +static CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; +static CK_ULONG secretLen = 32; + +/* Session P-256 private key usable for ECDH. */ +static CK_RV create_ec_base(CK_SESSION_HANDLE session, CK_BBOOL sensitive, + CK_BBOOL extractable, CK_OBJECT_HANDLE* obj) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &eccKeyType, sizeof(eccKeyType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &sensitive, sizeof(sensitive) }, + { CKA_EXTRACTABLE, &extractable, sizeof(extractable) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, + }; + CK_ULONG cnt = sizeof(tmpl) / sizeof(*tmpl); + + return funcList->C_CreateObject(session, tmpl, cnt, obj); +} + +/* ECDH derive of a public, extractable generic secret. */ +static CK_RV ecdh_derive(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE base, + CK_ECDH1_DERIVE_PARAMS* params, + CK_OBJECT_HANDLE* derived) +{ + CK_MECHANISM mech; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE_LEN, &secretLen, sizeof(secretLen) }, + }; + CK_ULONG cnt = sizeof(tmpl) / sizeof(*tmpl); + + mech.mechanism = CKM_ECDH1_DERIVE; + mech.pParameter = params; + mech.ulParameterLen = sizeof(*params); + + return funcList->C_DeriveKey(session, &mech, base, tmpl, cnt, derived); +} + +static void ecdh_params_init(CK_ECDH1_DERIVE_PARAMS* params, byte* point, + CK_ULONG pointLen) +{ + XMEMSET(params, 0, sizeof(*params)); + params->kdf = CKD_NULL; + params->pSharedData = NULL; + params->ulSharedDataLen = 0; + params->pPublicData = point; + params->ulPublicDataLen = pointLen; +} + +/* With CKD_NULL there is no KDF, so shared data must be absent. */ +static int test_null_kdf_shared_data(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_ECDH1_DERIVE_PARAMS params; + byte sharedData[4] = { 0x01, 0x02, 0x03, 0x04 }; + int result = 0; + + ret = create_ec_base(session, CK_FALSE, CK_TRUE, &base); + CHECK_CKR(ret, "create EC base key"); + + ecdh_params_init(¶ms, ecc_p256_point, sizeof(ecc_p256_point)); + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_CKR(ret, "CKD_NULL derive without shared data"); + funcList->C_DestroyObject(session, derived); + derived = CK_INVALID_HANDLE; + + params.pSharedData = sharedData; + params.ulSharedDataLen = sizeof(sharedData); + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "CKD_NULL derive rejects shared data"); + + params.pSharedData = NULL; + params.ulSharedDataLen = sizeof(sharedData); + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "CKD_NULL derive rejects shared data length"); + + params.pSharedData = sharedData; + params.ulSharedDataLen = 0; + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "CKD_NULL derive rejects shared data pointer"); + +cleanup: + if (derived != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, derived); + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + return result; +} +#endif /* HAVE_ECC */ + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); +#ifndef HAVE_PKCS11_STATIC + CK_C_GetFunctionList func; + + dlib = dlopen(WOLFPKCS11_DLL_FILENAME, RTLD_NOW | RTLD_LOCAL); + if (dlib == NULL) { + fprintf(stderr, "dlopen error: %s\n", dlerror()); + return -1; + } + func = (CK_C_GetFunctionList)dlsym(dlib, "C_GetFunctionList"); + if (func == NULL) { + dlclose(dlib); + dlib = NULL; + return -1; + } + ret = func(&funcList); + if (ret != CKR_OK) { + dlclose(dlib); + dlib = NULL; + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) + return ret; +#endif + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + ret = funcList->C_Initialize(&args); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + if (slotCount == 0) + return CKR_GENERAL_ERROR; + slot = slotList[0]; + return ret; +} + +static CK_RV pkcs11_final(void) +{ + if (funcList != NULL) { + funcList->C_Finalize(NULL); + funcList = NULL; + } +#ifndef HAVE_PKCS11_STATIC + if (dlib) { + dlclose(dlib); + dlib = NULL; + } +#endif + return CKR_OK; +} + +static CK_RV pkcs11_setup_token(void) +{ + CK_RV ret; + unsigned char label[32]; + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + ret = funcList->C_InitToken(slot, soPin, soPinLen, label); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) + return ret; + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret == CKR_OK) + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + return ret; +} + +static int ec_derive_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + int result = 0; + + printf("\n=== Testing C_DeriveKey with CKM_ECDH1_DERIVE ===\n"); + + ret = pkcs11_init(); + CHECK_CKR(ret, "C_Initialize"); + + ret = pkcs11_setup_token(); + CHECK_CKR(ret, "token setup"); + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &session); + CHECK_CKR(ret, "C_OpenSession"); + ret = funcList->C_Login(session, CKU_USER, userPin, userPinLen); + CHECK_CKR(ret, "C_Login"); + +#ifdef HAVE_ECC + if (test_null_kdf_shared_data(session) != 0) + result = -1; +#else + printf("ECC not available, skipping ECDH derive tests\n"); + test_skipped = 1; +#endif + +cleanup: + if (session != CK_INVALID_HANDLE) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + pkcs11_final(); + return result; +} + +static void print_results(void) +{ + printf("\n=== Test Results ===\n"); + printf("Tests passed: %d\n", test_passed); + printf("Tests failed: %d\n", test_failed); + if (test_skipped != 0) + printf("Tests skipped: %d\n", test_skipped); + if (test_failed == 0) + printf("ALL TESTS PASSED!\n"); + else + printf("SOME TESTS FAILED!\n"); +} + +int main(int argc, char* argv[]) +{ +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + (void)argc; + (void)argv; + + if (ec_derive_test() != 0 && test_failed == 0) + test_failed++; + + print_results(); +#ifndef HAVE_ECC + if (test_failed == 0) + return 77; +#endif + return (test_failed == 0) ? 0 : 1; +} diff --git a/tests/include.am b/tests/include.am index ce7dc956..c6984e3f 100644 --- a/tests/include.am +++ b/tests/include.am @@ -206,6 +206,11 @@ noinst_PROGRAMS += tests/derive_inherit_protection_test tests_derive_inherit_protection_test_SOURCES = tests/derive_inherit_protection_test.c tests_derive_inherit_protection_test_LDADD = +check_PROGRAMS += tests/ec_derive_test +noinst_PROGRAMS += tests/ec_derive_test +tests_ec_derive_test_SOURCES = tests/ec_derive_test.c +tests_ec_derive_test_LDADD = + check_PROGRAMS += tests/secret_extractable_default_test noinst_PROGRAMS += tests/secret_extractable_default_test tests_secret_extractable_default_test_SOURCES = tests/secret_extractable_default_test.c @@ -381,6 +386,7 @@ tests_private_object_empty_pin_test_LDADD += src/libwolfpkcs11.la tests_key_sensitive_default_test_LDADD += src/libwolfpkcs11.la tests_derive_disabled_key_test_LDADD += src/libwolfpkcs11.la tests_derive_inherit_protection_test_LDADD += src/libwolfpkcs11.la +tests_ec_derive_test_LDADD += src/libwolfpkcs11.la tests_secret_extractable_default_test_LDADD += src/libwolfpkcs11.la tests_rsa_private_usage_default_test_LDADD += src/libwolfpkcs11.la tests_copyobject_token_test_LDADD += src/libwolfpkcs11.la @@ -442,6 +448,7 @@ tests_private_object_empty_pin_test_LDADD += src/libwolfpkcs11.la tests_key_sensitive_default_test_LDADD += src/libwolfpkcs11.la tests_derive_disabled_key_test_LDADD += src/libwolfpkcs11.la tests_derive_inherit_protection_test_LDADD += src/libwolfpkcs11.la +tests_ec_derive_test_LDADD += src/libwolfpkcs11.la tests_secret_extractable_default_test_LDADD += src/libwolfpkcs11.la tests_rsa_private_usage_default_test_LDADD += src/libwolfpkcs11.la tests_copyobject_token_test_LDADD += src/libwolfpkcs11.la From cf09659a36faf0fbda89748d6900a1d6a7c06748 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:27:22 -0700 Subject: [PATCH 094/204] F-4272 - Bound the ECDH public data length before sizing the secret --- src/crypto.c | 7 +++++-- tests/ec_derive_test.c | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 49f844ae..c5f7389e 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9665,6 +9665,9 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, return CKR_MECHANISM_PARAM_INVALID; if (params->ulPublicDataLen == 0) return CKR_MECHANISM_PARAM_INVALID; + /* Largest DER-wrapped uncompressed point of a supported curve. */ + if (params->ulPublicDataLen > (CK_ULONG)(2 * MAX_ECC_BYTES + 4)) + return CKR_MECHANISM_PARAM_INVALID; if (params->kdf != CKD_NULL) return CKR_MECHANISM_PARAM_INVALID; if (params->pSharedData != NULL || params->ulSharedDataLen != 0) @@ -9677,8 +9680,8 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, XMEMSET(derivedKey, 0, keyLen); ret = WP11_EC_Derive(params->pPublicData, - (int)params->ulPublicDataLen, derivedKey, - &keyLen, obj); + (word32)params->ulPublicDataLen, derivedKey, + &keyLen, obj); if (ret != 0) rv = CKR_FUNCTION_FAILED; break; diff --git a/tests/ec_derive_test.c b/tests/ec_derive_test.c index 56012c04..a2fe1575 100644 --- a/tests/ec_derive_test.c +++ b/tests/ec_derive_test.c @@ -177,6 +177,45 @@ static int test_null_kdf_shared_data(CK_SESSION_HANDLE session) CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, "CKD_NULL derive rejects shared data pointer"); +cleanup: + if (derived != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, derived); + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + return result; +} + +/* A public data length beyond any encodable EC point is a parameter error. */ +static int test_public_data_length_bound(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_ECDH1_DERIVE_PARAMS params; + byte bigPoint[512]; + CK_ULONG wideLen; + int result = 0; + + ret = create_ec_base(session, CK_FALSE, CK_TRUE, &base); + CHECK_CKR(ret, "create EC base key"); + + if (sizeof(CK_ULONG) > sizeof(word32)) { + /* Upper bits set, low 32 bits equal to the real point length. */ + wideLen = ((CK_ULONG)2 << 16) << 16; + wideLen += sizeof(ecc_p256_point); + ecdh_params_init(¶ms, ecc_p256_point, wideLen); + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "public data length above 32 bits rejected"); + } + + XMEMSET(bigPoint, 0, sizeof(bigPoint)); + XMEMCPY(bigPoint, ecc_p256_point, sizeof(ecc_p256_point)); + ecdh_params_init(¶ms, bigPoint, sizeof(bigPoint)); + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "oversized public data length rejected"); + cleanup: if (derived != CK_INVALID_HANDLE) funcList->C_DestroyObject(session, derived); @@ -295,6 +334,8 @@ static int ec_derive_test(void) #ifdef HAVE_ECC if (test_null_kdf_shared_data(session) != 0) result = -1; + if (test_public_data_length_bound(session) != 0) + result = -1; #else printf("ECC not available, skipping ECDH derive tests\n"); test_skipped = 1; From 1b9b5878ad96b1b1c96c578b71312bebd333191c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:34:07 -0700 Subject: [PATCH 095/204] F-1610 - Keep the raw point fallback for an unsupported DER length form --- src/internal.c | 11 ++++++- tests/ec_derive_test.c | 75 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 80359868..64d0b12f 100644 --- a/src/internal.c +++ b/src/internal.c @@ -17430,9 +17430,10 @@ int WP11_EC_Derive(unsigned char* point, word32 pointLen, unsigned char* key, /* Not valid DER encoding, treat as raw X9.63 data */ x963Data = point; x963Len = pointLen; + i = 0; } } - if (i < (int)pointLen) { + if (i > 0 && i < (int)pointLen) { expectedPointLen = priv->data.ecKey->dp->size * 2 + 1; if (pointLen > expectedPointLen) { x963Len = point[i++]; @@ -17447,6 +17448,14 @@ int WP11_EC_Derive(unsigned char* point, word32 pointLen, unsigned char* key, } ret = wc_ecc_init_ex(&pubKey, NULL, priv->devId); + if (ret == 0 && priv->data.ecKey->dp != NULL) { + /* Compressed point is size + 1 bytes, uncompressed 2 * size + 1 */ + expectedPointLen = (word32)priv->data.ecKey->dp->size + 1; + if (x963Len > 0 && x963Data[0] == ECC_POINT_UNCOMP) + expectedPointLen += (word32)priv->data.ecKey->dp->size; + if (x963Len != expectedPointLen) + ret = ECC_BAD_ARG_E; + } if (ret == 0) { if (priv->data.ecKey->dp) { ret = wc_ecc_import_x963_ex(x963Data, x963Len, &pubKey, diff --git a/tests/ec_derive_test.c b/tests/ec_derive_test.c index a2fe1575..f7bed926 100644 --- a/tests/ec_derive_test.c +++ b/tests/ec_derive_test.c @@ -223,6 +223,79 @@ static int test_public_data_length_bound(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, base); return result; } + +/* An EC point wrapped with an unsupported DER length form is not accepted. */ +static int test_unsupported_der_length_form(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_ECDH1_DERIVE_PARAMS params; + CK_MECHANISM genMech = { CKM_EC_KEY_PAIR_GEN, NULL, 0 }; + byte p521Params[] = { 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23 }; + byte derPoint[2 * 66 + 4]; + byte badPoint[2 * 66 + 3]; + CK_ATTRIBUTE pointAttr = { CKA_EC_POINT, derPoint, sizeof(derPoint) }; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_EC_PARAMS, p521Params, sizeof(p521Params) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + }; + int result = 0; + + ret = funcList->C_GenerateKeyPair(session, &genMech, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &pub, &priv); + if (ret != CKR_OK) { + printf("SKIP: P-521 key generation unavailable (0x%lx)\n", + (unsigned long)ret); + test_skipped = 1; + goto cleanup; + } + + ret = funcList->C_GetAttributeValue(session, pub, &pointAttr, 1); + CHECK_CKR(ret, "read P-521 EC point"); + if (pointAttr.ulValueLen != sizeof(derPoint) || derPoint[0] != 0x04 || + derPoint[1] != 0x81 || derPoint[2] != sizeof(badPoint) - 2) { + ret = CKR_GENERAL_ERROR; + } + CHECK_CKR(ret, "P-521 EC point is DER wrapped"); + + ecdh_params_init(¶ms, derPoint, sizeof(derPoint)); + ret = ecdh_derive(session, priv, ¶ms, &derived); + CHECK_CKR(ret, "derive with DER wrapped point"); + funcList->C_DestroyObject(session, derived); + derived = CK_INVALID_HANDLE; + + /* Same point with a long-form length byte that has no length octets. */ + badPoint[0] = 0x04; + badPoint[1] = (byte)(sizeof(badPoint) - 2); + XMEMCPY(badPoint + 2, derPoint + 3, sizeof(badPoint) - 2); + ecdh_params_init(¶ms, badPoint, sizeof(badPoint)); + ret = ecdh_derive(session, priv, ¶ms, &derived); + if (ret == CKR_OK) { + fprintf(stderr, "FAIL: malformed DER length form accepted\n"); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: malformed DER length form rejected\n"); + test_passed++; + +cleanup: + if (derived != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, derived); + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + return result; +} #endif /* HAVE_ECC */ static CK_RV pkcs11_init(void) @@ -336,6 +409,8 @@ static int ec_derive_test(void) result = -1; if (test_public_data_length_bound(session) != 0) result = -1; + if (test_unsupported_der_length_form(session) != 0) + result = -1; #else printf("ECC not available, skipping ECDH derive tests\n"); test_skipped = 1; From 2a802a5945f971d3776addc434d03b68fca07424 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:42:03 -0700 Subject: [PATCH 096/204] F-5159 - Bound the DER point length by the ECDH public data length --- src/internal.c | 2 ++ tests/ec_derive_test.c | 46 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/internal.c b/src/internal.c index 64d0b12f..fed562c7 100644 --- a/src/internal.c +++ b/src/internal.c @@ -17448,6 +17448,8 @@ int WP11_EC_Derive(unsigned char* point, word32 pointLen, unsigned char* key, } ret = wc_ecc_init_ex(&pubKey, NULL, priv->devId); + if (ret == 0 && x963Len > pointLen - (word32)(x963Data - point)) + ret = BUFFER_E; if (ret == 0 && priv->data.ecKey->dp != NULL) { /* Compressed point is size + 1 bytes, uncompressed 2 * size + 1 */ expectedPointLen = (word32)priv->data.ecKey->dp->size + 1; diff --git a/tests/ec_derive_test.c b/tests/ec_derive_test.c index f7bed926..a8b2655e 100644 --- a/tests/ec_derive_test.c +++ b/tests/ec_derive_test.c @@ -296,6 +296,50 @@ static int test_unsupported_der_length_form(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, pub); return result; } + +/* The DER length of a wrapped point must fit in the supplied public data. */ +static int test_der_length_within_public_data(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_ECDH1_DERIVE_PARAMS params; + byte wrapped[3 + sizeof(ecc_p256_point)]; + int result = 0; + + ret = create_ec_base(session, CK_FALSE, CK_TRUE, &base); + CHECK_CKR(ret, "create EC base key"); + + wrapped[0] = 0x04; + wrapped[1] = 0x81; + wrapped[2] = (byte)sizeof(ecc_p256_point); + XMEMCPY(wrapped + 3, ecc_p256_point, sizeof(ecc_p256_point)); + + ecdh_params_init(¶ms, wrapped, sizeof(wrapped)); + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_CKR(ret, "derive with complete DER wrapped point"); + funcList->C_DestroyObject(session, derived); + derived = CK_INVALID_HANDLE; + + /* Declared length runs past the end of the supplied public data. */ + ecdh_params_init(¶ms, wrapped, sizeof(wrapped) - 2); + ret = ecdh_derive(session, base, ¶ms, &derived); + if (ret == CKR_OK) { + fprintf(stderr, "FAIL: truncated DER wrapped point accepted\n"); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: truncated DER wrapped point rejected\n"); + test_passed++; + +cleanup: + if (derived != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, derived); + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + return result; +} #endif /* HAVE_ECC */ static CK_RV pkcs11_init(void) @@ -411,6 +455,8 @@ static int ec_derive_test(void) result = -1; if (test_unsupported_der_length_form(session) != 0) result = -1; + if (test_der_length_within_public_data(session) != 0) + result = -1; #else printf("ECC not available, skipping ECDH derive tests\n"); test_skipped = 1; From eec2406dc1693e7c6d8dde06b1a56631e074ff5d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:52:14 -0700 Subject: [PATCH 097/204] F-10383 - Clear the ECDH key RNG before freeing the stack RNG --- src/internal.c | 12 ++- tests/ec_derive_test.c | 197 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+), 4 deletions(-) diff --git a/src/internal.c b/src/internal.c index fed562c7..683fae0e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -17415,8 +17415,10 @@ int WP11_EC_Derive(unsigned char* point, word32 pointLen, unsigned char* key, WC_RNG rng; #endif - if (priv->onToken) - WP11_Lock_LockRO(priv->lock); + /* Exclusive: the temporary RNG is stored in the shared key. */ + if (priv->onToken) { + WP11_Lock_LockRW(priv->lock); + } /* Check if the point data is DER-encoded (starts with OCTET STRING tag) */ if (pointLen >= 3 && point[0] == ASN_OCTET_STRING) { @@ -17518,12 +17520,14 @@ int WP11_EC_Derive(unsigned char* point, word32 pointLen, unsigned char* key, } #if defined(ECC_TIMING_RESISTANT) && (!defined(HAVE_FIPS) || \ (defined(HAVE_FIPS_VERSION) && (HAVE_FIPS_VERSION > 2))) + (void)wc_ecc_set_rng(priv->data.ecKey, NULL); Rng_Free(&rng); #endif } - if (priv->onToken) - WP11_Lock_UnlockRO(priv->lock); + if (priv->onToken) { + WP11_Lock_UnlockRW(priv->lock); + } wc_ecc_free(&pubKey); diff --git a/tests/ec_derive_test.c b/tests/ec_derive_test.c index a8b2655e..3bb1ff90 100644 --- a/tests/ec_derive_test.c +++ b/tests/ec_derive_test.c @@ -45,6 +45,12 @@ #include "testdata.h" +#if defined(HAVE_ECC) && !defined(SINGLE_THREADED) && \ + !defined(WOLFPKCS11_SINGLE_THREADED) + #define EC_DERIVE_THREAD_TEST + #include +#endif + #define TEST_DIR "./store/ec_derive_test" static int test_passed = 0; @@ -340,6 +346,191 @@ static int test_der_length_within_public_data(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, base); return result; } + +/* Repeated derives with one base key leave it usable and give one secret. */ +static int test_repeated_derive_same_key(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_ECDH1_DERIVE_PARAMS params; + byte secret[sizeof(ecc_secret_256)]; + CK_ATTRIBUTE valAttr = { CKA_VALUE, secret, sizeof(secret) }; + int i; + int result = 0; + + ret = create_ec_base(session, CK_FALSE, CK_TRUE, &base); + CHECK_CKR(ret, "create EC base key"); + + ecdh_params_init(¶ms, ecc_p256_point, sizeof(ecc_p256_point)); + for (i = 0; i < 3; i++) { + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_CKR(ret, "repeated derive with one base key"); + XMEMSET(secret, 0, sizeof(secret)); + valAttr.ulValueLen = sizeof(secret); + ret = funcList->C_GetAttributeValue(session, derived, &valAttr, 1); + CHECK_CKR(ret, "read derived secret"); + if (valAttr.ulValueLen != sizeof(ecc_secret_256) || + XMEMCMP(secret, ecc_secret_256, sizeof(secret)) != 0) { + ret = CKR_GENERAL_ERROR; + } + CHECK_CKR(ret, "derived secret matches expected value"); + ret = funcList->C_DestroyObject(session, derived); + derived = CK_INVALID_HANDLE; + CHECK_CKR(ret, "destroy derived secret"); + } + +cleanup: + if (derived != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, derived); + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + return result; +} + +#ifdef EC_DERIVE_THREAD_TEST +#define DERIVE_THREADS 2 +#define DERIVES_PER_THREAD 200 + +typedef struct DeriveCtx { + CK_OBJECT_HANDLE base; + int failures; +} DeriveCtx; + +static pthread_mutex_t deriveGateLock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t deriveGateCond = PTHREAD_COND_INITIALIZER; +static int deriveGateReady = 0; +static int deriveGateOpen = 0; + +/* Hold each worker until every worker has arrived so their derives overlap. */ +static void derive_gate_wait(void) +{ + if (pthread_mutex_lock(&deriveGateLock) == 0) { + deriveGateReady++; + (void)pthread_cond_broadcast(&deriveGateCond); + while (!deriveGateOpen) { + if (pthread_cond_wait(&deriveGateCond, &deriveGateLock) != 0) + break; + } + (void)pthread_mutex_unlock(&deriveGateLock); + } +} + +static int derive_gate_open(int workers) +{ + int ret; + + ret = pthread_mutex_lock(&deriveGateLock); + if (ret == 0) { + while (ret == 0 && deriveGateReady < workers) + ret = pthread_cond_wait(&deriveGateCond, &deriveGateLock); + deriveGateOpen = 1; + if (pthread_cond_broadcast(&deriveGateCond) != 0) + ret = -1; + if (pthread_mutex_unlock(&deriveGateLock) != 0) + ret = -1; + } + return ret; +} + +static void* derive_worker(void* arg) +{ + DeriveCtx* ctx = (DeriveCtx*)arg; + CK_RV ret; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived; + CK_ECDH1_DERIVE_PARAMS params; + byte secret[sizeof(ecc_secret_256)]; + CK_ATTRIBUTE valAttr = { CKA_VALUE, secret, sizeof(secret) }; + int i; + + derive_gate_wait(); + ret = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + if (ret != CKR_OK) { + ctx->failures = DERIVES_PER_THREAD; + return NULL; + } + + ecdh_params_init(¶ms, ecc_p256_point, sizeof(ecc_p256_point)); + for (i = 0; i < DERIVES_PER_THREAD; i++) { + derived = CK_INVALID_HANDLE; + ret = ecdh_derive(session, ctx->base, ¶ms, &derived); + if (ret == CKR_OK) { + valAttr.ulValueLen = sizeof(secret); + ret = funcList->C_GetAttributeValue(session, derived, &valAttr, 1); + } + if (ret == CKR_OK && (valAttr.ulValueLen != sizeof(ecc_secret_256) || + XMEMCMP(secret, ecc_secret_256, sizeof(secret)) != 0)) { + ret = CKR_GENERAL_ERROR; + } + if (derived != CK_INVALID_HANDLE && ret == CKR_OK) + ret = funcList->C_DestroyObject(session, derived); + else if (derived != CK_INVALID_HANDLE) + (void)funcList->C_DestroyObject(session, derived); + if (ret != CKR_OK) + ctx->failures++; + } + + if (funcList->C_CloseSession(session) != CKR_OK) + ctx->failures++; + return NULL; +} + +/* Derives that share one token key run concurrently and all succeed. */ +static int test_concurrent_derive_same_key(CK_SESSION_HANDLE session) +{ + CK_RV ret = CKR_OK; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &eccKeyType, sizeof(eccKeyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, + }; + pthread_t threads[DERIVE_THREADS]; + DeriveCtx ctx[DERIVE_THREADS]; + int started = 0; + int failures = 0; + int i; + int result = 0; + + ret = funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &base); + CHECK_CKR(ret, "create EC token base key"); + + for (i = 0; i < DERIVE_THREADS; i++) { + ctx[i].base = base; + ctx[i].failures = 0; + if (pthread_create(&threads[i], NULL, derive_worker, &ctx[i]) != 0) + break; + started++; + } + if (derive_gate_open(started) != 0) + failures++; + for (i = 0; i < started; i++) { + if (pthread_join(threads[i], NULL) != 0) + failures++; + failures += ctx[i].failures; + } + if (started != DERIVE_THREADS || failures != 0) { + fprintf(stderr, "%d of %d concurrent derives failed\n", failures, + DERIVE_THREADS * DERIVES_PER_THREAD); + ret = CKR_GENERAL_ERROR; + } + CHECK_CKR(ret, "concurrent derives with one token key all succeed"); + +cleanup: + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + return result; +} +#endif /* EC_DERIVE_THREAD_TEST */ #endif /* HAVE_ECC */ static CK_RV pkcs11_init(void) @@ -457,6 +648,12 @@ static int ec_derive_test(void) result = -1; if (test_der_length_within_public_data(session) != 0) result = -1; + if (test_repeated_derive_same_key(session) != 0) + result = -1; +#ifdef EC_DERIVE_THREAD_TEST + if (test_concurrent_derive_same_key(session) != 0) + result = -1; +#endif #else printf("ECC not available, skipping ECDH derive tests\n"); test_skipped = 1; From 09490dfed8a67faff61583c82c38a0363aaf7b63 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:04:55 -0700 Subject: [PATCH 098/204] F-6959 - Require user login to derive a private key object --- src/crypto.c | 5 +++ tests/ec_derive_test.c | 93 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index c5f7389e..548e555c 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9649,6 +9649,11 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, return ret; #endif + rv = CheckPrivateLogin(session, pTemplate, ulAttributeCount, + CKO_SECRET_KEY); + if (rv != CKR_OK) + return rv; + switch (pMechanism->mechanism) { #ifdef HAVE_ECC case CKM_ECDH1_DERIVE: { diff --git a/tests/ec_derive_test.c b/tests/ec_derive_test.c index 3bb1ff90..934e9534 100644 --- a/tests/ec_derive_test.c +++ b/tests/ec_derive_test.c @@ -112,12 +112,26 @@ static CK_RV create_ec_base(CK_SESSION_HANDLE session, CK_BBOOL sensitive, return funcList->C_CreateObject(session, tmpl, cnt, obj); } +static CK_RV ecdh_derive_tmpl(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE base, + CK_ECDH1_DERIVE_PARAMS* params, + CK_ATTRIBUTE* tmpl, CK_ULONG cnt, + CK_OBJECT_HANDLE* derived) +{ + CK_MECHANISM mech; + + mech.mechanism = CKM_ECDH1_DERIVE; + mech.pParameter = params; + mech.ulParameterLen = sizeof(*params); + + return funcList->C_DeriveKey(session, &mech, base, tmpl, cnt, derived); +} + /* ECDH derive of a public, extractable generic secret. */ static CK_RV ecdh_derive(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE base, CK_ECDH1_DERIVE_PARAMS* params, CK_OBJECT_HANDLE* derived) { - CK_MECHANISM mech; CK_ATTRIBUTE tmpl[] = { { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, @@ -128,11 +142,7 @@ static CK_RV ecdh_derive(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE base, }; CK_ULONG cnt = sizeof(tmpl) / sizeof(*tmpl); - mech.mechanism = CKM_ECDH1_DERIVE; - mech.pParameter = params; - mech.ulParameterLen = sizeof(*params); - - return funcList->C_DeriveKey(session, &mech, base, tmpl, cnt, derived); + return ecdh_derive_tmpl(session, base, params, tmpl, cnt, derived); } static void ecdh_params_init(CK_ECDH1_DERIVE_PARAMS* params, byte* point, @@ -531,6 +541,75 @@ static int test_concurrent_derive_same_key(CK_SESSION_HANDLE session) return result; } #endif /* EC_DERIVE_THREAD_TEST */ + +/* Deriving a private key object requires a user login. */ +static int test_private_derive_requires_login(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_ECDH1_DERIVE_PARAMS params; + CK_ATTRIBUTE privTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE_LEN, &secretLen, sizeof(secretLen) }, + }; +#ifndef WOLFPKCS11_LEGACY_PRIVATE_FALSE_DEFAULT + CK_ATTRIBUTE defTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE_LEN, &secretLen, sizeof(secretLen) }, + }; +#endif + int loggedOut = 0; + int result = 0; + + ret = funcList->C_Logout(session); + CHECK_CKR(ret, "C_Logout"); + loggedOut = 1; + + ret = create_ec_base(session, CK_FALSE, CK_TRUE, &base); + CHECK_CKR(ret, "create public EC base key without login"); + + ecdh_params_init(¶ms, ecc_p256_point, sizeof(ecc_p256_point)); + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_CKR(ret, "derive public key without login"); + funcList->C_DestroyObject(session, derived); + derived = CK_INVALID_HANDLE; + + ret = ecdh_derive_tmpl(session, base, ¶ms, privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &derived); + CHECK_RV(ret, CKR_USER_NOT_LOGGED_IN, + "derive private key without login rejected"); + +#ifndef WOLFPKCS11_LEGACY_PRIVATE_FALSE_DEFAULT + ret = ecdh_derive_tmpl(session, base, ¶ms, defTmpl, + sizeof(defTmpl) / sizeof(*defTmpl), &derived); + CHECK_RV(ret, CKR_USER_NOT_LOGGED_IN, + "derive default-private secret key without login rejected"); +#endif + + ret = funcList->C_Login(session, CKU_USER, userPin, userPinLen); + CHECK_CKR(ret, "C_Login"); + loggedOut = 0; + + ret = ecdh_derive_tmpl(session, base, ¶ms, privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &derived); + CHECK_CKR(ret, "derive private key after login"); + +cleanup: + if (derived != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, derived); + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + if (loggedOut && + funcList->C_Login(session, CKU_USER, userPin, userPinLen) != + CKR_OK) { + result = -1; + } + return result; +} #endif /* HAVE_ECC */ static CK_RV pkcs11_init(void) @@ -654,6 +733,8 @@ static int ec_derive_test(void) if (test_concurrent_derive_same_key(session) != 0) result = -1; #endif + if (test_private_derive_requires_login(session) != 0) + result = -1; #else printf("ECC not available, skipping ECDH derive tests\n"); test_skipped = 1; From 1ed2f54710373c5ed21493da9ae6cb47c53eba5a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:12:11 -0700 Subject: [PATCH 099/204] F-13818 - Set derived key initial states before adding the object --- src/crypto.c | 12 ++-- tests/ec_derive_test.c | 149 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 6 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 548e555c..3db85b09 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9995,8 +9995,12 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, rv = CKR_FUNCTION_FAILED; } if (ret == 0) { - rv = AddObject(session, obj, pTemplate, - ulAttributeCount, phKey); + /* Set before AddObject so a token store has them. */ + rv = SetInitialStates(obj); + if (rv == CKR_OK) { + rv = AddObject(session, obj, pTemplate, + ulAttributeCount, phKey); + } if (rv != CKR_OK) WP11_Object_Free(obj); } @@ -10008,10 +10012,6 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, } } - if ((rv == CKR_OK) && (derivedKey != NULL)) { - rv = SetInitialStates(obj); - } - if (derivedKey != NULL) { wc_ForceZero(derivedKey, keyLen); XFREE(derivedKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/tests/ec_derive_test.c b/tests/ec_derive_test.c index 934e9534..179ba8f0 100644 --- a/tests/ec_derive_test.c +++ b/tests/ec_derive_test.c @@ -43,6 +43,12 @@ #include #endif +#if defined(HAVE_ECC) && !defined(WOLFPKCS11_NO_STORE) && !defined(_WIN32) + #define EC_DERIVE_PERSIST_TEST + #include + #include +#endif + #include "testdata.h" #if defined(HAVE_ECC) && !defined(SINGLE_THREADED) && \ @@ -749,6 +755,145 @@ static int ec_derive_test(void) return result; } +#ifdef EC_DERIVE_PERSIST_TEST +static const char persistLabel[] = "ec-derive-persist"; + +static CK_RV open_user_session(CK_SESSION_HANDLE* session) +{ + CK_RV ret; + + ret = pkcs11_init(); + if (ret == CKR_OK) { + ret = funcList->C_OpenSession(slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, session); + } + if (ret == CKR_OK) + ret = funcList->C_Login(*session, CKU_USER, userPin, userPinLen); + return ret; +} + +/* Derive a sensitive, non-extractable token key from a generated base key. */ +static CK_RV derive_token_key(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_MECHANISM genMech = { CKM_EC_KEY_PAIR_GEN, NULL, 0 }; + CK_ECDH1_DERIVE_PARAMS params; + byte point[2 * 32 + 3]; + CK_ATTRIBUTE pointAttr = { CKA_EC_POINT, point, sizeof(point) }; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + }; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE_LEN, &secretLen, sizeof(secretLen) }, + { CKA_LABEL, (void*)persistLabel, sizeof(persistLabel) - 1 }, + }; + + ret = funcList->C_GenerateKeyPair(session, &genMech, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &pub, &priv); + if (ret == CKR_OK) + ret = funcList->C_GetAttributeValue(session, pub, &pointAttr, 1); + if (ret == CKR_OK) { + ecdh_params_init(¶ms, point, pointAttr.ulValueLen); + ret = ecdh_derive_tmpl(session, priv, ¶ms, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &derived); + } + return ret; +} + +/* Historical protection flags of a derived token key survive a reload even + * when the process ends without C_Finalize. */ +static int test_token_derive_states_persist(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ULONG found = 0; + CK_BBOOL alwaysSensitive = CK_FALSE; + CK_BBOOL neverExtractable = CK_FALSE; + CK_ATTRIBUTE findTmpl[] = { + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_LABEL, (void*)persistLabel, sizeof(persistLabel) - 1 }, + }; + CK_ATTRIBUTE stateTmpl[] = { + { CKA_ALWAYS_SENSITIVE, &alwaysSensitive, sizeof(CK_BBOOL) }, + { CKA_NEVER_EXTRACTABLE, &neverExtractable, sizeof(CK_BBOOL) }, + }; + pid_t pid; + int status = 0; + int result = 0; + + printf("\n=== Testing derived token key state persistence ===\n"); + + pid = fork(); + if (pid == 0) { + /* Exit without C_Finalize so only stores made by the derive count. */ + ret = open_user_session(&session); + if (ret == CKR_OK) + ret = derive_token_key(session); + _exit(ret == CKR_OK ? 0 : 1); + } + if (pid < 0) + ret = CKR_GENERAL_ERROR; + else if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) || + WEXITSTATUS(status) != 0) + ret = CKR_GENERAL_ERROR; + else + ret = CKR_OK; + CHECK_CKR(ret, "derive token key in child process"); + + ret = open_user_session(&session); + CHECK_CKR(ret, "reopen token"); + + ret = funcList->C_FindObjectsInit(session, findTmpl, + sizeof(findTmpl) / sizeof(*findTmpl)); + CHECK_CKR(ret, "C_FindObjectsInit"); + ret = funcList->C_FindObjects(session, &obj, 1, &found); + if (funcList->C_FindObjectsFinal(session) != CKR_OK && ret == CKR_OK) + ret = CKR_GENERAL_ERROR; + if (ret == CKR_OK && found != 1) + ret = CKR_GENERAL_ERROR; + CHECK_CKR(ret, "find derived token key"); + + ret = funcList->C_GetAttributeValue(session, obj, stateTmpl, + sizeof(stateTmpl) / sizeof(*stateTmpl)); + CHECK_CKR(ret, "read derived key historical flags"); + if (alwaysSensitive != CK_TRUE || neverExtractable != CK_TRUE) { + fprintf(stderr, "FAIL: reloaded derived key ALWAYS_SENSITIVE=%d " + "NEVER_EXTRACTABLE=%d, expected both TRUE\n", + (int)alwaysSensitive, (int)neverExtractable); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: reloaded derived key keeps its historical flags\n"); + test_passed++; + +cleanup: + if (obj != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, obj); + if (session != CK_INVALID_HANDLE) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + pkcs11_final(); + return result; +} +#endif /* EC_DERIVE_PERSIST_TEST */ + static void print_results(void) { printf("\n=== Test Results ===\n"); @@ -772,6 +917,10 @@ int main(int argc, char* argv[]) if (ec_derive_test() != 0 && test_failed == 0) test_failed++; +#ifdef EC_DERIVE_PERSIST_TEST + if (test_token_derive_states_persist() != 0 && test_failed == 0) + test_failed++; +#endif print_results(); #ifndef HAVE_ECC From d409daa20ee77de8300ce96c38b7a475a30671fc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:30:17 -0700 Subject: [PATCH 100/204] F-13816 - Derive key historical flags from the base key history --- src/crypto.c | 49 +++++++++++++++++- tests/ec_derive_test.c | 110 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index 3db85b09..ff095a29 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -599,6 +599,38 @@ static CK_RV SetInitialStates(WP11_Object* key) return rv; } +#if defined(HAVE_ECC) || !defined(NO_DH) || defined(WOLFPKCS11_HKDF) || \ + (!defined(NO_AES) && defined(HAVE_AES_CBC)) +/* PKCS#11 derive rules: the historical flags also need the base key's. */ +static CK_RV SetDerivedStates(WP11_Object* key, CK_BBOOL baseAlwaysSensitive, + CK_BBOOL baseNeverExtractable) +{ + CK_RV rv; + CK_BBOOL getVar = CK_FALSE; + CK_BBOOL state; + CK_ULONG getVarLen = sizeof(CK_BBOOL); + + rv = WP11_Object_GetAttr(key, CKA_SENSITIVE, &getVar, &getVarLen); + if (rv == CKR_OK) { + state = (baseAlwaysSensitive == CK_TRUE && getVar == CK_TRUE) ? + CK_TRUE : CK_FALSE; + rv = WP11_Object_SetAttr(key, CKA_ALWAYS_SENSITIVE, &state, + sizeof(CK_BBOOL)); + } + if (rv == CKR_OK) { + getVarLen = sizeof(CK_BBOOL); + rv = WP11_Object_GetAttr(key, CKA_EXTRACTABLE, &getVar, &getVarLen); + } + if (rv == CKR_OK) { + state = (baseNeverExtractable == CK_TRUE && getVar == CK_FALSE) ? + CK_TRUE : CK_FALSE; + rv = WP11_Object_SetAttr(key, CKA_NEVER_EXTRACTABLE, &state, + sizeof(CK_BBOOL)); + } + return rv; +} +#endif + static CK_RV TemplateHasAttribute(CK_ATTRIBUTE_TYPE type, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) { @@ -9585,6 +9617,12 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, CK_BBOOL baseExtractable = CK_TRUE; CK_ULONG bLen; #endif +#endif +#if defined(HAVE_ECC) || !defined(NO_DH) || defined(WOLFPKCS11_HKDF) || \ + (!defined(NO_AES) && defined(HAVE_AES_CBC)) + CK_BBOOL baseAlwaysSensitive = CK_FALSE; + CK_BBOOL baseNeverExtractable = CK_FALSE; + CK_ULONG histLen; #endif WOLFPKCS11_ENTER("C_DeriveKey"); @@ -9955,6 +9993,14 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, &bLen) != 0) baseExtractable = CK_TRUE; #endif + histLen = sizeof(CK_BBOOL); + if (WP11_Object_GetAttr(obj, CKA_ALWAYS_SENSITIVE, + &baseAlwaysSensitive, &histLen) != 0) + baseAlwaysSensitive = CK_FALSE; + histLen = sizeof(CK_BBOOL); + if (WP11_Object_GetAttr(obj, CKA_NEVER_EXTRACTABLE, + &baseNeverExtractable, &histLen) != 0) + baseNeverExtractable = CK_FALSE; rv = CreateObject(session, pTemplate, ulAttributeCount, &obj); if (rv == CKR_OK) { /* obj now refers to the newly created derived key. */ @@ -9996,7 +10042,8 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, } if (ret == 0) { /* Set before AddObject so a token store has them. */ - rv = SetInitialStates(obj); + rv = SetDerivedStates(obj, baseAlwaysSensitive, + baseNeverExtractable); if (rv == CKR_OK) { rv = AddObject(session, obj, pTemplate, ulAttributeCount, phKey); diff --git a/tests/ec_derive_test.c b/tests/ec_derive_test.c index 179ba8f0..fb4c5b16 100644 --- a/tests/ec_derive_test.c +++ b/tests/ec_derive_test.c @@ -616,6 +616,114 @@ static int test_private_derive_requires_login(CK_SESSION_HANDLE session) } return result; } + +static CK_RV read_states(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + CK_BBOOL* alwaysSensitive, CK_BBOOL* neverExtractable) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_ALWAYS_SENSITIVE, alwaysSensitive, sizeof(CK_BBOOL) }, + { CKA_NEVER_EXTRACTABLE, neverExtractable, sizeof(CK_BBOOL) }, + }; + + return funcList->C_GetAttributeValue(session, obj, tmpl, + sizeof(tmpl) / sizeof(*tmpl)); +} + +/* A derived key is only always-sensitive / never-extractable when its base + * key has those historical properties too. */ +static int test_derived_states_follow_base(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_ECDH1_DERIVE_PARAMS params; + CK_MECHANISM genMech = { CKM_EC_KEY_PAIR_GEN, NULL, 0 }; + CK_BBOOL alwaysSensitive = 0xAA; + CK_BBOOL neverExtractable = 0xAA; + byte point[2 * 32 + 3]; + CK_ATTRIBUTE pointAttr = { CKA_EC_POINT, point, sizeof(point) }; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + }; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE_LEN, &secretLen, sizeof(secretLen) }, + }; + int result = 0; + + /* An imported key was never always-sensitive or never-extractable. */ + ret = create_ec_base(session, CK_TRUE, CK_FALSE, &base); + CHECK_CKR(ret, "create imported EC base key"); + ret = read_states(session, base, &alwaysSensitive, &neverExtractable); + CHECK_CKR(ret, "read imported base historical flags"); + if (alwaysSensitive != CK_FALSE || neverExtractable != CK_FALSE) + ret = CKR_GENERAL_ERROR; + CHECK_CKR(ret, "imported base historical flags are FALSE"); + + ecdh_params_init(¶ms, ecc_p256_point, sizeof(ecc_p256_point)); + ret = ecdh_derive_tmpl(session, base, ¶ms, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &derived); + CHECK_CKR(ret, "derive from imported base key"); + ret = read_states(session, derived, &alwaysSensitive, &neverExtractable); + CHECK_CKR(ret, "read derived historical flags"); + if (alwaysSensitive != CK_FALSE || neverExtractable != CK_FALSE) { + fprintf(stderr, "FAIL: derived from imported base: " + "ALWAYS_SENSITIVE=%d NEVER_EXTRACTABLE=%d, expected FALSE\n", + (int)alwaysSensitive, (int)neverExtractable); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: derived key does not claim history its base lacks\n"); + test_passed++; + funcList->C_DestroyObject(session, derived); + derived = CK_INVALID_HANDLE; + funcList->C_DestroyObject(session, base); + base = CK_INVALID_HANDLE; + + /* A generated sensitive, non-extractable key passes its history on. */ + ret = funcList->C_GenerateKeyPair(session, &genMech, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &pub, &base); + CHECK_CKR(ret, "generate EC base key"); + ret = funcList->C_GetAttributeValue(session, pub, &pointAttr, 1); + CHECK_CKR(ret, "read generated EC point"); + + ecdh_params_init(¶ms, point, pointAttr.ulValueLen); + ret = ecdh_derive_tmpl(session, base, ¶ms, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &derived); + CHECK_CKR(ret, "derive from generated base key"); + ret = read_states(session, derived, &alwaysSensitive, &neverExtractable); + CHECK_CKR(ret, "read derived historical flags"); + if (alwaysSensitive != CK_TRUE || neverExtractable != CK_TRUE) { + fprintf(stderr, "FAIL: derived from generated base: " + "ALWAYS_SENSITIVE=%d NEVER_EXTRACTABLE=%d, expected TRUE\n", + (int)alwaysSensitive, (int)neverExtractable); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: derived key keeps history its base has\n"); + test_passed++; + +cleanup: + if (derived != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, derived); + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + return result; +} #endif /* HAVE_ECC */ static CK_RV pkcs11_init(void) @@ -741,6 +849,8 @@ static int ec_derive_test(void) #endif if (test_private_derive_requires_login(session) != 0) result = -1; + if (test_derived_states_follow_base(session) != 0) + result = -1; #else printf("ECC not available, skipping ECDH derive tests\n"); test_skipped = 1; From b977a37084149f122e55a2894e1786f7cd5dcdab Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:37:26 -0700 Subject: [PATCH 101/204] F-3403 - Map derived key length failures to a PKCS#11 return value --- src/crypto.c | 2 +- tests/ec_derive_test.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index ff095a29..2cff527a 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -10054,7 +10054,7 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, } else { WP11_Object_Free(obj); - rv = ret; + rv = CKR_FUNCTION_FAILED; } } } diff --git a/tests/ec_derive_test.c b/tests/ec_derive_test.c index fb4c5b16..cb9c6eb6 100644 --- a/tests/ec_derive_test.c +++ b/tests/ec_derive_test.c @@ -724,6 +724,46 @@ static int test_derived_states_follow_base(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, pub); return result; } + +/* A derived key type that cannot hold the secret fails with a defined + * PKCS#11 return value. */ +static int test_unusable_key_type_defined_error(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_ECDH1_DERIVE_PARAMS params; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &eccKeyType, sizeof(eccKeyType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + }; + int result = 0; + + ret = create_ec_base(session, CK_FALSE, CK_TRUE, &base); + CHECK_CKR(ret, "create EC base key"); + + ecdh_params_init(¶ms, ecc_p256_point, sizeof(ecc_p256_point)); + ret = ecdh_derive_tmpl(session, base, ¶ms, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &derived); + if (ret == CKR_OK || ret >= CKR_VENDOR_DEFINED) { + fprintf(stderr, "FAIL: derive to EC secret key returned 0x%lx\n", + (unsigned long)ret); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: derive to unusable key type returns 0x%lx\n", + (unsigned long)ret); + test_passed++; + +cleanup: + if (derived != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, derived); + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + return result; +} #endif /* HAVE_ECC */ static CK_RV pkcs11_init(void) @@ -851,6 +891,8 @@ static int ec_derive_test(void) result = -1; if (test_derived_states_follow_base(session) != 0) result = -1; + if (test_unusable_key_type_defined_error(session) != 0) + result = -1; #else printf("ECC not available, skipping ECDH derive tests\n"); test_skipped = 1; From 5beb9068711b421cd2d6d0b1db9aece3b7982344 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:29 -0700 Subject: [PATCH 102/204] Test ECDH derive with well-formed points of the wrong size --- tests/ec_derive_test.c | 76 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/tests/ec_derive_test.c b/tests/ec_derive_test.c index cb9c6eb6..be8a9ee6 100644 --- a/tests/ec_derive_test.c +++ b/tests/ec_derive_test.c @@ -90,7 +90,33 @@ static int userPinLen = 15; #define CHECK_CKR(rv, op) CHECK_RV(rv, CKR_OK, op) +#define CHECK_FAILS(rv, op) do { \ + if ((rv) == CKR_OK) { \ + fprintf(stderr, "FAIL: %s: got CKR_OK\n", op); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } \ + else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while (0) + #ifdef HAVE_ECC +/* Uncompressed P-384 public point. */ +static byte ecc_p384_point[] = { + 0x04, 0x2b, 0x46, 0xc2, 0xd4, 0x00, 0xbd, 0xba, 0xae, 0xa5, 0xd1, 0x4e, + 0xcd, 0x6e, 0x06, 0x05, 0x02, 0x41, 0xec, 0x49, 0x2f, 0x3a, 0xf6, 0x90, + 0xf4, 0x77, 0x8d, 0x2c, 0x77, 0x2e, 0x43, 0x21, 0x63, 0x47, 0x13, 0xd0, + 0x9e, 0xaf, 0x8e, 0x6c, 0x02, 0x2e, 0xd0, 0x00, 0x80, 0x5d, 0x6b, 0x3c, + 0xb8, 0x86, 0x69, 0x22, 0x1e, 0x58, 0x40, 0xdf, 0x2a, 0x8c, 0x64, 0x06, + 0x41, 0x37, 0xa0, 0xdf, 0x77, 0xd7, 0x56, 0x0f, 0x5d, 0x92, 0x5e, 0x0d, + 0x51, 0xdc, 0x79, 0xca, 0xa1, 0x7a, 0x84, 0x00, 0x98, 0x74, 0xd6, 0x82, + 0xa2, 0x55, 0xc8, 0xc6, 0x00, 0x4f, 0xf7, 0x33, 0x30, 0xfb, 0x2f, 0x8a, + 0x4a +}; + static CK_OBJECT_CLASS privKeyClass = CKO_PRIVATE_KEY; static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; static CK_KEY_TYPE eccKeyType = CKK_EC; @@ -207,6 +233,54 @@ static int test_null_kdf_shared_data(CK_SESSION_HANDLE session) return result; } +/* A well-formed point of the wrong size for the base key's curve is rejected + * raw and DER wrapped, as is a raw point one byte short or long. */ +static int test_wrong_size_point_rejected(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_ECDH1_DERIVE_PARAMS params; + byte wrapped[2 + sizeof(ecc_p384_point)]; + byte longPoint[sizeof(ecc_p256_point) + 1]; + int result = 0; + + ret = create_ec_base(session, CK_FALSE, CK_TRUE, &base); + CHECK_CKR(ret, "create EC base key"); + + ecdh_params_init(¶ms, ecc_p384_point, sizeof(ecc_p384_point)); + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_FAILS(ret, "raw P-384 point against a P-256 key rejected"); + + wrapped[0] = 0x04; + wrapped[1] = (byte)sizeof(ecc_p384_point); + XMEMCPY(wrapped + 2, ecc_p384_point, sizeof(ecc_p384_point)); + ecdh_params_init(¶ms, wrapped, sizeof(wrapped)); + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_FAILS(ret, "DER wrapped P-384 point against a P-256 key rejected"); + + ecdh_params_init(¶ms, ecc_p256_point, sizeof(ecc_p256_point) - 1); + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_FAILS(ret, "raw point one byte short rejected"); + + XMEMCPY(longPoint, ecc_p256_point, sizeof(ecc_p256_point)); + longPoint[sizeof(longPoint) - 1] = 0x00; + ecdh_params_init(¶ms, longPoint, sizeof(longPoint)); + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_FAILS(ret, "raw point one byte long rejected"); + + ecdh_params_init(¶ms, ecc_p256_point, sizeof(ecc_p256_point)); + ret = ecdh_derive(session, base, ¶ms, &derived); + CHECK_CKR(ret, "correctly sized point still derives"); + +cleanup: + if (derived != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, derived); + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + return result; +} + /* A public data length beyond any encodable EC point is a parameter error. */ static int test_public_data_length_bound(CK_SESSION_HANDLE session) { @@ -875,6 +949,8 @@ static int ec_derive_test(void) #ifdef HAVE_ECC if (test_null_kdf_shared_data(session) != 0) result = -1; + if (test_wrong_size_point_rejected(session) != 0) + result = -1; if (test_public_data_length_bound(session) != 0) result = -1; if (test_unsupported_der_length_form(session) != 0) From 7eb7c69d7ad431db9d34932b02aefcf72d5b7e47 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:08:54 -0700 Subject: [PATCH 103/204] F-4687 - Reject HKDF salt and info lengths beyond 32 bits --- src/crypto.c | 7 + tests/include.am | 7 + tests/kdf_tls_derive_test.c | 348 ++++++++++++++++++++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 tests/kdf_tls_derive_test.c diff --git a/src/crypto.c b/src/crypto.c index 2cff527a..65b4b14e 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9749,6 +9749,13 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, kdfParams = (CK_HKDF_PARAMS_PTR)pMechanism->pParameter; if (!kdfParams->bExpand && !kdfParams->bExtract) return CKR_MECHANISM_PARAM_INVALID; + if (kdfParams->bExtract && + kdfParams->ulSaltType == CKF_HKDF_SALT_DATA && + !CK_ULONG_FITS_WORD32(kdfParams->ulSaltLen)) + return CKR_MECHANISM_PARAM_INVALID; + if (kdfParams->bExpand && + !CK_ULONG_FITS_WORD32(kdfParams->ulInfoLen)) + return CKR_MECHANISM_PARAM_INVALID; FindAttributeType(pTemplate, ulAttributeCount, CKA_VALUE_LEN, &lenAttr); diff --git a/tests/include.am b/tests/include.am index c6984e3f..70d25714 100644 --- a/tests/include.am +++ b/tests/include.am @@ -211,6 +211,11 @@ noinst_PROGRAMS += tests/ec_derive_test tests_ec_derive_test_SOURCES = tests/ec_derive_test.c tests_ec_derive_test_LDADD = +check_PROGRAMS += tests/kdf_tls_derive_test +noinst_PROGRAMS += tests/kdf_tls_derive_test +tests_kdf_tls_derive_test_SOURCES = tests/kdf_tls_derive_test.c +tests_kdf_tls_derive_test_LDADD = + check_PROGRAMS += tests/secret_extractable_default_test noinst_PROGRAMS += tests/secret_extractable_default_test tests_secret_extractable_default_test_SOURCES = tests/secret_extractable_default_test.c @@ -387,6 +392,7 @@ tests_key_sensitive_default_test_LDADD += src/libwolfpkcs11.la tests_derive_disabled_key_test_LDADD += src/libwolfpkcs11.la tests_derive_inherit_protection_test_LDADD += src/libwolfpkcs11.la tests_ec_derive_test_LDADD += src/libwolfpkcs11.la +tests_kdf_tls_derive_test_LDADD += src/libwolfpkcs11.la tests_secret_extractable_default_test_LDADD += src/libwolfpkcs11.la tests_rsa_private_usage_default_test_LDADD += src/libwolfpkcs11.la tests_copyobject_token_test_LDADD += src/libwolfpkcs11.la @@ -449,6 +455,7 @@ tests_key_sensitive_default_test_LDADD += src/libwolfpkcs11.la tests_derive_disabled_key_test_LDADD += src/libwolfpkcs11.la tests_derive_inherit_protection_test_LDADD += src/libwolfpkcs11.la tests_ec_derive_test_LDADD += src/libwolfpkcs11.la +tests_kdf_tls_derive_test_LDADD += src/libwolfpkcs11.la tests_secret_extractable_default_test_LDADD += src/libwolfpkcs11.la tests_rsa_private_usage_default_test_LDADD += src/libwolfpkcs11.la tests_copyobject_token_test_LDADD += src/libwolfpkcs11.la diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c new file mode 100644 index 00000000..b37d1775 --- /dev/null +++ b/tests/kdf_tls_derive_test.c @@ -0,0 +1,348 @@ +/* kdf_tls_derive_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Tests for C_DeriveKey with the HKDF, DH, AES-CBC and TLS 1.2 mechanisms: + * mechanism parameter validation and the attributes of the derived keys. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" + +#define TEST_DIR "./store/kdf_tls_derive_test" + +/* CK_ULONG lengths above the 32-bit range can only be expressed on LP64. */ +#if ULONG_MAX > 0xFFFFFFFFUL + #define WIDE_CK_ULONG + #define LEN_ABOVE_WORD32(n) ((CK_ULONG)0xFFFFFFFFUL + 1 + (CK_ULONG)(n)) +#endif + +#if defined(WOLFPKCS11_HKDF) && defined(WIDE_CK_ULONG) + #define SECRET_BASE_TESTS +#endif + +static int test_passed = 0; +static int test_failed = 0; + +#ifndef HAVE_PKCS11_STATIC +static void* dlib; +#endif +static CK_FUNCTION_LIST* funcList; +static CK_SLOT_ID slot = 0; +static const char* tokenName = "wolfpkcs11"; +static byte* soPin = (byte*)"password123456"; +static int soPinLen = 14; +static byte* userPin = (byte*)"wolfpkcs11-test"; +static int userPinLen = 15; + +#define CHECK_RV(rv, exp, op) do { \ + if ((rv) != (exp)) { \ + fprintf(stderr, "FAIL: %s: got 0x%lx, expected 0x%lx\n", op, \ + (unsigned long)(rv), (unsigned long)(exp)); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } \ + else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while (0) + +#define CHECK_CKR(rv, op) CHECK_RV(rv, CKR_OK, op) + +#ifdef SECRET_BASE_TESTS +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; + +static byte baseSecret[32] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b +}; + +/* Session secret key of the given type usable as a derive base. */ +static CK_RV create_secret_base(CK_SESSION_HANDLE session, CK_KEY_TYPE type, + byte* value, CK_ULONG valueLen, + CK_OBJECT_HANDLE* obj) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &type, sizeof(type) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, value, valueLen }, + }; + CK_ULONG cnt = sizeof(tmpl) / sizeof(*tmpl); + + return funcList->C_CreateObject(session, tmpl, cnt, obj); +} + +/* Derive a public, extractable generic secret of the given length. */ +static CK_RV derive_generic(CK_SESSION_HANDLE session, CK_MECHANISM* mech, + CK_OBJECT_HANDLE base, CK_ULONG len, + CK_OBJECT_HANDLE* derived) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE_LEN, &len, sizeof(len) }, + }; + CK_ULONG cnt = sizeof(tmpl) / sizeof(*tmpl); + + *derived = CK_INVALID_HANDLE; + return funcList->C_DeriveKey(session, mech, base, tmpl, cnt, derived); +} + +static void destroy_obj(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* obj) +{ + if (*obj != CK_INVALID_HANDLE) { + funcList->C_DestroyObject(session, *obj); + *obj = CK_INVALID_HANDLE; + } +} +#endif /* SECRET_BASE_TESTS */ + +#if defined(WOLFPKCS11_HKDF) && defined(WIDE_CK_ULONG) +/* HKDF salt and info lengths must be representable without truncation. */ +static int test_hkdf_lengths_fit_word32(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + byte salt[4] = { 0x00, 0x01, 0x02, 0x03 }; + byte info[4] = { 0xf0, 0xf1, 0xf2, 0xf3 }; + CK_HKDF_PARAMS params; + CK_MECHANISM mech = { CKM_HKDF_DERIVE, ¶ms, sizeof(params) }; + int result = 0; + + ret = create_secret_base(session, CKK_GENERIC_SECRET, baseSecret, + sizeof(baseSecret), &base); + CHECK_CKR(ret, "create HKDF base key"); + + XMEMSET(¶ms, 0, sizeof(params)); + params.bExtract = CK_TRUE; + params.bExpand = CK_TRUE; + params.prfHashMechanism = CKM_SHA256; + params.ulSaltType = CKF_HKDF_SALT_DATA; + params.pSalt = salt; + params.ulSaltLen = sizeof(salt); + params.pInfo = info; + params.ulInfoLen = sizeof(info); + ret = derive_generic(session, &mech, base, 32, &derived); + CHECK_CKR(ret, "HKDF derive with in-range lengths"); + destroy_obj(session, &derived); + + params.ulSaltLen = LEN_ABOVE_WORD32(sizeof(salt)); + ret = derive_generic(session, &mech, base, 32, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "HKDF salt length beyond 32 bits rejected"); + params.ulSaltLen = sizeof(salt); + + params.bExtract = CK_FALSE; + params.ulInfoLen = LEN_ABOVE_WORD32(sizeof(info)); + ret = derive_generic(session, &mech, base, 32, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "HKDF info length beyond 32 bits rejected"); + +cleanup: + destroy_obj(session, &derived); + destroy_obj(session, &base); + return result; +} +#endif + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); +#ifndef HAVE_PKCS11_STATIC + CK_C_GetFunctionList func; + + dlib = dlopen(WOLFPKCS11_DLL_FILENAME, RTLD_NOW | RTLD_LOCAL); + if (dlib == NULL) { + fprintf(stderr, "dlopen error: %s\n", dlerror()); + return -1; + } + func = (CK_C_GetFunctionList)dlsym(dlib, "C_GetFunctionList"); + if (func == NULL) { + dlclose(dlib); + dlib = NULL; + return -1; + } + ret = func(&funcList); + if (ret != CKR_OK) { + dlclose(dlib); + dlib = NULL; + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) + return ret; +#endif + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + ret = funcList->C_Initialize(&args); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + if (slotCount == 0) + return CKR_GENERAL_ERROR; + slot = slotList[0]; + return ret; +} + +static CK_RV pkcs11_final(void) +{ + if (funcList != NULL) { + funcList->C_Finalize(NULL); + funcList = NULL; + } +#ifndef HAVE_PKCS11_STATIC + if (dlib) { + dlclose(dlib); + dlib = NULL; + } +#endif + return CKR_OK; +} + +static CK_RV pkcs11_setup_token(void) +{ + CK_RV ret; + unsigned char label[32]; + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + ret = funcList->C_InitToken(slot, soPin, soPinLen, label); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) + return ret; + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret == CKR_OK) + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + return ret; +} + +static int kdf_tls_derive_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + int result = 0; + + printf("\n=== Testing C_DeriveKey with KDF and TLS mechanisms ===\n"); + + ret = pkcs11_init(); + CHECK_CKR(ret, "C_Initialize"); + + ret = pkcs11_setup_token(); + CHECK_CKR(ret, "token setup"); + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &session); + CHECK_CKR(ret, "C_OpenSession"); + ret = funcList->C_Login(session, CKU_USER, userPin, userPinLen); + CHECK_CKR(ret, "C_Login"); + +#if defined(WOLFPKCS11_HKDF) && defined(WIDE_CK_ULONG) + if (test_hkdf_lengths_fit_word32(session) != 0) + result = -1; +#endif + +cleanup: + if (session != CK_INVALID_HANDLE) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + pkcs11_final(); + return result; +} + +static void print_results(void) +{ + printf("\n=== Test Results ===\n"); + printf("Tests passed: %d\n", test_passed); + printf("Tests failed: %d\n", test_failed); + if (test_failed == 0) + printf("ALL TESTS PASSED!\n"); + else + printf("SOME TESTS FAILED!\n"); +} + +int main(int argc, char* argv[]) +{ +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + (void)argc; + (void)argv; + + if (kdf_tls_derive_test() != 0 && test_failed == 0) + test_failed++; + + print_results(); +#ifndef SECRET_BASE_TESTS + if (test_failed == 0) + return 77; +#endif + return (test_failed == 0) ? 0 : 1; +} From 57ab0cf17e2c2a020e8d99cfc386703d71e2511a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:17:37 -0700 Subject: [PATCH 104/204] F-4804 - Reject an extended master secret session hash length beyond 32 bits --- src/crypto.c | 2 ++ tests/kdf_tls_derive_test.c | 53 ++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index 65b4b14e..50386bdb 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9961,6 +9961,8 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, return CKR_MECHANISM_PARAM_INVALID; nssParams = (CK_NSS_TLS_EXTENDED_MASTER_KEY_DERIVE_PARAMS*) pMechanism->pParameter; + if (!CK_ULONG_FITS_WORD32(nssParams->ulSessionHashLen)) + return CKR_MECHANISM_PARAM_INVALID; keyLen = PRF_KEY_SIZE; derivedKey = (byte*)XMALLOC(keyLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c index b37d1775..4ebb1f08 100644 --- a/tests/kdf_tls_derive_test.c +++ b/tests/kdf_tls_derive_test.c @@ -54,7 +54,13 @@ #define LEN_ABOVE_WORD32(n) ((CK_ULONG)0xFFFFFFFFUL + 1 + (CK_ULONG)(n)) #endif -#if defined(WOLFPKCS11_HKDF) && defined(WIDE_CK_ULONG) +#if defined(WOLFSSL_HAVE_PRF) && defined(WOLFPKCS11_NSS) && \ + defined(WIDE_CK_ULONG) + #define NSS_EMS_WIDE_TEST +#endif + +#if (defined(WOLFPKCS11_HKDF) && defined(WIDE_CK_ULONG)) || \ + defined(NSS_EMS_WIDE_TEST) #define SECRET_BASE_TESTS #endif @@ -197,6 +203,46 @@ static int test_hkdf_lengths_fit_word32(CK_SESSION_HANDLE session) } #endif +#ifdef NSS_EMS_WIDE_TEST +/* The extended master secret session hash length must not be truncated. */ +static int test_session_hash_length_fits_word32(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + byte sessionHash[32]; + CK_VERSION version = { 3, 3 }; + CK_NSS_TLS_EXTENDED_MASTER_KEY_DERIVE_PARAMS params; + CK_MECHANISM mech = { CKM_NSS_TLS_EXTENDED_MASTER_KEY_DERIVE, ¶ms, + sizeof(params) }; + int result = 0; + + XMEMSET(sessionHash, 0xAA, sizeof(sessionHash)); + ret = create_secret_base(session, CKK_GENERIC_SECRET, baseSecret, + sizeof(baseSecret), &base); + CHECK_CKR(ret, "create TLS base key"); + + XMEMSET(¶ms, 0, sizeof(params)); + params.prfHashMechanism = CKM_SHA256; + params.pSessionHash = sessionHash; + params.ulSessionHashLen = sizeof(sessionHash); + params.pVersion = &version; + ret = derive_generic(session, &mech, base, 48, &derived); + CHECK_CKR(ret, "extended master secret derive"); + destroy_obj(session, &derived); + + params.ulSessionHashLen = LEN_ABOVE_WORD32(sizeof(sessionHash)); + ret = derive_generic(session, &mech, base, 48, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "session hash length beyond 32 bits rejected"); + +cleanup: + destroy_obj(session, &derived); + destroy_obj(session, &base); + return result; +} +#endif + static CK_RV pkcs11_init(void) { CK_RV ret; @@ -308,6 +354,11 @@ static int kdf_tls_derive_test(void) result = -1; #endif +#ifdef NSS_EMS_WIDE_TEST + if (test_session_hash_length_fits_word32(session) != 0) + result = -1; +#endif + cleanup: if (session != CK_INVALID_HANDLE) { funcList->C_Logout(session); From 1462ef7428b7595ede310bf63e48d08d32b6dce0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:26:44 -0700 Subject: [PATCH 105/204] F-4274 - Bound the DH peer public value length by the largest supported prime --- src/crypto.c | 3 +- src/internal.c | 7 ---- tests/kdf_tls_derive_test.c | 78 +++++++++++++++++++++++++++++++++++-- wolfpkcs11/internal.h | 7 ++++ 4 files changed, 84 insertions(+), 11 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 50386bdb..8fe1e6dd 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9796,7 +9796,8 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, return CKR_KEY_TYPE_INCONSISTENT; if (pMechanism->pParameter == NULL) return CKR_MECHANISM_PARAM_INVALID; - if (pMechanism->ulParameterLen == 0) + if (pMechanism->ulParameterLen == 0 || + pMechanism->ulParameterLen > WP11_MAX_DH_KEY_SZ) return CKR_MECHANISM_PARAM_INVALID; keyLen = (word32)pMechanism->ulParameterLen; diff --git a/src/internal.c b/src/internal.c index 683fae0e..91046099 100644 --- a/src/internal.c +++ b/src/internal.c @@ -120,13 +120,6 @@ -/* Maximum size of storage for generated/derived DH key. */ -#ifdef WOLFPKCS11_NSS -#define WP11_MAX_DH_KEY_SZ (8192/8) -#else -#define WP11_MAX_DH_KEY_SZ (4096/8) -#endif - #ifndef WP11_MAX_CERT_SZ #define WP11_MAX_CERT_SZ 4096 #endif diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c index 4ebb1f08..c6618781 100644 --- a/tests/kdf_tls_derive_test.c +++ b/tests/kdf_tls_derive_test.c @@ -64,6 +64,10 @@ #define SECRET_BASE_TESTS #endif +#if defined(SECRET_BASE_TESTS) || !defined(NO_DH) + #define DERIVE_TESTS +#endif + static int test_passed = 0; static int test_failed = 0; @@ -94,11 +98,14 @@ static int userPinLen = 15; #define CHECK_CKR(rv, op) CHECK_RV(rv, CKR_OK, op) -#ifdef SECRET_BASE_TESTS +#ifdef DERIVE_TESTS static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; static CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; static CK_BBOOL ckTrue = CK_TRUE; static CK_BBOOL ckFalse = CK_FALSE; +#endif + +#ifdef SECRET_BASE_TESTS static byte baseSecret[32] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, @@ -125,7 +132,9 @@ static CK_RV create_secret_base(CK_SESSION_HANDLE session, CK_KEY_TYPE type, return funcList->C_CreateObject(session, tmpl, cnt, obj); } +#endif /* SECRET_BASE_TESTS */ +#ifdef DERIVE_TESTS /* Derive a public, extractable generic secret of the given length. */ static CK_RV derive_generic(CK_SESSION_HANDLE session, CK_MECHANISM* mech, CK_OBJECT_HANDLE base, CK_ULONG len, @@ -152,7 +161,7 @@ static void destroy_obj(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* obj) *obj = CK_INVALID_HANDLE; } } -#endif /* SECRET_BASE_TESTS */ +#endif /* DERIVE_TESTS */ #if defined(WOLFPKCS11_HKDF) && defined(WIDE_CK_ULONG) /* HKDF salt and info lengths must be representable without truncation. */ @@ -243,6 +252,64 @@ static int test_session_hash_length_fits_word32(CK_SESSION_HANDLE session) } #endif +#ifndef NO_DH +/* DH peer public value lengths are bounded by the largest supported prime. */ +static int test_dh_public_length_bound(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE dhType = CKK_DH; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_MECHANISM mech; + byte padded[8192 / 8 + 1]; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &dhType, sizeof(dhType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + { 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) }, + }; + int result = 0; + + ret = funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &base); + CHECK_CKR(ret, "create DH base key"); + + mech.mechanism = CKM_DH_PKCS_DERIVE; + mech.pParameter = dh_2048_peer; + mech.ulParameterLen = sizeof(dh_2048_peer); + ret = derive_generic(session, &mech, base, 32, &derived); + CHECK_CKR(ret, "DH derive with peer public value"); + destroy_obj(session, &derived); + + XMEMSET(padded, 0, sizeof(padded)); + XMEMCPY(padded + sizeof(padded) - sizeof(dh_2048_peer), dh_2048_peer, + sizeof(dh_2048_peer)); + mech.pParameter = padded; + mech.ulParameterLen = sizeof(padded); + ret = derive_generic(session, &mech, base, 32, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "DH public value longer than any prime rejected"); + +#ifdef WIDE_CK_ULONG + mech.pParameter = dh_2048_peer; + mech.ulParameterLen = LEN_ABOVE_WORD32(sizeof(dh_2048_peer)); + ret = derive_generic(session, &mech, base, 32, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "DH public value length beyond 32 bits rejected"); +#endif + +cleanup: + destroy_obj(session, &derived); + destroy_obj(session, &base); + return result; +} +#endif + static CK_RV pkcs11_init(void) { CK_RV ret; @@ -359,6 +426,11 @@ static int kdf_tls_derive_test(void) result = -1; #endif +#ifndef NO_DH + if (test_dh_public_length_bound(session) != 0) + result = -1; +#endif + cleanup: if (session != CK_INVALID_HANDLE) { funcList->C_Logout(session); @@ -391,7 +463,7 @@ int main(int argc, char* argv[]) test_failed++; print_results(); -#ifndef SECRET_BASE_TESTS +#ifndef DERIVE_TESTS if (test_failed == 0) return 77; #endif diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index ee4f6dfc..a4539bf0 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -359,6 +359,13 @@ C_EXTRA_FLAGS="-DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT" #define WP11_MAX_SYM_KEY_SZ 64 #endif +/* Maximum size of storage for generated/derived DH key. */ +#ifdef WOLFPKCS11_NSS +#define WP11_MAX_DH_KEY_SZ (8192/8) +#else +#define WP11_MAX_DH_KEY_SZ (4096/8) +#endif + /* Login failure constants. */ #ifndef WP11_MAX_LOGIN_FAILS_SO #define WP11_MAX_LOGIN_FAILS_SO 3 From 5e7f53a8fad55d7c8e4ae9ffd9451edba08a91e5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:33:03 -0700 Subject: [PATCH 106/204] F-5077 - Reject an AES-CBC encrypt-data length beyond 32 bits --- src/crypto.c | 2 +- tests/kdf_tls_derive_test.c | 57 ++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 8fe1e6dd..5c91fc53 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9825,7 +9825,7 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, sizeof(CK_AES_CBC_ENCRYPT_DATA_PARAMS)) return CKR_MECHANISM_PARAM_INVALID; params = (CK_AES_CBC_ENCRYPT_DATA_PARAMS*)pMechanism->pParameter; - if (params->length % 16) + if (!CK_ULONG_FITS_WORD32(params->length) || params->length % 16) return CKR_MECHANISM_PARAM_INVALID; keyLen = (word32)params->length; diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c index c6618781..ef8186c3 100644 --- a/tests/kdf_tls_derive_test.c +++ b/tests/kdf_tls_derive_test.c @@ -59,8 +59,12 @@ #define NSS_EMS_WIDE_TEST #endif +#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WIDE_CK_ULONG) + #define AES_CBC_WIDE_TEST +#endif + #if (defined(WOLFPKCS11_HKDF) && defined(WIDE_CK_ULONG)) || \ - defined(NSS_EMS_WIDE_TEST) + defined(NSS_EMS_WIDE_TEST) || defined(AES_CBC_WIDE_TEST) #define SECRET_BASE_TESTS #endif @@ -107,12 +111,20 @@ static CK_BBOOL ckFalse = CK_FALSE; #ifdef SECRET_BASE_TESTS +/* Not every SECRET_BASE_TESTS configuration uses the shared secret. */ +#if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-variable" +#endif static byte baseSecret[32] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b }; +#if defined(__GNUC__) + #pragma GCC diagnostic pop +#endif /* Session secret key of the given type usable as a derive base. */ static CK_RV create_secret_base(CK_SESSION_HANDLE session, CK_KEY_TYPE type, @@ -310,6 +322,44 @@ static int test_dh_public_length_bound(CK_SESSION_HANDLE session) } #endif +#ifdef AES_CBC_WIDE_TEST +/* The AES-CBC encrypt-data length must be representable in 32 bits. */ +static int test_aes_cbc_data_length_fits_word32(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + byte key[sizeof(aes_cbc_key)]; + byte plain[sizeof(aes_cbc_plain)]; + CK_AES_CBC_ENCRYPT_DATA_PARAMS params; + CK_MECHANISM mech = { CKM_AES_CBC_ENCRYPT_DATA, ¶ms, sizeof(params) }; + int result = 0; + + XMEMCPY(key, aes_cbc_key, sizeof(key)); + XMEMCPY(plain, aes_cbc_plain, sizeof(plain)); + ret = create_secret_base(session, CKK_AES, key, sizeof(key), &base); + CHECK_CKR(ret, "create AES base key"); + + XMEMSET(¶ms, 0, sizeof(params)); + XMEMCPY(params.iv, aes_cbc_iv, sizeof(params.iv)); + params.pData = plain; + params.length = sizeof(plain); + ret = derive_generic(session, &mech, base, sizeof(plain), &derived); + CHECK_CKR(ret, "AES-CBC encrypt-data derive"); + destroy_obj(session, &derived); + + params.length = LEN_ABOVE_WORD32(sizeof(plain)); + ret = derive_generic(session, &mech, base, sizeof(plain), &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "AES-CBC data length beyond 32 bits rejected"); + +cleanup: + destroy_obj(session, &derived); + destroy_obj(session, &base); + return result; +} +#endif + static CK_RV pkcs11_init(void) { CK_RV ret; @@ -431,6 +481,11 @@ static int kdf_tls_derive_test(void) result = -1; #endif +#ifdef AES_CBC_WIDE_TEST + if (test_aes_cbc_data_length_fits_word32(session) != 0) + result = -1; +#endif + cleanup: if (session != CK_INVALID_HANDLE) { funcList->C_Logout(session); From 9eb4a93e4ece94ef8797281ccb5fc797cacf9a16 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:39:21 -0700 Subject: [PATCH 107/204] F-10389 - Require whole-byte sizes for each TLS key material field --- src/crypto.c | 6 ++ tests/kdf_tls_derive_test.c | 121 +++++++++++++++++++++++++++++++++++- 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index 5c91fc53..8db5773c 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9868,6 +9868,12 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, tlsParams->ulIVSizeInBits > ((CK_ULONG)0xFFFFFFFF / 2)) { return CKR_MECHANISM_PARAM_INVALID; } + /* Each field is extracted separately in whole bytes */ + if ((tlsParams->ulMacSizeInBits % 8) != 0 || + (tlsParams->ulKeySizeInBits % 8) != 0 || + (tlsParams->ulIVSizeInBits % 8) != 0) { + return CKR_MECHANISM_PARAM_INVALID; + } /* Check sum won't overflow on 32-bit */ if (a > (CK_ULONG)0xFFFFFFFF - b) return CKR_MECHANISM_PARAM_INVALID; diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c index ef8186c3..7b70acd1 100644 --- a/tests/kdf_tls_derive_test.c +++ b/tests/kdf_tls_derive_test.c @@ -64,7 +64,8 @@ #endif #if (defined(WOLFPKCS11_HKDF) && defined(WIDE_CK_ULONG)) || \ - defined(NSS_EMS_WIDE_TEST) || defined(AES_CBC_WIDE_TEST) + defined(NSS_EMS_WIDE_TEST) || defined(AES_CBC_WIDE_TEST) || \ + defined(WOLFSSL_HAVE_PRF) #define SECRET_BASE_TESTS #endif @@ -360,6 +361,119 @@ static int test_aes_cbc_data_length_fits_word32(CK_SESSION_HANDLE session) } #endif +#ifdef WOLFSSL_HAVE_PRF +static byte clientRandom[32] = { + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80 +}; +static byte serverRandom[32] = { + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60 +}; + +static void tls_key_mat_init(CK_TLS12_KEY_MAT_PARAMS* params, + CK_SSL3_KEY_MAT_OUT* out, CK_ULONG macBits, + CK_ULONG keyBits, CK_ULONG ivBits) +{ + XMEMSET(out, 0, sizeof(*out)); + out->hClientMacSecret = CK_INVALID_HANDLE; + out->hServerMacSecret = CK_INVALID_HANDLE; + out->hClientKey = CK_INVALID_HANDLE; + out->hServerKey = CK_INVALID_HANDLE; + XMEMSET(params, 0, sizeof(*params)); + params->ulMacSizeInBits = macBits; + params->ulKeySizeInBits = keyBits; + params->ulIVSizeInBits = ivBits; + params->bIsExport = CK_FALSE; + params->RandomInfo.pClientRandom = clientRandom; + params->RandomInfo.ulClientRandomLen = sizeof(clientRandom); + params->RandomInfo.pServerRandom = serverRandom; + params->RandomInfo.ulServerRandomLen = sizeof(serverRandom); + params->pReturnedKeyMaterial = out; + params->prfHashMechanism = CKM_SHA256; +} + +static CK_RV tls_key_mat_derive(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE base, + CK_TLS12_KEY_MAT_PARAMS* params, + CK_ATTRIBUTE* tmpl, CK_ULONG cnt) +{ + CK_MECHANISM mech; + CK_ATTRIBUTE defTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + }; + + if (tmpl == NULL) { + tmpl = defTmpl; + cnt = sizeof(defTmpl) / sizeof(*defTmpl); + } + mech.mechanism = CKM_TLS12_KEY_AND_MAC_DERIVE; + mech.pParameter = params; + mech.ulParameterLen = sizeof(*params); + + return funcList->C_DeriveKey(session, &mech, base, tmpl, cnt, NULL); +} + +static void destroy_key_mat(CK_SESSION_HANDLE session, + CK_SSL3_KEY_MAT_OUT* out) +{ + destroy_obj(session, &out->hClientMacSecret); + destroy_obj(session, &out->hServerMacSecret); + destroy_obj(session, &out->hClientKey); + destroy_obj(session, &out->hServerKey); +} + +/* Every TLS key material size must be a whole number of bytes. */ +static int test_tls_key_sizes_byte_aligned(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_TLS12_KEY_MAT_PARAMS params; + CK_SSL3_KEY_MAT_OUT out; + byte ivClient[16]; + byte ivServer[16]; + byte master[48]; + int result = 0; + + XMEMSET(master, 0x5c, sizeof(master)); + tls_key_mat_init(¶ms, &out, 256, 128, 128); + ret = create_secret_base(session, CKK_GENERIC_SECRET, master, + sizeof(master), &base); + CHECK_CKR(ret, "create TLS master secret"); + + out.pIVClient = ivClient; + out.pIVServer = ivServer; + ret = tls_key_mat_derive(session, base, ¶ms, NULL, 0); + CHECK_CKR(ret, "TLS key and MAC derive"); + destroy_key_mat(session, &out); + + tls_key_mat_init(¶ms, &out, 4, 4, 0); + ret = tls_key_mat_derive(session, base, ¶ms, NULL, 0); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "TLS MAC and key sizes not whole bytes rejected"); + + tls_key_mat_init(¶ms, &out, 256, 128, 4); + out.pIVClient = ivClient; + out.pIVServer = ivServer; + ret = tls_key_mat_derive(session, base, ¶ms, NULL, 0); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "TLS IV size not whole bytes rejected"); + +cleanup: + destroy_key_mat(session, &out); + destroy_obj(session, &base); + return result; +} +#endif + static CK_RV pkcs11_init(void) { CK_RV ret; @@ -486,6 +600,11 @@ static int kdf_tls_derive_test(void) result = -1; #endif +#ifdef WOLFSSL_HAVE_PRF + if (test_tls_key_sizes_byte_aligned(session) != 0) + result = -1; +#endif + cleanup: if (session != CK_INVALID_HANDLE) { funcList->C_Logout(session); From 717a71c32a8637b6d08184ba32bc2b520eea5b5e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 15:11:29 -0700 Subject: [PATCH 108/204] F-10381 - Use one feature guard for the derive key locals and helpers --- src/crypto.c | 27 ++++++++++---------- tests/kdf_tls_derive_test.c | 50 +++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 8db5773c..5e15c1c5 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -54,6 +54,12 @@ #define PRF_KEY_SIZE 48 +/* Derive mechanisms that build their output into a secret key object. */ +#if defined(HAVE_ECC) || !defined(NO_DH) || defined(WOLFPKCS11_HKDF) || \ + (!defined(NO_AES) && defined(HAVE_AES_CBC)) || defined(WOLFSSL_HAVE_PRF) + #define WP11_DERIVE_SECRET_KEY +#endif + /* Check that a CK_ULONG value fits in word32 with room for overhead such as * authentication tags, key wrap blocks, or padding. On LP64 platforms CK_ULONG * is 64-bit but wolfCrypt functions use word32/int for lengths. */ @@ -599,8 +605,7 @@ static CK_RV SetInitialStates(WP11_Object* key) return rv; } -#if defined(HAVE_ECC) || !defined(NO_DH) || defined(WOLFPKCS11_HKDF) || \ - (!defined(NO_AES) && defined(HAVE_AES_CBC)) +#ifdef WP11_DERIVE_SECRET_KEY /* PKCS#11 derive rules: the historical flags also need the base key's. */ static CK_RV SetDerivedStates(WP11_Object* key, CK_BBOOL baseAlwaysSensitive, CK_BBOOL baseNeverExtractable) @@ -9370,7 +9375,7 @@ CK_RV C_UnwrapKey(CK_SESSION_HANDLE hSession, return rv; } -#if defined(HAVE_ECC) || !defined(NO_DH) +#ifdef WP11_DERIVE_SECRET_KEY /** * Determine the key length of the object. * @@ -9394,7 +9399,7 @@ static int SymmKeyLen(WP11_Object* obj, word32 len, CK_ULONG* symmKeyLen) switch (WP11_Object_GetType(obj)) { case CKK_AES: -#ifdef WOLFPKCS11_NSS +#if defined(WOLFPKCS11_NSS) && !defined(NO_AES) /* This is the only wrapping mechanism that we support. NSS chooses * the wrapping mechanism from the list in wrapMechanismList in * PK11_GetBestWrapMechanism. Unfortunately this relies on a default @@ -9605,7 +9610,7 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, CK_RV rv = CKR_OK; WP11_Session* session; WP11_Object* obj = NULL; -#if defined(HAVE_ECC) || !defined(NO_DH) || defined(WOLFPKCS11_HKDF) +#ifdef WP11_DERIVE_SECRET_KEY byte* derivedKey = NULL; word32 keyLen; CK_ULONG symmKeyLen; @@ -9618,8 +9623,7 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, CK_ULONG bLen; #endif #endif -#if defined(HAVE_ECC) || !defined(NO_DH) || defined(WOLFPKCS11_HKDF) || \ - (!defined(NO_AES) && defined(HAVE_AES_CBC)) +#ifdef WP11_DERIVE_SECRET_KEY CK_BBOOL baseAlwaysSensitive = CK_FALSE; CK_BBOOL baseNeverExtractable = CK_FALSE; CK_ULONG histLen; @@ -9994,11 +9998,9 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, return CKR_MECHANISM_INVALID; } -#if defined(HAVE_ECC) || !defined(NO_DH) || defined(WOLFPKCS11_HKDF) || \ - (!defined(NO_AES) && defined(HAVE_AES_CBC)) +#ifdef WP11_DERIVE_SECRET_KEY if ((ret == 0) && (derivedKey != NULL)) { -#if (defined(HAVE_ECC) || !defined(NO_DH) || defined(WOLFPKCS11_HKDF)) && \ - !defined(WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT) +#ifndef WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT /* F-4533: read the base key's protection bits while `obj' still * refers to it, before CreateObject reuses `obj' for the new key. */ bLen = sizeof(CK_BBOOL); @@ -10020,8 +10022,7 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, rv = CreateObject(session, pTemplate, ulAttributeCount, &obj); if (rv == CKR_OK) { /* obj now refers to the newly created derived key. */ -#if (defined(HAVE_ECC) || !defined(NO_DH) || defined(WOLFPKCS11_HKDF)) && \ - !defined(WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT) +#ifndef WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT /* F-4533: PKCS#11 v3.0 5.5.5 - a derived key must be at least as * protected as its base key. Force the inherited attributes after * the caller template has been applied so a weaker template diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c index 7b70acd1..0253b541 100644 --- a/tests/kdf_tls_derive_test.c +++ b/tests/kdf_tls_derive_test.c @@ -64,8 +64,8 @@ #endif #if (defined(WOLFPKCS11_HKDF) && defined(WIDE_CK_ULONG)) || \ - defined(NSS_EMS_WIDE_TEST) || defined(AES_CBC_WIDE_TEST) || \ - defined(WOLFSSL_HAVE_PRF) + defined(NSS_EMS_WIDE_TEST) || defined(WOLFSSL_HAVE_PRF) || \ + (!defined(NO_AES) && defined(HAVE_AES_CBC)) #define SECRET_BASE_TESTS #endif @@ -474,6 +474,47 @@ static int test_tls_key_sizes_byte_aligned(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AES_CBC) +/* AES-CBC encrypt-data derives the CBC encryption of the data. */ +static int test_aes_cbc_encrypt_data_value(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + byte key[sizeof(aes_cbc_key)]; + byte plain[sizeof(aes_cbc_plain)]; + byte value[sizeof(aes_cbc_cipher)]; + CK_ATTRIBUTE valueAttr = { CKA_VALUE, value, sizeof(value) }; + CK_AES_CBC_ENCRYPT_DATA_PARAMS params; + CK_MECHANISM mech = { CKM_AES_CBC_ENCRYPT_DATA, ¶ms, sizeof(params) }; + int result = 0; + + XMEMCPY(key, aes_cbc_key, sizeof(key)); + XMEMCPY(plain, aes_cbc_plain, sizeof(plain)); + ret = create_secret_base(session, CKK_AES, key, sizeof(key), &base); + CHECK_CKR(ret, "create AES base key"); + + XMEMSET(¶ms, 0, sizeof(params)); + XMEMCPY(params.iv, aes_cbc_iv, sizeof(params.iv)); + params.pData = plain; + params.length = sizeof(plain); + ret = derive_generic(session, &mech, base, sizeof(plain), &derived); + CHECK_CKR(ret, "AES-CBC encrypt-data derive"); + + ret = funcList->C_GetAttributeValue(session, derived, &valueAttr, 1); + CHECK_CKR(ret, "read derived key value"); + if (valueAttr.ulValueLen != sizeof(aes_cbc_cipher) || + XMEMCMP(value, aes_cbc_cipher, sizeof(aes_cbc_cipher)) != 0) + ret = CKR_GENERAL_ERROR; + CHECK_CKR(ret, "derived key is the CBC encryption of the data"); + +cleanup: + destroy_obj(session, &derived); + destroy_obj(session, &base); + return result; +} +#endif + static CK_RV pkcs11_init(void) { CK_RV ret; @@ -605,6 +646,11 @@ static int kdf_tls_derive_test(void) result = -1; #endif +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + if (test_aes_cbc_encrypt_data_value(session) != 0) + result = -1; +#endif + cleanup: if (session != CK_INVALID_HANDLE) { funcList->C_Logout(session); From b76022ef0e2e95f234a4c0e9e091168db5ed58e4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 15:27:52 -0700 Subject: [PATCH 109/204] F-11518 - Require TLS random and session hash pointers before deriving --- src/crypto.c | 7 +++- tests/kdf_tls_derive_test.c | 82 +++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 5e15c1c5..1cfc4184 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9858,7 +9858,9 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, sizeof(CK_TLS12_KEY_MAT_PARAMS)) return CKR_MECHANISM_PARAM_INVALID; tlsParams = (CK_TLS12_KEY_MAT_PARAMS*) pMechanism->pParameter; - if (tlsParams->pReturnedKeyMaterial == NULL) + if (tlsParams->pReturnedKeyMaterial == NULL || + tlsParams->RandomInfo.pClientRandom == NULL || + tlsParams->RandomInfo.pServerRandom == NULL) return CKR_MECHANISM_PARAM_INVALID; { @@ -9972,7 +9974,8 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, return CKR_MECHANISM_PARAM_INVALID; nssParams = (CK_NSS_TLS_EXTENDED_MASTER_KEY_DERIVE_PARAMS*) pMechanism->pParameter; - if (!CK_ULONG_FITS_WORD32(nssParams->ulSessionHashLen)) + if (nssParams->pSessionHash == NULL || + !CK_ULONG_FITS_WORD32(nssParams->ulSessionHashLen)) return CKR_MECHANISM_PARAM_INVALID; keyLen = PRF_KEY_SIZE; diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c index 0253b541..1201fca6 100644 --- a/tests/kdf_tls_derive_test.c +++ b/tests/kdf_tls_derive_test.c @@ -515,6 +515,79 @@ static int test_aes_cbc_encrypt_data_value(CK_SESSION_HANDLE session) } #endif +#ifdef WOLFSSL_HAVE_PRF +/* TLS key expansion requires both client and server random data. */ +static int test_tls_randoms_required(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_TLS12_KEY_MAT_PARAMS params; + CK_SSL3_KEY_MAT_OUT out; + byte master[48]; + int result = 0; + + XMEMSET(master, 0x5c, sizeof(master)); + tls_key_mat_init(¶ms, &out, 256, 128, 0); + ret = create_secret_base(session, CKK_GENERIC_SECRET, master, + sizeof(master), &base); + CHECK_CKR(ret, "create TLS master secret"); + + params.RandomInfo.pClientRandom = NULL; + ret = tls_key_mat_derive(session, base, ¶ms, NULL, 0); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "TLS key and MAC derive without client random rejected"); + + tls_key_mat_init(¶ms, &out, 256, 128, 0); + params.RandomInfo.pServerRandom = NULL; + ret = tls_key_mat_derive(session, base, ¶ms, NULL, 0); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "TLS key and MAC derive without server random rejected"); + +cleanup: + destroy_key_mat(session, &out); + destroy_obj(session, &base); + return result; +} +#endif + +#if defined(WOLFSSL_HAVE_PRF) && defined(WOLFPKCS11_NSS) +/* The extended master secret is bound to a session hash. */ +static int test_session_hash_required(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_VERSION version = { 3, 3 }; + CK_NSS_TLS_EXTENDED_MASTER_KEY_DERIVE_PARAMS params; + CK_MECHANISM mech = { CKM_NSS_TLS_EXTENDED_MASTER_KEY_DERIVE, ¶ms, + sizeof(params) }; + int result = 0; + + ret = create_secret_base(session, CKK_GENERIC_SECRET, baseSecret, + sizeof(baseSecret), &base); + CHECK_CKR(ret, "create TLS base key"); + + XMEMSET(¶ms, 0, sizeof(params)); + params.prfHashMechanism = CKM_SHA256; + params.pSessionHash = NULL; + params.ulSessionHashLen = 0; + params.pVersion = &version; + ret = derive_generic(session, &mech, base, 48, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "extended master secret without session hash rejected"); + + params.ulSessionHashLen = 32; + ret = derive_generic(session, &mech, base, 48, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "extended master secret with NULL session hash rejected"); + +cleanup: + destroy_obj(session, &derived); + destroy_obj(session, &base); + return result; +} +#endif + static CK_RV pkcs11_init(void) { CK_RV ret; @@ -651,6 +724,15 @@ static int kdf_tls_derive_test(void) result = -1; #endif +#ifdef WOLFSSL_HAVE_PRF + if (test_tls_randoms_required(session) != 0) + result = -1; +#endif +#if defined(WOLFSSL_HAVE_PRF) && defined(WOLFPKCS11_NSS) + if (test_session_hash_required(session) != 0) + result = -1; +#endif + cleanup: if (session != CK_INVALID_HANDLE) { funcList->C_Logout(session); From a58200268b0e2f5a38b819d0b8b569d24b27f3bf Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 15:38:00 -0700 Subject: [PATCH 110/204] F-4066 - Report an unsupported TLS master secret version as a parameter error --- src/crypto.c | 2 +- tests/kdf_tls_derive_test.c | 48 +++++++++++++++++++++++++++++++++++++ tests/pkcs11test.c | 2 +- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 1cfc4184..c28b6062 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9943,7 +9943,7 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, return CKR_MECHANISM_PARAM_INVALID; if ((prfParams->pVersion->major != 3) || (prfParams->pVersion->minor != 3)) - return CKR_MECHANISM_INVALID; + return CKR_MECHANISM_PARAM_INVALID; } keyLen = PRF_KEY_SIZE; diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c index 1201fca6..e0ff0416 100644 --- a/tests/kdf_tls_derive_test.c +++ b/tests/kdf_tls_derive_test.c @@ -588,6 +588,50 @@ static int test_session_hash_required(CK_SESSION_HANDLE session) } #endif +#ifdef WOLFSSL_HAVE_PRF +/* An unsupported protocol version is a mechanism parameter error. */ +static int test_tls_master_version_param(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_VERSION version = { 3, 3 }; + CK_TLS12_MASTER_KEY_DERIVE_PARAMS params; + CK_MECHANISM mech = { CKM_TLS12_MASTER_KEY_DERIVE, ¶ms, + sizeof(params) }; + byte preMaster[48]; + int result = 0; + + XMEMSET(preMaster, 0x33, sizeof(preMaster)); + preMaster[0] = 3; + preMaster[1] = 3; + ret = create_secret_base(session, CKK_GENERIC_SECRET, preMaster, + sizeof(preMaster), &base); + CHECK_CKR(ret, "create TLS pre-master secret"); + + XMEMSET(¶ms, 0, sizeof(params)); + params.RandomInfo.pClientRandom = clientRandom; + params.RandomInfo.ulClientRandomLen = sizeof(clientRandom); + params.RandomInfo.pServerRandom = serverRandom; + params.RandomInfo.ulServerRandomLen = sizeof(serverRandom); + params.pVersion = &version; + params.prfHashMechanism = CKM_SHA256; + ret = derive_generic(session, &mech, base, 48, &derived); + CHECK_CKR(ret, "TLS 1.2 master secret derive"); + destroy_obj(session, &derived); + + version.minor = 1; + ret = derive_generic(session, &mech, base, 48, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "TLS master secret derive with other version rejected"); + +cleanup: + destroy_obj(session, &derived); + destroy_obj(session, &base); + return result; +} +#endif + static CK_RV pkcs11_init(void) { CK_RV ret; @@ -728,6 +772,10 @@ static int kdf_tls_derive_test(void) if (test_tls_randoms_required(session) != 0) result = -1; #endif +#ifdef WOLFSSL_HAVE_PRF + if (test_tls_master_version_param(session) != 0) + result = -1; +#endif #if defined(WOLFSSL_HAVE_PRF) && defined(WOLFPKCS11_NSS) if (test_session_hash_required(session) != 0) result = -1; diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index 96da0f1d..3325be1a 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -16538,7 +16538,7 @@ static CK_RV test_derive_tls12_master_key(void* args) { ret = funcList->C_DeriveKey(session, &mechanism, hBaseKey, derivedKeyTemplate, ulDerivedKeyTemplateCount, &hDerivedKey); - CHECK_CKR_FAIL(ret, CKR_MECHANISM_INVALID, "Invalid version"); + CHECK_CKR_FAIL(ret, CKR_MECHANISM_PARAM_INVALID, "Invalid version"); version.major = 3; } From 97a7d982ea27c28d2606b5a420765ecbd75045b7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 16:00:13 -0700 Subject: [PATCH 111/204] F-13245 - Reject a derived key length larger than the derived secret --- src/crypto.c | 29 ++++++++- tests/kdf_tls_derive_test.c | 119 +++++++++++++++++++++++++++++++++++- tests/pkcs11test.c | 2 +- 3 files changed, 147 insertions(+), 3 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index c28b6062..ec5e9e16 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9627,6 +9627,11 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, CK_BBOOL baseAlwaysSensitive = CK_FALSE; CK_BBOOL baseNeverExtractable = CK_FALSE; CK_ULONG histLen; + CK_ATTRIBUTE* valueLenAttr = NULL; + CK_ULONG reqValueLen = 0; +#endif +#ifndef NO_DH + CK_ULONG dhPrimeLen = 0; #endif WOLFPKCS11_ENTER("C_DeriveKey"); @@ -10022,7 +10027,29 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, if (WP11_Object_GetAttr(obj, CKA_NEVER_EXTRACTABLE, &baseNeverExtractable, &histLen) != 0) baseNeverExtractable = CK_FALSE; - rv = CreateObject(session, pTemplate, ulAttributeCount, &obj); + FindAttributeType(pTemplate, ulAttributeCount, CKA_VALUE_LEN, + &valueLenAttr); + if ((valueLenAttr != NULL) && (valueLenAttr->pValue != NULL) && + (valueLenAttr->ulValueLen == sizeof(CK_ULONG))) { + XMEMCPY(&reqValueLen, valueLenAttr->pValue, sizeof(CK_ULONG)); + } +#ifndef NO_DH + /* DH agreement drops leading zero bytes; restore up to the prime + * length on request. */ + if ((pMechanism->mechanism == CKM_DH_PKCS_DERIVE) && + (reqValueLen > keyLen) && + (reqValueLen <= pMechanism->ulParameterLen) && + (WP11_Object_GetAttr(obj, CKA_PRIME, NULL, &dhPrimeLen) == 0) && + (reqValueLen <= dhPrimeLen)) { + XMEMMOVE(derivedKey + (reqValueLen - keyLen), derivedKey, keyLen); + XMEMSET(derivedKey, 0, reqValueLen - keyLen); + keyLen = (word32)reqValueLen; + } +#endif + if (reqValueLen > keyLen) + rv = CKR_ATTRIBUTE_VALUE_INVALID; + if (rv == CKR_OK) + rv = CreateObject(session, pTemplate, ulAttributeCount, &obj); if (rv == CKR_OK) { /* obj now refers to the newly created derived key. */ #ifndef WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c index e0ff0416..ba850ded 100644 --- a/tests/kdf_tls_derive_test.c +++ b/tests/kdf_tls_derive_test.c @@ -63,7 +63,7 @@ #define AES_CBC_WIDE_TEST #endif -#if (defined(WOLFPKCS11_HKDF) && defined(WIDE_CK_ULONG)) || \ +#if defined(WOLFPKCS11_HKDF) || \ defined(NSS_EMS_WIDE_TEST) || defined(WOLFSSL_HAVE_PRF) || \ (!defined(NO_AES) && defined(HAVE_AES_CBC)) #define SECRET_BASE_TESTS @@ -632,6 +632,117 @@ static int test_tls_master_version_param(CK_SESSION_HANDLE session) } #endif +#if defined(WOLFPKCS11_HKDF) || !defined(NO_DH) || \ + (!defined(NO_AES) && defined(HAVE_AES_CBC)) +/* A derived key has the requested CKA_VALUE_LEN or the derive fails. */ +static int test_derived_length_matches_template(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_MECHANISM mech; +#ifdef WOLFPKCS11_HKDF + CK_HKDF_PARAMS hkdfParams; +#endif +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + byte key[sizeof(aes_cbc_key)]; + byte plain[sizeof(aes_cbc_plain)]; + CK_AES_CBC_ENCRYPT_DATA_PARAMS cbcParams; +#endif +#ifndef NO_DH + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE dhType = CKK_DH; + byte peer[2 * sizeof(dh_ffdhe2048_p)]; + byte value[sizeof(dh_ffdhe2048_p)]; + CK_ATTRIBUTE valueAttr = { CKA_VALUE, value, sizeof(value) }; + CK_ATTRIBUTE dhTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &dhType, sizeof(dhType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + { 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) }, + }; +#endif + int result = 0; + +#ifdef WOLFPKCS11_HKDF + ret = create_secret_base(session, CKK_GENERIC_SECRET, baseSecret, + sizeof(baseSecret), &base); + CHECK_CKR(ret, "create HKDF base key"); + XMEMSET(&hkdfParams, 0, sizeof(hkdfParams)); + hkdfParams.bExtract = CK_TRUE; + hkdfParams.bExpand = CK_FALSE; + hkdfParams.prfHashMechanism = CKM_SHA256; + hkdfParams.ulSaltType = CKF_HKDF_SALT_NULL; + mech.mechanism = CKM_HKDF_DERIVE; + mech.pParameter = &hkdfParams; + mech.ulParameterLen = sizeof(hkdfParams); + ret = derive_generic(session, &mech, base, 32, &derived); + CHECK_CKR(ret, "HKDF extract with digest-sized length"); + destroy_obj(session, &derived); + ret = derive_generic(session, &mech, base, 48, &derived); + CHECK_RV(ret, CKR_ATTRIBUTE_VALUE_INVALID, + "HKDF extract longer than the digest rejected"); + destroy_obj(session, &base); +#endif + +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + XMEMCPY(key, aes_cbc_key, sizeof(key)); + XMEMCPY(plain, aes_cbc_plain, sizeof(plain)); + ret = create_secret_base(session, CKK_AES, key, sizeof(key), &base); + CHECK_CKR(ret, "create AES base key"); + XMEMSET(&cbcParams, 0, sizeof(cbcParams)); + XMEMCPY(cbcParams.iv, aes_cbc_iv, sizeof(cbcParams.iv)); + cbcParams.pData = plain; + cbcParams.length = sizeof(plain); + mech.mechanism = CKM_AES_CBC_ENCRYPT_DATA; + mech.pParameter = &cbcParams; + mech.ulParameterLen = sizeof(cbcParams); + ret = derive_generic(session, &mech, base, 2 * sizeof(plain), &derived); + CHECK_RV(ret, CKR_ATTRIBUTE_VALUE_INVALID, + "AES-CBC encrypt-data longer than the data rejected"); + destroy_obj(session, &base); +#endif + +#ifndef NO_DH + ret = funcList->C_CreateObject(session, dhTmpl, + sizeof(dhTmpl) / sizeof(*dhTmpl), &base); + CHECK_CKR(ret, "create DH base key"); + /* This peer value gives a shared secret with a leading zero byte. */ + XMEMSET(peer, 0, sizeof(peer)); + peer[sizeof(peer) - 2] = 0x01; + peer[sizeof(peer) - 1] = 0x5f; + mech.mechanism = CKM_DH_PKCS_DERIVE; + mech.pParameter = peer + sizeof(dh_ffdhe2048_p); + mech.ulParameterLen = sizeof(dh_ffdhe2048_p); + ret = derive_generic(session, &mech, base, sizeof(dh_ffdhe2048_p), + &derived); + CHECK_CKR(ret, "DH derive of a prime-sized secret"); + ret = funcList->C_GetAttributeValue(session, derived, &valueAttr, 1); + CHECK_CKR(ret, "read DH derived value"); + if (valueAttr.ulValueLen != sizeof(dh_ffdhe2048_p) || value[0] != 0) + ret = CKR_GENERAL_ERROR; + CHECK_CKR(ret, "DH derived value keeps its leading zero byte"); + destroy_obj(session, &derived); + + mech.pParameter = peer; + mech.ulParameterLen = sizeof(peer); + ret = derive_generic(session, &mech, base, sizeof(peer), &derived); + CHECK_RV(ret, CKR_ATTRIBUTE_VALUE_INVALID, + "DH derive longer than the prime rejected"); +#endif + +cleanup: + destroy_obj(session, &derived); + destroy_obj(session, &base); + return result; +} +#endif + static CK_RV pkcs11_init(void) { CK_RV ret; @@ -781,6 +892,12 @@ static int kdf_tls_derive_test(void) result = -1; #endif +#if defined(WOLFPKCS11_HKDF) || !defined(NO_DH) || \ + (!defined(NO_AES) && defined(HAVE_AES_CBC)) + if (test_derived_length_matches_template(session) != 0) + result = -1; +#endif + cleanup: if (session != CK_INVALID_HANDLE) { funcList->C_Logout(session); diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index 3325be1a..c5e935df 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -10541,8 +10541,8 @@ static CK_RV aes_cbc_encrypt_data_test(CK_SESSION_HANDLE session, word32 outSz = sizeof(out); CK_OBJECT_HANDLE secret; CK_KEY_TYPE keyType = CKK_GENERIC_SECRET; - CK_ULONG secSz = outSz; CK_BYTE data[16] = { 0 }; + CK_ULONG secSz = sizeof(data); CK_AES_CBC_ENCRYPT_DATA_PARAMS aesParams = { { 0 }, data, sizeof(data) }; From 1de7bd8d2d0b938b6644cbf90b3bc4d1317b6904 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 16:14:16 -0700 Subject: [PATCH 112/204] F-3636 - Set derive historical flags on TLS key material keys --- src/crypto.c | 41 ++++++++++--- tests/kdf_tls_derive_test.c | 116 ++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 8 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index ec5e9e16..3873f25d 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9430,7 +9430,9 @@ static int SymmKeyLen(WP11_Object* obj, word32 len, CK_ULONG* symmKeyLen) #ifdef WOLFSSL_HAVE_PRF static int SetKeyExtract(WP11_Session* session, byte* ptr, CK_ULONG length, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulAttributeCount, - CK_BBOOL isMac, CK_OBJECT_HANDLE* handle) + CK_BBOOL isMac, CK_BBOOL baseAlwaysSensitive, + CK_BBOOL baseNeverExtractable, + CK_OBJECT_HANDLE* handle) { WP11_Object* secret = NULL; int ret; @@ -9457,6 +9459,12 @@ static int SetKeyExtract(WP11_Session* session, byte* ptr, CK_ULONG length, WP11_Object_Free(secret); return CKR_FUNCTION_FAILED; } + ret = (int)SetDerivedStates(secret, baseAlwaysSensitive, + baseNeverExtractable); + if (ret != CKR_OK) { + WP11_Object_Free(secret); + return CKR_FUNCTION_FAILED; + } ret = (int)AddObject(session, secret, pTemplate, ulAttributeCount, handle); if (ret != CKR_OK) { @@ -9516,7 +9524,9 @@ static int SetKeyExtract(WP11_Session* session, byte* ptr, CK_ULONG length, static int Tls12_Extract_Keys(WP11_Session* session, CK_TLS12_KEY_MAT_PARAMS* tlsParams, CK_ATTRIBUTE_PTR pTemplate, - CK_ULONG ulAttributeCount, byte* derivedKey) + CK_ULONG ulAttributeCount, byte* derivedKey, + CK_BBOOL baseAlwaysSensitive, + CK_BBOOL baseNeverExtractable) { int ret = 0; unsigned char* ptr = derivedKey; @@ -9529,7 +9539,8 @@ static int Tls12_Extract_Keys(WP11_Session* session, /* Client MAC key */ length = tlsParams->ulMacSizeInBits / 8; ret = SetKeyExtract(session, ptr, length, pTemplate, - ulAttributeCount, CK_TRUE, + ulAttributeCount, CK_TRUE, baseAlwaysSensitive, + baseNeverExtractable, &tlsParams->pReturnedKeyMaterial->hClientMacSecret); if (ret != 0) { return ret; @@ -9537,7 +9548,8 @@ static int Tls12_Extract_Keys(WP11_Session* session, ptr += length; /* Server MAC key */ ret = SetKeyExtract(session, ptr, length, pTemplate, - ulAttributeCount, CK_TRUE, + ulAttributeCount, CK_TRUE, baseAlwaysSensitive, + baseNeverExtractable, &tlsParams->pReturnedKeyMaterial->hServerMacSecret); if (ret != 0) { return ret; @@ -9546,7 +9558,8 @@ static int Tls12_Extract_Keys(WP11_Session* session, /* Client key */ length = tlsParams->ulKeySizeInBits / 8; ret = SetKeyExtract(session, ptr, length, pTemplate, - ulAttributeCount, CK_FALSE, + ulAttributeCount, CK_FALSE, baseAlwaysSensitive, + baseNeverExtractable, &tlsParams->pReturnedKeyMaterial->hClientKey); if (ret != 0) { return ret; @@ -9554,7 +9567,8 @@ static int Tls12_Extract_Keys(WP11_Session* session, ptr += length; /* Server key */ ret = SetKeyExtract(session, ptr, length, pTemplate, - ulAttributeCount, CK_FALSE, + ulAttributeCount, CK_FALSE, baseAlwaysSensitive, + baseNeverExtractable, &tlsParams->pReturnedKeyMaterial->hServerKey); if (ret != 0) { return ret; @@ -9912,9 +9926,20 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, "key expansion", 13, derivedKey, keyLen, CK_FALSE, obj); - if (ret == 0) + if (ret == 0) { + histLen = sizeof(CK_BBOOL); + if (WP11_Object_GetAttr(obj, CKA_ALWAYS_SENSITIVE, + &baseAlwaysSensitive, &histLen) != 0) + baseAlwaysSensitive = CK_FALSE; + histLen = sizeof(CK_BBOOL); + if (WP11_Object_GetAttr(obj, CKA_NEVER_EXTRACTABLE, + &baseNeverExtractable, &histLen) != 0) + baseNeverExtractable = CK_FALSE; ret = Tls12_Extract_Keys(session, tlsParams, pTemplate, - ulAttributeCount, derivedKey); + ulAttributeCount, derivedKey, + baseAlwaysSensitive, + baseNeverExtractable); + } /* Freeing here so that we don't attempt to generate a key at the * end of the function */ diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c index ba850ded..f3a5012a 100644 --- a/tests/kdf_tls_derive_test.c +++ b/tests/kdf_tls_derive_test.c @@ -743,6 +743,118 @@ static int test_derived_length_matches_template(CK_SESSION_HANDLE session) } #endif +#ifdef WOLFSSL_HAVE_PRF +static CK_RV read_states(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + CK_BBOOL* alwaysSensitive, CK_BBOOL* neverExtractable) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_ALWAYS_SENSITIVE, alwaysSensitive, sizeof(CK_BBOOL) }, + { CKA_NEVER_EXTRACTABLE, neverExtractable, sizeof(CK_BBOOL) }, + }; + + return funcList->C_GetAttributeValue(session, obj, tmpl, + sizeof(tmpl) / sizeof(*tmpl)); +} + +/* All four TLS keys must report exactly the expected historical flags. */ +static CK_RV check_key_mat_states(CK_SESSION_HANDLE session, + CK_SSL3_KEY_MAT_OUT* out, CK_BBOOL expected) +{ + CK_RV ret = CKR_OK; + CK_OBJECT_HANDLE keys[4]; + CK_BBOOL alwaysSensitive; + CK_BBOOL neverExtractable; + int i; + + keys[0] = out->hClientMacSecret; + keys[1] = out->hServerMacSecret; + keys[2] = out->hClientKey; + keys[3] = out->hServerKey; + for (i = 0; i < 4 && ret == CKR_OK; i++) { + alwaysSensitive = !expected; + neverExtractable = !expected; + ret = read_states(session, keys[i], &alwaysSensitive, + &neverExtractable); + if (ret == CKR_OK && (alwaysSensitive != expected || + neverExtractable != expected)) { + fprintf(stderr, "TLS key %d: ALWAYS_SENSITIVE=%d " + "NEVER_EXTRACTABLE=%d, expected %d\n", i, + (int)alwaysSensitive, (int)neverExtractable, + (int)expected); + ret = CKR_GENERAL_ERROR; + } + } + return ret; +} + +/* TLS key material keys carry the derive historical protection flags. */ +static int test_tls_key_states_follow_base(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_TLS12_KEY_MAT_PARAMS params; + CK_SSL3_KEY_MAT_OUT out; + CK_MECHANISM genMech = { CKM_GENERIC_SECRET_KEY_GEN, NULL, 0 }; + CK_ULONG masterLen = 48; + byte master[48]; + CK_ATTRIBUTE genTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE_LEN, &masterLen, sizeof(masterLen) }, + }; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE importTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, master, sizeof(master) }, + }; + int result = 0; + + tls_key_mat_init(¶ms, &out, 256, 128, 0); + ret = funcList->C_GenerateKey(session, &genMech, genTmpl, + sizeof(genTmpl) / sizeof(*genTmpl), &base); + CHECK_CKR(ret, "generate TLS master secret"); + ret = tls_key_mat_derive(session, base, ¶ms, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl)); + CHECK_CKR(ret, "TLS key and MAC derive from generated secret"); + ret = check_key_mat_states(session, &out, CK_TRUE); + CHECK_CKR(ret, "TLS keys from generated secret keep history"); + destroy_key_mat(session, &out); + destroy_obj(session, &base); + + XMEMSET(master, 0x5c, sizeof(master)); + ret = funcList->C_CreateObject(session, importTmpl, + sizeof(importTmpl) / sizeof(*importTmpl), + &base); + CHECK_CKR(ret, "import TLS master secret"); + tls_key_mat_init(¶ms, &out, 256, 128, 0); + ret = tls_key_mat_derive(session, base, ¶ms, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl)); + CHECK_CKR(ret, "TLS key and MAC derive from imported secret"); + ret = check_key_mat_states(session, &out, CK_FALSE); + CHECK_CKR(ret, "TLS keys from imported secret have no history"); + +cleanup: + destroy_key_mat(session, &out); + destroy_obj(session, &base); + return result; +} +#endif + static CK_RV pkcs11_init(void) { CK_RV ret; @@ -887,6 +999,10 @@ static int kdf_tls_derive_test(void) if (test_tls_master_version_param(session) != 0) result = -1; #endif +#ifdef WOLFSSL_HAVE_PRF + if (test_tls_key_states_follow_base(session) != 0) + result = -1; +#endif #if defined(WOLFSSL_HAVE_PRF) && defined(WOLFPKCS11_NSS) if (test_session_hash_required(session) != 0) result = -1; From 69455c8bd516570fb0f1b2c2cb5fa01c9508c39e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 16:21:09 -0700 Subject: [PATCH 113/204] F-4074 - Add TLS key material keys only after all attributes are set --- src/crypto.c | 75 ++++++++----------- tests/kdf_tls_derive_test.c | 143 ++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 44 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 3873f25d..803be54b 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9465,59 +9465,46 @@ static int SetKeyExtract(WP11_Session* session, byte* ptr, CK_ULONG length, WP11_Object_Free(secret); return CKR_FUNCTION_FAILED; } - ret = (int)AddObject(session, secret, pTemplate, ulAttributeCount, - handle); - if (ret != CKR_OK) { - WP11_Object_Free(secret); - return ret; - } } else { WP11_Object_Free(secret); - return ret; + return CKR_FUNCTION_FAILED; } if (isMac) { ret = WP11_Object_SetAttr(secret, CKA_KEY_TYPE, (byte*)&keyType, sizeof(keyType)); - if (ret != CKR_OK) - return ret; - - ret = WP11_Object_SetAttr(secret, CKA_DERIVE, &ckTrue, - sizeof(CK_BBOOL)); - if (ret != CKR_OK) - return ret; - - ret = WP11_Object_SetAttr(secret, CKA_ENCRYPT, &ckFalse, - sizeof(CK_BBOOL)); - if (ret != CKR_OK) - return ret; - - ret = WP11_Object_SetAttr(secret, CKA_DECRYPT, &ckFalse, - sizeof(CK_BBOOL)); - if (ret != CKR_OK) - return ret; - - ret = WP11_Object_SetAttr(secret, CKA_SIGN, &ckTrue, - sizeof(CK_BBOOL)); - if (ret != CKR_OK) - return ret; - - ret = WP11_Object_SetAttr(secret, CKA_VERIFY, &ckTrue, - sizeof(CK_BBOOL)); - if (ret != CKR_OK) - return ret; - - ret = WP11_Object_SetAttr(secret, CKA_WRAP, &ckFalse, - sizeof(CK_BBOOL)); - if (ret != CKR_OK) - return ret; - - ret = WP11_Object_SetAttr(secret, CKA_UNWRAP, &ckFalse, - sizeof(CK_BBOOL)); - if (ret != CKR_OK) - return ret; + if (ret == CKR_OK) + ret = WP11_Object_SetAttr(secret, CKA_DERIVE, &ckTrue, + sizeof(CK_BBOOL)); + if (ret == CKR_OK) + ret = WP11_Object_SetAttr(secret, CKA_ENCRYPT, &ckFalse, + sizeof(CK_BBOOL)); + if (ret == CKR_OK) + ret = WP11_Object_SetAttr(secret, CKA_DECRYPT, &ckFalse, + sizeof(CK_BBOOL)); + if (ret == CKR_OK) + ret = WP11_Object_SetAttr(secret, CKA_SIGN, &ckTrue, + sizeof(CK_BBOOL)); + if (ret == CKR_OK) + ret = WP11_Object_SetAttr(secret, CKA_VERIFY, &ckTrue, + sizeof(CK_BBOOL)); + if (ret == CKR_OK) + ret = WP11_Object_SetAttr(secret, CKA_WRAP, &ckFalse, + sizeof(CK_BBOOL)); + if (ret == CKR_OK) + ret = WP11_Object_SetAttr(secret, CKA_UNWRAP, &ckFalse, + sizeof(CK_BBOOL)); + if (ret != CKR_OK) { + WP11_Object_Free(secret); + return CKR_FUNCTION_FAILED; + } } + /* Add only once the key is complete so a failure leaves no object. */ + ret = (int)AddObject(session, secret, pTemplate, ulAttributeCount, handle); + if (ret != CKR_OK) + WP11_Object_Free(secret); + return ret; } diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c index f3a5012a..9ed0be6c 100644 --- a/tests/kdf_tls_derive_test.c +++ b/tests/kdf_tls_derive_test.c @@ -44,6 +44,13 @@ #include #endif +#if defined(WOLFSSL_HAVE_PRF) && !defined(WOLFPKCS11_NO_STORE) && \ + !defined(_WIN32) + #define TLS_PERSIST_TEST + #include + #include +#endif + #include "testdata.h" #define TEST_DIR "./store/kdf_tls_derive_test" @@ -1023,6 +1030,138 @@ static int kdf_tls_derive_test(void) return result; } +#ifdef TLS_PERSIST_TEST +static const char persistLabel[] = "tls-key-mat-persist"; + +static CK_RV open_user_session(CK_SESSION_HANDLE* session) +{ + CK_RV ret; + + ret = pkcs11_init(); + if (ret == CKR_OK) { + ret = funcList->C_OpenSession(slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, session); + } + if (ret == CKR_OK) + ret = funcList->C_Login(*session, CKU_USER, userPin, userPinLen); + return ret; +} + +/* Derive TLS key material into token keys whose template disallows signing. */ +static CK_RV derive_token_key_mat(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_TLS12_KEY_MAT_PARAMS params; + CK_SSL3_KEY_MAT_OUT out; + byte master[48]; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_SIGN, &ckFalse, sizeof(ckFalse) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_LABEL, (void*)persistLabel, sizeof(persistLabel) - 1 }, + }; + + XMEMSET(master, 0x5c, sizeof(master)); + ret = create_secret_base(session, CKK_GENERIC_SECRET, master, + sizeof(master), &base); + if (ret == CKR_OK) { + tls_key_mat_init(¶ms, &out, 256, 128, 0); + ret = tls_key_mat_derive(session, base, ¶ms, tmpl, + sizeof(tmpl) / sizeof(*tmpl)); + } + return ret; +} + +/* The MAC key usage attributes are part of the stored token object even when + * the process ends without C_Finalize. */ +static int test_token_mac_key_usage_persist(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE objs[8]; + CK_ULONG found = 0; + CK_ULONG i; + CK_ULONG macKeys = 0; + CK_BBOOL sign; + CK_BBOOL encrypt; + CK_ATTRIBUTE findTmpl[] = { + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_LABEL, (void*)persistLabel, sizeof(persistLabel) - 1 }, + }; + CK_ATTRIBUTE usageTmpl[] = { + { CKA_SIGN, &sign, sizeof(CK_BBOOL) }, + { CKA_ENCRYPT, &encrypt, sizeof(CK_BBOOL) }, + }; + pid_t pid; + int status = 0; + int result = 0; + + printf("\n=== Testing TLS MAC key token persistence ===\n"); + + pid = fork(); + if (pid == 0) { + /* Exit without C_Finalize so only stores made by the derive count. */ + ret = open_user_session(&session); + if (ret == CKR_OK) + ret = derive_token_key_mat(session); + _exit(ret == CKR_OK ? 0 : 1); + } + if (pid < 0) + ret = CKR_GENERAL_ERROR; + else if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) || + WEXITSTATUS(status) != 0) + ret = CKR_GENERAL_ERROR; + else + ret = CKR_OK; + CHECK_CKR(ret, "derive token key material in child process"); + + ret = open_user_session(&session); + CHECK_CKR(ret, "reopen token"); + + ret = funcList->C_FindObjectsInit(session, findTmpl, + sizeof(findTmpl) / sizeof(*findTmpl)); + CHECK_CKR(ret, "C_FindObjectsInit"); + ret = funcList->C_FindObjects(session, objs, + sizeof(objs) / sizeof(*objs), &found); + if (funcList->C_FindObjectsFinal(session) != CKR_OK && ret == CKR_OK) + ret = CKR_GENERAL_ERROR; + if (ret == CKR_OK && found != 4) + ret = CKR_GENERAL_ERROR; + CHECK_CKR(ret, "find derived token keys"); + + for (i = 0; i < found && ret == CKR_OK; i++) { + ret = funcList->C_GetAttributeValue(session, objs[i], usageTmpl, + sizeof(usageTmpl) / sizeof(*usageTmpl)); + if (ret == CKR_OK && sign == CK_TRUE && encrypt == CK_FALSE) + macKeys++; + } + CHECK_CKR(ret, "read derived key usage"); + if (macKeys != 2) { + fprintf(stderr, "FAIL: reloaded MAC keys with MAC usage: %lu, " + "expected 2\n", (unsigned long)macKeys); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: reloaded MAC keys keep MAC usage\n"); + test_passed++; + +cleanup: + if (session != CK_INVALID_HANDLE) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + pkcs11_final(); + return result; +} +#endif /* TLS_PERSIST_TEST */ + static void print_results(void) { printf("\n=== Test Results ===\n"); @@ -1044,6 +1183,10 @@ int main(int argc, char* argv[]) if (kdf_tls_derive_test() != 0 && test_failed == 0) test_failed++; +#ifdef TLS_PERSIST_TEST + if (test_token_mac_key_usage_persist() != 0 && test_failed == 0) + test_failed++; +#endif print_results(); #ifndef DERIVE_TESTS From 1baf270f25399b40802c2c340bba39ff7fbc1eb3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 16:46:36 -0700 Subject: [PATCH 114/204] F-8644 - Require an HKDF salt key handle to be a symmetric secret key --- src/crypto.c | 5 ++- src/internal.c | 9 ++++++ tests/kdf_tls_derive_test.c | 61 +++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index 803be54b..e55a188b 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9795,7 +9795,10 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, ret = WP11_KDF_Derive(session, kdfParams, derivedKey, &keyLen, obj); - if (ret != 0) + /* A parameter check reports a CK_RV, not a wolfCrypt error. */ + if (ret == (int)CKR_MECHANISM_PARAM_INVALID) + rv = CKR_MECHANISM_PARAM_INVALID; + else if (ret != 0) rv = CKR_FUNCTION_FAILED; break; } diff --git a/src/internal.c b/src/internal.c index 91046099..89b15e16 100644 --- a/src/internal.c +++ b/src/internal.c @@ -17817,6 +17817,15 @@ int WP11_KDF_Derive(WP11_Session* session, CK_HKDF_PARAMS_PTR params, ret = WP11_Object_Find(session, params->hSaltKey, &saltKey); if (ret != 0) return CKR_OBJECT_HANDLE_INVALID; + /* Only these secret key types hold their value in symmKey. */ + if (saltKey->objClass != CKO_SECRET_KEY || + (saltKey->type != CKK_GENERIC_SECRET && +#ifndef NO_AES + saltKey->type != CKK_AES && +#endif + saltKey->type != CKK_HKDF) || + saltKey->data.symmKey == NULL) + return CKR_MECHANISM_PARAM_INVALID; salt = saltKey->data.symmKey->data; saltLen = saltKey->data.symmKey->len; break; diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c index 9ed0be6c..e8e4f0ef 100644 --- a/tests/kdf_tls_derive_test.c +++ b/tests/kdf_tls_derive_test.c @@ -862,6 +862,63 @@ static int test_tls_key_states_follow_base(CK_SESSION_HANDLE session) } #endif +#ifdef WOLFPKCS11_HKDF +/* An HKDF salt key handle must refer to a symmetric secret key. */ +static int test_hkdf_salt_key_is_secret(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE salt = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_OBJECT_CLASS dataClass = CKO_DATA; + byte saltValue[16]; + byte dataValue[64]; + CK_ATTRIBUTE dataTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, dataValue, sizeof(dataValue) }, + }; + CK_HKDF_PARAMS params; + CK_MECHANISM mech = { CKM_HKDF_DERIVE, ¶ms, sizeof(params) }; + int result = 0; + + XMEMSET(saltValue, 0x5a, sizeof(saltValue)); + XMEMSET(dataValue, 0xa5, sizeof(dataValue)); + ret = create_secret_base(session, CKK_GENERIC_SECRET, baseSecret, + sizeof(baseSecret), &base); + CHECK_CKR(ret, "create HKDF base key"); + ret = create_secret_base(session, CKK_GENERIC_SECRET, saltValue, + sizeof(saltValue), &salt); + CHECK_CKR(ret, "create HKDF salt key"); + + XMEMSET(¶ms, 0, sizeof(params)); + params.bExtract = CK_TRUE; + params.bExpand = CK_TRUE; + params.prfHashMechanism = CKM_SHA256; + params.ulSaltType = CKF_HKDF_SALT_KEY; + params.hSaltKey = salt; + ret = derive_generic(session, &mech, base, 32, &derived); + CHECK_CKR(ret, "HKDF derive with secret salt key"); + destroy_obj(session, &derived); + destroy_obj(session, &salt); + + ret = funcList->C_CreateObject(session, dataTmpl, + sizeof(dataTmpl) / sizeof(*dataTmpl), + &salt); + CHECK_CKR(ret, "create data object"); + params.hSaltKey = salt; + ret = derive_generic(session, &mech, base, 32, &derived); + CHECK_RV(ret, CKR_MECHANISM_PARAM_INVALID, + "HKDF salt key that is a data object rejected"); + +cleanup: + destroy_obj(session, &derived); + destroy_obj(session, &salt); + destroy_obj(session, &base); + return result; +} +#endif + static CK_RV pkcs11_init(void) { CK_RV ret; @@ -973,6 +1030,10 @@ static int kdf_tls_derive_test(void) result = -1; #endif +#ifdef WOLFPKCS11_HKDF + if (test_hkdf_salt_key_is_secret(session) != 0) + result = -1; +#endif #ifdef NSS_EMS_WIDE_TEST if (test_session_hash_length_fits_word32(session) != 0) result = -1; From b359492c35d4dfed91eb14379a1aea1b64652306 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:32 -0700 Subject: [PATCH 115/204] Pad the DH derived secret to the prime length --- src/crypto.c | 11 +++++++---- tests/kdf_tls_derive_test.c | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index e55a188b..1de0686f 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -9813,7 +9813,13 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, pMechanism->ulParameterLen > WP11_MAX_DH_KEY_SZ) return CKR_MECHANISM_PARAM_INVALID; + /* The secret is bounded by the prime, not by the peer value. */ + if (WP11_Object_GetAttr(obj, CKA_PRIME, NULL, &dhPrimeLen) != 0 || + dhPrimeLen > WP11_MAX_DH_KEY_SZ) + return CKR_FUNCTION_FAILED; keyLen = (word32)pMechanism->ulParameterLen; + if (dhPrimeLen > keyLen) + keyLen = (word32)dhPrimeLen; derivedKey = (byte*)XMALLOC(keyLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (derivedKey == NULL) return CKR_DEVICE_MEMORY; @@ -10052,10 +10058,7 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession, /* DH agreement drops leading zero bytes; restore up to the prime * length on request. */ if ((pMechanism->mechanism == CKM_DH_PKCS_DERIVE) && - (reqValueLen > keyLen) && - (reqValueLen <= pMechanism->ulParameterLen) && - (WP11_Object_GetAttr(obj, CKA_PRIME, NULL, &dhPrimeLen) == 0) && - (reqValueLen <= dhPrimeLen)) { + (reqValueLen > keyLen) && (reqValueLen <= dhPrimeLen)) { XMEMMOVE(derivedKey + (reqValueLen - keyLen), derivedKey, keyLen); XMEMSET(derivedKey, 0, reqValueLen - keyLen); keyLen = (word32)reqValueLen; diff --git a/tests/kdf_tls_derive_test.c b/tests/kdf_tls_derive_test.c index e8e4f0ef..f619085e 100644 --- a/tests/kdf_tls_derive_test.c +++ b/tests/kdf_tls_derive_test.c @@ -661,6 +661,7 @@ static int test_derived_length_matches_template(CK_SESSION_HANDLE session) CK_KEY_TYPE dhType = CKK_DH; byte peer[2 * sizeof(dh_ffdhe2048_p)]; byte value[sizeof(dh_ffdhe2048_p)]; + byte expect[sizeof(dh_ffdhe2048_p)]; CK_ATTRIBUTE valueAttr = { CKA_VALUE, value, sizeof(value) }; CK_ATTRIBUTE dhTmpl[] = { { CKA_CLASS, &privClass, sizeof(privClass) }, @@ -736,6 +737,23 @@ static int test_derived_length_matches_template(CK_SESSION_HANDLE session) CHECK_CKR(ret, "DH derived value keeps its leading zero byte"); destroy_obj(session, &derived); + /* The same peer value encoded without its leading zero bytes. */ + XMEMCPY(expect, value, sizeof(expect)); + mech.pParameter = peer + sizeof(peer) - 2; + mech.ulParameterLen = 2; + ret = derive_generic(session, &mech, base, sizeof(dh_ffdhe2048_p), + &derived); + CHECK_CKR(ret, "DH derive of a prime-sized secret from a short peer value"); + valueAttr.ulValueLen = sizeof(value); + ret = funcList->C_GetAttributeValue(session, derived, &valueAttr, 1); + CHECK_CKR(ret, "read DH derived value from a short peer value"); + if (valueAttr.ulValueLen != sizeof(expect) || + XMEMCMP(value, expect, sizeof(expect)) != 0) { + ret = CKR_GENERAL_ERROR; + } + CHECK_CKR(ret, "DH derived value matches the zero padded peer derive"); + destroy_obj(session, &derived); + mech.pParameter = peer; mech.ulParameterLen = sizeof(peer); ret = derive_generic(session, &mech, base, sizeof(peer), &derived); From 1360a0660eb9b0b05298e33ea3bc7995e26843ad Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 5 Oct 2026 17:14:50 -0700 Subject: [PATCH 116/204] F-11503 - Default companion RSA public key to a session object on unwrap --- src/crypto.c | 2 +- tests/attribute_validation_test.c | 204 ++++++++++++++++++++++++++++++ tests/include.am | 7 + 3 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 tests/attribute_validation_test.c diff --git a/src/crypto.c b/src/crypto.c index 1de0686f..0cf36321 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1411,7 +1411,7 @@ static CK_RV AddRSAPrivateKeyObject(WP11_Session* session, CK_OBJECT_HANDLE hPub; CK_ATTRIBUTE pubt[] = { - {CKA_TOKEN, NULL, sizeof(CK_BBOOL)}, + {CKA_TOKEN, &falseVal, sizeof(CK_BBOOL)}, {CKA_LABEL, NULL, 0}, {CKA_WRAP, &falseVal, sizeof(falseVal)}, {CKA_VERIFY, &trueVal, sizeof(trueVal)}, diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c new file mode 100644 index 00000000..0d59047c --- /dev/null +++ b/tests/attribute_validation_test.c @@ -0,0 +1,204 @@ +/* attribute_validation_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Attribute templates supplied when creating or updating objects are + * validated, and defaulted attributes behave as PKCS#11 specifies. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#define TEST_DIR "./store/attribute_validation_test" + +#if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ + defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ + !defined(NO_RSA) && !defined(NO_AES) && \ + (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA)) +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_OBJECT_CLASS privKeyClass = CKO_PRIVATE_KEY; +static CK_OBJECT_CLASS pubKeyClass = CKO_PUBLIC_KEY; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; +static CK_KEY_TYPE aesKeyType = CKK_AES; +static CK_KEY_TYPE rsaKeyType = CKK_RSA; + +static void destroy_obj(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* obj) +{ + if (*obj != CK_INVALID_HANDLE) { + funcList->C_DestroyObject(session, *obj); + *obj = CK_INVALID_HANDLE; + } +} + +/* An unwrap template that omits CKA_TOKEN creates session objects, including + * the companion RSA public key. */ +static void test_unwrap_rsa_token_default(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_MECHANISM mech = { CKM_AES_KEY_WRAP_PAD, NULL, 0 }; + CK_OBJECT_HANDLE wrappingKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE privKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE unwrapped = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pubKey = CK_INVALID_HANDLE; + byte wrapped[2048]; + CK_ULONG wrappedLen = sizeof(wrapped); + CK_ULONG count = 0; + CK_ATTRIBUTE aesTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_WRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_UNWRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, (void*)aes_cbc_key, sizeof(aes_cbc_key) }, + }; + CK_ATTRIBUTE rsaTmpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, + { CKA_DECRYPT, &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) }, + { 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) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE unwrapTmpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE findTmpl[] = { + { CKA_CLASS, &pubKeyClass, sizeof(pubKeyClass) }, + { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + + rv = funcList->C_CreateObject(session, aesTmpl, + sizeof(aesTmpl) / sizeof(*aesTmpl), + &wrappingKey); + CHECK_RV(rv, "create AES wrapping key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, rsaTmpl, + sizeof(rsaTmpl) / sizeof(*rsaTmpl), + &privKey); + CHECK_RV(rv, "create RSA private key", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_WrapKey(session, &mech, wrappingKey, privKey, + wrapped, &wrappedLen); + CHECK_RV(rv, "wrap RSA private key", CKR_OK); + } + destroy_obj(session, &privKey); + if (rv == CKR_OK) { + rv = funcList->C_UnwrapKey(session, &mech, wrappingKey, wrapped, + wrappedLen, unwrapTmpl, + sizeof(unwrapTmpl) / sizeof(*unwrapTmpl), + &unwrapped); + CHECK_RV(rv, "unwrap RSA private key without CKA_TOKEN", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_FindObjectsInit(session, findTmpl, + sizeof(findTmpl) / sizeof(*findTmpl)); + CHECK_RV(rv, "find companion public key init", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_FindObjects(session, &pubKey, 1, &count); + CHECK_RV(rv, "find companion public key", CKR_OK); + funcList->C_FindObjectsFinal(session); + } + CHECK_TRUE(count == 1, "companion public key is a session object"); + if (count != 1) + pubKey = CK_INVALID_HANDLE; + } + + destroy_obj(session, &pubKey); + destroy_obj(session, &unwrapped); + destroy_obj(session, &wrappingKey); +} +#endif + +static int run_test(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = 0; + + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv != CKR_OK) + return -1; + + rv = pkcs11_open_session(&session); + CHECK_RV(rv, "open session", CKR_OK); + if (rv == CKR_OK) { +#if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ + defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ + !defined(NO_RSA) && !defined(NO_AES) && \ + (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA)) + test_unwrap_rsa_token_default(session); +#endif + } + + if (session != 0) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); + pkcs11_unload(); + return 0; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + + printf("=== wolfPKCS11 attribute validation test ===\n"); + run_test(); + return pkcs11_test_summary(); +} diff --git a/tests/include.am b/tests/include.am index 70d25714..89cde6cf 100644 --- a/tests/include.am +++ b/tests/include.am @@ -350,6 +350,11 @@ noinst_PROGRAMS += tests/info_mech_table_test tests_info_mech_table_test_SOURCES = tests/info_mech_table_test.c tests_info_mech_table_test_LDADD = +check_PROGRAMS += tests/attribute_validation_test +noinst_PROGRAMS += tests/attribute_validation_test +tests_attribute_validation_test_SOURCES = tests/attribute_validation_test.c +tests_attribute_validation_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -421,6 +426,7 @@ tests_object_store_decode_test_LDADD += src/libwolfpkcs11.la tests_object_policy_integrity_test_LDADD += src/libwolfpkcs11.la tests_session_op_lifecycle_test_LDADD += src/libwolfpkcs11.la tests_info_mech_table_test_LDADD += src/libwolfpkcs11.la +tests_attribute_validation_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -484,6 +490,7 @@ tests_object_store_decode_test_LDADD += src/libwolfpkcs11.la tests_object_policy_integrity_test_LDADD += src/libwolfpkcs11.la tests_session_op_lifecycle_test_LDADD += src/libwolfpkcs11.la tests_info_mech_table_test_LDADD += src/libwolfpkcs11.la +tests_attribute_validation_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ From bc94bfce440f128b4b4be47b817e9fbb931e173a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:26:40 -0700 Subject: [PATCH 117/204] F-12460 - Validate CKA_TOKEN as a boolean attribute --- src/crypto.c | 11 ++-- tests/attribute_validation_test.c | 83 +++++++++++++++++++++++++++---- 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 0cf36321..cd7b765c 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -269,7 +269,7 @@ typedef struct AttributeType { /* List of recognized attributes and their data type. */ static AttributeType attrType[] = { { CKA_CLASS, ATTR_TYPE_ULONG }, - { CKA_TOKEN, ATTR_TYPE_DATA }, + { CKA_TOKEN, ATTR_TYPE_BOOL }, { CKA_PRIVATE, ATTR_TYPE_BOOL }, { CKA_LABEL, ATTR_TYPE_DATA }, { CKA_UNIQUE_ID, ATTR_TYPE_DATA }, @@ -538,8 +538,13 @@ static CK_RV CheckAttributes(CK_ATTRIBUTE* pTemplate, CK_ULONG ulCount, int set) if (attr->pValue == NULL && set) return CKR_ATTRIBUTE_VALUE_INVALID; if ((attr->pValue != NULL) && - (attr->ulValueLen != sizeof(CK_BBOOL))) - return CKR_BUFFER_TOO_SMALL; + (attr->ulValueLen != sizeof(CK_BBOOL))) { + /* Object creation reports a malformed CKA_TOKEN as invalid. */ + if (attr->type != CKA_TOKEN) + return CKR_BUFFER_TOO_SMALL; + if (set) + return CKR_ATTRIBUTE_VALUE_INVALID; + } if (set && *(CK_BBOOL*)attr->pValue != CK_TRUE && *(CK_BBOOL*)attr->pValue != CK_FALSE) { return CKR_ATTRIBUTE_VALUE_INVALID; diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index 0d59047c..1d57203b 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -49,17 +49,9 @@ #define TEST_DIR "./store/attribute_validation_test" -#if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ - defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ - !defined(NO_RSA) && !defined(NO_AES) && \ - (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA)) -static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; -static CK_OBJECT_CLASS privKeyClass = CKO_PRIVATE_KEY; -static CK_OBJECT_CLASS pubKeyClass = CKO_PUBLIC_KEY; -static CK_BBOOL ckTrue = CK_TRUE; +static CK_OBJECT_CLASS dataClass = CKO_DATA; static CK_BBOOL ckFalse = CK_FALSE; -static CK_KEY_TYPE aesKeyType = CKK_AES; -static CK_KEY_TYPE rsaKeyType = CKK_RSA; +static const byte dataValue[] = "attribute-validation"; static void destroy_obj(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* obj) { @@ -69,6 +61,76 @@ static void destroy_obj(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* obj) } } +/* Create a public, modifiable session data object. */ +static CK_RV create_data_object(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* obj) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, (void*)dataValue, sizeof(dataValue) - 1 }, + }; + + *obj = CK_INVALID_HANDLE; + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), obj); +} + +/* CKA_TOKEN is validated as a CK_BBOOL like every other boolean attribute. */ +static void test_token_attr_is_bool(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE bad = CK_INVALID_HANDLE; + CK_BBOOL badBool = 5; + byte wide[2] = { 0, 0 }; + CK_ATTRIBUTE nullTmpl[] = { { CKA_TOKEN, NULL, sizeof(CK_BBOOL) } }; + CK_ATTRIBUTE wideTmpl[] = { { CKA_TOKEN, wide, sizeof(wide) } }; + CK_ATTRIBUTE badTmpl[] = { { CKA_TOKEN, &badBool, sizeof(badBool) } }; + CK_ATTRIBUTE createTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &badBool, sizeof(badBool) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, (void*)dataValue, sizeof(dataValue) - 1 }, + }; + + rv = create_data_object(session, &obj); + CHECK_RV(rv, "create data object", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SetAttributeValue(session, obj, nullTmpl, 1); + CHECK_RV(rv, "set CKA_TOKEN with NULL value", CKR_ATTRIBUTE_VALUE_INVALID); + rv = funcList->C_SetAttributeValue(session, obj, wideTmpl, 1); + CHECK_RV(rv, "set CKA_TOKEN with wrong length", + CKR_ATTRIBUTE_VALUE_INVALID); + rv = funcList->C_SetAttributeValue(session, obj, badTmpl, 1); + CHECK_RV(rv, "set CKA_TOKEN with non-boolean value", + CKR_ATTRIBUTE_VALUE_INVALID); + + rv = funcList->C_CreateObject(session, createTmpl, + sizeof(createTmpl) / sizeof(*createTmpl), + &bad); + CHECK_RV(rv, "create object with non-boolean CKA_TOKEN", + CKR_ATTRIBUTE_VALUE_INVALID); + if (rv == CKR_OK) + destroy_obj(session, &bad); + + destroy_obj(session, &obj); +} + +#if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ + defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ + !defined(NO_RSA) && !defined(NO_AES) && \ + (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA)) +static CK_BBOOL ckTrue = CK_TRUE; +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_OBJECT_CLASS privKeyClass = CKO_PRIVATE_KEY; +static CK_OBJECT_CLASS pubKeyClass = CKO_PUBLIC_KEY; +static CK_KEY_TYPE aesKeyType = CKK_AES; +static CK_KEY_TYPE rsaKeyType = CKK_RSA; + /* An unwrap template that omits CKA_TOKEN creates session objects, including * the companion RSA public key. */ static void test_unwrap_rsa_token_default(CK_SESSION_HANDLE session) @@ -174,6 +236,7 @@ static int run_test(void) rv = pkcs11_open_session(&session); CHECK_RV(rv, "open session", CKR_OK); if (rv == CKR_OK) { + test_token_attr_is_bool(session); #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ From a61032e1b242c58f6df7ad8f253bb1bfdba32249 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:33:31 -0700 Subject: [PATCH 118/204] F-5255 - Reject oversized attribute counts in SetAttributeValue --- src/crypto.c | 4 +-- tests/attribute_validation_test.c | 43 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index cd7b765c..c49bcbf0 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -909,7 +909,7 @@ static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, * @param obj [in] Object to set value against. * @param pTemplate [in] Template of attributes set against object. * @param ulCount [in] Number of attribute triplets in template. - * @return CKR_ARGUMENTS_BAD when pTemplate is NULL. + * @return CKR_ARGUMENTS_BAD when pTemplate is NULL or ulCount is too large. * CKR_SESSION_READ_ONLY when the session cannot modify objects. * CKR_ATTRIBUTE_TYPE_INVALID if the attribute type is not supported. * CKR_ATTRIBUTE_VALUE_INVALID if value is not valid for data type. @@ -941,7 +941,7 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, byte roCur[sizeof(CK_ULONG)]; CK_ULONG roCurLen; - if (pTemplate == NULL) + if (pTemplate == NULL || ulCount > (CK_ULONG)INT_MAX) return CKR_ARGUMENTS_BAD; /* Only require R/W session for token objects */ if (!WP11_Session_IsRW(session) && WP11_Object_OnToken(obj)) diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index 1d57203b..c20fff0d 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -26,6 +26,7 @@ #include #endif +#include #include #include @@ -52,6 +53,22 @@ static CK_OBJECT_CLASS dataClass = CKO_DATA; static CK_BBOOL ckFalse = CK_FALSE; static const byte dataValue[] = "attribute-validation"; +static const byte dataLabel[] = "attribute-validation-label"; +static const byte newLabel[] = "replaced"; + +/* Check that the object's CKA_LABEL is exactly the expected bytes. */ +static void expect_label(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + const byte* expect, CK_ULONG expectLen, + const char* name) +{ + CK_RV rv; + byte buf[64]; + CK_ATTRIBUTE attr = { CKA_LABEL, buf, sizeof(buf) }; + + rv = funcList->C_GetAttributeValue(session, obj, &attr, 1); + CHECK_TRUE(rv == CKR_OK && attr.ulValueLen == expectLen && + XMEMCMP(buf, expect, expectLen) == 0, name); +} static void destroy_obj(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* obj) { @@ -69,6 +86,7 @@ static CK_RV create_data_object(CK_SESSION_HANDLE session, { CKA_CLASS, &dataClass, sizeof(dataClass) }, { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_LABEL, (void*)dataLabel, sizeof(dataLabel) - 1 }, { CKA_VALUE, (void*)dataValue, sizeof(dataValue) - 1 }, }; @@ -120,6 +138,30 @@ static void test_token_attr_is_bool(CK_SESSION_HANDLE session) destroy_obj(session, &obj); } +/* Attribute counts beyond what the library can index are rejected rather + * than treated as an empty template. */ +static void test_set_attr_count_range(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ATTRIBUTE setTmpl[] = { + { CKA_LABEL, (void*)newLabel, sizeof(newLabel) - 1 }, + }; + + rv = create_data_object(session, &obj); + CHECK_RV(rv, "create data object", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SetAttributeValue(session, obj, setTmpl, + (CK_ULONG)INT_MAX + 1); + CHECK_RV(rv, "set attributes with oversized count", CKR_ARGUMENTS_BAD); + expect_label(session, obj, dataLabel, sizeof(dataLabel) - 1, + "label unchanged after oversized count"); + + destroy_obj(session, &obj); +} + #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ @@ -237,6 +279,7 @@ static int run_test(void) CHECK_RV(rv, "open session", CKR_OK); if (rv == CKR_OK) { test_token_attr_is_bool(session); + test_set_attr_count_range(session); #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ From e63b524aa78292c85acc2bcaeaa188995b8b510c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:38:55 -0700 Subject: [PATCH 119/204] F-5254 - Reject oversized attribute counts in C_GetAttributeValue --- src/crypto.c | 4 ++-- tests/attribute_validation_test.c | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index c49bcbf0..8b6af6a4 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -2093,7 +2093,7 @@ CK_RV C_GetObjectSize(CK_SESSION_HANDLE hSession, * @param ulCount [in] Number of attribute triplets in template. * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SESSION_HANDLE_INVALID when session handle is not valid. - * CKR_ARGUMENTS_BAD when pTemplate is NULL. + * CKR_ARGUMENTS_BAD when pTemplate is NULL or ulCount is too large. * CKR_OBJECT_HANDLE_INVALID when handle is not to a valid object. * CKR_ATTRIBUTE_TYPE_INVALID if the attribute type is not supported * or not valid for the object. @@ -2133,7 +2133,7 @@ static CK_RV wp11_C_GetAttributeValue(CK_SESSION_HANDLE hSession, WOLFPKCS11_LEAVE("C_GetAttributeValue", rv); return rv; } - if (pTemplate == NULL) { + if (pTemplate == NULL || ulCount > (CK_ULONG)INT_MAX) { rv = CKR_ARGUMENTS_BAD; WOLFPKCS11_LEAVE("C_GetAttributeValue", rv); return rv; diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index c20fff0d..be263c2c 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -162,6 +162,25 @@ static void test_set_attr_count_range(CK_SESSION_HANDLE session) destroy_obj(session, &obj); } +static void test_get_attr_count_range(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + byte buf[64]; + CK_ATTRIBUTE getTmpl[] = { { CKA_LABEL, buf, sizeof(buf) } }; + + rv = create_data_object(session, &obj); + CHECK_RV(rv, "create data object", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_GetAttributeValue(session, obj, getTmpl, + (CK_ULONG)INT_MAX + 1); + CHECK_RV(rv, "get attributes with oversized count", CKR_ARGUMENTS_BAD); + + destroy_obj(session, &obj); +} + #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ @@ -280,6 +299,7 @@ static int run_test(void) if (rv == CKR_OK) { test_token_attr_is_bool(session); test_set_attr_count_range(session); + test_get_attr_count_range(session); #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ From ad0e4c6eb7d25a4b7e510ee3f96c4a434a95f9f1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:43:38 -0700 Subject: [PATCH 120/204] F-5256 - Use a CK_ULONG index in C_FindObjects --- src/crypto.c | 4 ++-- tests/attribute_validation_test.c | 34 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 8b6af6a4..61cee278 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -2403,7 +2403,7 @@ CK_RV C_FindObjects(CK_SESSION_HANDLE hSession, CK_ULONG_PTR pulObjectCount) { CK_RV rv; - int i; + CK_ULONG i; CK_OBJECT_HANDLE handle; WP11_Session* session; @@ -2436,7 +2436,7 @@ CK_RV C_FindObjects(CK_SESSION_HANDLE hSession, return rv; } - for (i = 0; i < (int)ulMaxObjectCount; i++) { + for (i = 0; i < ulMaxObjectCount; i++) { if (WP11_Session_FindGet(session, &handle) == FIND_NO_MORE_E) break; phObject[i] = handle; diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index be263c2c..4f20203c 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -181,6 +181,39 @@ static void test_get_attr_count_range(CK_SESSION_HANDLE session) destroy_obj(session, &obj); } +/* A large result array returns the matching objects rather than none. */ +static void test_find_objects_large_max(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE found = CK_INVALID_HANDLE; + CK_ULONG count = 0; + CK_ATTRIBUTE findTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_LABEL, (void*)dataLabel, sizeof(dataLabel) - 1 }, + }; + + rv = create_data_object(session, &obj); + CHECK_RV(rv, "create data object", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_FindObjectsInit(session, findTmpl, + sizeof(findTmpl) / sizeof(*findTmpl)); + CHECK_RV(rv, "find init", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_FindObjects(session, &found, (CK_ULONG)INT_MAX + 1, + &count); + CHECK_RV(rv, "find with large maximum", CKR_OK); + CHECK_TRUE(count == 1 && found == obj, + "find with large maximum returns the object"); + funcList->C_FindObjectsFinal(session); + } + + destroy_obj(session, &obj); +} + #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ @@ -300,6 +333,7 @@ static int run_test(void) test_token_attr_is_bool(session); test_set_attr_count_range(session); test_get_attr_count_range(session); + test_find_objects_large_max(session); #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ From c2e38bacbdd9476e00cec271b9ba172097a1cff0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:49:29 -0700 Subject: [PATCH 121/204] F-13831 - Reject post-creation changes to copy-only attributes --- src/crypto.c | 42 ++++++++++++++++++-- tests/attribute_validation_test.c | 64 ++++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 61cee278..ada3f0db 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -2200,6 +2200,40 @@ CK_RV C_GetAttributeValue(CK_SESSION_HANDLE hSession, return rv; } +/* PKCS#11 v2.40 sec 4.4: CKA_TOKEN, CKA_PRIVATE and CKA_MODIFIABLE can only be + * changed by C_CopyObject. Restating the current value is allowed. Malformed + * values are left for CheckAttributes to report. */ +static CK_RV CheckCopyOnlyAttributes(WP11_Object* obj, + CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulCount) +{ + CK_RV rv = CKR_OK; + CK_ATTRIBUTE* attr; + CK_ULONG i; + CK_BBOOL cur; + CK_ULONG curLen; + + for (i = 0; rv == CKR_OK && i < ulCount; i++) { + attr = &pTemplate[i]; + if (attr->type != CKA_TOKEN && attr->type != CKA_PRIVATE && + attr->type != CKA_MODIFIABLE) { + continue; + } + if (attr->pValue == NULL || attr->ulValueLen != sizeof(CK_BBOOL) || + (*(CK_BBOOL*)attr->pValue != CK_TRUE && + *(CK_BBOOL*)attr->pValue != CK_FALSE)) { + continue; + } + curLen = sizeof(cur); + if (WP11_Object_GetAttr(obj, attr->type, &cur, &curLen) != 0 || + cur != *(CK_BBOOL*)attr->pValue) { + rv = CKR_ATTRIBUTE_READ_ONLY; + } + } + + return rv; +} + /** * Set the values of the attributes into the object. * @@ -2209,7 +2243,7 @@ CK_RV C_GetAttributeValue(CK_SESSION_HANDLE hSession, * @param ulCount [in] Number of attribute triplets in template. * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SESSION_HANDLE_INVALID when session handle is not valid. - * CKR_ARGUMENTS_BAD when pTemplate is NULL. + * CKR_ARGUMENTS_BAD when pTemplate is NULL or ulCount is too large. * CKR_SESSION_READ_ONLY when the session cannot modify objects. * CKR_OBJECT_HANDLE_INVALID when handle is not to a valid object. * CKR_ATTRIBUTE_TYPE_INVALID if the attribute type is not supported. @@ -2250,7 +2284,7 @@ static CK_RV wp11_C_SetAttributeValue(CK_SESSION_HANDLE hSession, WOLFPKCS11_LEAVE("C_SetAttributeValue", rv); return rv; } - if (pTemplate == NULL) { + if (pTemplate == NULL || ulCount > (CK_ULONG)INT_MAX) { rv = CKR_ARGUMENTS_BAD; WOLFPKCS11_LEAVE("C_SetAttributeValue", rv); return rv; @@ -2280,7 +2314,9 @@ static CK_RV wp11_C_SetAttributeValue(CK_SESSION_HANDLE hSession, return rv; } - rv = SetAttributeValue(session, obj, pTemplate, ulCount, CK_FALSE); + rv = CheckCopyOnlyAttributes(obj, pTemplate, ulCount); + if (rv == CKR_OK) + rv = SetAttributeValue(session, obj, pTemplate, ulCount, CK_FALSE); WOLFPKCS11_LEAVE("C_SetAttributeValue", rv); return rv; } diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index 4f20203c..5c89fcb4 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -51,6 +51,7 @@ #define TEST_DIR "./store/attribute_validation_test" static CK_OBJECT_CLASS dataClass = CKO_DATA; +static CK_BBOOL ckTrue = CK_TRUE; static CK_BBOOL ckFalse = CK_FALSE; static const byte dataValue[] = "attribute-validation"; static const byte dataLabel[] = "attribute-validation-label"; @@ -70,6 +71,19 @@ static void expect_label(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, XMEMCMP(buf, expect, expectLen) == 0, name); } +/* Check that a boolean attribute of the object has the expected value. */ +static void expect_bool(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + CK_ATTRIBUTE_TYPE type, CK_BBOOL expect, + const char* name) +{ + CK_RV rv; + CK_BBOOL val = (CK_BBOOL)!expect; + CK_ATTRIBUTE attr = { type, &val, sizeof(val) }; + + rv = funcList->C_GetAttributeValue(session, obj, &attr, 1); + CHECK_TRUE(rv == CKR_OK && val == expect, name); +} + static void destroy_obj(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* obj) { if (*obj != CK_INVALID_HANDLE) { @@ -214,11 +228,58 @@ static void test_find_objects_large_max(CK_SESSION_HANDLE session) destroy_obj(session, &obj); } +/* CKA_TOKEN, CKA_PRIVATE and CKA_MODIFIABLE only change through + * C_CopyObject; C_SetAttributeValue may only restate the current value. */ +static void test_copy_only_attrs_read_only(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE copy = CK_INVALID_HANDLE; + CK_ATTRIBUTE tokenTmpl[] = { { CKA_TOKEN, &ckTrue, 1 } }; + CK_ATTRIBUTE privTmpl[] = { { CKA_PRIVATE, &ckTrue, 1 } }; + CK_ATTRIBUTE modTmpl[] = { { CKA_MODIFIABLE, &ckFalse, 1 } }; + CK_ATTRIBUTE sameTmpl[] = { + { CKA_TOKEN, &ckFalse, 1 }, + { CKA_PRIVATE, &ckFalse, 1 }, + { CKA_MODIFIABLE, &ckTrue, 1 }, + }; + + rv = create_data_object(session, &obj); + CHECK_RV(rv, "create data object", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SetAttributeValue(session, obj, tokenTmpl, 1); + CHECK_RV(rv, "set CKA_TOKEN after creation", CKR_ATTRIBUTE_READ_ONLY); + rv = funcList->C_SetAttributeValue(session, obj, privTmpl, 1); + CHECK_RV(rv, "set CKA_PRIVATE after creation", CKR_ATTRIBUTE_READ_ONLY); + rv = funcList->C_SetAttributeValue(session, obj, modTmpl, 1); + CHECK_RV(rv, "set CKA_MODIFIABLE after creation", + CKR_ATTRIBUTE_READ_ONLY); + expect_bool(session, obj, CKA_TOKEN, CK_FALSE, "CKA_TOKEN unchanged"); + expect_bool(session, obj, CKA_PRIVATE, CK_FALSE, "CKA_PRIVATE unchanged"); + expect_bool(session, obj, CKA_MODIFIABLE, CK_TRUE, + "CKA_MODIFIABLE unchanged"); + + rv = funcList->C_SetAttributeValue(session, obj, sameTmpl, + sizeof(sameTmpl) / sizeof(*sameTmpl)); + CHECK_RV(rv, "restate current CKA_TOKEN/PRIVATE/MODIFIABLE", CKR_OK); + + rv = funcList->C_CopyObject(session, obj, modTmpl, 1, ©); + CHECK_RV(rv, "copy with CKA_MODIFIABLE changed", CKR_OK); + if (rv == CKR_OK) { + expect_bool(session, copy, CKA_MODIFIABLE, CK_FALSE, + "copy has CKA_MODIFIABLE false"); + } + + destroy_obj(session, ©); + destroy_obj(session, &obj); +} + #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA)) -static CK_BBOOL ckTrue = CK_TRUE; static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; static CK_OBJECT_CLASS privKeyClass = CKO_PRIVATE_KEY; static CK_OBJECT_CLASS pubKeyClass = CKO_PUBLIC_KEY; @@ -334,6 +395,7 @@ static int run_test(void) test_set_attr_count_range(session); test_get_attr_count_range(session); test_find_objects_large_max(session); + test_copy_only_attrs_read_only(session); #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ From 3a06f230f929bb31d70baa5a88380ed30209bba2 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:58:00 -0700 Subject: [PATCH 122/204] F-11511 - Reject derived key state attributes in creation templates --- src/crypto.c | 5 +++ tests/attribute_validation_test.c | 52 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index ada3f0db..42f9fa1f 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1202,6 +1202,11 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, WP11_Session_GetState(session) != WP11_APP_STATE_RW_SO) { return CKR_ATTRIBUTE_READ_ONLY; } + /* Derived by the token; never accepted in a creation template. */ + if (newObject && (attr->type == CKA_ALWAYS_SENSITIVE || + attr->type == CKA_NEVER_EXTRACTABLE)) { + return CKR_ATTRIBUTE_READ_ONLY; + } /* These class/identity and generated-state attributes are read-only * once the object exists; reject a change. Setting the current value * is a no-op. */ diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index 5c89fcb4..de17623d 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -276,6 +276,55 @@ static void test_copy_only_attrs_read_only(CK_SESSION_HANDLE session) destroy_obj(session, &obj); } +#ifndef NO_AES +/* CKA_ALWAYS_SENSITIVE and CKA_NEVER_EXTRACTABLE are derived by the token and + * cannot be supplied when a key is created or generated. */ +static void test_derived_attrs_not_settable_at_create(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_AES; + CK_ULONG keyLen = 16; + CK_MECHANISM mech = { CKM_AES_KEY_GEN, NULL, 0 }; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + byte keyData[16]; + CK_ATTRIBUTE createTmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_ALWAYS_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + }; + CK_ULONG createCnt = sizeof(createTmpl) / sizeof(*createTmpl); + CK_ATTRIBUTE genTmpl[] = { + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE_LEN, &keyLen, sizeof(keyLen) }, + { CKA_NEVER_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + }; + CK_ULONG genCnt = sizeof(genTmpl) / sizeof(*genTmpl); + + XMEMSET(keyData, 0x5a, sizeof(keyData)); + + rv = funcList->C_CreateObject(session, createTmpl, createCnt, &key); + CHECK_RV(rv, "create key with CKA_ALWAYS_SENSITIVE", + CKR_ATTRIBUTE_READ_ONLY); + destroy_obj(session, &key); + + createTmpl[createCnt - 1].type = CKA_NEVER_EXTRACTABLE; + rv = funcList->C_CreateObject(session, createTmpl, createCnt, &key); + CHECK_RV(rv, "create key with CKA_NEVER_EXTRACTABLE", + CKR_ATTRIBUTE_READ_ONLY); + destroy_obj(session, &key); + + rv = funcList->C_GenerateKey(session, &mech, genTmpl, genCnt, &key); + CHECK_RV(rv, "generate key with CKA_NEVER_EXTRACTABLE", + CKR_ATTRIBUTE_READ_ONLY); + destroy_obj(session, &key); +} +#endif + #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ @@ -396,6 +445,9 @@ static int run_test(void) test_get_attr_count_range(session); test_find_objects_large_max(session); test_copy_only_attrs_read_only(session); +#ifndef NO_AES + test_derived_attrs_not_settable_at_create(session); +#endif #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ From 4df1f3ca62c125d61488713b02f25d9243437635 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:06:38 -0700 Subject: [PATCH 123/204] F-8670 - Reject a conflicting CKA_CLASS during object creation --- src/crypto.c | 5 +++++ tests/attribute_validation_test.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index 42f9fa1f..3225149a 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1202,6 +1202,11 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, WP11_Session_GetState(session) != WP11_APP_STATE_RW_SO) { return CKR_ATTRIBUTE_READ_ONLY; } + /* The type-specific storage was allocated for the creation class. */ + if (newObject && attr->type == CKA_CLASS && + *(CK_OBJECT_CLASS*)attr->pValue != objClass) { + return CKR_TEMPLATE_INCONSISTENT; + } /* Derived by the token; never accepted in a creation template. */ if (newObject && (attr->type == CKA_ALWAYS_SENSITIVE || attr->type == CKA_NEVER_EXTRACTABLE)) { diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index de17623d..43fa4d45 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -325,6 +325,34 @@ static void test_derived_attrs_not_settable_at_create(CK_SESSION_HANDLE session) } #endif +#ifndef NO_AES +/* A creation template must name one object class; a conflicting duplicate is + * rejected rather than overriding the class the object was built for. */ +static void test_create_class_consistent(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_AES; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + byte keyData[16]; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + }; + + XMEMSET(keyData, 0x3c, sizeof(keyData)); + rv = funcList->C_CreateObject(session, tmpl, sizeof(tmpl) / sizeof(*tmpl), + &key); + CHECK_RV(rv, "create key with conflicting CKA_CLASS", + CKR_TEMPLATE_INCONSISTENT); + destroy_obj(session, &key); +} +#endif + #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ @@ -447,6 +475,7 @@ static int run_test(void) test_copy_only_attrs_read_only(session); #ifndef NO_AES test_derived_attrs_not_settable_at_create(session); + test_create_class_consistent(session); #endif #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ From b5cb99dfb8b1fa113f951839278de43481da3101 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:21:37 -0700 Subject: [PATCH 124/204] F-13242 - Validate the whole template before applying attribute updates --- src/crypto.c | 200 +++++++++++++++++------------- tests/attribute_validation_test.c | 69 +++++++++++ 2 files changed, 182 insertions(+), 87 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 3225149a..37aefb09 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -902,6 +902,107 @@ static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, return ret; } +/* Policy checks for one template attribute against the object's current + * state. Run over the whole template before any change, and again just before + * each write so a concurrent update is not undone using a stale result. */ +static CK_RV CheckAttributeUpdate(WP11_Session* session, WP11_Object* obj, + CK_ATTRIBUTE* attr, CK_OBJECT_CLASS objClass, + CK_BBOOL newObject) +{ + CK_RV rv; + CK_BBOOL getVar; + CK_ULONG getVarLen; + byte roCur[sizeof(CK_ULONG)]; + CK_ULONG roCurLen; + int ret; + + /* Cannot change sensitive from true to false */ + if (attr->type == CKA_SENSITIVE) { + getVarLen = sizeof(getVar); + rv = WP11_Object_GetAttr(obj, CKA_SENSITIVE, &getVar, &getVarLen); + if (rv != CKR_OK) + return rv; + + if ((getVar == CK_TRUE) && (*(CK_BBOOL*)attr->pValue == CK_FALSE)) + return CKR_ATTRIBUTE_READ_ONLY; + } + /* Cannot change extractable from false to true */ + if (!newObject && attr->type == CKA_EXTRACTABLE) { + getVarLen = sizeof(getVar); + rv = WP11_Object_GetAttr(obj, CKA_EXTRACTABLE, &getVar, + &getVarLen); + if (rv != CKR_OK) + return rv; + + if ((getVar == CK_FALSE) && (*(CK_BBOOL*)attr->pValue == CK_TRUE)) + return CKR_ATTRIBUTE_READ_ONLY; + } + /* PKCS#11 v2.40 sec 4.4.1: once CKA_COPYABLE/CKA_DESTROYABLE has been + * set to CK_FALSE it cannot be set back to CK_TRUE. Read the stored + * flag bit directly so the check is independent of the GetAttr view + * (which the legacy macro may override). */ + if (!newObject && attr->type == CKA_COPYABLE) { + if (attr->pValue == NULL || + attr->ulValueLen != sizeof(CK_BBOOL)) + return CKR_ATTRIBUTE_VALUE_INVALID; + if (!WP11_Object_IsCopyable(obj) && + *(CK_BBOOL*)attr->pValue == CK_TRUE) + return CKR_ATTRIBUTE_READ_ONLY; + } + if (!newObject && attr->type == CKA_DESTROYABLE) { + if (attr->pValue == NULL || + attr->ulValueLen != sizeof(CK_BBOOL)) + return CKR_ATTRIBUTE_VALUE_INVALID; + if (!WP11_Object_IsDestroyable(obj) && + *(CK_BBOOL*)attr->pValue == CK_TRUE) + return CKR_ATTRIBUTE_READ_ONLY; + } + /* PKCS#11 v2.40 sec 4.5: only an SO session may set CKA_TRUSTED to + * CK_TRUE. A regular-user session must not forge trust and bypass the + * CKA_WRAP_WITH_TRUSTED export gate enforced by C_WrapKey. Not + * qualified with !newObject so it also stops C_CreateObject / + * C_GenerateKey from minting a trusted key. CheckAttributes above has + * already validated CKA_TRUSTED as a well-formed CK_BBOOL. */ + if (attr->type == CKA_TRUSTED && + *(CK_BBOOL*)attr->pValue == CK_TRUE && + WP11_Session_GetState(session) != WP11_APP_STATE_RW_SO) { + return CKR_ATTRIBUTE_READ_ONLY; + } + /* The type-specific storage was allocated for the creation class. */ + if (newObject && attr->type == CKA_CLASS && + *(CK_OBJECT_CLASS*)attr->pValue != objClass) { + return CKR_TEMPLATE_INCONSISTENT; + } + /* Derived by the token; never accepted in a creation template. */ + if (newObject && (attr->type == CKA_ALWAYS_SENSITIVE || + attr->type == CKA_NEVER_EXTRACTABLE)) { + return CKR_ATTRIBUTE_READ_ONLY; + } + /* These class/identity and generated-state attributes are read-only + * once the object exists; reject a change. Setting the current value + * is a no-op. */ + if (!newObject && (attr->type == CKA_CLASS || + attr->type == CKA_KEY_TYPE || + attr->type == CKA_LOCAL || + attr->type == CKA_KEY_GEN_MECHANISM || + attr->type == CKA_ALWAYS_SENSITIVE || + attr->type == CKA_NEVER_EXTRACTABLE)) { + roCurLen = sizeof(roCur); + ret = WP11_Object_GetAttr(obj, attr->type, roCur, &roCurLen); + /* Not an attribute of this object, e.g. CKA_KEY_TYPE on a + * certificate. */ + if (ret == CKR_ATTRIBUTE_TYPE_INVALID) + return CKR_ATTRIBUTE_TYPE_INVALID; + if (ret == 0 && + (attr->pValue == NULL || attr->ulValueLen != roCurLen || + XMEMCMP(attr->pValue, roCur, roCurLen) != 0)) { + return CKR_ATTRIBUTE_READ_ONLY; + } + } + + return CKR_OK; +} + /** * Set the values of the attributes into the object. * @@ -936,10 +1037,6 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, CK_BBOOL attrsFound = 0; CK_KEY_TYPE type; CK_OBJECT_CLASS objClass; - CK_BBOOL getVar; - CK_ULONG getVarLen = 1; - byte roCur[sizeof(CK_ULONG)]; - CK_ULONG roCurLen; if (pTemplate == NULL || ulCount > (CK_ULONG)INT_MAX) return CKR_ARGUMENTS_BAD; @@ -1044,6 +1141,15 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, } } + /* Validate the whole template before any setter runs so a rejected + * update leaves the object unchanged. */ + for (i = 0; i < (int)ulCount; i++) { + rv = CheckAttributeUpdate(session, obj, &pTemplate[i], objClass, + newObject); + if (rv != CKR_OK) + return rv; + } + for (i = 0; i < cnt; i++) { for (j = 0; j < (int)ulCount; j++) { if (attrs[i] == pTemplate[j].type) { @@ -1150,89 +1256,9 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, /* Set remaining attributes - key specific attributes ignored. */ for (i = 0; i < (int)ulCount; i++) { attr = &pTemplate[i]; - - /* Cannot change sensitive from true to false */ - if (attr->type == CKA_SENSITIVE) { - rv = WP11_Object_GetAttr(obj, CKA_SENSITIVE, &getVar, &getVarLen); - if (rv != CKR_OK) - return rv; - - if ((getVar == CK_TRUE) && (*(CK_BBOOL*)attr->pValue == CK_FALSE)) - return CKR_ATTRIBUTE_READ_ONLY; - } - /* Cannot change extractable from false to true */ - if (!newObject && attr->type == CKA_EXTRACTABLE) { - getVarLen = sizeof(getVar); - rv = WP11_Object_GetAttr(obj, CKA_EXTRACTABLE, &getVar, - &getVarLen); - if (rv != CKR_OK) - return rv; - - if ((getVar == CK_FALSE) && (*(CK_BBOOL*)attr->pValue == CK_TRUE)) - return CKR_ATTRIBUTE_READ_ONLY; - } - /* PKCS#11 v2.40 sec 4.4.1: once CKA_COPYABLE/CKA_DESTROYABLE has been - * set to CK_FALSE it cannot be set back to CK_TRUE. Read the stored - * flag bit directly so the check is independent of the GetAttr view - * (which the legacy macro may override). */ - if (!newObject && attr->type == CKA_COPYABLE) { - if (attr->pValue == NULL || - attr->ulValueLen != sizeof(CK_BBOOL)) - return CKR_ATTRIBUTE_VALUE_INVALID; - if (!WP11_Object_IsCopyable(obj) && - *(CK_BBOOL*)attr->pValue == CK_TRUE) - return CKR_ATTRIBUTE_READ_ONLY; - } - if (!newObject && attr->type == CKA_DESTROYABLE) { - if (attr->pValue == NULL || - attr->ulValueLen != sizeof(CK_BBOOL)) - return CKR_ATTRIBUTE_VALUE_INVALID; - if (!WP11_Object_IsDestroyable(obj) && - *(CK_BBOOL*)attr->pValue == CK_TRUE) - return CKR_ATTRIBUTE_READ_ONLY; - } - /* PKCS#11 v2.40 sec 4.5: only an SO session may set CKA_TRUSTED to - * CK_TRUE. A regular-user session must not forge trust and bypass the - * CKA_WRAP_WITH_TRUSTED export gate enforced by C_WrapKey. Not - * qualified with !newObject so it also stops C_CreateObject / - * C_GenerateKey from minting a trusted key. CheckAttributes above has - * already validated CKA_TRUSTED as a well-formed CK_BBOOL. */ - if (attr->type == CKA_TRUSTED && - *(CK_BBOOL*)attr->pValue == CK_TRUE && - WP11_Session_GetState(session) != WP11_APP_STATE_RW_SO) { - return CKR_ATTRIBUTE_READ_ONLY; - } - /* The type-specific storage was allocated for the creation class. */ - if (newObject && attr->type == CKA_CLASS && - *(CK_OBJECT_CLASS*)attr->pValue != objClass) { - return CKR_TEMPLATE_INCONSISTENT; - } - /* Derived by the token; never accepted in a creation template. */ - if (newObject && (attr->type == CKA_ALWAYS_SENSITIVE || - attr->type == CKA_NEVER_EXTRACTABLE)) { - return CKR_ATTRIBUTE_READ_ONLY; - } - /* These class/identity and generated-state attributes are read-only - * once the object exists; reject a change. Setting the current value - * is a no-op. */ - if (!newObject && (attr->type == CKA_CLASS || - attr->type == CKA_KEY_TYPE || - attr->type == CKA_LOCAL || - attr->type == CKA_KEY_GEN_MECHANISM || - attr->type == CKA_ALWAYS_SENSITIVE || - attr->type == CKA_NEVER_EXTRACTABLE)) { - roCurLen = sizeof(roCur); - ret = WP11_Object_GetAttr(obj, attr->type, roCur, &roCurLen); - /* Not an attribute of this object, e.g. CKA_KEY_TYPE on a - * certificate. */ - if (ret == CKR_ATTRIBUTE_TYPE_INVALID) - return CKR_ATTRIBUTE_TYPE_INVALID; - if (ret == 0 && - (attr->pValue == NULL || attr->ulValueLen != roCurLen || - XMEMCMP(attr->pValue, roCur, roCurLen) != 0)) { - return CKR_ATTRIBUTE_READ_ONLY; - } - } + rv = CheckAttributeUpdate(session, obj, attr, objClass, newObject); + if (rv != CKR_OK) + return rv; ret = WP11_Object_SetAttr(obj, attr->type, (byte*)attr->pValue, attr->ulValueLen); if (ret == MEMORY_E) diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index 43fa4d45..2ae453c4 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -353,6 +353,74 @@ static void test_create_class_consistent(CK_SESSION_HANDLE session) } #endif +#ifndef NO_AES +/* A rejected multi-attribute update leaves every attribute unchanged. */ +static void test_rejected_update_is_atomic(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_AES; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + byte keyData[16]; + byte valueBuf[64]; + CK_ATTRIBUTE valueAttr = { CKA_VALUE, valueBuf, sizeof(valueBuf) }; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_LABEL, (void*)dataLabel, sizeof(dataLabel) - 1 }, + { CKA_VALUE, keyData, sizeof(keyData) }, + }; + CK_ATTRIBUTE labelAndClass[] = { + { CKA_LABEL, (void*)newLabel, sizeof(newLabel) - 1 }, + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + }; + CK_ATTRIBUTE labelAndSensitive[] = { + { CKA_LABEL, (void*)newLabel, sizeof(newLabel) - 1 }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE valueAndClass[] = { + { CKA_VALUE, (void*)newLabel, sizeof(newLabel) - 1 }, + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + }; + + XMEMSET(keyData, 0x7e, sizeof(keyData)); + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &key); + CHECK_RV(rv, "create labelled secret key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, key, labelAndClass, 2); + CHECK_RV(rv, "set label with read-only class", CKR_ATTRIBUTE_READ_ONLY); + expect_label(session, key, dataLabel, sizeof(dataLabel) - 1, + "label unchanged after rejected class update"); + + rv = funcList->C_SetAttributeValue(session, key, labelAndSensitive, 2); + CHECK_RV(rv, "set label with sensitive cleared", + CKR_ATTRIBUTE_READ_ONLY); + expect_label(session, key, dataLabel, sizeof(dataLabel) - 1, + "label unchanged after rejected sensitive update"); + } + + rv = create_data_object(session, &obj); + CHECK_RV(rv, "create data object", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, obj, valueAndClass, 2); + CHECK_RV(rv, "set value with read-only class", CKR_ATTRIBUTE_READ_ONLY); + rv = funcList->C_GetAttributeValue(session, obj, &valueAttr, 1); + CHECK_TRUE(rv == CKR_OK && + valueAttr.ulValueLen == sizeof(dataValue) - 1 && + XMEMCMP(valueBuf, dataValue, sizeof(dataValue) - 1) == 0, + "value unchanged after rejected class update"); + } + + destroy_obj(session, &obj); + destroy_obj(session, &key); +} +#endif + #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ @@ -476,6 +544,7 @@ static int run_test(void) #ifndef NO_AES test_derived_attrs_not_settable_at_create(session); test_create_class_consistent(session); + test_rejected_update_is_atomic(session); #endif #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ From 40d6822cfb04074fea544b3e0bcabcc6f798390c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:29:21 -0700 Subject: [PATCH 125/204] F-4268 - Reject attribute lengths that do not fit the object setters --- src/crypto.c | 5 +- tests/attribute_validation_test.c | 80 +++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index 37aefb09..65da55bd 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -560,6 +560,9 @@ static CK_RV CheckAttributes(CK_ATTRIBUTE* pTemplate, CK_ULONG ulCount, int set) else if (attrType[j].type == ATTR_TYPE_DATA) { if (set && attr->ulValueLen == CK_UNAVAILABLE_INFORMATION) return CKR_ATTRIBUTE_VALUE_INVALID; + /* Object setters take int lengths. */ + if (set && attr->ulValueLen > (CK_ULONG)INT_MAX) + return CKR_ATTRIBUTE_VALUE_INVALID; } } @@ -1161,7 +1164,7 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, if (objClass != CKO_DATA) return CKR_ATTRIBUTE_VALUE_INVALID; } - len[i] = (int)pTemplate[j].ulValueLen; + len[i] = pTemplate[j].ulValueLen; break; } } diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index 2ae453c4..648f5669 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -421,6 +421,85 @@ static void test_rejected_update_is_atomic(CK_SESSION_HANDLE session) } #endif +/* Attribute lengths beyond what the library stores are rejected before any + * value is read, for both generic and key component attributes. */ +static void test_attr_length_range(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_ULONG wrapLen = 0; + CK_ATTRIBUTE labelTmpl[] = { + { CKA_LABEL, (void*)newLabel, (CK_ULONG)INT_MAX + 1 }, + }; +#ifndef NO_RSA + CK_OBJECT_CLASS rsaClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE rsaType = CKK_RSA; + CK_ATTRIBUTE rsaTmpl[] = { + { CKA_CLASS, &rsaClass, sizeof(rsaClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { 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) }, + }; +#endif +#ifndef NO_DH + CK_OBJECT_CLASS dhClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE dhType = CKK_DH; + CK_ATTRIBUTE dhTmpl[] = { + { CKA_CLASS, &dhClass, sizeof(dhClass) }, + { CKA_KEY_TYPE, &dhType, sizeof(dhType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { 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_pub, sizeof(dh_2048_pub) }, + }; +#endif + + rv = create_data_object(session, &obj); + CHECK_RV(rv, "create data object", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, obj, labelTmpl, 1); + CHECK_RV(rv, "set label with oversized length", + CKR_ATTRIBUTE_VALUE_INVALID); + expect_label(session, obj, dataLabel, sizeof(dataLabel) - 1, + "label unchanged after oversized length"); + } + destroy_obj(session, &obj); + + /* Only a 64-bit CK_ULONG can carry a length whose low 32 bits look + * valid. */ + if (sizeof(CK_ULONG) <= 4) + return; + wrapLen = (CK_ULONG)1 << 16 << 16; +#ifndef NO_RSA + rsaTmpl[6].ulValueLen += wrapLen; + rv = funcList->C_CreateObject(session, rsaTmpl, + sizeof(rsaTmpl) / sizeof(*rsaTmpl), &key); + CHECK_RV(rv, "create RSA key with oversized prime length", + CKR_ATTRIBUTE_VALUE_INVALID); + destroy_obj(session, &key); +#endif +#ifndef NO_DH + dhTmpl[4].ulValueLen += wrapLen; + rv = funcList->C_CreateObject(session, dhTmpl, + sizeof(dhTmpl) / sizeof(*dhTmpl), &key); + CHECK_RV(rv, "create DH key with oversized prime length", + CKR_ATTRIBUTE_VALUE_INVALID); + destroy_obj(session, &key); +#endif + (void)wrapLen; + (void)key; +} + #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ @@ -546,6 +625,7 @@ static int run_test(void) test_create_class_consistent(session); test_rejected_update_is_atomic(session); #endif + test_attr_length_range(session); #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ From d0cef09a6a3d8ec0afdf2d1c3841bec63d04a895 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:37:28 -0700 Subject: [PATCH 126/204] F-2773 - Reject CKA_ALWAYS_AUTHENTICATE since it cannot be enforced --- src/crypto.c | 6 ++++ tests/attribute_validation_test.c | 47 +++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index 65da55bd..e6bf8208 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -971,6 +971,12 @@ static CK_RV CheckAttributeUpdate(WP11_Session* session, WP11_Object* obj, WP11_Session_GetState(session) != WP11_APP_STATE_RW_SO) { return CKR_ATTRIBUTE_READ_ONLY; } + /* Context-specific login is not supported, so per-use authentication + * could not be enforced. */ + if (attr->type == CKA_ALWAYS_AUTHENTICATE && + *(CK_BBOOL*)attr->pValue == CK_TRUE) { + return CKR_ATTRIBUTE_VALUE_INVALID; + } /* The type-specific storage was allocated for the creation class. */ if (newObject && attr->type == CKA_CLASS && *(CK_OBJECT_CLASS*)attr->pValue != objClass) { diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index 648f5669..290bda61 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -500,6 +500,50 @@ static void test_attr_length_range(CK_SESSION_HANDLE session) (void)key; } +#ifndef NO_AES +/* Keys cannot be marked as requiring per-use authentication, which the token + * does not enforce. */ +static void test_always_authenticate_rejected(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_AES; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + byte keyData[16]; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_ALWAYS_AUTHENTICATE, &ckTrue, sizeof(ckTrue) }, + }; + CK_ULONG keyCnt = sizeof(keyTmpl) / sizeof(*keyTmpl); + CK_ATTRIBUTE setTrue[] = { { CKA_ALWAYS_AUTHENTICATE, &ckTrue, 1 } }; + CK_ATTRIBUTE setFalse[] = { { CKA_ALWAYS_AUTHENTICATE, &ckFalse, 1 } }; + + XMEMSET(keyData, 0x42, sizeof(keyData)); + rv = funcList->C_CreateObject(session, keyTmpl, keyCnt, &key); + CHECK_RV(rv, "create key requiring per-use authentication", + CKR_ATTRIBUTE_VALUE_INVALID); + destroy_obj(session, &key); + + /* Create without the attribute, then try to turn it on. */ + rv = funcList->C_CreateObject(session, keyTmpl, keyCnt - 1, &key); + CHECK_RV(rv, "create key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, key, setTrue, 1); + CHECK_RV(rv, "set CKA_ALWAYS_AUTHENTICATE true", + CKR_ATTRIBUTE_VALUE_INVALID); + expect_bool(session, key, CKA_ALWAYS_AUTHENTICATE, CK_FALSE, + "CKA_ALWAYS_AUTHENTICATE remains false"); + rv = funcList->C_SetAttributeValue(session, key, setFalse, 1); + CHECK_RV(rv, "set CKA_ALWAYS_AUTHENTICATE false", CKR_OK); + } + destroy_obj(session, &key); +} +#endif + #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ @@ -626,6 +670,9 @@ static int run_test(void) test_rejected_update_is_atomic(session); #endif test_attr_length_range(session); +#ifndef NO_AES + test_always_authenticate_rejected(session); +#endif #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ From 370add100c8902ed6492d2ccb3646e0e09a2afe9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:45:21 -0700 Subject: [PATCH 127/204] F-11517 - Reject NULL values with a non-zero length for data attributes --- src/crypto.c | 2 ++ src/internal.c | 12 +++++++++-- tests/attribute_validation_test.c | 35 +++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index e6bf8208..71a5c2f3 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -560,6 +560,8 @@ static CK_RV CheckAttributes(CK_ATTRIBUTE* pTemplate, CK_ULONG ulCount, int set) else if (attrType[j].type == ATTR_TYPE_DATA) { if (set && attr->ulValueLen == CK_UNAVAILABLE_INFORMATION) return CKR_ATTRIBUTE_VALUE_INVALID; + if (set && attr->pValue == NULL && attr->ulValueLen != 0) + return CKR_ATTRIBUTE_VALUE_INVALID; /* Object setters take int lengths. */ if (set && attr->ulValueLen > (CK_ULONG)INT_MAX) return CKR_ATTRIBUTE_VALUE_INVALID; diff --git a/src/internal.c b/src/internal.c index 89b15e16..4fcf5447 100644 --- a/src/internal.c +++ b/src/internal.c @@ -15310,7 +15310,8 @@ static void WP11_Object_SetOpFlag(WP11_Object* object, word32 flag, int set) * @param object [in] Object object. * @param keyId [in] Key identifier data. * @param keyIdLen [in] Length of key identifier in bytes. - * @return MEMORY_E when dynamic memory allocation fails. + * @return BAD_FUNC_ARG when keyId is NULL and keyIdLen is not zero. + * MEMORY_E when dynamic memory allocation fails. * 0 on success. */ static int WP11_Object_SetKeyId(WP11_Object* object, unsigned char* keyId, @@ -15318,6 +15319,9 @@ static int WP11_Object_SetKeyId(WP11_Object* object, unsigned char* keyId, { int ret = 0; + if (keyId == NULL && keyIdLen > 0) + return BAD_FUNC_ARG; + XFREE(object->keyId, NULL, DYNAMIC_TYPE_TMP_BUFFER); object->keyId = NULL; object->keyIdLen = 0; @@ -15342,7 +15346,8 @@ static int WP11_Object_SetKeyId(WP11_Object* object, unsigned char* keyId, * @param attributeLen [out] Length of attribute in bytes. * @param data [in] Data to be set. * @param dataLen [in] Length of data in bytes. - * @return MEMORY_E when dynamic memory allocation fails. + * @return BAD_FUNC_ARG when data is NULL and dataLen is not zero. + * MEMORY_E when dynamic memory allocation fails. * 0 on success. */ static int WP11_Object_SetData(byte** attribute, int* attributeLen, byte* data, @@ -15350,6 +15355,9 @@ static int WP11_Object_SetData(byte** attribute, int* attributeLen, byte* data, { int ret = 0; + if (data == NULL && dataLen > 0) + return BAD_FUNC_ARG; + XFREE(*attribute, NULL, DYNAMIC_TYPE_TMP_BUFFER); *attribute = NULL; *attributeLen = 0; diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index 290bda61..c265b718 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -544,6 +544,40 @@ static void test_always_authenticate_rejected(CK_SESSION_HANDLE session) } #endif +/* A byte-string attribute with a length must come with a value. */ +static void test_data_attr_requires_value(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE bad = CK_INVALID_HANDLE; + CK_ATTRIBUTE labelTmpl[] = { { CKA_LABEL, NULL, 5 } }; + CK_ATTRIBUTE createTmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, (void*)dataValue, sizeof(dataValue) - 1 }, + { CKA_ID, NULL, 4 }, + }; + + rv = create_data_object(session, &obj); + CHECK_RV(rv, "create data object", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, obj, labelTmpl, 1); + CHECK_RV(rv, "set label with length and no value", + CKR_ATTRIBUTE_VALUE_INVALID); + expect_label(session, obj, dataLabel, sizeof(dataLabel) - 1, + "label unchanged after missing value"); + } + destroy_obj(session, &obj); + + rv = funcList->C_CreateObject(session, createTmpl, + sizeof(createTmpl) / sizeof(*createTmpl), + &bad); + CHECK_RV(rv, "create object with CKA_ID length and no value", + CKR_ATTRIBUTE_VALUE_INVALID); + destroy_obj(session, &bad); +} + #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ @@ -673,6 +707,7 @@ static int run_test(void) #ifndef NO_AES test_always_authenticate_rejected(session); #endif + test_data_attr_requires_value(session); #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ From 342dd952b8565e4698cd5a9794570abadcb78087 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 10:54:29 -0700 Subject: [PATCH 128/204] F-6234 - Accept an empty CKA_START_DATE or CKA_END_DATE to clear the date --- src/crypto.c | 3 ++ src/internal.c | 16 ++++++---- tests/attribute_validation_test.c | 51 +++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 71a5c2f3..4c6a76a9 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -551,6 +551,9 @@ static CK_RV CheckAttributes(CK_ATTRIBUTE* pTemplate, CK_ULONG ulCount, int set) } } else if (attrType[j].type == ATTR_TYPE_DATE) { + /* An empty date is valid and leaves the date unspecified. */ + if (set && attr->ulValueLen == 0) + continue; if (attr->pValue == NULL && set) return CKR_ATTRIBUTE_VALUE_INVALID; if ((attr->pValue != NULL) && diff --git a/src/internal.c b/src/internal.c index 4fcf5447..2d7c962f 100644 --- a/src/internal.c +++ b/src/internal.c @@ -15380,7 +15380,7 @@ static int WP11_Object_SetData(byte** attribute, int* attributeLen, byte* data, * * @param object [in] Object object. * @param startDate [in] Start data as a string. - * @param len [in] Length of string. + * @param len [in] Length of string. Zero clears the date. * @return BUFFER_E when string is too small. * 0 on success. */ @@ -15389,9 +15389,11 @@ static int WP11_Object_SetStartDate(WP11_Object* object, char* startDate, { int ret = 0; - if (len != sizeof(object->startDate)) + if (len == 0) + XMEMSET(object->startDate, 0, sizeof(object->startDate)); + else if (len != sizeof(object->startDate)) ret = BUFFER_E; - if (ret == 0) + else XMEMCPY(object->startDate, startDate, sizeof(object->startDate)); return ret; @@ -15402,7 +15404,7 @@ static int WP11_Object_SetStartDate(WP11_Object* object, char* startDate, * * @param object [in] Object object. * @param endData [in] End data as a string. - * @param len [in] Length of string. + * @param len [in] Length of string. Zero clears the date. * @return BUFFER_E when string is too small. * 0 on success. */ @@ -15410,9 +15412,11 @@ static int WP11_Object_SetEndDate(WP11_Object* object, char* endDate, int len) { int ret = 0; - if (len != sizeof(object->endDate)) + if (len == 0) + XMEMSET(object->endDate, 0, sizeof(object->endDate)); + else if (len != sizeof(object->endDate)) ret = BUFFER_E; - if (ret == 0) + else XMEMCPY(object->endDate, endDate, sizeof(object->endDate)); return ret; diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index c265b718..2721da48 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -578,6 +578,56 @@ static void test_data_attr_requires_value(CK_SESSION_HANDLE session) destroy_obj(session, &bad); } +/* An empty CKA_START_DATE/CKA_END_DATE, as read from an object without + * dates, can be written back and clears a set date. */ +static void check_date_roundtrip(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE obj, CK_ATTRIBUTE_TYPE type, + const char* name) +{ + CK_RV rv; + CK_DATE date = { {'2','0','3','0'}, {'0','6'}, {'1','5'} }; + CK_DATE got; + CK_ATTRIBUTE getAttr = { type, &got, sizeof(got) }; + CK_ATTRIBUTE emptyNull[] = { { type, NULL, 0 } }; + CK_ATTRIBUTE emptyBuf[] = { { type, &got, 0 } }; + CK_ATTRIBUTE setDate[] = { { type, &date, sizeof(date) } }; + + rv = funcList->C_GetAttributeValue(session, obj, &getAttr, 1); + CHECK_TRUE(rv == CKR_OK && getAttr.ulValueLen == 0, name); + + rv = funcList->C_SetAttributeValue(session, obj, emptyNull, 1); + CHECK_RV(rv, "write back empty date", CKR_OK); + + rv = funcList->C_SetAttributeValue(session, obj, setDate, 1); + CHECK_RV(rv, "set date", CKR_OK); + getAttr.ulValueLen = sizeof(got); + rv = funcList->C_GetAttributeValue(session, obj, &getAttr, 1); + CHECK_TRUE(rv == CKR_OK && getAttr.ulValueLen == sizeof(date) && + XMEMCMP(&got, &date, sizeof(date)) == 0, "date read back"); + + rv = funcList->C_SetAttributeValue(session, obj, emptyBuf, 1); + CHECK_RV(rv, "clear date with empty value", CKR_OK); + getAttr.ulValueLen = sizeof(got); + rv = funcList->C_GetAttributeValue(session, obj, &getAttr, 1); + CHECK_TRUE(rv == CKR_OK && getAttr.ulValueLen == 0, "date cleared"); +} + +static void test_empty_date_roundtrip(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + + rv = create_data_object(session, &obj); + CHECK_RV(rv, "create data object", CKR_OK); + if (rv == CKR_OK) { + check_date_roundtrip(session, obj, CKA_START_DATE, + "unset CKA_START_DATE reads as empty"); + check_date_roundtrip(session, obj, CKA_END_DATE, + "unset CKA_END_DATE reads as empty"); + } + destroy_obj(session, &obj); +} + #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ @@ -708,6 +758,7 @@ static int run_test(void) test_always_authenticate_rejected(session); #endif test_data_attr_requires_value(session); + test_empty_date_roundtrip(session); #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ From b5979bdcceb3f7f9365a4ad01cb41091b2290932 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:04:06 -0700 Subject: [PATCH 129/204] F-12462 - Keep the previous attribute value when a replacement fails --- src/internal.c | 41 ++++++++-------- tests/attribute_validation_test.c | 79 +++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 19 deletions(-) diff --git a/src/internal.c b/src/internal.c index 2d7c962f..f1b493d4 100644 --- a/src/internal.c +++ b/src/internal.c @@ -15318,22 +15318,24 @@ static int WP11_Object_SetKeyId(WP11_Object* object, unsigned char* keyId, int keyIdLen) { int ret = 0; + unsigned char* newId = NULL; if (keyId == NULL && keyIdLen > 0) return BAD_FUNC_ARG; - XFREE(object->keyId, NULL, DYNAMIC_TYPE_TMP_BUFFER); - object->keyId = NULL; - object->keyIdLen = 0; + /* Build the replacement first so a failure keeps the current value. */ if (keyIdLen > 0) { - object->keyId = (unsigned char*)XMALLOC(keyIdLen, NULL, + newId = (unsigned char*)XMALLOC(keyIdLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); - if (object->keyId == NULL) + if (newId == NULL) ret = MEMORY_E; - if (ret == 0) { - XMEMCPY(object->keyId, keyId, keyIdLen); - object->keyIdLen = keyIdLen; - } + else + XMEMCPY(newId, keyId, keyIdLen); + } + if (ret == 0) { + XFREE(object->keyId, NULL, DYNAMIC_TYPE_TMP_BUFFER); + object->keyId = newId; + object->keyIdLen = (newId != NULL) ? keyIdLen : 0; } return ret; @@ -15354,22 +15356,23 @@ static int WP11_Object_SetData(byte** attribute, int* attributeLen, byte* data, int dataLen) { int ret = 0; + byte* newData = NULL; if (data == NULL && dataLen > 0) return BAD_FUNC_ARG; - XFREE(*attribute, NULL, DYNAMIC_TYPE_TMP_BUFFER); - *attribute = NULL; - *attributeLen = 0; + /* Build the replacement first so a failure keeps the current value. */ if (dataLen > 0) { - *attribute = (byte*)XMALLOC(dataLen, NULL, - DYNAMIC_TYPE_TMP_BUFFER); - if (*attribute == NULL) + newData = (byte*)XMALLOC(dataLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (newData == NULL) ret = MEMORY_E; - if (ret == 0) { - XMEMCPY(*attribute, data, dataLen); - *attributeLen = dataLen; - } + else + XMEMCPY(newData, data, dataLen); + } + if (ret == 0) { + XFREE(*attribute, NULL, DYNAMIC_TYPE_TMP_BUFFER); + *attribute = newData; + *attributeLen = (newData != NULL) ? dataLen : 0; } return ret; diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index 2721da48..308354cd 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -28,6 +28,7 @@ #include #include +#include #include #ifndef WOLFSSL_USER_SETTINGS @@ -35,6 +36,7 @@ #endif #include #include +#include #ifndef WOLFPKCS11_USER_SETTINGS #include @@ -50,6 +52,33 @@ #define TEST_DIR "./store/attribute_validation_test" +#if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY) && \ + !defined(WOLFSSL_NO_MALLOC) && !defined(WOLFSSL_DEBUG_MEMORY) +#define ATTR_TEST_ALLOC_HOOK +/* Allocation of exactly this size fails while armed. */ +#define FAIL_ALLOC_SIZE 97 +static int failAllocArmed = 0; + +static void* test_malloc(size_t n) +{ + if (failAllocArmed && n == FAIL_ALLOC_SIZE) + return NULL; + return malloc(n); +} + +static void test_free(void* p) +{ + free(p); +} + +static void* test_realloc(void* p, size_t n) +{ + if (failAllocArmed && n == FAIL_ALLOC_SIZE) + return NULL; + return realloc(p, n); +} +#endif + static CK_OBJECT_CLASS dataClass = CKO_DATA; static CK_BBOOL ckTrue = CK_TRUE; static CK_BBOOL ckFalse = CK_FALSE; @@ -628,6 +657,47 @@ static void test_empty_date_roundtrip(CK_SESSION_HANDLE session) destroy_obj(session, &obj); } +#ifdef ATTR_TEST_ALLOC_HOOK +/* A replacement value that cannot be stored leaves the previous one. */ +static void test_failed_replace_keeps_value(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + byte big[FAIL_ALLOC_SIZE]; + byte idBuf[16]; + CK_ATTRIBUTE labelTmpl[] = { { CKA_LABEL, big, sizeof(big) } }; + CK_ATTRIBUTE idTmpl[] = { { CKA_ID, big, sizeof(big) } }; + CK_ATTRIBUTE idSet[] = { { CKA_ID, (void*)newLabel, + sizeof(newLabel) - 1 } }; + CK_ATTRIBUTE idGet = { CKA_ID, idBuf, sizeof(idBuf) }; + + XMEMSET(big, 'x', sizeof(big)); + rv = create_data_object(session, &obj); + CHECK_RV(rv, "create data object", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, obj, idSet, 1); + CHECK_RV(rv, "set CKA_ID", CKR_OK); + + failAllocArmed = 1; + rv = funcList->C_SetAttributeValue(session, obj, labelTmpl, 1); + failAllocArmed = 0; + CHECK_RV(rv, "label replacement out of memory", CKR_DEVICE_MEMORY); + expect_label(session, obj, dataLabel, sizeof(dataLabel) - 1, + "label kept after failed replacement"); + + failAllocArmed = 1; + rv = funcList->C_SetAttributeValue(session, obj, idTmpl, 1); + failAllocArmed = 0; + CHECK_RV(rv, "CKA_ID replacement out of memory", CKR_DEVICE_MEMORY); + rv = funcList->C_GetAttributeValue(session, obj, &idGet, 1); + CHECK_TRUE(rv == CKR_OK && idGet.ulValueLen == sizeof(newLabel) - 1 && + XMEMCMP(idBuf, newLabel, sizeof(newLabel) - 1) == 0, + "CKA_ID kept after failed replacement"); + } + destroy_obj(session, &obj); +} +#endif + #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ @@ -759,6 +829,9 @@ static int run_test(void) #endif test_data_attr_requires_value(session); test_empty_date_roundtrip(session); +#ifdef ATTR_TEST_ALLOC_HOOK + test_failed_replace_keeps_value(session); +#endif #if defined(WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL) && \ defined(HAVE_AES_KEYWRAP) && !defined(WOLFPKCS11_NO_STORE) && \ !defined(NO_RSA) && !defined(NO_AES) && \ @@ -784,6 +857,12 @@ int main(int argc, char* argv[]) #endif printf("=== wolfPKCS11 attribute validation test ===\n"); +#ifdef ATTR_TEST_ALLOC_HOOK + if (wolfSSL_SetAllocators(test_malloc, test_free, test_realloc) != 0) { + fprintf(stderr, "FAIL: wolfSSL_SetAllocators\n"); + return 1; + } +#endif run_test(); return pkcs11_test_summary(); } From 2c18779827787e1521130f0be9c1db8da65d04ee Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:14:45 -0700 Subject: [PATCH 130/204] F-2030 - Read CKA_CERTIFICATE_CATEGORY as a full CK_ULONG --- src/internal.c | 2 +- tests/attribute_validation_test.c | 34 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index f1b493d4..d14cda2d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -15539,7 +15539,7 @@ int WP11_Object_SetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, (int)len); break; case CKA_CERTIFICATE_CATEGORY: - object->category = *(word32*)data; + object->category = (word32)*(CK_ULONG*)data; break; case CKA_PRIVATE: WP11_Object_SetOpFlag(object, WP11_FLAG_PRIVATE, *(CK_BBOOL*)data); diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index 308354cd..c1e1907c 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -657,6 +657,39 @@ static void test_empty_date_roundtrip(CK_SESSION_HANDLE session) destroy_obj(session, &obj); } +/* CKA_CERTIFICATE_CATEGORY is consumed as a full CK_ULONG. */ +static void test_certificate_category_ulong(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS certClass = CKO_CERTIFICATE; + CK_CERTIFICATE_TYPE certType = CKC_X_509; + CK_ULONG category = 2; + word32 shortCategory = 2; + byte certData[] = { 0x30, 0x82, 0x01, 0x00 }; + CK_OBJECT_HANDLE cert = CK_INVALID_HANDLE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &certClass, sizeof(certClass) }, + { CKA_CERTIFICATE_TYPE, &certType, sizeof(certType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, certData, sizeof(certData) }, + { CKA_CERTIFICATE_CATEGORY, &category, sizeof(category) }, + }; + CK_ULONG cnt = sizeof(tmpl) / sizeof(*tmpl); + + rv = funcList->C_CreateObject(session, tmpl, cnt, &cert); + CHECK_RV(rv, "create certificate with category", CKR_OK); + destroy_obj(session, &cert); + + if (sizeof(CK_ULONG) != sizeof(word32)) { + tmpl[cnt - 1].pValue = &shortCategory; + tmpl[cnt - 1].ulValueLen = sizeof(shortCategory); + rv = funcList->C_CreateObject(session, tmpl, cnt, &cert); + CHECK_TRUE(rv != CKR_OK, "certificate category shorter than CK_ULONG"); + destroy_obj(session, &cert); + } +} + #ifdef ATTR_TEST_ALLOC_HOOK /* A replacement value that cannot be stored leaves the previous one. */ static void test_failed_replace_keeps_value(CK_SESSION_HANDLE session) @@ -829,6 +862,7 @@ static int run_test(void) #endif test_data_attr_requires_value(session); test_empty_date_roundtrip(session); + test_certificate_category_ulong(session); #ifdef ATTR_TEST_ALLOC_HOOK test_failed_replace_keeps_value(session); #endif From 3ca0b46ec1ddec079732d58ef1302505012a5229 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:35 -0700 Subject: [PATCH 131/204] Use a bounded handle array in the find test --- tests/attribute_validation_test.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/attribute_validation_test.c b/tests/attribute_validation_test.c index c1e1907c..49041955 100644 --- a/tests/attribute_validation_test.c +++ b/tests/attribute_validation_test.c @@ -229,27 +229,34 @@ static void test_find_objects_large_max(CK_SESSION_HANDLE session) { CK_RV rv; CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; - CK_OBJECT_HANDLE found = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE found[8]; CK_ULONG count = 0; + static const byte label[] = "attribute-validation-large-max"; + CK_ATTRIBUTE labelTmpl[] = { + { CKA_LABEL, (void*)label, sizeof(label) - 1 }, + }; CK_ATTRIBUTE findTmpl[] = { - { CKA_CLASS, &dataClass, sizeof(dataClass) }, - { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, - { CKA_LABEL, (void*)dataLabel, sizeof(dataLabel) - 1 }, + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_LABEL, (void*)label, sizeof(label) - 1 }, }; rv = create_data_object(session, &obj); CHECK_RV(rv, "create data object", CKR_OK); if (rv != CKR_OK) return; + rv = funcList->C_SetAttributeValue(session, obj, labelTmpl, 1); + CHECK_RV(rv, "set a label only this test uses", CKR_OK); + XMEMSET(found, 0, sizeof(found)); rv = funcList->C_FindObjectsInit(session, findTmpl, sizeof(findTmpl) / sizeof(*findTmpl)); CHECK_RV(rv, "find init", CKR_OK); if (rv == CKR_OK) { - rv = funcList->C_FindObjects(session, &found, (CK_ULONG)INT_MAX + 1, + rv = funcList->C_FindObjects(session, found, (CK_ULONG)INT_MAX + 1, &count); CHECK_RV(rv, "find with large maximum", CKR_OK); - CHECK_TRUE(count == 1 && found == obj, + CHECK_TRUE(count == 1 && found[0] == obj, "find with large maximum returns the object"); funcList->C_FindObjectsFinal(session); } From a49bf42e022b9be87e7d20c5e65323f429b6ad1a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 15:20:17 -0700 Subject: [PATCH 132/204] F-11500 - Reject key material changes on existing objects --- src/crypto.c | 17 ++ tests/include.am | 7 + tests/key_material_update_test.c | 350 +++++++++++++++++++++++++++++++ tests/pkcs11mtt.c | 3 +- tests/pkcs11test.c | 65 +++--- tests/pkcs11v3test.c | 11 +- 6 files changed, 420 insertions(+), 33 deletions(-) create mode 100644 tests/key_material_update_test.c diff --git a/src/crypto.c b/src/crypto.c index 4c6a76a9..070470e6 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1155,6 +1155,23 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, } } + /* Key components and a certificate's value are fixed once the object + * exists (PKCS#11 v2.40 sec 4.6.3, 4.8). */ + if (!newObject && objClass != CKO_DATA +#ifdef WOLFPKCS11_NSS + && objClass != CKO_NSS_TRUST +#endif + ) { + for (i = 0; i < cnt; i++) { + if (objClass == CKO_CERTIFICATE && attrs[i] != CKA_VALUE) + continue; + for (j = 0; j < (int)ulCount; j++) { + if (attrs[i] == pTemplate[j].type) + return CKR_ATTRIBUTE_READ_ONLY; + } + } + } + /* Validate the whole template before any setter runs so a rejected * update leaves the object unchanged. */ for (i = 0; i < (int)ulCount; i++) { diff --git a/tests/include.am b/tests/include.am index 89cde6cf..28118193 100644 --- a/tests/include.am +++ b/tests/include.am @@ -355,6 +355,11 @@ noinst_PROGRAMS += tests/attribute_validation_test tests_attribute_validation_test_SOURCES = tests/attribute_validation_test.c tests_attribute_validation_test_LDADD = +check_PROGRAMS += tests/key_material_update_test +noinst_PROGRAMS += tests/key_material_update_test +tests_key_material_update_test_SOURCES = tests/key_material_update_test.c +tests_key_material_update_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -427,6 +432,7 @@ tests_object_policy_integrity_test_LDADD += src/libwolfpkcs11.la tests_session_op_lifecycle_test_LDADD += src/libwolfpkcs11.la tests_info_mech_table_test_LDADD += src/libwolfpkcs11.la tests_attribute_validation_test_LDADD += src/libwolfpkcs11.la +tests_key_material_update_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -491,6 +497,7 @@ tests_object_policy_integrity_test_LDADD += src/libwolfpkcs11.la tests_session_op_lifecycle_test_LDADD += src/libwolfpkcs11.la tests_info_mech_table_test_LDADD += src/libwolfpkcs11.la tests_attribute_validation_test_LDADD += src/libwolfpkcs11.la +tests_key_material_update_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c new file mode 100644 index 00000000..db17ba86 --- /dev/null +++ b/tests/key_material_update_test.c @@ -0,0 +1,350 @@ +/* key_material_update_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Key material and certificate values are fixed once an object exists, and + * what the token persists matches the live object. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#define TEST_DIR "./store/key_material_update_test" + +static CK_SLOT_ID slot = 0; +static const char* soPin = "password123456"; +static const char* userPin = "wolfpkcs11-test"; +static const char tokenLabel[] = "key-material-update"; + +static CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; +static CK_KEY_TYPE genericType = CKK_GENERIC_SECRET; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; + +static CK_RV token_setup(void) +{ + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + CK_SESSION_HANDLE soSession = 0; + unsigned char label[32]; + CK_RV rv; + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + if (rv == CKR_OK) + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv == CKR_OK && slotCount == 0) + rv = CKR_TOKEN_NOT_PRESENT; + if (rv == CKR_OK) { + slot = slotList[0]; + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenLabel, XSTRLEN(tokenLabel)); + rv = funcList->C_InitToken(slot, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin), label); + } + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &soSession); + } + if (rv == CKR_OK) { + rv = funcList->C_Login(soSession, CKU_SO, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin)); + if (rv == CKR_OK) { + rv = funcList->C_InitPIN(soSession, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + funcList->C_Logout(soSession); + } + funcList->C_CloseSession(soSession); + } + return rv; +} + +static CK_RV open_session(CK_SESSION_HANDLE* session, int login) +{ + CK_RV rv; + + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, session); + if (rv == CKR_OK && login) { + rv = funcList->C_Login(*session, CKU_USER, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + } + return rv; +} + +#ifndef WOLFPKCS11_NO_STORE +/* Finalize so token objects are written out, then load them again. */ +static CK_RV reload_token(CK_SESSION_HANDLE* session, int login) +{ + CK_C_INITIALIZE_ARGS args; + CK_RV rv; + + funcList->C_CloseSession(*session); + *session = 0; + funcList->C_Finalize(NULL); + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + if (rv == CKR_OK) + rv = open_session(session, login); + return rv; +} + +static CK_OBJECT_HANDLE find_by_label(CK_SESSION_HANDLE session, + const char* label) +{ + CK_RV rv; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ULONG count = 0; + CK_ATTRIBUTE tmpl[] = { + { CKA_LABEL, (void*)label, (CK_ULONG)XSTRLEN(label) }, + }; + + rv = funcList->C_FindObjectsInit(session, tmpl, 1); + if (rv == CKR_OK) { + rv = funcList->C_FindObjects(session, &obj, 1, &count); + funcList->C_FindObjectsFinal(session); + } + if (rv != CKR_OK || count != 1) + obj = CK_INVALID_HANDLE; + return obj; +} +#endif + +/* Read an attribute value and compare it with the expected bytes. */ +static int attr_equals(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + CK_ATTRIBUTE_TYPE type, const byte* expect, + CK_ULONG expectLen) +{ + CK_RV rv; + byte buf[512]; + CK_ATTRIBUTE attr; + + attr.type = type; + attr.pValue = buf; + attr.ulValueLen = sizeof(buf); + rv = funcList->C_GetAttributeValue(session, obj, &attr, 1); + return rv == CKR_OK && attr.ulValueLen == expectLen && + XMEMCMP(buf, expect, expectLen) == 0; +} + +static void destroy_obj(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* obj) +{ + if (*obj != CK_INVALID_HANDLE) { + funcList->C_DestroyObject(session, *obj); + *obj = CK_INVALID_HANDLE; + } +} + +#ifndef NO_RSA +/* A token public key's modulus cannot be replaced and the persisted key + * matches the live key. */ +static void check_token_modulus_fixed(CK_SESSION_HANDLE* session) +{ + CK_RV rv; + CK_OBJECT_HANDLE rsa = CK_INVALID_HANDLE; + static const char rsaLabel[] = "kmu-token-rsa"; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE rsaType = CKK_RSA; + byte newMod[sizeof(rsa_2048_modulus)]; + byte liveMod[sizeof(rsa_2048_modulus) + 16]; + CK_ATTRIBUTE liveAttr = { CKA_MODULUS, liveMod, sizeof(liveMod) }; + CK_ATTRIBUTE rsaTmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_LABEL, (void*)rsaLabel, sizeof(rsaLabel) - 1 }, + { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + CK_ATTRIBUTE setMod[] = { { CKA_MODULUS, newMod, sizeof(newMod) } }; + + XMEMCPY(newMod, rsa_2048_modulus, sizeof(newMod)); + newMod[sizeof(newMod) - 1] ^= 0x02; + rv = funcList->C_CreateObject(*session, rsaTmpl, + sizeof(rsaTmpl) / sizeof(*rsaTmpl), &rsa); + CHECK_RV(rv, "create token RSA public key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SetAttributeValue(*session, rsa, setMod, 1); + CHECK_RV(rv, "set modulus on existing key", CKR_ATTRIBUTE_READ_ONLY); + rv = funcList->C_GetAttributeValue(*session, rsa, &liveAttr, 1); + CHECK_RV(rv, "read live modulus", CKR_OK); + CHECK_TRUE(liveAttr.ulValueLen == sizeof(rsa_2048_modulus) && + XMEMCMP(liveMod, rsa_2048_modulus, + sizeof(rsa_2048_modulus)) == 0, + "live modulus unchanged"); + +#ifndef WOLFPKCS11_NO_STORE + /* Token objects are decoded at login. */ + rv = reload_token(session, 1); + CHECK_RV(rv, "reload token", CKR_OK); + if (rv != CKR_OK) + return; + rsa = find_by_label(*session, rsaLabel); + CHECK_TRUE(rsa != CK_INVALID_HANDLE && + attr_equals(*session, rsa, CKA_MODULUS, liveMod, + liveAttr.ulValueLen), + "persisted modulus matches live modulus"); +#endif + destroy_obj(*session, &rsa); +} +#endif + +/* A token secret key's value cannot be replaced directly or through a copy + * template, and C_CopyObject still carries the key material over. */ +static void check_token_secret_fixed(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE copy = CK_INVALID_HANDLE; + static const char keyLabel[] = "kmu-token-key"; + static const char copyLabel[] = "kmu-token-key-copy"; + byte origVal[16]; + byte newVal[16]; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_LABEL, (void*)keyLabel, sizeof(keyLabel) - 1 }, + { CKA_VALUE, origVal, sizeof(origVal) }, + }; + CK_ATTRIBUTE setValue[] = { { CKA_VALUE, newVal, sizeof(newVal) } }; + CK_ATTRIBUTE copyWithValue[] = { + { CKA_LABEL, (void*)copyLabel, sizeof(copyLabel) - 1 }, + { CKA_VALUE, newVal, sizeof(newVal) }, + }; + CK_ATTRIBUTE copyTmpl[] = { + { CKA_LABEL, (void*)copyLabel, sizeof(copyLabel) - 1 }, + }; + + XMEMSET(origVal, 0x11, sizeof(origVal)); + XMEMSET(newVal, 0x22, sizeof(newVal)); + + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &key); + CHECK_RV(rv, "create token secret key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SetAttributeValue(session, key, setValue, 1); + CHECK_RV(rv, "set key value on existing key", CKR_ATTRIBUTE_READ_ONLY); + CHECK_TRUE(attr_equals(session, key, CKA_VALUE, origVal, sizeof(origVal)), + "live key value unchanged"); + + rv = funcList->C_CopyObject(session, key, copyWithValue, 2, ©); + CHECK_RV(rv, "copy with a new key value", CKR_ATTRIBUTE_READ_ONLY); + destroy_obj(session, ©); + + rv = funcList->C_CopyObject(session, key, copyTmpl, 1, ©); + CHECK_RV(rv, "copy token secret key", CKR_OK); + CHECK_TRUE(attr_equals(session, copy, CKA_VALUE, origVal, sizeof(origVal)), + "copy carries the key value"); + + destroy_obj(session, ©); + destroy_obj(session, &key); +} + +static void test_token_key_value_fixed(CK_SESSION_HANDLE* session) +{ + CK_RV rv; + +#ifndef NO_RSA + check_token_modulus_fixed(session); +#endif + rv = funcList->C_Login(*session, CKU_USER, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + if (rv == CKR_USER_ALREADY_LOGGED_IN) + rv = CKR_OK; + CHECK_RV(rv, "login user", CKR_OK); + if (rv == CKR_OK) + check_token_secret_fixed(*session); +} + +static int run_test(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = 0; + + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv != CKR_OK) + return -1; + + rv = token_setup(); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) { + rv = open_session(&session, 0); + CHECK_RV(rv, "open session", CKR_OK); + } + if (rv == CKR_OK) { + test_token_key_value_fixed(&session); + } + + if (session != 0) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + funcList->C_Finalize(NULL); + pkcs11_unload(); + return 0; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + + printf("=== wolfPKCS11 key material update test ===\n"); + run_test(); + return pkcs11_test_summary(); +} diff --git a/tests/pkcs11mtt.c b/tests/pkcs11mtt.c index 2727a2ec..4609e653 100644 --- a/tests/pkcs11mtt.c +++ b/tests/pkcs11mtt.c @@ -239,8 +239,9 @@ static CK_RV test_object(void* args) { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, }; CK_ULONG tmplOnTokenCnt = sizeof(tmplOnToken) / sizeof(*tmplOnToken); + static byte copyLabel[] = "copy"; CK_ATTRIBUTE copyTmpl[] = { - { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_LABEL, copyLabel, sizeof(copyLabel)-1 }, }; CK_ULONG copyTmplCnt = sizeof(copyTmpl) / sizeof(*copyTmpl); CK_ULONG count; diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index c5e935df..65b467a6 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -2026,8 +2026,9 @@ static CK_RV test_object(void* args) { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, }; CK_ULONG tmplOnTokenCnt = sizeof(tmplOnToken) / sizeof(*tmplOnToken); + static byte copyLabel[] = "copy"; CK_ATTRIBUTE copyTmpl[] = { - { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_LABEL, copyLabel, sizeof(copyLabel)-1 }, }; CK_ULONG copyTmplCnt = sizeof(copyTmpl) / sizeof(*copyTmpl); CK_ULONG count; @@ -2456,12 +2457,24 @@ static CK_RV test_copy_object_deep_copy(void* args) } } - /* Test 3: Verify independence (deep copy) by modifying original object */ + /* Test 3: Verify independence (deep copy) by modifying original object. + * Key material is read-only after creation, so modify CKA_ID instead. */ if (ret == CKR_OK) { - CK_ATTRIBUTE modifyOriginalTmpl[] = { + CK_ATTRIBUTE modifyValueTmpl[] = { { CKA_VALUE, modifiedKeyData, sizeof(modifiedKeyData) }, }; + + ret = funcList->C_SetAttributeValue(session, originalObj, + modifyValueTmpl, 1); + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_READ_ONLY, + "Modify original object key value is read-only"); + } + if (ret == CKR_OK) { + CK_ATTRIBUTE modifyOriginalTmpl[] = { + { CKA_ID, modifiedKeyData, + sizeof(modifiedKeyData) }, + }; CK_ULONG modifyOriginalTmplCnt = sizeof(modifyOriginalTmpl) / sizeof(*modifyOriginalTmpl); @@ -2472,45 +2485,44 @@ static CK_RV test_copy_object_deep_copy(void* args) } if (ret == CKR_OK) { - /* Get the modified original object's value */ - XMEMSET(origValue, 0, sizeof(origValue)); - getOriginalAttrs[0].pValue = origValue; - getOriginalAttrs[0].ulValueLen = sizeof(origValue); + /* Get the modified original object's ID */ + XMEMSET(origId, 0, sizeof(origId)); + getOriginalAttrs[1].pValue = origId; + getOriginalAttrs[1].ulValueLen = sizeof(origId); ret = funcList->C_GetAttributeValue(session, originalObj, - getOriginalAttrs, 1); - CHECK_CKR(ret, "Get modified original object value"); + &getOriginalAttrs[1], 1); + CHECK_CKR(ret, "Get modified original object ID"); } if (ret == CKR_OK) { - /* Get the copied object's value (should be unchanged) */ - XMEMSET(copiedValue, 0, sizeof(copiedValue)); - getCopiedAttrs[0].pValue = copiedValue; - getCopiedAttrs[0].ulValueLen = sizeof(copiedValue); + /* Get the copied object's ID (should be unchanged) */ + XMEMSET(copiedId, 0, sizeof(copiedId)); + getCopiedAttrs[1].pValue = copiedId; + getCopiedAttrs[1].ulValueLen = sizeof(copiedId); - ret = funcList->C_GetAttributeValue(session, copiedObj, getCopiedAttrs, - 1); - CHECK_CKR(ret, "Get copied object value after original " + ret = funcList->C_GetAttributeValue(session, copiedObj, + &getCopiedAttrs[1], 1); + CHECK_CKR(ret, "Get copied object ID after original " "modification"); } if (ret == CKR_OK) { - /* Verify original object has modified value */ - if (getOriginalAttrs[0].ulValueLen != sizeof(modifiedKeyData) || - XMEMCMP(origValue, modifiedKeyData, + /* Verify original object has modified ID */ + if (getOriginalAttrs[1].ulValueLen != sizeof(modifiedKeyData) || + XMEMCMP(origId, modifiedKeyData, sizeof(modifiedKeyData)) != 0) { ret = -1; - CHECK_CKR(ret, "Original object should have modified value"); + CHECK_CKR(ret, "Original object should have modified ID"); } } if (ret == CKR_OK) { - /* Verify copied object still has original value (proving deep copy) */ - if (getCopiedAttrs[0].ulValueLen != sizeof(keyData) || - XMEMCMP(copiedValue, keyData, - sizeof(keyData)) != 0) { + /* Verify copied object still has original ID (proving deep copy) */ + if (getCopiedAttrs[1].ulValueLen != sizeof(keyId) || + XMEMCMP(copiedId, keyId, sizeof(keyId)) != 0) { ret = -1; - CHECK_CKR(ret, "Copied object should retain original value " + CHECK_CKR(ret, "Copied object should retain original ID " "(deep copy test)"); } } @@ -17592,8 +17604,9 @@ static CK_RV test_create_session_obj_ro_session(void* args) CK_ULONG tmplOnTokenCnt = sizeof(tmplOnToken) / sizeof(*tmplOnToken); CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE, objOnToken = CK_INVALID_HANDLE; CK_OBJECT_HANDLE copyObj = CK_INVALID_HANDLE, copyBad = CK_INVALID_HANDLE; + static byte copyLabel[] = "copy"; CK_ATTRIBUTE copyTmpl[] = { - { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_LABEL, copyLabel, sizeof(copyLabel)-1 }, }; CK_ULONG copyTmplCnt = sizeof(copyTmpl) / sizeof(*copyTmpl); char newLabel[] = "updated"; diff --git a/tests/pkcs11v3test.c b/tests/pkcs11v3test.c index 6b9abd50..a0bd054e 100644 --- a/tests/pkcs11v3test.c +++ b/tests/pkcs11v3test.c @@ -3560,9 +3560,8 @@ static CK_RV test_hss_paramset_rejected(void* args) return ret; } -/* A failed re-import via C_SetAttributeValue (a bad CKA_VALUE) must leave the - * existing key intact: the object still verifies its original signature. This - * exercises the atomic temporary-key swap in WP11_Object_SetHssKey. */ +/* A re-import via C_SetAttributeValue is rejected because key material is + * read-only after creation, and the existing key stays usable. */ static CK_RV test_hss_reimport_atomic(void* args) { CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; @@ -3590,7 +3589,7 @@ static CK_RV test_hss_reimport_atomic(void* args) CHECK_CKR(ret, "HSS key import for re-import test"); if (ret == CKR_OK) { ret = funcList->C_SetAttributeValue(session, obj, badVal, 1); - CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_VALUE_INVALID, + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_READ_ONLY, "HSS re-import of invalid CKA_VALUE rejected"); } if (ret == CKR_OK) { @@ -3876,12 +3875,12 @@ static CK_RV test_xmss_paramset_setattr_rejected(void* args) /* A wrong OID via C_SetAttributeValue must be rejected, not a no-op. */ if (ret == CKR_OK) { ret = funcList->C_SetAttributeValue(session, obj, setBadOnly, 1); - CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_VALUE_INVALID, + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_READ_ONLY, "XMSS set wrong CKA_PARAMETER_SET rejected"); } if (ret == CKR_OK) { ret = funcList->C_SetAttributeValue(session, obj, setBadWithVal, 2); - CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_VALUE_INVALID, + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_READ_ONLY, "XMSS set wrong CKA_PARAMETER_SET + value rejected"); } /* The key-derived OID must be unchanged by the rejected sets. */ From cf2a3530b254d61e956a3dec002e4f13e2d1845e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 15:32:36 -0700 Subject: [PATCH 133/204] F-10387 - Validate a secret key update before replacing the key --- src/internal.c | 26 +++++++------- tests/key_material_update_test.c | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/src/internal.c b/src/internal.c index d14cda2d..9c084c3d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -13652,13 +13652,12 @@ int WP11_Object_SetSecretKey(WP11_Object* object, unsigned char** data, int ret = 0; WP11_Data* key; CK_ULONG keyLen = 0; + word32 newLen = 0; if (object->onToken) WP11_Lock_LockRW(object->lock); key = object->data.symmKey; - key->len = 0; - wc_ForceZero(key->data, sizeof(key->data)); /* First item is the key's length. */ if (ret == 0 && data[0] != NULL && len[0] != (int)sizeof(CK_ULONG)) @@ -13682,28 +13681,27 @@ int WP11_Object_SetSecretKey(WP11_Object* object, unsigned char** data, } #endif if (ret == 0 && data[0] != NULL) - key->len = (word32)keyLen; + newLen = (word32)keyLen; /* Second item is the key data. */ if (ret == 0 && data[1] != NULL) { - if (key->len == 0) { + if (newLen == 0) { if (len[1] > WP11_MAX_SYM_KEY_SZ) ret = BUFFER_E; else - key->len = (word32)len[1]; + newLen = (word32)len[1]; } - else if (len[1] != (CK_ULONG)key->len) + else if (len[1] != (CK_ULONG)newLen) ret = BUFFER_E; } - if (ret == 0 && key->len > WP11_MAX_SYM_KEY_SZ) - ret = BUFFER_E; - if (ret == 0 && data[1] != NULL) - XMEMCPY(key->data, data[1], key->len); - /* On any error, record no length so later teardown does not zeroize or - * copy past the fixed key buffer. */ - if (ret != 0) - key->len = 0; + /* Replace the key only once the new value is known to be valid. */ + if (ret == 0) { + wc_ForceZero(key->data, sizeof(key->data)); + key->len = newLen; + if (data[1] != NULL) + XMEMCPY(key->data, data[1], newLen); + } if (object->onToken) WP11_Lock_UnlockRW(object->lock); diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c index db17ba86..df58630c 100644 --- a/tests/key_material_update_test.c +++ b/tests/key_material_update_test.c @@ -306,6 +306,67 @@ static void test_token_key_value_fixed(CK_SESSION_HANDLE* session) check_token_secret_fixed(*session); } +/* A rejected secret key update leaves the existing key usable. */ +static void test_rejected_secret_update_keeps_key(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + byte origVal[16]; + byte newVal[16]; + CK_ULONG shortLen = 8; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, origVal, sizeof(origVal) }, + }; + CK_ATTRIBUTE badUpdate[] = { + { CKA_VALUE_LEN, &shortLen, sizeof(shortLen) }, + { CKA_VALUE, newVal, sizeof(newVal) }, + }; +#ifndef NO_AES + CK_KEY_TYPE aesType = CKK_AES; + CK_ULONG badAesLen = 17; + CK_ATTRIBUTE badAesLenTmpl[] = { + { CKA_VALUE_LEN, &badAesLen, sizeof(badAesLen) }, + }; +#endif + + XMEMSET(origVal, 0x33, sizeof(origVal)); + XMEMSET(newVal, 0x44, sizeof(newVal)); + + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &key); + CHECK_RV(rv, "create secret key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, key, badUpdate, 2); + CHECK_RV(rv, "inconsistent key update rejected", + CKR_ATTRIBUTE_READ_ONLY); + CHECK_TRUE(attr_equals(session, key, CKA_VALUE, origVal, + sizeof(origVal)), + "key kept after rejected update"); + } + destroy_obj(session, &key); + +#ifndef NO_AES + keyTmpl[1].pValue = &aesType; + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &key); + CHECK_RV(rv, "create AES key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, key, badAesLenTmpl, 1); + CHECK_RV(rv, "invalid AES key length rejected", + CKR_ATTRIBUTE_READ_ONLY); + CHECK_TRUE(attr_equals(session, key, CKA_VALUE, origVal, + sizeof(origVal)), + "AES key kept after rejected length"); + } + destroy_obj(session, &key); +#endif +} + static int run_test(void) { CK_RV rv; @@ -324,6 +385,7 @@ static int run_test(void) } if (rv == CKR_OK) { test_token_key_value_fixed(&session); + test_rejected_secret_update_keeps_key(session); } if (session != 0) { From 344dab3faeed65a5b909c3fcd4d9821687f45e3e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 15:40:04 -0700 Subject: [PATCH 134/204] F-10399 - Clear the whole DH key buffer before storing a private value --- src/internal.c | 1 + tests/key_material_update_test.c | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/internal.c b/src/internal.c index 9c084c3d..fe4aa4df 100644 --- a/src/internal.c +++ b/src/internal.c @@ -13504,6 +13504,7 @@ int WP11_Object_SetDhKey(WP11_Object* object, unsigned char** data, if (len[2] > (int)sizeof(key->key)) ret = BAD_FUNC_ARG; else { + wc_ForceZero(key->key, sizeof(key->key)); XMEMCPY(key->key, data[2], len[2]); key->len = (word32)len[2]; } diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c index df58630c..1fd537b7 100644 --- a/tests/key_material_update_test.c +++ b/tests/key_material_update_test.c @@ -367,6 +367,42 @@ static void test_rejected_secret_update_keeps_key(CK_SESSION_HANDLE session) #endif } +#ifndef NO_DH +/* A DH private value cannot be replaced once the key exists. */ +static void test_dh_private_value_fixed(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE dhType = CKK_DH; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &dhType, sizeof(dhType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { 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 shorterValue[] = { + { CKA_VALUE, dh_2048_priv, sizeof(dh_2048_priv) / 2 }, + }; + + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &key); + CHECK_RV(rv, "create DH private key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, key, shorterValue, 1); + CHECK_RV(rv, "set shorter DH private value", CKR_ATTRIBUTE_READ_ONLY); + CHECK_TRUE(attr_equals(session, key, CKA_VALUE, dh_2048_priv, + sizeof(dh_2048_priv)), + "DH private value unchanged"); + } + destroy_obj(session, &key); +} +#endif + static int run_test(void) { CK_RV rv; @@ -386,6 +422,9 @@ static int run_test(void) if (rv == CKR_OK) { test_token_key_value_fixed(&session); test_rejected_secret_update_keeps_key(session); +#ifndef NO_DH + test_dh_private_value_fixed(session); +#endif } if (session != 0) { From cf233b8abcac7d9efb7ba092960a62a62f6422e1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 15:45:37 -0700 Subject: [PATCH 135/204] F-11515 - Test that asymmetric private components stay fixed after creation --- tests/key_material_update_test.c | 133 +++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c index 1fd537b7..c6b368d4 100644 --- a/tests/key_material_update_test.c +++ b/tests/key_material_update_test.c @@ -403,6 +403,138 @@ static void test_dh_private_value_fixed(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_RSA) || defined(HAVE_ECC) +/* Sign with the private key and verify with the public key. */ +static CK_RV sign_verify(CK_SESSION_HANDLE session, CK_MECHANISM_TYPE type, + CK_OBJECT_HANDLE priv, CK_OBJECT_HANDLE pub) +{ + CK_RV rv; + CK_MECHANISM mech = { 0, NULL, 0 }; + byte hash[32]; + byte sig[512]; + CK_ULONG sigLen = sizeof(sig); + + mech.mechanism = type; + XMEMSET(hash, 0x5a, sizeof(hash)); + rv = funcList->C_SignInit(session, &mech, priv); + if (rv == CKR_OK) + rv = funcList->C_Sign(session, hash, sizeof(hash), sig, &sigLen); + if (rv == CKR_OK) + rv = funcList->C_VerifyInit(session, &mech, pub); + if (rv == CKR_OK) + rv = funcList->C_Verify(session, hash, sizeof(hash), sig, sigLen); + return rv; +} +#endif + +/* Private components of an existing asymmetric key cannot be replaced, and + * the key still works after the rejected update. */ +static void test_asym_private_fixed(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + byte other[32]; +#ifndef NO_RSA + CK_KEY_TYPE rsaType = CKK_RSA; + CK_ATTRIBUTE rsaPrivTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_SIGN, &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) }, + { 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 rsaPubTmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + CK_ATTRIBUTE setPrivExp[] = { + { CKA_PRIVATE_EXPONENT, other, sizeof(other) }, + }; +#endif +#ifdef HAVE_ECC + CK_KEY_TYPE ecType = CKK_EC; + CK_ATTRIBUTE ecPrivTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, + }; + CK_ATTRIBUTE ecPubTmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_EC_POINT, ecc_p256_pub, sizeof(ecc_p256_pub) }, + }; + CK_ATTRIBUTE setEcValue[] = { { CKA_VALUE, other, sizeof(other) } }; +#endif + + XMEMSET(other, 0x07, sizeof(other)); +#ifndef NO_RSA + rv = funcList->C_CreateObject(session, rsaPrivTmpl, + sizeof(rsaPrivTmpl) / sizeof(*rsaPrivTmpl), + &priv); + CHECK_RV(rv, "create RSA private key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, rsaPubTmpl, + sizeof(rsaPubTmpl) / sizeof(*rsaPubTmpl), + &pub); + CHECK_RV(rv, "create RSA public key", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, priv, setPrivExp, 1); + CHECK_RV(rv, "set RSA private exponent", CKR_ATTRIBUTE_READ_ONLY); + rv = sign_verify(session, CKM_RSA_PKCS, priv, pub); + CHECK_RV(rv, "RSA key still signs after rejected update", CKR_OK); + } + destroy_obj(session, &pub); + destroy_obj(session, &priv); +#endif +#ifdef HAVE_ECC + rv = funcList->C_CreateObject(session, ecPrivTmpl, + sizeof(ecPrivTmpl) / sizeof(*ecPrivTmpl), + &priv); + CHECK_RV(rv, "create EC private key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, ecPubTmpl, + sizeof(ecPubTmpl) / sizeof(*ecPubTmpl), + &pub); + CHECK_RV(rv, "create EC public key", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, priv, setEcValue, 1); + CHECK_RV(rv, "set EC private value", CKR_ATTRIBUTE_READ_ONLY); + rv = sign_verify(session, CKM_ECDSA, priv, pub); + CHECK_RV(rv, "EC key still signs after rejected update", CKR_OK); + } + destroy_obj(session, &pub); + destroy_obj(session, &priv); +#endif + (void)rv; + (void)priv; + (void)pub; + (void)other; + (void)privClass; + (void)pubClass; +} + static int run_test(void) { CK_RV rv; @@ -425,6 +557,7 @@ static int run_test(void) #ifndef NO_DH test_dh_private_value_fixed(session); #endif + test_asym_private_fixed(session); } if (session != 0) { From 99262c00aa5fb017855f90da5866fa5f895810c2 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 15:59:01 -0700 Subject: [PATCH 136/204] F-13815 - Require the EC curve before importing EC key material --- src/internal.c | 5 +++ tests/key_material_update_test.c | 54 ++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/internal.c b/src/internal.c index fe4aa4df..ff954722 100644 --- a/src/internal.c +++ b/src/internal.c @@ -13292,6 +13292,11 @@ int WP11_Object_SetEcKey(WP11_Object* object, unsigned char** data, if (ret == 0) { if (ret == 0 && data[0] != NULL) ret = EcSetParams(key, data[0], (int)len[0]); + /* Key material is only meaningful on a known curve. */ + if (ret == 0 && key->dp == NULL && + (data[1] != NULL || data[2] != NULL)) { + ret = BAD_FUNC_ARG; + } if (ret == 0 && data[1] != NULL) { key->type = ECC_PRIVATEKEY_ONLY; #if defined(HAVE_FIPS_VERSION) && (HAVE_FIPS_VERSION <= 5) diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c index c6b368d4..97ee7433 100644 --- a/tests/key_material_update_test.c +++ b/tests/key_material_update_test.c @@ -535,6 +535,57 @@ static void test_asym_private_fixed(CK_SESSION_HANDLE session) (void)pubClass; } +#ifdef HAVE_ECC +/* EC key material is only accepted together with the curve, and an existing + * key's point cannot be replaced. */ +static void test_ec_point_needs_curve(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE ecType = CKK_EC; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_EC_POINT, ecc_p256_pub, sizeof(ecc_p256_pub) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + }; + CK_ATTRIBUTE privNoCurve[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, + }; + CK_ATTRIBUTE setPoint[] = { + { CKA_EC_POINT, ecc_p256_pub, sizeof(ecc_p256_pub) }, + }; + + /* Leave out CKA_EC_PARAMS. */ + rv = funcList->C_CreateObject(session, pubTmpl, 4, &key); + CHECK_TRUE(rv != CKR_OK, "EC public key without curve rejected"); + destroy_obj(session, &key); + rv = funcList->C_CreateObject(session, privNoCurve, + sizeof(privNoCurve) / sizeof(*privNoCurve), + &key); + CHECK_TRUE(rv != CKR_OK, "EC private key without curve rejected"); + destroy_obj(session, &key); + + rv = funcList->C_CreateObject(session, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), &key); + CHECK_RV(rv, "create EC public key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, key, setPoint, 1); + CHECK_RV(rv, "set EC point on existing key", CKR_ATTRIBUTE_READ_ONLY); + CHECK_TRUE(attr_equals(session, key, CKA_EC_PARAMS, ecc_p256_params, + sizeof(ecc_p256_params)), + "EC curve unchanged"); + } + destroy_obj(session, &key); +} +#endif + static int run_test(void) { CK_RV rv; @@ -558,6 +609,9 @@ static int run_test(void) test_dh_private_value_fixed(session); #endif test_asym_private_fixed(session); +#ifdef HAVE_ECC + test_ec_point_needs_curve(session); +#endif } if (session != 0) { From 5945e0605b71a894108606089a69f0d170332bc9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 16:10:54 -0700 Subject: [PATCH 137/204] F-5158 - Reject RSA modulus sizes larger than the supported maximum --- src/internal.c | 5 +++- tests/key_material_update_test.c | 40 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index ff954722..5d5e605b 100644 --- a/src/internal.c +++ b/src/internal.c @@ -13133,7 +13133,10 @@ int WP11_Object_SetRsaKey(WP11_Object* object, unsigned char** data, } } if (ret == 0) { - if (len[8] == sizeof(CK_ULONG)) + if (len[8] == sizeof(CK_ULONG) && + *(CK_ULONG*)data[8] > (CK_ULONG)RSA_MAX_SIZE) + ret = BAD_FUNC_ARG; + else if (len[8] == sizeof(CK_ULONG)) object->size = (word32)*(CK_ULONG*)data[8]; else if (len[8] != 0) ret = BUFFER_E; diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c index 97ee7433..8ae3b498 100644 --- a/tests/key_material_update_test.c +++ b/tests/key_material_update_test.c @@ -586,6 +586,43 @@ static void test_ec_point_needs_curve(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) +/* A modulus size the key generator cannot honour is rejected rather than + * narrowed to a different size. */ +static void test_rsa_modulus_bits_range(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_MECHANISM mech = { CKM_RSA_PKCS_KEY_PAIR_GEN, NULL, 0 }; + CK_ULONG bits; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_MODULUS_BITS, &bits, sizeof(bits) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + }; + + /* Only a 64-bit CK_ULONG can carry a size whose low 32 bits look valid. */ + if (sizeof(CK_ULONG) <= 4) + return; + bits = ((CK_ULONG)1 << 16 << 16) + 2048; + rv = funcList->C_GenerateKeyPair(session, &mech, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), + privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), + &pub, &priv); + CHECK_TRUE(rv != CKR_OK, "oversized modulus bits rejected"); + if (rv == CKR_OK) { + destroy_obj(session, &pub); + destroy_obj(session, &priv); + } +} +#endif + static int run_test(void) { CK_RV rv; @@ -611,6 +648,9 @@ static int run_test(void) test_asym_private_fixed(session); #ifdef HAVE_ECC test_ec_point_needs_curve(session); +#endif +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) + test_rsa_modulus_bits_range(session); #endif } From b055f078d6e1eb8eef792972ba5dc19e28944e97 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 16:31:18 -0700 Subject: [PATCH 138/204] F-12458 - Keep the certificate value when an update omits CKA_VALUE --- src/crypto.c | 1 + src/internal.c | 1 - tests/key_material_update_test.c | 41 ++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index 070470e6..09cef416 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -997,6 +997,7 @@ static CK_RV CheckAttributeUpdate(WP11_Session* session, WP11_Object* obj, * is a no-op. */ if (!newObject && (attr->type == CKA_CLASS || attr->type == CKA_KEY_TYPE || + attr->type == CKA_CERTIFICATE_TYPE || attr->type == CKA_LOCAL || attr->type == CKA_KEY_GEN_MECHANISM || attr->type == CKA_ALWAYS_SENSITIVE || diff --git a/src/internal.c b/src/internal.c index 5d5e605b..3cb967b2 100644 --- a/src/internal.c +++ b/src/internal.c @@ -13825,7 +13825,6 @@ int WP11_Object_SetCert(WP11_Object* object, unsigned char** data, WP11_Lock_LockRW(object->lock); cert = &object->data.cert; - cert->len = 0; /* First item is certificate type */ if (ret == 0 && data[0] != NULL && len[0] != (int)sizeof(CK_ULONG)) diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c index 8ae3b498..f9d230ba 100644 --- a/tests/key_material_update_test.c +++ b/tests/key_material_update_test.c @@ -623,6 +623,46 @@ static void test_rsa_modulus_bits_range(CK_SESSION_HANDLE session) } #endif +/* Restating the certificate type keeps the certificate value, and the type + * cannot be changed. */ +static void test_cert_type_update_keeps_value(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS certClass = CKO_CERTIFICATE; + CK_CERTIFICATE_TYPE certType = CKC_X_509; + CK_OBJECT_HANDLE cert = CK_INVALID_HANDLE; + byte certVal[48]; + CK_ATTRIBUTE certTmpl[] = { + { CKA_CLASS, &certClass, sizeof(certClass) }, + { CKA_CERTIFICATE_TYPE, &certType, sizeof(certType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, certVal, sizeof(certVal) }, + }; + CK_ATTRIBUTE setType[] = { + { CKA_CERTIFICATE_TYPE, &certType, sizeof(certType) }, + }; + CK_CERTIFICATE_TYPE otherType = CKC_X_509_ATTR_CERT; + CK_ATTRIBUTE setOtherType[] = { + { CKA_CERTIFICATE_TYPE, &otherType, sizeof(otherType) }, + }; + + XMEMSET(certVal, 0x30, sizeof(certVal)); + rv = funcList->C_CreateObject(session, certTmpl, + sizeof(certTmpl) / sizeof(*certTmpl), &cert); + CHECK_RV(rv, "create certificate", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SetAttributeValue(session, cert, setType, 1); + CHECK_RV(rv, "set certificate type", CKR_OK); + CHECK_TRUE(attr_equals(session, cert, CKA_VALUE, certVal, + sizeof(certVal)), + "certificate value kept after type update"); + rv = funcList->C_SetAttributeValue(session, cert, setOtherType, 1); + CHECK_RV(rv, "change certificate type", CKR_ATTRIBUTE_READ_ONLY); + } + destroy_obj(session, &cert); +} + static int run_test(void) { CK_RV rv; @@ -652,6 +692,7 @@ static int run_test(void) #if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) test_rsa_modulus_bits_range(session); #endif + test_cert_type_update_keeps_value(session); } if (session != 0) { From 6ceca6e904e39b711e0f34cb216167a36003b94d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 16:44:18 -0700 Subject: [PATCH 139/204] F-4528 - Allocate the new certificate value before freeing the old one --- src/internal.c | 19 ++++---- tests/key_material_update_test.c | 84 ++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/src/internal.c b/src/internal.c index 3cb967b2..440dcb58 100644 --- a/src/internal.c +++ b/src/internal.c @@ -13820,6 +13820,7 @@ int WP11_Object_SetCert(WP11_Object* object, unsigned char** data, { int ret = 0; WP11_Cert* cert; + byte* newData = NULL; if (object->onToken) WP11_Lock_LockRW(object->lock); @@ -13832,21 +13833,21 @@ int WP11_Object_SetCert(WP11_Object* object, unsigned char** data, if (ret == 0 && data[0] != NULL) cert->type = (word32)*(CK_ULONG*)data[0]; - /* Second item is certificate data (CKA_VALUE) */ + /* Second item is certificate data (CKA_VALUE). Copy it before releasing + * the current value so a failed allocation leaves the object intact. */ if (ret == 0 && data[1] != NULL) { - cert->len = (word32)len[1]; + newData = (byte*)XMALLOC(len[1], NULL, DYNAMIC_TYPE_CERT); + if (newData == NULL) + ret = MEMORY_E; + else + XMEMCPY(newData, data[1], len[1]); } if (ret == 0 && data[1] != NULL) { if (cert->data != NULL) { XFREE(cert->data, NULL, DYNAMIC_TYPE_CERT); } - cert->data = (byte *)XMALLOC(cert->len, NULL, DYNAMIC_TYPE_CERT); - if (cert->data == NULL) { - ret = MEMORY_E; - } - } - if (ret == 0 && data[1] != NULL) { - XMEMCPY(cert->data, data[1], cert->len); + cert->data = newData; + cert->len = (word32)len[1]; } if (object->onToken) diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c index f9d230ba..cf9841ad 100644 --- a/tests/key_material_update_test.c +++ b/tests/key_material_update_test.c @@ -27,6 +27,7 @@ #endif #include +#include #include #ifndef WOLFSSL_USER_SETTINGS @@ -34,6 +35,7 @@ #endif #include #include +#include #ifndef WOLFPKCS11_USER_SETTINGS #include @@ -49,6 +51,33 @@ #define TEST_DIR "./store/key_material_update_test" +#if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY) && \ + !defined(WOLFSSL_NO_MALLOC) && !defined(WOLFSSL_DEBUG_MEMORY) +#define KMU_ALLOC_HOOK +/* Allocation of exactly this size fails while armed. */ +#define FAIL_ALLOC_SIZE 101 +static int failAllocArmed = 0; + +static void* test_malloc(size_t n) +{ + if (failAllocArmed && n == FAIL_ALLOC_SIZE) + return NULL; + return malloc(n); +} + +static void test_free(void* p) +{ + free(p); +} + +static void* test_realloc(void* p, size_t n) +{ + if (failAllocArmed && n == FAIL_ALLOC_SIZE) + return NULL; + return realloc(p, n); +} +#endif + static CK_SLOT_ID slot = 0; static const char* soPin = "password123456"; static const char* userPin = "wolfpkcs11-test"; @@ -663,6 +692,52 @@ static void test_cert_type_update_keeps_value(CK_SESSION_HANDLE session) destroy_obj(session, &cert); } +#ifdef KMU_ALLOC_HOOK +/* A certificate value that cannot be stored leaves a consistent object, and + * an existing certificate's value cannot be replaced. */ +static void test_cert_value_failure_consistent(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS certClass = CKO_CERTIFICATE; + CK_CERTIFICATE_TYPE certType = CKC_X_509; + CK_OBJECT_HANDLE cert = CK_INVALID_HANDLE; + byte certVal[FAIL_ALLOC_SIZE]; + byte newVal[FAIL_ALLOC_SIZE]; + CK_ATTRIBUTE certTmpl[] = { + { CKA_CLASS, &certClass, sizeof(certClass) }, + { CKA_CERTIFICATE_TYPE, &certType, sizeof(certType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, certVal, sizeof(certVal) }, + }; + CK_ATTRIBUTE setValue[] = { { CKA_VALUE, newVal, sizeof(newVal) } }; + + XMEMSET(certVal, 0x30, sizeof(certVal)); + XMEMSET(newVal, 0x31, sizeof(newVal)); + + failAllocArmed = 1; + rv = funcList->C_CreateObject(session, certTmpl, + sizeof(certTmpl) / sizeof(*certTmpl), &cert); + failAllocArmed = 0; + CHECK_RV(rv, "certificate create out of memory", CKR_DEVICE_MEMORY); + destroy_obj(session, &cert); + + rv = funcList->C_CreateObject(session, certTmpl, + sizeof(certTmpl) / sizeof(*certTmpl), &cert); + CHECK_RV(rv, "create certificate", CKR_OK); + if (rv == CKR_OK) { + failAllocArmed = 1; + rv = funcList->C_SetAttributeValue(session, cert, setValue, 1); + failAllocArmed = 0; + CHECK_TRUE(rv != CKR_OK, "certificate value replacement rejected"); + CHECK_TRUE(attr_equals(session, cert, CKA_VALUE, certVal, + sizeof(certVal)), + "certificate value kept"); + } + destroy_obj(session, &cert); +} +#endif + static int run_test(void) { CK_RV rv; @@ -693,6 +768,9 @@ static int run_test(void) test_rsa_modulus_bits_range(session); #endif test_cert_type_update_keeps_value(session); +#ifdef KMU_ALLOC_HOOK + test_cert_value_failure_consistent(session); +#endif } if (session != 0) { @@ -714,6 +792,12 @@ int main(int argc, char* argv[]) #endif printf("=== wolfPKCS11 key material update test ===\n"); +#ifdef KMU_ALLOC_HOOK + if (wolfSSL_SetAllocators(test_malloc, test_free, test_realloc) != 0) { + fprintf(stderr, "FAIL: wolfSSL_SetAllocators\n"); + return 1; + } +#endif run_test(); return pkcs11_test_summary(); } From 1ae6bb5ad6509aba048e08834dd586c35270b9d0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 17:00:16 -0700 Subject: [PATCH 140/204] F-11501 - Drop the storage alias when replacing a loaded certificate value --- src/internal.c | 25 +++++++----- tests/key_material_update_test.c | 65 ++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 9 deletions(-) diff --git a/src/internal.c b/src/internal.c index 440dcb58..55172972 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3900,6 +3900,14 @@ int WP11_Object_Copy(WP11_Object *src, WP11_Object *dest) if (src == NULL || dest == NULL) return BAD_FUNC_ARG; + /* Not copyable; reject before any field is copied. */ + if (src->objClass == CKO_CERTIFICATE) + return BAD_FUNC_ARG; +#ifdef WOLFPKCS11_NSS + if (src->objClass == CKO_NSS_TRUST) + return BAD_FUNC_ARG; +#endif + /* We save data copying for the last step */ /* Copy the common mutable fields, and a data object's payload, under the @@ -3954,15 +3962,7 @@ int WP11_Object_Copy(WP11_Object *src, WP11_Object *dest) if (ret != 0) return ret; - if (src->objClass == CKO_CERTIFICATE) { - return BAD_FUNC_ARG; - } -#ifdef WOLFPKCS11_NSS - else if (src->objClass == CKO_NSS_TRUST) { - return BAD_FUNC_ARG; - } -#endif - else if (src->objClass == CKO_DATA) { + if (src->objClass == CKO_DATA) { /* Payload copied above under the source lock. */ } else { @@ -13843,6 +13843,13 @@ int WP11_Object_SetCert(WP11_Object* object, unsigned char** data, XMEMCPY(newData, data[1], len[1]); } if (ret == 0 && data[1] != NULL) { +#ifndef WOLFPKCS11_NO_STORE + /* A loaded certificate's value is the storage buffer itself. */ + if (cert->data != NULL && cert->data == object->keyData) { + object->keyData = NULL; + object->keyDataLen = 0; + } +#endif if (cert->data != NULL) { XFREE(cert->data, NULL, DYNAMIC_TYPE_CERT); } diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c index cf9841ad..63870677 100644 --- a/tests/key_material_update_test.c +++ b/tests/key_material_update_test.c @@ -738,6 +738,68 @@ static void test_cert_value_failure_consistent(CK_SESSION_HANDLE session) } #endif +#ifndef WOLFPKCS11_NO_STORE +/* A certificate loaded from the token keeps its value through a rejected + * replacement and a rejected copy, and persists unchanged. */ +static void test_loaded_cert_value_fixed(CK_SESSION_HANDLE* session) +{ + CK_RV rv; + CK_OBJECT_CLASS certClass = CKO_CERTIFICATE; + CK_CERTIFICATE_TYPE certType = CKC_X_509; + CK_OBJECT_HANDLE cert = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE copy = CK_INVALID_HANDLE; + static const char certLabel[] = "kmu-token-cert"; + byte certVal[64]; + byte newVal[80]; + CK_ATTRIBUTE certTmpl[] = { + { CKA_CLASS, &certClass, sizeof(certClass) }, + { CKA_CERTIFICATE_TYPE, &certType, sizeof(certType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_LABEL, (void*)certLabel, sizeof(certLabel) - 1 }, + { CKA_VALUE, certVal, sizeof(certVal) }, + }; + CK_ATTRIBUTE setValue[] = { { CKA_VALUE, newVal, sizeof(newVal) } }; + + XMEMSET(certVal, 0x30, sizeof(certVal)); + XMEMSET(newVal, 0x31, sizeof(newVal)); + rv = funcList->C_CreateObject(*session, certTmpl, + sizeof(certTmpl) / sizeof(*certTmpl), &cert); + CHECK_RV(rv, "create token certificate", CKR_OK); + if (rv != CKR_OK) + return; + + rv = reload_token(session, 1); + CHECK_RV(rv, "reload token with certificate", CKR_OK); + if (rv != CKR_OK) + return; + cert = find_by_label(*session, certLabel); + CHECK_TRUE(cert != CK_INVALID_HANDLE, "find loaded certificate"); + if (cert == CK_INVALID_HANDLE) + return; + + rv = funcList->C_SetAttributeValue(*session, cert, setValue, 1); + CHECK_RV(rv, "replace loaded certificate value", CKR_ATTRIBUTE_READ_ONLY); + rv = funcList->C_CopyObject(*session, cert, NULL, 0, ©); + CHECK_TRUE(rv != CKR_OK, "copy of certificate rejected"); + destroy_obj(*session, ©); + CHECK_TRUE(attr_equals(*session, cert, CKA_VALUE, certVal, + sizeof(certVal)), + "loaded certificate value kept"); + + rv = reload_token(session, 1); + CHECK_RV(rv, "reload token again", CKR_OK); + if (rv != CKR_OK) + return; + cert = find_by_label(*session, certLabel); + CHECK_TRUE(cert != CK_INVALID_HANDLE && + attr_equals(*session, cert, CKA_VALUE, certVal, + sizeof(certVal)), + "persisted certificate value kept"); + destroy_obj(*session, &cert); +} +#endif + static int run_test(void) { CK_RV rv; @@ -770,6 +832,9 @@ static int run_test(void) test_cert_type_update_keeps_value(session); #ifdef KMU_ALLOC_HOOK test_cert_value_failure_consistent(session); +#endif +#ifndef WOLFPKCS11_NO_STORE + test_loaded_cert_value_fixed(&session); #endif } From edbae0f5d7bf1d9fe0b2e86f54807af9ea201d4c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 17:13:20 -0700 Subject: [PATCH 141/204] F-4318 - Reject DH domain parameters larger than the supported key size --- src/internal.c | 8 +++++++- tests/key_material_update_test.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 55172972..896023c3 100644 --- a/src/internal.c +++ b/src/internal.c @@ -13505,9 +13505,15 @@ int WP11_Object_SetDhKey(WP11_Object* object, unsigned char** data, key = object->data.dhKey; ret = wc_InitDhKey_ex(&key->params, NULL, object->devId); if (ret == 0) { - if (data[0] != NULL && data[1] != NULL) + /* Parameters larger than any storable DH key are not supported. */ + if (data[0] != NULL && data[1] != NULL && + (len[0] > WP11_MAX_DH_KEY_SZ || len[1] > WP11_MAX_DH_KEY_SZ)) { + ret = BAD_FUNC_ARG; + } + else if (data[0] != NULL && data[1] != NULL) { ret = wc_DhSetKey(&key->params, data[0], (int)len[0], data[1], (int)len[1]); + } if (ret == 0 && data[2] != NULL) { if (len[2] > (int)sizeof(key->key)) ret = BAD_FUNC_ARG; diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c index 63870677..14166bd0 100644 --- a/tests/key_material_update_test.c +++ b/tests/key_material_update_test.c @@ -800,6 +800,35 @@ static void test_loaded_cert_value_fixed(CK_SESSION_HANDLE* session) } #endif +#ifndef NO_DH +/* Domain parameters larger than any supported DH key are rejected. */ +static void test_dh_param_size_range(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE dhType = CKK_DH; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + static byte bigPrime[2049]; + byte base[1] = { 0x02 }; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &dhType, sizeof(dhType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIME, bigPrime, sizeof(bigPrime) }, + { CKA_BASE, base, sizeof(base) }, + { CKA_VALUE, base, sizeof(base) }, + }; + + XMEMSET(bigPrime, 0xff, sizeof(bigPrime)); + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &key); + CHECK_TRUE(rv != CKR_OK, "oversized DH prime rejected"); + if (rv == CKR_OK) + destroy_obj(session, &key); +} +#endif + static int run_test(void) { CK_RV rv; @@ -835,6 +864,9 @@ static int run_test(void) #endif #ifndef WOLFPKCS11_NO_STORE test_loaded_cert_value_fixed(&session); +#endif +#ifndef NO_DH + test_dh_param_size_range(session); #endif } From a9fc91448288585a38910509b5902e74302cb759 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:38 -0700 Subject: [PATCH 142/204] Return template errors for bad RSA, EC and DH keys --- src/crypto.c | 11 +++++++++++ tests/key_material_update_test.c | 10 ++++++---- tests/pkcs11mtt.c | 2 +- tests/pkcs11test.c | 2 +- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 09cef416..1d6e8e05 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1219,11 +1219,19 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, #ifndef NO_RSA case CKK_RSA: ret = WP11_Object_SetRsaKey(obj, data, len); + /* A modulus size or component the key cannot take. */ + if (ret == BAD_FUNC_ARG) + return CKR_ATTRIBUTE_VALUE_INVALID; break; #endif #ifdef HAVE_ECC case CKK_EC: ret = WP11_Object_SetEcKey(obj, data, len); + /* Key material without CKA_EC_PARAMS, or a bad value. */ + if (ret == BAD_FUNC_ARG && data[0] == NULL) + return CKR_TEMPLATE_INCOMPLETE; + if (ret == BAD_FUNC_ARG) + return CKR_ATTRIBUTE_VALUE_INVALID; break; #endif #ifdef WOLFPKCS11_MLDSA @@ -1234,6 +1242,9 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, #ifndef NO_DH case CKK_DH: ret = WP11_Object_SetDhKey(obj, data, len); + /* Parameters or a value larger than the key can hold. */ + if (ret == BAD_FUNC_ARG) + return CKR_ATTRIBUTE_VALUE_INVALID; break; #endif #ifdef WOLFPKCS11_MLKEM diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c index 14166bd0..093731d0 100644 --- a/tests/key_material_update_test.c +++ b/tests/key_material_update_test.c @@ -593,12 +593,14 @@ static void test_ec_point_needs_curve(CK_SESSION_HANDLE session) /* Leave out CKA_EC_PARAMS. */ rv = funcList->C_CreateObject(session, pubTmpl, 4, &key); - CHECK_TRUE(rv != CKR_OK, "EC public key without curve rejected"); + CHECK_RV(rv, "EC public key without curve rejected", + CKR_TEMPLATE_INCOMPLETE); destroy_obj(session, &key); rv = funcList->C_CreateObject(session, privNoCurve, sizeof(privNoCurve) / sizeof(*privNoCurve), &key); - CHECK_TRUE(rv != CKR_OK, "EC private key without curve rejected"); + CHECK_RV(rv, "EC private key without curve rejected", + CKR_TEMPLATE_INCOMPLETE); destroy_obj(session, &key); rv = funcList->C_CreateObject(session, pubTmpl, @@ -644,7 +646,7 @@ static void test_rsa_modulus_bits_range(CK_SESSION_HANDLE session) privTmpl, sizeof(privTmpl) / sizeof(*privTmpl), &pub, &priv); - CHECK_TRUE(rv != CKR_OK, "oversized modulus bits rejected"); + CHECK_RV(rv, "oversized modulus bits rejected", CKR_ATTRIBUTE_VALUE_INVALID); if (rv == CKR_OK) { destroy_obj(session, &pub); destroy_obj(session, &priv); @@ -823,7 +825,7 @@ static void test_dh_param_size_range(CK_SESSION_HANDLE session) XMEMSET(bigPrime, 0xff, sizeof(bigPrime)); rv = funcList->C_CreateObject(session, keyTmpl, sizeof(keyTmpl) / sizeof(*keyTmpl), &key); - CHECK_TRUE(rv != CKR_OK, "oversized DH prime rejected"); + CHECK_RV(rv, "oversized DH prime rejected", CKR_ATTRIBUTE_VALUE_INVALID); if (rv == CKR_OK) destroy_obj(session, &key); } diff --git a/tests/pkcs11mtt.c b/tests/pkcs11mtt.c index 4609e653..5fbdbb40 100644 --- a/tests/pkcs11mtt.c +++ b/tests/pkcs11mtt.c @@ -4052,7 +4052,7 @@ static CK_RV test_ecc_create_key_fail(void* args) ecc_p256_priv_key[3].ulValueLen = sizeof(paramsBadOid); ret = funcList->C_CreateObject(session, ecc_p256_priv_key, ecc_p256_priv_key_cnt, &obj); - CHECK_CKR_FAIL(ret, CKR_FUNCTION_FAILED, + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_VALUE_INVALID, "EC Private Key Create Object - bad OID in params"); ecc_p256_priv_key[3].pValue = ecc_p256_params; ecc_p256_priv_key[3].ulValueLen = sizeof(ecc_p256_params); diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index 65b467a6..155c0a0d 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -9877,7 +9877,7 @@ static CK_RV test_ecc_create_key_fail(void* args) ecc_p256_priv_key[3].ulValueLen = sizeof(paramsBadOid); ret = funcList->C_CreateObject(session, ecc_p256_priv_key, ecc_p256_priv_key_cnt, &obj); - CHECK_CKR_FAIL(ret, CKR_FUNCTION_FAILED, + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_VALUE_INVALID, "EC Private Key Create Object - bad OID in params"); ecc_p256_priv_key[3].pValue = ecc_p256_params; ecc_p256_priv_key[3].ulValueLen = sizeof(ecc_p256_params); From 310fe73b22b483eb704c8604eeab446fed8b82bb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:38 -0700 Subject: [PATCH 143/204] Test the secret key setter through a derive --- src/crypto.c | 3 ++ tests/key_material_update_test.c | 62 +++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index 1d6e8e05..5de022ba 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1285,6 +1285,9 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, #endif case CKK_GENERIC_SECRET: ret = WP11_Object_SetSecretKey(obj, data, len); + /* A length the key type cannot take. */ + if (ret == BAD_FUNC_ARG) + return CKR_ATTRIBUTE_VALUE_INVALID; break; default: break; diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c index 093731d0..33d16424 100644 --- a/tests/key_material_update_test.c +++ b/tests/key_material_update_test.c @@ -335,7 +335,64 @@ static void test_token_key_value_fixed(CK_SESSION_HANDLE* session) check_token_secret_fixed(*session); } -/* A rejected secret key update leaves the existing key usable. */ +#if defined(HAVE_ECC) && !defined(NO_AES) && !defined(WOLFPKCS11_NSS) +/* A derive hands the new key to WP11_Object_SetSecretKey directly, so a + * length the key type cannot take is rejected by the setter itself. */ +static void test_derive_invalid_secret_length(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE ecType = CKK_EC; + CK_KEY_TYPE aesType = CKK_AES; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_ECDH1_DERIVE_PARAMS params; + CK_MECHANISM mech = { CKM_ECDH1_DERIVE, NULL, 0 }; + CK_ULONG badLen = 17; + CK_ATTRIBUTE baseTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, + }; + CK_ATTRIBUTE deriveTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE_LEN, &badLen, sizeof(badLen) }, + }; + + XMEMSET(¶ms, 0, sizeof(params)); + params.kdf = CKD_NULL; + params.pPublicData = ecc_p256_point; + params.ulPublicDataLen = sizeof(ecc_p256_point); + mech.pParameter = ¶ms; + mech.ulParameterLen = sizeof(params); + + rv = funcList->C_CreateObject(session, baseTmpl, + sizeof(baseTmpl) / sizeof(*baseTmpl), &base); + CHECK_RV(rv, "create EC base key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_DeriveKey(session, &mech, base, deriveTmpl, + sizeof(deriveTmpl) / sizeof(*deriveTmpl), + &derived); + CHECK_RV(rv, "derive an AES key with an invalid length", + CKR_ATTRIBUTE_VALUE_INVALID); + if (rv == CKR_OK) + destroy_obj(session, &derived); + } + destroy_obj(session, &base); +} +#endif + +/* The read-only gate in SetAttributeValue rejects a secret key update before + * WP11_Object_SetSecretKey runs, so the existing key is left untouched. */ static void test_rejected_secret_update_keeps_key(CK_SESSION_HANDLE session) { CK_RV rv; @@ -849,6 +906,9 @@ static int run_test(void) } if (rv == CKR_OK) { test_token_key_value_fixed(&session); +#if defined(HAVE_ECC) && !defined(NO_AES) && !defined(WOLFPKCS11_NSS) + test_derive_invalid_secret_length(session); +#endif test_rejected_secret_update_keeps_key(session); #ifndef NO_DH test_dh_private_value_fixed(session); From 03bef3f82b9294bb4692bb22e86e1fe71533faa4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:39 -0700 Subject: [PATCH 144/204] Describe key material tests as read-only gate tests --- tests/key_material_update_test.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/key_material_update_test.c b/tests/key_material_update_test.c index 33d16424..a41092f6 100644 --- a/tests/key_material_update_test.c +++ b/tests/key_material_update_test.c @@ -752,8 +752,9 @@ static void test_cert_type_update_keeps_value(CK_SESSION_HANDLE session) } #ifdef KMU_ALLOC_HOOK -/* A certificate value that cannot be stored leaves a consistent object, and - * an existing certificate's value cannot be replaced. */ +/* A certificate value that cannot be allocated at creation leaves no object, + * which reaches WP11_Object_SetCert directly. Replacing the value of an + * existing certificate stops at the read-only gate before the setter runs. */ static void test_cert_value_failure_consistent(CK_SESSION_HANDLE session) { CK_RV rv; @@ -785,10 +786,9 @@ static void test_cert_value_failure_consistent(CK_SESSION_HANDLE session) sizeof(certTmpl) / sizeof(*certTmpl), &cert); CHECK_RV(rv, "create certificate", CKR_OK); if (rv == CKR_OK) { - failAllocArmed = 1; rv = funcList->C_SetAttributeValue(session, cert, setValue, 1); - failAllocArmed = 0; - CHECK_TRUE(rv != CKR_OK, "certificate value replacement rejected"); + CHECK_RV(rv, "certificate value replacement stops at the gate", + CKR_ATTRIBUTE_READ_ONLY); CHECK_TRUE(attr_equals(session, cert, CKA_VALUE, certVal, sizeof(certVal)), "certificate value kept"); @@ -798,8 +798,9 @@ static void test_cert_value_failure_consistent(CK_SESSION_HANDLE session) #endif #ifndef WOLFPKCS11_NO_STORE -/* A certificate loaded from the token keeps its value through a rejected - * replacement and a rejected copy, and persists unchanged. */ +/* The read-only gate rejects replacing a loaded certificate's value before + * WP11_Object_SetCert can touch the storage buffer, a copy of it is rejected, + * and the value persists unchanged. */ static void test_loaded_cert_value_fixed(CK_SESSION_HANDLE* session) { CK_RV rv; From 540672ca6ba3097ee8380733e912e2220b1a5a5f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 08:56:55 -0700 Subject: [PATCH 145/204] F-6660 - Require a certificate value when creating a certificate object --- src/crypto.c | 45 ++++++++ tests/include.am | 7 ++ tests/object_create_getattr_test.c | 172 +++++++++++++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 tests/object_create_getattr_test.c diff --git a/src/crypto.c b/src/crypto.c index 5de022ba..b1bd758d 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1686,6 +1686,45 @@ static CK_RV CreateObject(WP11_Session* session, CK_ATTRIBUTE_PTR pTemplate, return rv; } +/* Find an attribute that must be supplied with a non-empty value. */ +static CK_RV RequireAttribute(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, + CK_ATTRIBUTE_TYPE type) +{ + CK_ATTRIBUTE* attr; + + FindAttributeType(pTemplate, ulCount, type, &attr); + if (attr == NULL) + return CKR_TEMPLATE_INCOMPLETE; + if (attr->pValue == NULL || attr->ulValueLen == 0) + return CKR_ATTRIBUTE_VALUE_INVALID; + return CKR_OK; +} + +/* Attributes required at C_CreateObject. Unwrap, derive and decapsulate also + * use CreateObject but supply the key value afterwards. A malformed class is + * left for CreateObject to report. */ +static CK_RV CheckCreateRequiredAttributes(CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulCount) +{ + CK_RV rv = CKR_OK; + CK_ATTRIBUTE* attr; + CK_OBJECT_CLASS objectClass; + + FindAttributeType(pTemplate, ulCount, CKA_CLASS, &attr); + if (attr == NULL || attr->pValue == NULL || + attr->ulValueLen != sizeof(CK_OBJECT_CLASS)) { + return CKR_OK; + } + objectClass = *(CK_OBJECT_CLASS*)attr->pValue; + + if (objectClass == CKO_CERTIFICATE) { + /* CKA_URL is not stored, so a certificate needs its value. */ + rv = RequireAttribute(pTemplate, ulCount, CKA_VALUE); + } + + return rv; +} + /** * Create an object in the session or on the token associated with the session. * @@ -1770,6 +1809,12 @@ static CK_RV wp11_C_CreateObject(CK_SESSION_HANDLE hSession, return rv; } + rv = CheckCreateRequiredAttributes(pTemplate, ulCount); + 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); diff --git a/tests/include.am b/tests/include.am index 28118193..11f79cd0 100644 --- a/tests/include.am +++ b/tests/include.am @@ -360,6 +360,11 @@ noinst_PROGRAMS += tests/key_material_update_test tests_key_material_update_test_SOURCES = tests/key_material_update_test.c tests_key_material_update_test_LDADD = +check_PROGRAMS += tests/object_create_getattr_test +noinst_PROGRAMS += tests/object_create_getattr_test +tests_object_create_getattr_test_SOURCES = tests/object_create_getattr_test.c +tests_object_create_getattr_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -433,6 +438,7 @@ tests_session_op_lifecycle_test_LDADD += src/libwolfpkcs11.la tests_info_mech_table_test_LDADD += src/libwolfpkcs11.la tests_attribute_validation_test_LDADD += src/libwolfpkcs11.la tests_key_material_update_test_LDADD += src/libwolfpkcs11.la +tests_object_create_getattr_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -498,6 +504,7 @@ tests_session_op_lifecycle_test_LDADD += src/libwolfpkcs11.la tests_info_mech_table_test_LDADD += src/libwolfpkcs11.la tests_attribute_validation_test_LDADD += src/libwolfpkcs11.la tests_key_material_update_test_LDADD += src/libwolfpkcs11.la +tests_object_create_getattr_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ diff --git a/tests/object_create_getattr_test.c b/tests/object_create_getattr_test.c new file mode 100644 index 00000000..a491d50e --- /dev/null +++ b/tests/object_create_getattr_test.c @@ -0,0 +1,172 @@ +/* object_create_getattr_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Objects created with C_CreateObject carry the attributes PKCS#11 requires, + * and attributes read back with C_GetAttributeValue follow the PKCS#11 + * length and error conventions. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#define TEST_DIR "./store/object_create_getattr_test" + +static CK_BBOOL ckFalse = CK_FALSE; +static const byte certValue[] = { 0x30, 0x82, 0x01, 0x00 }; +static const byte certSubject[] = { 0x30, 0x00 }; +static const char certUrl[] = "http://example.com/cert.der"; + +static void destroy_obj(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* obj) +{ + if (*obj != CK_INVALID_HANDLE) { + funcList->C_DestroyObject(session, *obj); + *obj = CK_INVALID_HANDLE; + } +} + +/* Create an object, expecting rv, and destroy anything that was created. */ +static void expect_create(CK_SESSION_HANDLE session, CK_ATTRIBUTE* tmpl, + CK_ULONG cnt, CK_RV expected, const char* name) +{ + CK_RV rv; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + + rv = funcList->C_CreateObject(session, tmpl, cnt, &obj); + CHECK_RV(rv, name, expected); + if (rv == CKR_OK) + destroy_obj(session, &obj); +} + +/* A certificate object needs its encoding. */ +static void test_cert_requires_value(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS certClass = CKO_CERTIFICATE; + CK_CERTIFICATE_TYPE certType = CKC_X_509; + CK_CERTIFICATE_TYPE attrCertType = CKC_X_509_ATTR_CERT; + CK_ATTRIBUTE noValue[] = { + { CKA_CLASS, &certClass, sizeof(certClass) }, + { CKA_CERTIFICATE_TYPE, &certType, sizeof(certType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_SUBJECT, (void*)certSubject, sizeof(certSubject) }, + }; + CK_ATTRIBUTE attrCertNoValue[] = { + { CKA_CLASS, &certClass, sizeof(certClass) }, + { CKA_CERTIFICATE_TYPE, &attrCertType, sizeof(attrCertType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE emptyValue[] = { + { CKA_CLASS, &certClass, sizeof(certClass) }, + { CKA_CERTIFICATE_TYPE, &certType, sizeof(certType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_SUBJECT, (void*)certSubject, sizeof(certSubject) }, + { CKA_VALUE, (void*)certValue, 0 }, + }; + CK_ATTRIBUTE urlOnly[] = { + { CKA_CLASS, &certClass, sizeof(certClass) }, + { CKA_CERTIFICATE_TYPE, &certType, sizeof(certType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_SUBJECT, (void*)certSubject, sizeof(certSubject) }, + { CKA_VALUE, (void*)certValue, 0 }, + { CKA_URL, (void*)certUrl, sizeof(certUrl) - 1 }, + }; + CK_ATTRIBUTE complete[] = { + { CKA_CLASS, &certClass, sizeof(certClass) }, + { CKA_CERTIFICATE_TYPE, &certType, sizeof(certType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_SUBJECT, (void*)certSubject, sizeof(certSubject) }, + { CKA_VALUE, (void*)certValue, sizeof(certValue) }, + }; + + expect_create(session, noValue, sizeof(noValue) / sizeof(*noValue), + CKR_TEMPLATE_INCOMPLETE, "certificate without CKA_VALUE"); + expect_create(session, attrCertNoValue, + sizeof(attrCertNoValue) / sizeof(*attrCertNoValue), + CKR_TEMPLATE_INCOMPLETE, + "attribute certificate without CKA_VALUE"); + expect_create(session, emptyValue, + sizeof(emptyValue) / sizeof(*emptyValue), + CKR_ATTRIBUTE_VALUE_INVALID, + "certificate with empty CKA_VALUE"); + expect_create(session, urlOnly, sizeof(urlOnly) / sizeof(*urlOnly), + CKR_ATTRIBUTE_VALUE_INVALID, + "certificate located only by CKA_URL"); + expect_create(session, complete, sizeof(complete) / sizeof(*complete), + CKR_OK, "complete certificate"); +} + +static int run_test(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = 0; + + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv != CKR_OK) + return -1; + + rv = pkcs11_open_session(&session); + CHECK_RV(rv, "open session", CKR_OK); + if (rv == CKR_OK) { + test_cert_requires_value(session); + } + + if (session != 0) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); + pkcs11_unload(); + return 0; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + + printf("=== wolfPKCS11 object create and get attribute test ===\n"); + run_test(); + return pkcs11_test_summary(); +} From fa2d47871eeded6944a4d4f9237ede5d1985a0f8 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:07:26 -0700 Subject: [PATCH 146/204] F-6661 - Require a key value when creating a secret key object --- src/crypto.c | 15 +++++--- tests/object_create_getattr_test.c | 61 ++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index b1bd758d..76eba4b4 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1700,9 +1700,9 @@ static CK_RV RequireAttribute(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, return CKR_OK; } -/* Attributes required at C_CreateObject. Unwrap, derive and decapsulate also - * use CreateObject but supply the key value afterwards. A malformed class is - * left for CreateObject to report. */ +/* Attributes required at C_CreateObject, checked once CreateObject has + * validated the template. Unwrap, derive and decapsulate also use CreateObject + * but supply the key value afterwards. */ static CK_RV CheckCreateRequiredAttributes(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) { @@ -1721,6 +1721,9 @@ static CK_RV CheckCreateRequiredAttributes(CK_ATTRIBUTE_PTR pTemplate, /* CKA_URL is not stored, so a certificate needs its value. */ rv = RequireAttribute(pTemplate, ulCount, CKA_VALUE); } + else if (objectClass == CKO_SECRET_KEY) { + rv = RequireAttribute(pTemplate, ulCount, CKA_VALUE); + } return rv; } @@ -1809,14 +1812,14 @@ static CK_RV wp11_C_CreateObject(CK_SESSION_HANDLE hSession, return rv; } - rv = CheckCreateRequiredAttributes(pTemplate, ulCount); + rv = CreateObject(session, pTemplate, ulCount, &object); if (rv != CKR_OK) { WOLFPKCS11_LEAVE("C_CreateObject", rv); return rv; } - - rv = CreateObject(session, pTemplate, ulCount, &object); + rv = CheckCreateRequiredAttributes(pTemplate, ulCount); if (rv != CKR_OK) { + WP11_Object_Free(object); WOLFPKCS11_LEAVE("C_CreateObject", rv); return rv; } diff --git a/tests/object_create_getattr_test.c b/tests/object_create_getattr_test.c index a491d50e..5cdf8f3b 100644 --- a/tests/object_create_getattr_test.c +++ b/tests/object_create_getattr_test.c @@ -55,6 +55,10 @@ static CK_BBOOL ckFalse = CK_FALSE; static const byte certValue[] = { 0x30, 0x82, 0x01, 0x00 }; static const byte certSubject[] = { 0x30, 0x00 }; static const char certUrl[] = "http://example.com/cert.der"; +static const byte secretValue[] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff +}; static void destroy_obj(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* obj) { @@ -134,6 +138,62 @@ static void test_cert_requires_value(CK_SESSION_HANDLE session) CKR_OK, "complete certificate"); } +/* A secret key object needs its key value. */ +static void test_secret_key_requires_value(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; + CK_KEY_TYPE genericType = CKK_GENERIC_SECRET; + CK_ULONG valueLen = sizeof(secretValue); + CK_ATTRIBUTE noValue[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE lenOnly[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE_LEN, &valueLen, sizeof(valueLen) }, + }; + CK_ATTRIBUTE emptyValue[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, (void*)secretValue, 0 }, + }; + CK_ATTRIBUTE complete[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, (void*)secretValue, sizeof(secretValue) }, + }; +#ifndef NO_AES + CK_KEY_TYPE aesType = CKK_AES; + CK_ATTRIBUTE aesNoValue[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; +#endif + + expect_create(session, noValue, sizeof(noValue) / sizeof(*noValue), + CKR_TEMPLATE_INCOMPLETE, "secret key without CKA_VALUE"); + expect_create(session, lenOnly, sizeof(lenOnly) / sizeof(*lenOnly), + CKR_TEMPLATE_INCOMPLETE, + "secret key with only CKA_VALUE_LEN"); + expect_create(session, emptyValue, + sizeof(emptyValue) / sizeof(*emptyValue), + CKR_ATTRIBUTE_VALUE_INVALID, + "secret key with empty CKA_VALUE"); +#ifndef NO_AES + expect_create(session, aesNoValue, + sizeof(aesNoValue) / sizeof(*aesNoValue), + CKR_TEMPLATE_INCOMPLETE, "AES key without CKA_VALUE"); +#endif + expect_create(session, complete, sizeof(complete) / sizeof(*complete), + CKR_OK, "complete secret key"); +} + static int run_test(void) { CK_RV rv; @@ -148,6 +208,7 @@ static int run_test(void) CHECK_RV(rv, "open session", CKR_OK); if (rv == CKR_OK) { test_cert_requires_value(session); + test_secret_key_requires_value(session); } if (session != 0) From e7905c82ad61974a26fc9d2dd8cf750e62b1500c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:58:43 -0700 Subject: [PATCH 147/204] F-11507 - Require RSA and EC key material when creating key objects --- src/crypto.c | 117 +++++++++++++-- tests/object_create_getattr_test.c | 227 +++++++++++++++++++++++++++++ tests/pkcs11mtt.c | 20 ++- tests/pkcs11test.c | 20 ++- 4 files changed, 366 insertions(+), 18 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 76eba4b4..80a71660 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1700,15 +1700,18 @@ static CK_RV RequireAttribute(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, return CKR_OK; } -/* Attributes required at C_CreateObject, checked once CreateObject has - * validated the template. Unwrap, derive and decapsulate also use CreateObject - * but supply the key value afterwards. */ +/* Attributes required at C_CreateObject, and a secret key class needs a secret + * key type. Unwrap, derive and decapsulate also use CreateObject but supply the + * key value afterwards. A malformed or unsupported class or key type is left + * for CreateObject to report. */ static CK_RV CheckCreateRequiredAttributes(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) { CK_RV rv = CKR_OK; CK_ATTRIBUTE* attr; CK_OBJECT_CLASS objectClass; + CK_KEY_TYPE keyType; + int secretType; FindAttributeType(pTemplate, ulCount, CKA_CLASS, &attr); if (attr == NULL || attr->pValue == NULL || @@ -1720,10 +1723,102 @@ static CK_RV CheckCreateRequiredAttributes(CK_ATTRIBUTE_PTR pTemplate, if (objectClass == CKO_CERTIFICATE) { /* CKA_URL is not stored, so a certificate needs its value. */ rv = RequireAttribute(pTemplate, ulCount, CKA_VALUE); + return rv; + } + + FindAttributeType(pTemplate, ulCount, CKA_KEY_TYPE, &attr); + if (attr == NULL || attr->pValue == NULL || + attr->ulValueLen != sizeof(CK_KEY_TYPE)) { + return CKR_OK; + } + keyType = *(CK_KEY_TYPE*)attr->pValue; + + switch (keyType) { + case CKK_GENERIC_SECRET: + #ifndef NO_AES + case CKK_AES: + #endif + #ifdef WOLFPKCS11_HKDF + case CKK_HKDF: + #endif + secretType = 1; + break; + #ifndef NO_RSA + case CKK_RSA: + #endif + #ifdef HAVE_ECC + case CKK_EC: + #endif + #ifndef NO_DH + case CKK_DH: + #endif + #ifdef WOLFPKCS11_MLDSA + case CKK_ML_DSA: + #endif + #ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + #endif + #ifdef WOLFPKCS11_LMS + case CKK_HSS: + #endif + #ifdef WOLFPKCS11_XMSS + case CKK_XMSS: + case CKK_XMSSMT: + #endif + secretType = 0; + break; + default: + return CKR_OK; } - else if (objectClass == CKO_SECRET_KEY) { + + if (objectClass == CKO_SECRET_KEY) { + if (!secretType) + return CKR_TEMPLATE_INCONSISTENT; rv = RequireAttribute(pTemplate, ulCount, CKA_VALUE); } + else if (objectClass == CKO_PUBLIC_KEY || objectClass == CKO_PRIVATE_KEY) { + switch (keyType) { + #ifndef NO_RSA + case CKK_RSA: + FindAttributeType(pTemplate, ulCount, CKA_MODULUS, &attr); + if (objectClass == CKO_PUBLIC_KEY) { + rv = RequireAttribute(pTemplate, ulCount, CKA_MODULUS); + if (rv == CKR_OK) { + rv = RequireAttribute(pTemplate, ulCount, + CKA_PUBLIC_EXPONENT); + } + } + else if (attr == NULL) { + /* The modulus is computed from the primes. */ + rv = RequireAttribute(pTemplate, ulCount, CKA_PRIME_1); + if (rv == CKR_OK) { + rv = RequireAttribute(pTemplate, ulCount, + CKA_PRIME_2); + } + } + else { + rv = RequireAttribute(pTemplate, ulCount, CKA_MODULUS); + } + if (rv == CKR_OK && objectClass == CKO_PRIVATE_KEY) { + rv = RequireAttribute(pTemplate, ulCount, + CKA_PRIVATE_EXPONENT); + } + break; + #endif + #ifdef HAVE_ECC + case CKK_EC: + rv = RequireAttribute(pTemplate, ulCount, CKA_EC_PARAMS); + if (rv == CKR_OK) { + rv = RequireAttribute(pTemplate, ulCount, + (objectClass == CKO_PUBLIC_KEY) ? + CKA_EC_POINT : CKA_VALUE); + } + break; + #endif + default: + break; + } + } return rv; } @@ -1737,9 +1832,11 @@ static CK_RV CheckCreateRequiredAttributes(CK_ATTRIBUTE_PTR pTemplate, * @param phObject [out] Handle of object created. * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SESSION_HANDLE_INVALID when session handle is not valid. - * CKR_ARGUMENTS_BAD when pTemplate or phObject is NULL. + * CKR_ARGUMENTS_BAD when pTemplate or phObject is NULL, or ulCount + * is too large. * CKR_SESSION_READ_ONLY when the session cannot create objects. - * CKR_TEMPLATE_INCOMPLETE when CKA_KEY_TYPE is missing. + * CKR_TEMPLATE_INCOMPLETE when CKA_KEY_TYPE or an attribute required + * for the object is missing. * CKR_ATTRIBUTE_VALUE_INVALID when an attribute has invalid value or * length. * CKR_DEVICE_MEMORY when dynamic memory allocation fails. @@ -1772,7 +1869,7 @@ static CK_RV wp11_C_CreateObject(CK_SESSION_HANDLE hSession, WOLFPKCS11_LEAVE("C_CreateObject", rv); return rv; } - if (pTemplate == NULL || phObject == NULL) { + if (pTemplate == NULL || phObject == NULL || ulCount > (CK_ULONG)INT_MAX) { rv = CKR_ARGUMENTS_BAD; WOLFPKCS11_LEAVE("C_CreateObject", rv); return rv; @@ -1812,14 +1909,14 @@ static CK_RV wp11_C_CreateObject(CK_SESSION_HANDLE hSession, return rv; } - rv = CreateObject(session, pTemplate, ulCount, &object); + rv = CheckCreateRequiredAttributes(pTemplate, ulCount); if (rv != CKR_OK) { WOLFPKCS11_LEAVE("C_CreateObject", rv); return rv; } - rv = CheckCreateRequiredAttributes(pTemplate, ulCount); + + rv = CreateObject(session, pTemplate, ulCount, &object); if (rv != CKR_OK) { - WP11_Object_Free(object); WOLFPKCS11_LEAVE("C_CreateObject", rv); return rv; } diff --git a/tests/object_create_getattr_test.c b/tests/object_create_getattr_test.c index 5cdf8f3b..3a40f43f 100644 --- a/tests/object_create_getattr_test.c +++ b/tests/object_create_getattr_test.c @@ -27,6 +27,7 @@ #include #endif +#include #include #include #include @@ -194,6 +195,222 @@ static void test_secret_key_requires_value(CK_SESSION_HANDLE session) CKR_OK, "complete secret key"); } +/* An attribute count beyond what the library can index is rejected. */ +static void test_create_count_range(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; + CK_KEY_TYPE genericType = CKK_GENERIC_SECRET; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericType, sizeof(genericType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, (void*)secretValue, sizeof(secretValue) }, + }; + + expect_create(session, tmpl, (CK_ULONG)INT_MAX + 1, CKR_ARGUMENTS_BAD, + "create with oversized attribute count"); +} + +#if !defined(NO_RSA) || defined(HAVE_ECC) +/* A secret key object needs a secret key type, for session and token objects, + * and nothing is stored when it does not. */ +static void test_key_type_matches_class(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; + CK_BBOOL onToken = CK_FALSE; + CK_OBJECT_HANDLE found[2]; + CK_ULONG foundCnt = 0; + int i; +#ifndef NO_RSA + CK_KEY_TYPE rsaType = CKK_RSA; + CK_ATTRIBUTE rsaSecret[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_TOKEN, &onToken, sizeof(onToken) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + CK_ATTRIBUTE rsaFind[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + }; +#endif +#ifdef HAVE_ECC + CK_KEY_TYPE ecType = CKK_EC; + CK_ATTRIBUTE ecSecret[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_TOKEN, &onToken, sizeof(onToken) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_EC_POINT, ecc_p256_pub, sizeof(ecc_p256_pub) }, + }; +#endif + + for (i = 0; i < 2; i++) { + onToken = (i == 0) ? CK_FALSE : CK_TRUE; +#ifndef NO_RSA + expect_create(session, rsaSecret, + sizeof(rsaSecret) / sizeof(*rsaSecret), + CKR_TEMPLATE_INCONSISTENT, "RSA key type as secret key"); +#endif +#ifdef HAVE_ECC + expect_create(session, ecSecret, sizeof(ecSecret) / sizeof(*ecSecret), + CKR_TEMPLATE_INCONSISTENT, "EC key type as secret key"); +#endif + } + +#ifndef NO_RSA + rv = funcList->C_FindObjectsInit(session, rsaFind, + sizeof(rsaFind) / sizeof(*rsaFind)); + CHECK_RV(rv, "find inconsistent objects init", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_FindObjects(session, found, 2, &foundCnt); + CHECK_TRUE(rv == CKR_OK && foundCnt == 0, + "no inconsistent object stored"); + funcList->C_FindObjectsFinal(session); + } +#else + (void)rv; + (void)found; + (void)foundCnt; +#endif +} +#endif + +#if !defined(NO_RSA) || defined(HAVE_ECC) +#define MAX_TMPL 16 + +/* The full template creates the object, and leaving out any one of the + * required attributes is reported as an incomplete template. */ +static void expect_required(CK_SESSION_HANDLE session, CK_ATTRIBUTE* tmpl, + CK_ULONG cnt, const CK_ATTRIBUTE_TYPE* required, + CK_ULONG reqCnt, const char* name) +{ + CK_ATTRIBUTE partial[MAX_TMPL]; + CK_ULONG i; + CK_ULONG j; + CK_ULONG n; + char msg[96]; + + if (cnt > MAX_TMPL) { + CHECK_TRUE(0, "template fits"); + return; + } + for (i = 0; i < reqCnt; i++) { + n = 0; + for (j = 0; j < cnt; j++) { + if (tmpl[j].type != required[i]) + partial[n++] = tmpl[j]; + } + snprintf(msg, sizeof(msg), "%s without attribute 0x%lx", name, + (unsigned long)required[i]); + expect_create(session, partial, n, CKR_TEMPLATE_INCOMPLETE, msg); + } + snprintf(msg, sizeof(msg), "complete %s", name); + expect_create(session, tmpl, cnt, CKR_OK, msg); +} +#endif + +#ifndef NO_RSA +/* An RSA key object needs the components that define the key. */ +static void test_rsa_key_requires_material(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE rsaType = CKK_RSA; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { 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_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + CK_ATTRIBUTE primesTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE_EXPONENT, rsa_2048_priv_exp, sizeof(rsa_2048_priv_exp) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_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) }, + }; + static const CK_ATTRIBUTE_TYPE pubReq[] = { + CKA_MODULUS, CKA_PUBLIC_EXPONENT + }; + static const CK_ATTRIBUTE_TYPE privReq[] = { + CKA_MODULUS, CKA_PRIVATE_EXPONENT + }; + static const CK_ATTRIBUTE_TYPE primesReq[] = { + CKA_PRIME_1, CKA_PRIME_2, CKA_PRIVATE_EXPONENT + }; + + expect_required(session, pubTmpl, sizeof(pubTmpl) / sizeof(*pubTmpl), + pubReq, sizeof(pubReq) / sizeof(*pubReq), + "RSA public key"); + expect_required(session, privTmpl, sizeof(privTmpl) / sizeof(*privTmpl), + privReq, sizeof(privReq) / sizeof(*privReq), + "RSA private key"); + expect_required(session, primesTmpl, + sizeof(primesTmpl) / sizeof(*primesTmpl), primesReq, + sizeof(primesReq) / sizeof(*primesReq), + "RSA private key from primes"); +} +#endif + +#ifdef HAVE_ECC +/* An EC key object needs its curve and its point or private value. */ +static void test_ec_key_requires_material(CK_SESSION_HANDLE session) +{ + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE ecType = CKK_EC; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_EC_POINT, ecc_p256_pub, sizeof(ecc_p256_pub) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, + }; + static const CK_ATTRIBUTE_TYPE pubReq[] = { + CKA_EC_PARAMS, CKA_EC_POINT + }; + static const CK_ATTRIBUTE_TYPE privReq[] = { + CKA_EC_PARAMS, CKA_VALUE + }; + + expect_required(session, pubTmpl, sizeof(pubTmpl) / sizeof(*pubTmpl), + pubReq, sizeof(pubReq) / sizeof(*pubReq), + "EC public key"); + expect_required(session, privTmpl, sizeof(privTmpl) / sizeof(*privTmpl), + privReq, sizeof(privReq) / sizeof(*privReq), + "EC private key"); +} +#endif + static int run_test(void) { CK_RV rv; @@ -209,6 +426,16 @@ static int run_test(void) if (rv == CKR_OK) { test_cert_requires_value(session); test_secret_key_requires_value(session); + test_create_count_range(session); +#if !defined(NO_RSA) || defined(HAVE_ECC) + test_key_type_matches_class(session); +#endif +#ifndef NO_RSA + test_rsa_key_requires_material(session); +#endif +#ifdef HAVE_ECC + test_ec_key_requires_material(session); +#endif } if (session != 0) diff --git a/tests/pkcs11mtt.c b/tests/pkcs11mtt.c index 5fbdbb40..373c7341 100644 --- a/tests/pkcs11mtt.c +++ b/tests/pkcs11mtt.c @@ -284,6 +284,18 @@ static CK_RV test_object(void* args) { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, { CKA_TOKEN, &ckTrue, 0 }, }; + CK_ATTRIBUTE tokenNullCreate[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_TOKEN, NULL, sizeof(CK_BBOOL) }, + }; + CK_ATTRIBUTE tokenBadLenCreate[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_TOKEN, &ckTrue, 0 }, + }; CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; CK_OBJECT_HANDLE objOnToken = CK_INVALID_HANDLE; CK_OBJECT_HANDLE copyObj = CK_INVALID_HANDLE; @@ -345,14 +357,14 @@ static CK_RV test_object(void* args) "Create Object zero len key class"); } if (ret == CKR_OK) { - count = sizeof(tokenNull) / sizeof(*tokenNull); - ret = funcList->C_CreateObject(session, tokenNull, count, &obj); + count = sizeof(tokenNullCreate) / sizeof(*tokenNullCreate); + ret = funcList->C_CreateObject(session, tokenNullCreate, count, &obj); CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_VALUE_INVALID, "Create Object NULL token"); } if (ret == CKR_OK) { - count = sizeof(tokenBadLen) / sizeof(*tokenBadLen); - ret = funcList->C_CreateObject(session, tokenBadLen, count, &obj); + count = sizeof(tokenBadLenCreate) / sizeof(*tokenBadLenCreate); + ret = funcList->C_CreateObject(session, tokenBadLenCreate, count, &obj); CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_VALUE_INVALID, "Create Object zero token len"); } diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index 155c0a0d..6e0f427c 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -2071,6 +2071,18 @@ static CK_RV test_object(void* args) { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, { CKA_TOKEN, &ckTrue, 0 }, }; + CK_ATTRIBUTE tokenNullCreate[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_TOKEN, NULL, sizeof(CK_BBOOL) }, + }; + CK_ATTRIBUTE tokenBadLenCreate[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_TOKEN, &ckTrue, 0 }, + }; CK_OBJECT_HANDLE obj, objOnToken, copyObj; CK_ULONG size; @@ -2130,14 +2142,14 @@ static CK_RV test_object(void* args) "Create Object zero len key class"); } if (ret == CKR_OK) { - count = sizeof(tokenNull) / sizeof(*tokenNull); - ret = funcList->C_CreateObject(session, tokenNull, count, &obj); + count = sizeof(tokenNullCreate) / sizeof(*tokenNullCreate); + ret = funcList->C_CreateObject(session, tokenNullCreate, count, &obj); CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_VALUE_INVALID, "Create Object NULL token"); } if (ret == CKR_OK) { - count = sizeof(tokenBadLen) / sizeof(*tokenBadLen); - ret = funcList->C_CreateObject(session, tokenBadLen, count, &obj); + count = sizeof(tokenBadLenCreate) / sizeof(*tokenBadLenCreate); + ret = funcList->C_CreateObject(session, tokenBadLenCreate, count, &obj); CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_VALUE_INVALID, "Create Object zero token len"); } From 546a370f631ce1197b5d9d1183672222220f1d32 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 10:08:56 -0700 Subject: [PATCH 148/204] F-6662 - Allow data objects to be created without CKA_VALUE --- src/crypto.c | 4 +-- tests/object_create_getattr_test.c | 48 ++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 80a71660..7b87447f 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1609,9 +1609,7 @@ static CK_RV CreateObject(WP11_Session* session, CK_ATTRIBUTE_PTR pTemplate, } } else if (objectClass == CKO_DATA) { - FindAttributeType(pTemplate, ulCount, CKA_VALUE, &attr); - if (attr == NULL) - return CKR_TEMPLATE_INCOMPLETE; + /* Every data object attribute is optional. */ } #ifdef WOLFPKCS11_NSS else if (objectClass == CKO_NSS_TRUST) { diff --git a/tests/object_create_getattr_test.c b/tests/object_create_getattr_test.c index 3a40f43f..5018842c 100644 --- a/tests/object_create_getattr_test.c +++ b/tests/object_create_getattr_test.c @@ -56,6 +56,8 @@ static CK_BBOOL ckFalse = CK_FALSE; static const byte certValue[] = { 0x30, 0x82, 0x01, 0x00 }; static const byte certSubject[] = { 0x30, 0x00 }; static const char certUrl[] = "http://example.com/cert.der"; +static const byte dataValue[] = "object-create-data"; +static const byte dataLabel[] = "object-create-data-label"; static const byte secretValue[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff @@ -411,6 +413,51 @@ static void test_ec_key_requires_material(CK_SESSION_HANDLE session) } #endif +/* CKA_VALUE of a data object is optional and defaults to empty. */ +static void test_data_object_value_optional(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_BBOOL onToken = CK_FALSE; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + byte buf[32]; + CK_ATTRIBUTE getValue = { CKA_VALUE, buf, sizeof(buf) }; + CK_ATTRIBUTE setValue = { CKA_VALUE, (void*)dataValue, + sizeof(dataValue) - 1 }; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &onToken, sizeof(onToken) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_LABEL, (void*)dataLabel, sizeof(dataLabel) - 1 }, + }; + int i; + + for (i = 0; i < 2; i++) { + onToken = (i == 0) ? CK_FALSE : CK_TRUE; + rv = funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &obj); + CHECK_RV(rv, "create data object without CKA_VALUE", CKR_OK); + if (rv != CKR_OK) + continue; + + getValue.ulValueLen = sizeof(buf); + rv = funcList->C_GetAttributeValue(session, obj, &getValue, 1); + CHECK_TRUE(rv == CKR_OK && getValue.ulValueLen == 0, + "omitted data object value reads as empty"); + + rv = funcList->C_SetAttributeValue(session, obj, &setValue, 1); + CHECK_RV(rv, "set data object value later", CKR_OK); + getValue.ulValueLen = sizeof(buf); + rv = funcList->C_GetAttributeValue(session, obj, &getValue, 1); + CHECK_TRUE(rv == CKR_OK && + getValue.ulValueLen == sizeof(dataValue) - 1 && + XMEMCMP(buf, dataValue, sizeof(dataValue) - 1) == 0, + "data object value set after creation"); + + destroy_obj(session, &obj); + } +} + static int run_test(void) { CK_RV rv; @@ -427,6 +474,7 @@ static int run_test(void) test_cert_requires_value(session); test_secret_key_requires_value(session); test_create_count_range(session); + test_data_object_value_optional(session); #if !defined(NO_RSA) || defined(HAVE_ECC) test_key_type_matches_class(session); #endif From 37b4affab1472507794d21c3a95f9f0553e63dae Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 10:27:30 -0700 Subject: [PATCH 149/204] F-10382 - Report unavailable EC and PQ key attributes as errors --- src/internal.c | 14 +++- tests/object_create_getattr_test.c | 129 +++++++++++++++++++++++++++++ tests/pkcs11mtt.c | 8 +- tests/pkcs11test.c | 8 +- 4 files changed, 152 insertions(+), 7 deletions(-) diff --git a/src/internal.c b/src/internal.c index 896023c3..b7cd087e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -14525,8 +14525,10 @@ static int EcObject_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, #endif break; case CKA_EC_POINT: - if (noPub) + if (noPub) { *len = CK_UNAVAILABLE_INFORMATION; + ret = NOT_AVAILABLE_E; + } else ret = GetEcPoint(object->data.ecKey, data, len); break; @@ -14670,6 +14672,7 @@ static int MldsaObject_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, break; case CKA_SEED: *len = CK_UNAVAILABLE_INFORMATION; + ret = NOT_AVAILABLE_E; break; case CKA_VALUE: if (object->objClass == CKO_PRIVATE_KEY) { @@ -14681,8 +14684,10 @@ static int MldsaObject_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, ret = GetMldsaPrivateKey(object->data.mldsaKey, data, len); } else if (object->objClass == CKO_PUBLIC_KEY) { - if (noPub) + if (noPub) { *len = CK_UNAVAILABLE_INFORMATION; + ret = NOT_AVAILABLE_E; + } else ret = GetMldsaPublicKey(object->data.mldsaKey, data, len); } @@ -14857,6 +14862,7 @@ static int MlKemObject_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, break; case CKA_SEED: *len = CK_UNAVAILABLE_INFORMATION; + ret = NOT_AVAILABLE_E; break; case CKA_VALUE: if (object->objClass == CKO_PRIVATE_KEY) { @@ -14868,8 +14874,10 @@ static int MlKemObject_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, ret = GetMlKemPrivateKey(object->data.mlKemKey, data, len); } else if (object->objClass == CKO_PUBLIC_KEY) { - if (noPub) + if (noPub) { *len = CK_UNAVAILABLE_INFORMATION; + ret = NOT_AVAILABLE_E; + } else ret = GetMlKemPublicKey(object->data.mlKemKey, data, len); } diff --git a/tests/object_create_getattr_test.c b/tests/object_create_getattr_test.c index 5018842c..f45eb470 100644 --- a/tests/object_create_getattr_test.c +++ b/tests/object_create_getattr_test.c @@ -458,6 +458,134 @@ static void test_data_object_value_optional(CK_SESSION_HANDLE session) } } +#if defined(HAVE_ECC) || defined(WOLFPKCS11_MLDSA) || defined(WOLFPKCS11_MLKEM) +/* An attribute the object cannot provide is reported as an error, with the + * length set to CK_UNAVAILABLE_INFORMATION, for size queries and reads. */ +static void expect_unavailable(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + CK_ATTRIBUTE_TYPE type, const char* name) +{ + CK_RV rv; + byte buf[64]; + CK_ATTRIBUTE query = { type, NULL, 0 }; + CK_ATTRIBUTE read = { type, buf, sizeof(buf) }; + char msg[96]; + + rv = funcList->C_GetAttributeValue(session, obj, &query, 1); + snprintf(msg, sizeof(msg), "%s size query fails", name); + CHECK_TRUE(rv != CKR_OK && + query.ulValueLen == CK_UNAVAILABLE_INFORMATION, msg); + rv = funcList->C_GetAttributeValue(session, obj, &read, 1); + snprintf(msg, sizeof(msg), "%s read fails", name); + CHECK_TRUE(rv != CKR_OK && + read.ulValueLen == CK_UNAVAILABLE_INFORMATION, msg); +} +#endif + +#if defined(WOLFPKCS11_MLDSA) || defined(WOLFPKCS11_MLKEM) +/* Generate a parameter-set based key pair as public session objects. */ +static CK_RV gen_pq_key_pair(CK_SESSION_HANDLE session, CK_MECHANISM_TYPE type, + CK_ULONG paramSet, CK_OBJECT_HANDLE* pub, + CK_OBJECT_HANDLE* priv) +{ + CK_MECHANISM mech = { type, NULL, 0 }; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + }; + + return funcList->C_GenerateKeyPair(session, &mech, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), pub, priv); +} + +/* Create a public key object carrying only its parameter set. */ +static CK_RV create_pq_params_only(CK_SESSION_HANDLE session, + CK_KEY_TYPE keyType, CK_ULONG paramSet, + CK_OBJECT_HANDLE* obj) +{ + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), obj); +} + +static void check_pq_unavailable(CK_SESSION_HANDLE session, + CK_MECHANISM_TYPE genMech, + CK_KEY_TYPE keyType, CK_ULONG paramSet, + const char* name) +{ + CK_RV rv; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE noPub = CK_INVALID_HANDLE; + char msg[96]; + + rv = gen_pq_key_pair(session, genMech, paramSet, &pub, &priv); + snprintf(msg, sizeof(msg), "%s generate key pair", name); + CHECK_RV(rv, msg, CKR_OK); + if (rv == CKR_OK) { + snprintf(msg, sizeof(msg), "%s private key CKA_SEED", name); + expect_unavailable(session, priv, CKA_SEED, msg); + } + + rv = create_pq_params_only(session, keyType, paramSet, &noPub); + snprintf(msg, sizeof(msg), "%s public key without value", name); + CHECK_RV(rv, msg, CKR_OK); + if (rv == CKR_OK) { + snprintf(msg, sizeof(msg), "%s missing public CKA_VALUE", name); + expect_unavailable(session, noPub, CKA_VALUE, msg); + } + + destroy_obj(session, &noPub); + destroy_obj(session, &priv); + destroy_obj(session, &pub); +} +#endif + +static void test_unavailable_attr_is_error(CK_SESSION_HANDLE session) +{ +#ifdef HAVE_ECC + CK_RV rv; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE ecType = CKK_EC; + CK_OBJECT_HANDLE ecPriv = CK_INVALID_HANDLE; + CK_ATTRIBUTE ecTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, + }; + + rv = funcList->C_CreateObject(session, ecTmpl, + sizeof(ecTmpl) / sizeof(*ecTmpl), &ecPriv); + CHECK_RV(rv, "create EC private key without point", CKR_OK); + if (rv == CKR_OK) + expect_unavailable(session, ecPriv, CKA_EC_POINT, "EC point"); + destroy_obj(session, &ecPriv); +#endif +#ifdef WOLFPKCS11_MLDSA + check_pq_unavailable(session, CKM_ML_DSA_KEY_PAIR_GEN, CKK_ML_DSA, + CKP_ML_DSA_44, "ML-DSA"); +#endif +#ifdef WOLFPKCS11_MLKEM + check_pq_unavailable(session, CKM_ML_KEM_KEY_PAIR_GEN, CKK_ML_KEM, + CKP_ML_KEM_512, "ML-KEM"); +#endif + (void)session; +} + static int run_test(void) { CK_RV rv; @@ -475,6 +603,7 @@ static int run_test(void) test_secret_key_requires_value(session); test_create_count_range(session); test_data_object_value_optional(session); + test_unavailable_attr_is_error(session); #if !defined(NO_RSA) || defined(HAVE_ECC) test_key_type_matches_class(session); #endif diff --git a/tests/pkcs11mtt.c b/tests/pkcs11mtt.c index 373c7341..3eba8429 100644 --- a/tests/pkcs11mtt.c +++ b/tests/pkcs11mtt.c @@ -3763,7 +3763,9 @@ 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"); + /* No public point is stored, so CKA_EC_POINT is unavailable. */ + CHECK_CKR_FAIL(ret, CK_UNAVAILABLE_INFORMATION, + "Get Attributes EC Private Key NULL values"); } if (ret == CKR_OK) { CHECK_COND(eccPrivTmpl[0].ulValueLen == sizeof(ecc_p256_params), ret, @@ -3782,7 +3784,9 @@ 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 values"); + /* No public point is stored, so CKA_EC_POINT is unavailable. */ + CHECK_CKR_FAIL(ret, CK_UNAVAILABLE_INFORMATION, + "Get Attributes EC Private Key values"); } funcList->C_DestroyObject(session, priv); diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index 6e0f427c..e1787060 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -9299,7 +9299,9 @@ 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"); + /* No public point is stored, so CKA_EC_POINT is unavailable. */ + CHECK_CKR_FAIL(ret, CK_UNAVAILABLE_INFORMATION, + "Get Attributes EC Private Key NULL values"); } if (ret == CKR_OK) { CHECK_COND(eccPrivTmpl[0].ulValueLen == sizeof(ecc_p256_params), ret, @@ -9318,7 +9320,9 @@ 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 values"); + /* No public point is stored, so CKA_EC_POINT is unavailable. */ + CHECK_CKR_FAIL(ret, CK_UNAVAILABLE_INFORMATION, + "Get Attributes EC Private Key values"); } funcList->C_DestroyObject(session, priv); From 357b5f8ab717815757086455082c7d31637cf0f2 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 10:35:30 -0700 Subject: [PATCH 150/204] F-6955 - Return CKA_CERTIFICATE_CATEGORY from certificate objects --- src/internal.c | 3 ++ tests/object_create_getattr_test.c | 45 ++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/internal.c b/src/internal.c index b7cd087e..26c05d14 100644 --- a/src/internal.c +++ b/src/internal.c @@ -14198,6 +14198,9 @@ static int GetCertAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, case CKA_SUBJECT: ret = GetData(object->subject, object->subjectLen, data, len); break; + case CKA_CERTIFICATE_CATEGORY: + ret = GetULong(object->category, data, len); + break; #ifdef WOLFPKCS11_NSS case CKA_NSS_EMAIL: ret = GetData(object->email, object->emailLen, data, len); diff --git a/tests/object_create_getattr_test.c b/tests/object_create_getattr_test.c index f45eb470..652d0872 100644 --- a/tests/object_create_getattr_test.c +++ b/tests/object_create_getattr_test.c @@ -586,6 +586,50 @@ static void test_unavailable_attr_is_error(CK_SESSION_HANDLE session) (void)session; } +/* CKA_CERTIFICATE_CATEGORY reads back as supplied, and defaults to 0. */ +static void test_cert_category_roundtrip(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS certClass = CKO_CERTIFICATE; + CK_CERTIFICATE_TYPE certType = CKC_X_509; + CK_ULONG category = 2; + CK_ULONG got; + CK_OBJECT_HANDLE cert = CK_INVALID_HANDLE; + CK_ATTRIBUTE get = { CKA_CERTIFICATE_CATEGORY, NULL, 0 }; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &certClass, sizeof(certClass) }, + { CKA_CERTIFICATE_TYPE, &certType, sizeof(certType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_SUBJECT, (void*)certSubject, sizeof(certSubject) }, + { CKA_VALUE, (void*)certValue, sizeof(certValue) }, + { CKA_CERTIFICATE_CATEGORY, &category, sizeof(category) }, + }; + CK_ULONG cnt = sizeof(tmpl) / sizeof(*tmpl); + int i; + + for (i = 0; i < 2; i++) { + rv = funcList->C_CreateObject(session, tmpl, cnt - (CK_ULONG)i, &cert); + CHECK_RV(rv, "create certificate", CKR_OK); + if (rv != CKR_OK) + continue; + + get.pValue = NULL; + get.ulValueLen = 0; + rv = funcList->C_GetAttributeValue(session, cert, &get, 1); + CHECK_TRUE(rv == CKR_OK && get.ulValueLen == sizeof(CK_ULONG), + "certificate category size"); + got = (CK_ULONG)-1; + get.pValue = &got; + get.ulValueLen = sizeof(got); + rv = funcList->C_GetAttributeValue(session, cert, &get, 1); + CHECK_TRUE(rv == CKR_OK && get.ulValueLen == sizeof(got) && + got == ((i == 0) ? category : 0), + (i == 0) ? "certificate category reads back" : + "certificate category defaults to 0"); + destroy_obj(session, &cert); + } +} + static int run_test(void) { CK_RV rv; @@ -604,6 +648,7 @@ static int run_test(void) test_create_count_range(session); test_data_object_value_optional(session); test_unavailable_attr_is_error(session); + test_cert_category_roundtrip(session); #if !defined(NO_RSA) || defined(HAVE_ECC) test_key_type_matches_class(session); #endif From 9affd4e312795eae67881415ae9ecfb3496cee2e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 10:49:57 -0700 Subject: [PATCH 151/204] F-6956 - Expose key CKA_SUBJECT and restrict issuer and serial to certs --- src/internal.c | 27 ++++++++-- tests/object_create_getattr_test.c | 84 ++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 5 deletions(-) diff --git a/src/internal.c b/src/internal.c index 26c05d14..a7702f49 100644 --- a/src/internal.c +++ b/src/internal.c @@ -15250,6 +15250,12 @@ int WP11_Object_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, #endif else if (object->objClass == CKO_DATA) ret = GetDataAttr(object, type, data, len); + else if (type == CKA_SUBJECT && + (object->objClass == CKO_PUBLIC_KEY || + object->objClass == CKO_PRIVATE_KEY)) { + ret = GetData(object->subject, object->subjectLen, data, + len); + } else { switch (object->type) { #ifndef NO_RSA @@ -15738,12 +15744,23 @@ int WP11_Object_SetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, /* Handled in layer above */ break; case CKA_ISSUER: - ret = WP11_Object_SetData(&object->issuer, &object->issuerLen, - data, (int)len); - break; case CKA_SERIAL_NUMBER: - ret = WP11_Object_SetData(&object->serial, &object->serialLen, - data, (int)len); + /* Certificate attributes; NSS trust objects carry them too. */ + if (object->objClass != CKO_CERTIFICATE +#ifdef WOLFPKCS11_NSS + && object->objClass != CKO_NSS_TRUST +#endif + ) { + ret = BAD_FUNC_ARG; + } + else if (type == CKA_ISSUER) { + ret = WP11_Object_SetData(&object->issuer, &object->issuerLen, + data, (int)len); + } + else { + ret = WP11_Object_SetData(&object->serial, &object->serialLen, + data, (int)len); + } break; case CKA_SUBJECT: ret = WP11_Object_SetData(&object->subject, &object->subjectLen, diff --git a/tests/object_create_getattr_test.c b/tests/object_create_getattr_test.c index 652d0872..40053b3c 100644 --- a/tests/object_create_getattr_test.c +++ b/tests/object_create_getattr_test.c @@ -56,6 +56,13 @@ static CK_BBOOL ckFalse = CK_FALSE; static const byte certValue[] = { 0x30, 0x82, 0x01, 0x00 }; static const byte certSubject[] = { 0x30, 0x00 }; static const char certUrl[] = "http://example.com/cert.der"; +#ifdef HAVE_ECC +static const byte keySubject[] = { + 0x30, 0x0f, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0c, 0x04, 0x74, 0x65, 0x73, 0x74 +}; +static const byte keySerial[] = { 0x02, 0x01, 0x01 }; +#endif static const byte dataValue[] = "object-create-data"; static const byte dataLabel[] = "object-create-data-label"; static const byte secretValue[] = { @@ -630,6 +637,80 @@ static void test_cert_category_roundtrip(CK_SESSION_HANDLE session) } } +#ifdef HAVE_ECC +/* CKA_SUBJECT on a public or private key reads back as supplied, and the + * certificate-only CKA_ISSUER and CKA_SERIAL_NUMBER are not accepted. */ +static void test_key_subject_roundtrip(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE ecType = CKK_EC; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + byte buf[32]; + CK_ATTRIBUTE get = { CKA_SUBJECT, buf, sizeof(buf) }; + CK_ATTRIBUTE issuer = { CKA_ISSUER, (void*)keySubject, + sizeof(keySubject) }; + CK_ATTRIBUTE serial = { CKA_SERIAL_NUMBER, (void*)keySerial, + sizeof(keySerial) }; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_EC_POINT, ecc_p256_pub, sizeof(ecc_p256_pub) }, + { CKA_SUBJECT, (void*)keySubject, sizeof(keySubject) }, + { CKA_ISSUER, (void*)keySubject, sizeof(keySubject) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, + { CKA_SUBJECT, (void*)keySubject, sizeof(keySubject) }, + { CKA_SERIAL_NUMBER, (void*)keySerial, sizeof(keySerial) }, + }; + CK_ULONG pubCnt = sizeof(pubTmpl) / sizeof(*pubTmpl); + CK_ULONG privCnt = sizeof(privTmpl) / sizeof(*privTmpl); + + rv = funcList->C_CreateObject(session, pubTmpl, pubCnt, &key); + CHECK_TRUE(rv != CKR_OK, "public key with CKA_ISSUER rejected"); + destroy_obj(session, &key); + rv = funcList->C_CreateObject(session, privTmpl, privCnt, &key); + CHECK_TRUE(rv != CKR_OK, "private key with CKA_SERIAL_NUMBER rejected"); + destroy_obj(session, &key); + + rv = funcList->C_CreateObject(session, pubTmpl, pubCnt - 1, &key); + CHECK_RV(rv, "create public key with subject", CKR_OK); + if (rv == CKR_OK) { + get.ulValueLen = sizeof(buf); + rv = funcList->C_GetAttributeValue(session, key, &get, 1); + CHECK_TRUE(rv == CKR_OK && get.ulValueLen == sizeof(keySubject) && + XMEMCMP(buf, keySubject, sizeof(keySubject)) == 0, + "public key subject reads back"); + rv = funcList->C_SetAttributeValue(session, key, &issuer, 1); + CHECK_TRUE(rv != CKR_OK, "set CKA_ISSUER on public key rejected"); + } + destroy_obj(session, &key); + + rv = funcList->C_CreateObject(session, privTmpl, privCnt - 1, &key); + CHECK_RV(rv, "create private key with subject", CKR_OK); + if (rv == CKR_OK) { + get.ulValueLen = sizeof(buf); + rv = funcList->C_GetAttributeValue(session, key, &get, 1); + CHECK_TRUE(rv == CKR_OK && get.ulValueLen == sizeof(keySubject) && + XMEMCMP(buf, keySubject, sizeof(keySubject)) == 0, + "private key subject reads back"); + rv = funcList->C_SetAttributeValue(session, key, &serial, 1); + CHECK_TRUE(rv != CKR_OK, + "set CKA_SERIAL_NUMBER on private key rejected"); + } + destroy_obj(session, &key); +} +#endif + static int run_test(void) { CK_RV rv; @@ -649,6 +730,9 @@ static int run_test(void) test_data_object_value_optional(session); test_unavailable_attr_is_error(session); test_cert_category_roundtrip(session); +#ifdef HAVE_ECC + test_key_subject_roundtrip(session); +#endif #if !defined(NO_RSA) || defined(HAVE_ECC) test_key_type_matches_class(session); #endif From a91f93beab19551be6049bb7a5eb875d7237817b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 10:57:34 -0700 Subject: [PATCH 152/204] F-6658 - Preserve CKA_LOCAL when copying an object --- src/internal.c | 1 + tests/object_create_getattr_test.c | 76 ++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/internal.c b/src/internal.c index a7702f49..094149f3 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3926,6 +3926,7 @@ int WP11_Object_Copy(WP11_Object *src, WP11_Object *dest) #endif dest->objClass = src->objClass; dest->keyGenMech = src->keyGenMech; + dest->local = src->local; dest->opFlag = src->opFlag; XMEMCPY(dest->startDate, src->startDate, sizeof(dest->startDate)); XMEMCPY(dest->endDate, src->endDate, sizeof(dest->endDate)); diff --git a/tests/object_create_getattr_test.c b/tests/object_create_getattr_test.c index 40053b3c..1eb9c7b9 100644 --- a/tests/object_create_getattr_test.c +++ b/tests/object_create_getattr_test.c @@ -711,6 +711,79 @@ static void test_key_subject_roundtrip(CK_SESSION_HANDLE session) } #endif +#ifndef NO_AES +static CK_BBOOL get_local(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj) +{ + CK_BBOOL local = 2; + CK_ATTRIBUTE attr = { CKA_LOCAL, &local, sizeof(local) }; + + if (funcList->C_GetAttributeValue(session, obj, &attr, 1) != CKR_OK) + return 2; + return local; +} + +/* A copy keeps CKA_LOCAL of the key it was copied from. */ +static void test_copy_keeps_local(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; + CK_KEY_TYPE aesType = CKK_AES; + CK_ULONG keyLen = 16; + CK_MECHANISM mech = { CKM_AES_KEY_GEN, NULL, 0 }; + CK_OBJECT_HANDLE gen = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE imported = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE copy = CK_INVALID_HANDLE; + static const byte copyLabel[] = "copy"; + CK_ATTRIBUTE copyTmpl[] = { + { CKA_LABEL, (void*)copyLabel, sizeof(copyLabel) - 1 }, + }; + CK_ATTRIBUTE genTmpl[] = { + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE_LEN, &keyLen, sizeof(keyLen) }, + }; + CK_ATTRIBUTE importTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, (void*)secretValue, sizeof(secretValue) }, + }; + + rv = funcList->C_GenerateKey(session, &mech, genTmpl, + sizeof(genTmpl) / sizeof(*genTmpl), &gen); + CHECK_RV(rv, "generate AES key", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(get_local(session, gen) == CK_TRUE, + "generated key is local"); + rv = funcList->C_CopyObject(session, gen, copyTmpl, 1, ©); + CHECK_RV(rv, "copy generated key", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(get_local(session, copy) == CK_TRUE, + "copy of generated key is local"); + } + destroy_obj(session, ©); + } + + rv = funcList->C_CreateObject(session, importTmpl, + sizeof(importTmpl) / sizeof(*importTmpl), + &imported); + CHECK_RV(rv, "import AES key", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_CopyObject(session, imported, copyTmpl, 1, ©); + CHECK_RV(rv, "copy imported key", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(get_local(session, copy) == CK_FALSE, + "copy of imported key is not local"); + } + destroy_obj(session, ©); + } + + destroy_obj(session, &imported); + destroy_obj(session, &gen); +} +#endif + static int run_test(void) { CK_RV rv; @@ -733,6 +806,9 @@ static int run_test(void) #ifdef HAVE_ECC test_key_subject_roundtrip(session); #endif +#ifndef NO_AES + test_copy_keeps_local(session); +#endif #if !defined(NO_RSA) || defined(HAVE_ECC) test_key_type_matches_class(session); #endif From 17c6e81ac20a9848bf39bc9b8137fb054d1fe9cb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 11:03:08 -0700 Subject: [PATCH 153/204] F-4061 - Set the ML-DSA parameter set length on a successful read --- src/internal.c | 4 ++- tests/object_create_getattr_test.c | 48 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 094149f3..9165b4f2 100644 --- a/src/internal.c +++ b/src/internal.c @@ -14575,8 +14575,10 @@ static int GetMldsaParams(wc_MlDsaKey* key, byte* data, CK_ULONG* len) *len = sizeof(CK_ML_DSA_PARAMETER_SET_TYPE); else if (*len < sizeof(CK_ML_DSA_PARAMETER_SET_TYPE)) ret = BUFFER_E; - else + else { XMEMCPY(data, ¶ms, sizeof(CK_ML_DSA_PARAMETER_SET_TYPE)); + *len = sizeof(CK_ML_DSA_PARAMETER_SET_TYPE); + } return ret; } diff --git a/tests/object_create_getattr_test.c b/tests/object_create_getattr_test.c index 1eb9c7b9..8d7f551b 100644 --- a/tests/object_create_getattr_test.c +++ b/tests/object_create_getattr_test.c @@ -557,6 +557,37 @@ static void check_pq_unavailable(CK_SESSION_HANDLE session, destroy_obj(session, &priv); destroy_obj(session, &pub); } + +/* Reading CKA_PARAMETER_SET into a larger buffer reports the real length. */ +static void check_param_set_len(CK_SESSION_HANDLE session, + CK_MECHANISM_TYPE genMech, CK_ULONG paramSet, + const char* name) +{ + CK_RV rv; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_ULONG buf[4]; + CK_ATTRIBUTE get = { CKA_PARAMETER_SET, buf, sizeof(buf) }; + char msg[96]; + int i; + + rv = gen_pq_key_pair(session, genMech, paramSet, &pub, &priv); + snprintf(msg, sizeof(msg), "%s generate key pair", name); + CHECK_RV(rv, msg, CKR_OK); + for (i = 0; rv == CKR_OK && i < 2; i++) { + XMEMSET(buf, 0, sizeof(buf)); + get.ulValueLen = sizeof(buf); + rv = funcList->C_GetAttributeValue(session, (i == 0) ? pub : priv, + &get, 1); + snprintf(msg, sizeof(msg), "%s %s parameter set length", name, + (i == 0) ? "public" : "private"); + CHECK_TRUE(rv == CKR_OK && get.ulValueLen == sizeof(CK_ULONG) && + buf[0] == paramSet, msg); + } + + destroy_obj(session, &priv); + destroy_obj(session, &pub); +} #endif static void test_unavailable_attr_is_error(CK_SESSION_HANDLE session) @@ -784,6 +815,20 @@ static void test_copy_keeps_local(CK_SESSION_HANDLE session) } #endif +#if defined(WOLFPKCS11_MLDSA) || defined(WOLFPKCS11_MLKEM) +static void test_param_set_len(CK_SESSION_HANDLE session) +{ +#ifdef WOLFPKCS11_MLDSA + check_param_set_len(session, CKM_ML_DSA_KEY_PAIR_GEN, CKP_ML_DSA_44, + "ML-DSA"); +#endif +#ifdef WOLFPKCS11_MLKEM + check_param_set_len(session, CKM_ML_KEM_KEY_PAIR_GEN, CKP_ML_KEM_512, + "ML-KEM"); +#endif +} +#endif + static int run_test(void) { CK_RV rv; @@ -809,6 +854,9 @@ static int run_test(void) #ifndef NO_AES test_copy_keeps_local(session); #endif +#if defined(WOLFPKCS11_MLDSA) || defined(WOLFPKCS11_MLKEM) + test_param_set_len(session); +#endif #if !defined(NO_RSA) || defined(HAVE_ECC) test_key_type_matches_class(session); #endif From 7bd0e3bb8ecb45c9c9d9ef34cbe3860fd4aefdd9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 11:09:18 -0700 Subject: [PATCH 154/204] F-4062 - Answer size queries for trust object certificate hashes --- src/internal.c | 4 +- tests/object_create_getattr_test.c | 62 ++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index 9165b4f2..18715a1f 100644 --- a/src/internal.c +++ b/src/internal.c @@ -14255,14 +14255,14 @@ static int GetTrustAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, switch (type) { case CKA_CERT_SHA1_HASH: - if (*len < WC_SHA_DIGEST_SIZE) + if (data != NULL && *len < WC_SHA_DIGEST_SIZE) return BUFFER_E; *len = WC_SHA_DIGEST_SIZE; if (data != NULL) XMEMCPY(data, &object->data.trust.sha1Hash, WC_SHA_DIGEST_SIZE); break; case CKA_CERT_MD5_HASH: - if (*len < WC_MD5_DIGEST_SIZE) + if (data != NULL && *len < WC_MD5_DIGEST_SIZE) return BUFFER_E; *len = WC_MD5_DIGEST_SIZE; if (data != NULL) diff --git a/tests/object_create_getattr_test.c b/tests/object_create_getattr_test.c index 8d7f551b..a90c1ef6 100644 --- a/tests/object_create_getattr_test.c +++ b/tests/object_create_getattr_test.c @@ -829,6 +829,65 @@ static void test_param_set_len(CK_SESSION_HANDLE session) } #endif +#ifdef WOLFPKCS11_NSS +static void check_trust_hash(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + CK_ATTRIBUTE_TYPE type, CK_ULONG hashLen, + const char* name) +{ + CK_RV rv; + byte buf[32]; + CK_ATTRIBUTE get = { type, NULL, 0 }; + char msg[96]; + + rv = funcList->C_GetAttributeValue(session, obj, &get, 1); + snprintf(msg, sizeof(msg), "%s size query", name); + CHECK_TRUE(rv == CKR_OK && get.ulValueLen == hashLen, msg); + + XMEMSET(buf, 0xAB, sizeof(buf)); + get.pValue = buf; + get.ulValueLen = hashLen - 1; + rv = funcList->C_GetAttributeValue(session, obj, &get, 1); + snprintf(msg, sizeof(msg), "%s undersized buffer", name); + CHECK_TRUE(rv == CKR_BUFFER_TOO_SMALL && buf[hashLen - 1] == 0xAB, msg); + + get.ulValueLen = sizeof(buf); + rv = funcList->C_GetAttributeValue(session, obj, &get, 1); + snprintf(msg, sizeof(msg), "%s read", name); + CHECK_TRUE(rv == CKR_OK && get.ulValueLen == hashLen, msg); +} + +/* Trust object hashes follow the size query convention. */ +static void test_trust_hash_size_query(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS trustClass = CKO_NSS_TRUST; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + static const byte issuer[] = "CN=Test,O=wolfSSL,C=US"; + static const byte serial[] = { 0x02, 0x01, 0x01 }; + static const byte sha1Hash[20] = { 0x01 }; + static const byte md5Hash[16] = { 0x02 }; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &trustClass, sizeof(trustClass) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_ISSUER, (void*)issuer, sizeof(issuer) - 1 }, + { CKA_SERIAL_NUMBER, (void*)serial, sizeof(serial) }, + { CKA_CERT_SHA1_HASH, (void*)sha1Hash, sizeof(sha1Hash) }, + { CKA_CERT_MD5_HASH, (void*)md5Hash, sizeof(md5Hash) }, + }; + + rv = funcList->C_CreateObject(session, tmpl, sizeof(tmpl) / sizeof(*tmpl), + &obj); + CHECK_RV(rv, "create trust object", CKR_OK); + if (rv == CKR_OK) { + check_trust_hash(session, obj, CKA_CERT_SHA1_HASH, sizeof(sha1Hash), + "trust SHA-1 hash"); + check_trust_hash(session, obj, CKA_CERT_MD5_HASH, sizeof(md5Hash), + "trust MD5 hash"); + } + destroy_obj(session, &obj); +} +#endif + static int run_test(void) { CK_RV rv; @@ -857,6 +916,9 @@ static int run_test(void) #if defined(WOLFPKCS11_MLDSA) || defined(WOLFPKCS11_MLKEM) test_param_set_len(session); #endif +#ifdef WOLFPKCS11_NSS + test_trust_hash_size_query(session); +#endif #if !defined(NO_RSA) || defined(HAVE_ECC) test_key_type_matches_class(session); #endif From f9b3fc8c4b769f4b9d44ec86847aa918ec74f39f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 11:16:44 -0700 Subject: [PATCH 155/204] F-6235 - Check the buffer size before encoding the EC curve OID --- src/internal.c | 25 ++++++----- tests/object_create_getattr_test.c | 69 ++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/src/internal.c b/src/internal.c index 18715a1f..08e219f2 100644 --- a/src/internal.c +++ b/src/internal.c @@ -14414,17 +14414,22 @@ static int GetEcParams(ecc_key* key, byte* data, CK_ULONG* len) { int ret = 0; #if defined(HAVE_OID_ENCODING) - word32 dataLen = (word32)*len; - byte* out = (data != NULL) ? (data + 2) : NULL; - ret = wc_EncodeObjectId(key->dp->oid, key->dp->oidSz, out, &dataLen); - if (ret != 0) { - return ret; - } - if (data != NULL) { - data[0] = ASN_OBJECT_ID; - data[1] = dataLen; + word32 dataLen = 0; + + ret = wc_EncodeObjectId(key->dp->oid, key->dp->oidSz, NULL, &dataLen); + if (ret == 0 && data != NULL) { + if (*len < (CK_ULONG)dataLen + 2) + ret = BUFFER_E; + else + ret = wc_EncodeObjectId(key->dp->oid, key->dp->oidSz, data + 2, + &dataLen); + if (ret == 0) { + data[0] = ASN_OBJECT_ID; + data[1] = (byte)dataLen; + } } - *len = dataLen + 2; + if (ret == 0) + *len = (CK_ULONG)dataLen + 2; #else const byte* oid = NULL; word32 oidSz = 0; diff --git a/tests/object_create_getattr_test.c b/tests/object_create_getattr_test.c index a90c1ef6..420e97ee 100644 --- a/tests/object_create_getattr_test.c +++ b/tests/object_create_getattr_test.c @@ -888,6 +888,72 @@ static void test_trust_hash_size_query(CK_SESSION_HANDLE session) } #endif +#ifdef HAVE_ECC +/* CKA_EC_PARAMS read into a buffer shorter than the encoding is reported as + * too small and nothing is written past the length given. */ +static void test_ec_params_short_buffer(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE ecType = CKK_EC; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + byte buf[64]; + CK_ATTRIBUTE get = { CKA_EC_PARAMS, NULL, 0 }; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_EC_POINT, ecc_p256_pub, sizeof(ecc_p256_pub) }, + }; + CK_ULONG need; + CK_ULONG shortLen; + CK_ULONG i; + int intact; + + rv = funcList->C_CreateObject(session, tmpl, sizeof(tmpl) / sizeof(*tmpl), + &key); + CHECK_RV(rv, "create EC public key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_GetAttributeValue(session, key, &get, 1); + CHECK_TRUE(rv == CKR_OK && get.ulValueLen == sizeof(ecc_p256_params), + "EC params size query"); + need = get.ulValueLen; + + for (shortLen = 0; rv == CKR_OK && shortLen < need; shortLen++) { + XMEMSET(buf, 0xAB, sizeof(buf)); + get.pValue = buf; + get.ulValueLen = shortLen; + rv = funcList->C_GetAttributeValue(session, key, &get, 1); + intact = 1; + for (i = shortLen; i < sizeof(buf); i++) { + if (buf[i] != 0xAB) + intact = 0; + } + if (rv != CKR_BUFFER_TOO_SMALL || !intact) { + CHECK_TRUE(0, "EC params short buffer rejected and untouched"); + rv = CKR_GENERAL_ERROR; + } + else { + rv = CKR_OK; + } + } + if (rv == CKR_OK) + CHECK_TRUE(1, "EC params short buffer rejected and untouched"); + + get.pValue = buf; + get.ulValueLen = sizeof(buf); + rv = funcList->C_GetAttributeValue(session, key, &get, 1); + CHECK_TRUE(rv == CKR_OK && get.ulValueLen == sizeof(ecc_p256_params) && + XMEMCMP(buf, ecc_p256_params, sizeof(ecc_p256_params)) == 0, + "EC params read"); + + destroy_obj(session, &key); +} +#endif + static int run_test(void) { CK_RV rv; @@ -919,6 +985,9 @@ static int run_test(void) #ifdef WOLFPKCS11_NSS test_trust_hash_size_query(session); #endif +#ifdef HAVE_ECC + test_ec_params_short_buffer(session); +#endif #if !defined(NO_RSA) || defined(HAVE_ECC) test_key_type_matches_class(session); #endif From a454733c0b6c735a9299f4f21fe38f0b25c05578 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:03:18 -0700 Subject: [PATCH 156/204] Expect CKR_ATTRIBUTE_TYPE_INVALID for unavailable EC attributes --- tests/pkcs11mtt.c | 4 ++-- tests/pkcs11test.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/pkcs11mtt.c b/tests/pkcs11mtt.c index 3eba8429..0d3a4788 100644 --- a/tests/pkcs11mtt.c +++ b/tests/pkcs11mtt.c @@ -3764,7 +3764,7 @@ static CK_RV test_attributes_ecc(void* args) ret = funcList->C_GetAttributeValue(session, priv, eccPrivTmpl, eccPrivTmplCnt); /* No public point is stored, so CKA_EC_POINT is unavailable. */ - CHECK_CKR_FAIL(ret, CK_UNAVAILABLE_INFORMATION, + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_TYPE_INVALID, "Get Attributes EC Private Key NULL values"); } if (ret == CKR_OK) { @@ -3785,7 +3785,7 @@ static CK_RV test_attributes_ecc(void* args) ret = funcList->C_GetAttributeValue(session, priv, eccPrivTmpl, eccPrivTmplCnt); /* No public point is stored, so CKA_EC_POINT is unavailable. */ - CHECK_CKR_FAIL(ret, CK_UNAVAILABLE_INFORMATION, + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_TYPE_INVALID, "Get Attributes EC Private Key values"); } funcList->C_DestroyObject(session, priv); diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index e1787060..12bd9c9e 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -9300,7 +9300,7 @@ static CK_RV test_attributes_ecc(void* args) ret = funcList->C_GetAttributeValue(session, priv, eccPrivTmpl, eccPrivTmplCnt); /* No public point is stored, so CKA_EC_POINT is unavailable. */ - CHECK_CKR_FAIL(ret, CK_UNAVAILABLE_INFORMATION, + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_TYPE_INVALID, "Get Attributes EC Private Key NULL values"); } if (ret == CKR_OK) { @@ -9321,7 +9321,7 @@ static CK_RV test_attributes_ecc(void* args) ret = funcList->C_GetAttributeValue(session, priv, eccPrivTmpl, eccPrivTmplCnt); /* No public point is stored, so CKA_EC_POINT is unavailable. */ - CHECK_CKR_FAIL(ret, CK_UNAVAILABLE_INFORMATION, + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_TYPE_INVALID, "Get Attributes EC Private Key values"); } funcList->C_DestroyObject(session, priv); From e3d6e97f16cdd2219d9d826a9fe0c6e9bbfc23a4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:43 -0700 Subject: [PATCH 157/204] Reject issuer and serial on non-certificates up front --- src/crypto.c | 9 +++++++++ tests/object_create_getattr_test.c | 29 ++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 7b87447f..ad0aaf46 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -982,6 +982,15 @@ static CK_RV CheckAttributeUpdate(WP11_Session* session, WP11_Object* obj, *(CK_BBOOL*)attr->pValue == CK_TRUE) { return CKR_ATTRIBUTE_VALUE_INVALID; } + /* Certificate attributes; NSS trust objects carry them too. */ + if ((attr->type == CKA_ISSUER || attr->type == CKA_SERIAL_NUMBER) && + objClass != CKO_CERTIFICATE +#ifdef WOLFPKCS11_NSS + && objClass != CKO_NSS_TRUST +#endif + ) { + return CKR_ATTRIBUTE_TYPE_INVALID; + } /* The type-specific storage was allocated for the creation class. */ if (newObject && attr->type == CKA_CLASS && *(CK_OBJECT_CLASS*)attr->pValue != objClass) { diff --git a/tests/object_create_getattr_test.c b/tests/object_create_getattr_test.c index 420e97ee..ba582a00 100644 --- a/tests/object_create_getattr_test.c +++ b/tests/object_create_getattr_test.c @@ -684,6 +684,13 @@ static void test_key_subject_roundtrip(CK_SESSION_HANDLE session) sizeof(keySubject) }; CK_ATTRIBUTE serial = { CKA_SERIAL_NUMBER, (void*)keySerial, sizeof(keySerial) }; + byte labelBuf[8]; + CK_ATTRIBUTE getLabel = { CKA_LABEL, labelBuf, sizeof(labelBuf) }; + CK_ATTRIBUTE labelTmpl[] = { { CKA_LABEL, (void*)"A", 1 } }; + CK_ATTRIBUTE mixedTmpl[] = { + { CKA_LABEL, (void*)"B", 1 }, + { CKA_ISSUER, (void*)keySubject, sizeof(keySubject) }, + }; CK_ATTRIBUTE pubTmpl[] = { { CKA_CLASS, &pubClass, sizeof(pubClass) }, { CKA_KEY_TYPE, &ecType, sizeof(ecType) }, @@ -707,10 +714,12 @@ static void test_key_subject_roundtrip(CK_SESSION_HANDLE session) CK_ULONG privCnt = sizeof(privTmpl) / sizeof(*privTmpl); rv = funcList->C_CreateObject(session, pubTmpl, pubCnt, &key); - CHECK_TRUE(rv != CKR_OK, "public key with CKA_ISSUER rejected"); + CHECK_RV(rv, "public key with CKA_ISSUER rejected", + CKR_ATTRIBUTE_TYPE_INVALID); destroy_obj(session, &key); rv = funcList->C_CreateObject(session, privTmpl, privCnt, &key); - CHECK_TRUE(rv != CKR_OK, "private key with CKA_SERIAL_NUMBER rejected"); + CHECK_RV(rv, "private key with CKA_SERIAL_NUMBER rejected", + CKR_ATTRIBUTE_TYPE_INVALID); destroy_obj(session, &key); rv = funcList->C_CreateObject(session, pubTmpl, pubCnt - 1, &key); @@ -722,7 +731,17 @@ static void test_key_subject_roundtrip(CK_SESSION_HANDLE session) XMEMCMP(buf, keySubject, sizeof(keySubject)) == 0, "public key subject reads back"); rv = funcList->C_SetAttributeValue(session, key, &issuer, 1); - CHECK_TRUE(rv != CKR_OK, "set CKA_ISSUER on public key rejected"); + CHECK_RV(rv, "set CKA_ISSUER on public key rejected", + CKR_ATTRIBUTE_TYPE_INVALID); + rv = funcList->C_SetAttributeValue(session, key, labelTmpl, 1); + CHECK_RV(rv, "set public key label", CKR_OK); + rv = funcList->C_SetAttributeValue(session, key, mixedTmpl, 2); + CHECK_RV(rv, "label with CKA_ISSUER on public key rejected", + CKR_ATTRIBUTE_TYPE_INVALID); + rv = funcList->C_GetAttributeValue(session, key, &getLabel, 1); + CHECK_TRUE(rv == CKR_OK && getLabel.ulValueLen == 1 && + labelBuf[0] == 'A', + "label unchanged after the rejected template"); } destroy_obj(session, &key); @@ -735,8 +754,8 @@ static void test_key_subject_roundtrip(CK_SESSION_HANDLE session) XMEMCMP(buf, keySubject, sizeof(keySubject)) == 0, "private key subject reads back"); rv = funcList->C_SetAttributeValue(session, key, &serial, 1); - CHECK_TRUE(rv != CKR_OK, - "set CKA_SERIAL_NUMBER on private key rejected"); + CHECK_RV(rv, "set CKA_SERIAL_NUMBER on private key rejected", + CKR_ATTRIBUTE_TYPE_INVALID); } destroy_obj(session, &key); } From f144495e0304fee255db0551ca07d6a5af7ff98f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 08:20:41 -0700 Subject: [PATCH 158/204] F-10377 - Report AES-CBC-PAD final block length from C_EncryptFinal --- src/crypto.c | 1 + tests/encrypt_decrypt_params_test.c | 175 ++++++++++++++++++++++++++++ tests/include.am | 7 ++ tests/pkcs11test.c | 1 + 4 files changed, 184 insertions(+) create mode 100644 tests/encrypt_decrypt_params_test.c diff --git a/src/crypto.c b/src/crypto.c index ad0aaf46..078f6a3a 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -3811,6 +3811,7 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } + *pulLastEncryptedPartLen = encPartLen; break; #endif #ifdef HAVE_AESCTR diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c new file mode 100644 index 00000000..05485ef0 --- /dev/null +++ b/tests/encrypt_decrypt_params_test.c @@ -0,0 +1,175 @@ +/* encrypt_decrypt_params_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Checks that encrypt/decrypt operations report output lengths correctly and + * validate mechanism parameters and data lengths. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#define TEST_DIR "./store/encrypt_decrypt_params_test" + +#ifndef NO_AES +static CK_RV create_aes_key(CK_SESSION_HANDLE session, unsigned char* key, + CK_ULONG keyLen, CK_OBJECT_HANDLE* hKey) +{ + CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; + CK_KEY_TYPE aesType = CKK_AES; + CK_BBOOL ckTrue = CK_TRUE; + CK_BBOOL ckFalse = CK_FALSE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &aesType, sizeof(aesType) }, + { CKA_VALUE, NULL, 0 }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + }; + + tmpl[2].pValue = key; + tmpl[2].ulValueLen = keyLen; + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), hKey); +} +#endif + +#if !defined(NO_AES) && defined(HAVE_AES_CBC) +/* C_EncryptFinal must report the number of bytes it wrote, not leave the + * caller's buffer capacity in place. */ +static void test_cbc_pad_encrypt_final_len(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech; + byte iv[16]; + byte plain[20]; + byte enc[64]; + byte dec[64]; + CK_ULONG updLen; + CK_ULONG lastLen; + CK_ULONG decLen; + + XMEMSET(iv, 0x11, sizeof(iv)); + XMEMSET(plain, 0x22, sizeof(plain)); + XMEMSET(enc, 0, sizeof(enc)); + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "CBC-PAD: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + mech.mechanism = CKM_AES_CBC_PAD; + mech.pParameter = iv; + mech.ulParameterLen = sizeof(iv); + + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "CBC-PAD: C_EncryptInit", CKR_OK); + if (rv != CKR_OK) + return; + updLen = sizeof(enc); + rv = funcList->C_EncryptUpdate(session, plain, sizeof(plain), enc, + &updLen); + CHECK_RV(rv, "CBC-PAD: C_EncryptUpdate", CKR_OK); + if (rv != CKR_OK) + return; + CHECK_TRUE(updLen == 16, "CBC-PAD: update emits one full block"); + + lastLen = sizeof(enc) - updLen; + rv = funcList->C_EncryptFinal(session, enc + updLen, &lastLen); + CHECK_RV(rv, "CBC-PAD: C_EncryptFinal (large buffer)", CKR_OK); + CHECK_TRUE(lastLen == 16, + "CBC-PAD: C_EncryptFinal reports the bytes written"); + if (rv != CKR_OK || lastLen != 16) + return; + + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "CBC-PAD: C_DecryptInit", CKR_OK); + if (rv != CKR_OK) + return; + decLen = sizeof(dec); + rv = funcList->C_Decrypt(session, enc, updLen + lastLen, dec, &decLen); + CHECK_RV(rv, "CBC-PAD: C_Decrypt of multi-part output", CKR_OK); + CHECK_TRUE(decLen == sizeof(plain) && + XMEMCMP(dec, plain, sizeof(plain)) == 0, + "CBC-PAD: multi-part ciphertext round-trips"); +} +#endif + +static int run_test(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = 0; + + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv != CKR_OK) + return -1; + + rv = pkcs11_open_session(&session); + CHECK_RV(rv, "open session", CKR_OK); + if (rv == CKR_OK) { +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + test_cbc_pad_encrypt_final_len(session); +#endif + } + + if (session != 0) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); + pkcs11_unload(); + return 0; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + + printf("=== wolfPKCS11 encrypt/decrypt parameter and length test ===\n"); + run_test(); + return pkcs11_test_summary(); +} diff --git a/tests/include.am b/tests/include.am index 11f79cd0..2e541b02 100644 --- a/tests/include.am +++ b/tests/include.am @@ -365,6 +365,11 @@ noinst_PROGRAMS += tests/object_create_getattr_test tests_object_create_getattr_test_SOURCES = tests/object_create_getattr_test.c tests_object_create_getattr_test_LDADD = +check_PROGRAMS += tests/encrypt_decrypt_params_test +noinst_PROGRAMS += tests/encrypt_decrypt_params_test +tests_encrypt_decrypt_params_test_SOURCES = tests/encrypt_decrypt_params_test.c +tests_encrypt_decrypt_params_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -439,6 +444,7 @@ tests_info_mech_table_test_LDADD += src/libwolfpkcs11.la tests_attribute_validation_test_LDADD += src/libwolfpkcs11.la tests_key_material_update_test_LDADD += src/libwolfpkcs11.la tests_object_create_getattr_test_LDADD += src/libwolfpkcs11.la +tests_encrypt_decrypt_params_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -505,6 +511,7 @@ tests_info_mech_table_test_LDADD += src/libwolfpkcs11.la tests_attribute_validation_test_LDADD += src/libwolfpkcs11.la tests_key_material_update_test_LDADD += src/libwolfpkcs11.la tests_object_create_getattr_test_LDADD += src/libwolfpkcs11.la +tests_encrypt_decrypt_params_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index 12bd9c9e..208bcf0b 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -11526,6 +11526,7 @@ static CK_RV test_aes_cbc_pad(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE key, if (ret == CKR_OK) { ret = funcList->C_EncryptFinal(session, pOut, &encSz); CHECK_CKR(ret, "AES-CBC Pad Encrypt Final"); + encSz += cumSz; } if (ret == CKR_OK) { From 4d1296711b661eaa4f8f0bb8b5cd78c3e243f72a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 08:24:03 -0700 Subject: [PATCH 159/204] F-10393 - Reject OAEP source data that is NULL with a non-zero length --- src/internal.c | 5 +- tests/encrypt_decrypt_params_test.c | 111 ++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 08e219f2..159ccdd2 100644 --- a/src/internal.c +++ b/src/internal.c @@ -11659,7 +11659,8 @@ static int wp11_mgf(CK_MECHANISM_TYPE mgfType, int *mgf) * @param labelSz [in] Length of data in bytes. * @return MEMORY_E when dynamic memory allocation fails. * BAD_FUNC_ARG when the digest algorithm id or the mask generation - * function id are not recognized. + * function id are not recognized, or label is NULL with a non-zero + * length. * 0 on success. */ int WP11_Session_SetOaepParams(WP11_Session* session, CK_MECHANISM_TYPE hashAlg, @@ -11673,6 +11674,8 @@ int WP11_Session_SetOaepParams(WP11_Session* session, CK_MECHANISM_TYPE hashAlg, ret = wp11_hash_type(hashAlg, &hashType); if (ret == 0) ret = wp11_mgf(mgf, &mgfType); + if (ret == 0 && label == NULL && labelSz != 0) + ret = BAD_FUNC_ARG; if (ret != 0) return ret; diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index 05485ef0..c3dd5537 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -135,6 +135,114 @@ static void test_cbc_pad_encrypt_final_len(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) +static CK_RV create_rsa_keys(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* pub, + CK_OBJECT_HANDLE* priv) +{ + CK_RV rv; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE rsaType = CKK_RSA; + CK_BBOOL ckTrue = CK_TRUE; + CK_BBOOL ckFalse = CK_FALSE; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_CLASS, &pubClass, sizeof(pubClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_CLASS, &privClass, sizeof(privClass) }, + { CKA_KEY_TYPE, &rsaType, sizeof(rsaType) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, &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) }, + }; + + rv = funcList->C_CreateObject(session, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), pub); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), + priv); + } + return rv; +} + +static void oaep_params_init(CK_RSA_PKCS_OAEP_PARAMS* params, + CK_MECHANISM* mech) +{ + params->hashAlg = CKM_SHA256; + params->mgf = CKG_MGF1_SHA256; + params->source = CKZ_DATA_SPECIFIED; + params->pSourceData = NULL; + params->ulSourceDataLen = 0; + mech->mechanism = CKM_RSA_PKCS_OAEP; + mech->pParameter = params; + mech->ulParameterLen = sizeof(*params); +} + +/* An OAEP encoding parameter with no source data must have zero length. */ +static void test_oaep_source_ptr_len(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_RSA_PKCS_OAEP_PARAMS params; + CK_MECHANISM mech; + byte plain[16]; + byte enc[256]; + byte dec[256]; + CK_ULONG encLen; + CK_ULONG decLen; + + XMEMSET(plain, 0x33, sizeof(plain)); + rv = create_rsa_keys(session, &pub, &priv); + CHECK_RV(rv, "OAEP: create RSA keys", CKR_OK); + if (rv != CKR_OK) + return; + + oaep_params_init(¶ms, &mech); + params.ulSourceDataLen = 1; + rv = funcList->C_EncryptInit(session, &mech, pub); + CHECK_RV(rv, "OAEP: C_EncryptInit NULL source with length", + CKR_MECHANISM_PARAM_INVALID); + rv = funcList->C_DecryptInit(session, &mech, priv); + CHECK_RV(rv, "OAEP: C_DecryptInit NULL source with length", + CKR_MECHANISM_PARAM_INVALID); + + oaep_params_init(¶ms, &mech); + rv = funcList->C_EncryptInit(session, &mech, pub); + CHECK_RV(rv, "OAEP: C_EncryptInit empty source", CKR_OK); + if (rv != CKR_OK) + return; + encLen = sizeof(enc); + rv = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encLen); + CHECK_RV(rv, "OAEP: C_Encrypt empty source", CKR_OK); + if (rv != CKR_OK) + return; + rv = funcList->C_DecryptInit(session, &mech, priv); + CHECK_RV(rv, "OAEP: C_DecryptInit empty source", CKR_OK); + if (rv != CKR_OK) + return; + decLen = sizeof(dec); + rv = funcList->C_Decrypt(session, enc, encLen, dec, &decLen); + CHECK_RV(rv, "OAEP: C_Decrypt empty source", CKR_OK); + CHECK_TRUE(decLen == sizeof(plain) && + XMEMCMP(dec, plain, sizeof(plain)) == 0, + "OAEP: empty source round-trips"); +} +#endif + static int run_test(void) { CK_RV rv; @@ -150,6 +258,9 @@ static int run_test(void) if (rv == CKR_OK) { #if !defined(NO_AES) && defined(HAVE_AES_CBC) test_cbc_pad_encrypt_final_len(session); +#endif +#if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) + test_oaep_source_ptr_len(session); #endif } From 04bec71c29b7199f858a55bef63a16e83c17eca6 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 08:26:21 -0700 Subject: [PATCH 160/204] F-11513 - Restrict AES-CCM nonce length to 7 through 13 bytes --- src/internal.c | 4 +- tests/encrypt_decrypt_params_test.c | 60 +++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index 159ccdd2..b8aee2fb 100644 --- a/src/internal.c +++ b/src/internal.c @@ -12038,7 +12038,7 @@ int WP11_Session_SetGcmParams(WP11_Session* session, unsigned char* iv, * @param aad [in] Additional authentication data. * @param aadSz [in] Length of additional authentication data. * @param macSz [in] Length of the MAC in bytes. - * @return BAD_FUNC_ARG if the IV/nonce is too big. + * @return BAD_FUNC_ARG if the nonce is not 7 to 13 bytes long. * Other -ve value on failure. * 0 on success. */ @@ -12050,7 +12050,7 @@ int WP11_Session_SetCcmParams(WP11_Session* session, int dataSz, int ret = 0; WP11_CcmParams* ccm = &session->params.ccm; - if (ivSz < 0 || ivSz > WP11_MAX_GCM_NONCE_SZ) + if (ivSz < CCM_NONCE_MIN_SZ || ivSz > CCM_NONCE_MAX_SZ) ret = BAD_FUNC_ARG; /* Caller-supplied IV pointer and length must agree: NULL pairs with 0 * and only with 0, and a non-NULL buffer must come with a positive diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index c3dd5537..df29f580 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -243,6 +243,63 @@ static void test_oaep_source_ptr_len(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AESCCM) +static void ccm_params_init(CK_CCM_PARAMS* params, CK_MECHANISM* mech, + byte* iv, CK_ULONG ivLen) +{ + XMEMSET(params, 0, sizeof(*params)); + params->pIv = iv; + params->ulIvLen = ivLen; + params->ulMacLen = 16; + mech->mechanism = CKM_AES_CCM; + mech->pParameter = params; + mech->ulParameterLen = sizeof(*params); +} + +/* CCM nonces must be 7 to 13 bytes long (NIST SP 800-38C). */ +static void test_ccm_nonce_len(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_CCM_PARAMS params; + CK_MECHANISM mech; + byte iv[16]; + byte plain[16]; + byte enc[32]; + CK_ULONG encLen; + static const CK_ULONG badLens[] = { 1, 6, 14, 16 }; + int i; + + XMEMSET(iv, 0x44, sizeof(iv)); + XMEMSET(plain, 0x55, sizeof(plain)); + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "CCM: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + for (i = 0; i < (int)(sizeof(badLens) / sizeof(*badLens)); i++) { + ccm_params_init(¶ms, &mech, iv, badLens[i]); + printf("CCM: nonce length %lu\n", (unsigned long)badLens[i]); + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "CCM: C_EncryptInit rejects nonce length", + CKR_MECHANISM_PARAM_INVALID); + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "CCM: C_DecryptInit rejects nonce length", + CKR_MECHANISM_PARAM_INVALID); + } + + ccm_params_init(¶ms, &mech, iv, 7); + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "CCM: C_EncryptInit nonce length 7", CKR_OK); + if (rv != CKR_OK) + return; + encLen = sizeof(enc); + rv = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encLen); + CHECK_RV(rv, "CCM: C_Encrypt nonce length 7", CKR_OK); +} +#endif + static int run_test(void) { CK_RV rv; @@ -261,6 +318,9 @@ static int run_test(void) #endif #if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) test_oaep_source_ptr_len(session); +#endif +#if !defined(NO_AES) && defined(HAVE_AESCCM) + test_ccm_nonce_len(session); #endif } From 85ceba57b2140e7e4d80e15532277088b7de3f6b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 08:28:22 -0700 Subject: [PATCH 161/204] F-3143 - Fix AES-CTR C_Decrypt size query to check the output buffer --- src/crypto.c | 2 +- tests/encrypt_decrypt_params_test.c | 59 +++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index 078f6a3a..8482b0a2 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -4393,7 +4393,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTR_DEC)) return CKR_OPERATION_NOT_INITIALIZED; - if (pEncryptedData == NULL) { + if (pData == NULL) { *pulDataLen = ulEncryptedDataLen; return CKR_OK; } diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index df29f580..da36118e 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -300,6 +300,62 @@ static void test_ccm_nonce_len(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AESCTR) +/* A NULL output buffer to C_Decrypt is a length query that leaves the + * operation active. */ +static void test_ctr_decrypt_size_query(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_AES_CTR_PARAMS params; + CK_MECHANISM mech; + byte plain[32]; + byte enc[32]; + byte dec[32]; + CK_ULONG encLen; + CK_ULONG decLen; + + XMEMSET(¶ms, 0, sizeof(params)); + params.ulCounterBits = 32; + XMEMSET(params.cb, 0x66, sizeof(params.cb)); + XMEMSET(plain, 0x77, sizeof(plain)); + mech.mechanism = CKM_AES_CTR; + mech.pParameter = ¶ms; + mech.ulParameterLen = sizeof(params); + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "CTR: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "CTR: C_EncryptInit", CKR_OK); + if (rv != CKR_OK) + return; + encLen = sizeof(enc); + rv = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encLen); + CHECK_RV(rv, "CTR: C_Encrypt", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "CTR: C_DecryptInit", CKR_OK); + if (rv != CKR_OK) + return; + decLen = 0; + rv = funcList->C_Decrypt(session, enc, encLen, NULL, &decLen); + CHECK_RV(rv, "CTR: C_Decrypt length query", CKR_OK); + CHECK_TRUE(decLen == encLen, "CTR: length query reports plaintext size"); + + decLen = sizeof(dec); + rv = funcList->C_Decrypt(session, enc, encLen, dec, &decLen); + CHECK_RV(rv, "CTR: C_Decrypt after length query", CKR_OK); + CHECK_TRUE(decLen == sizeof(plain) && + XMEMCMP(dec, plain, sizeof(plain)) == 0, + "CTR: decrypt after length query round-trips"); +} +#endif + static int run_test(void) { CK_RV rv; @@ -321,6 +377,9 @@ static int run_test(void) #endif #if !defined(NO_AES) && defined(HAVE_AESCCM) test_ccm_nonce_len(session); +#endif +#if !defined(NO_AES) && defined(HAVE_AESCTR) + test_ctr_decrypt_size_query(session); #endif } From b56578982548bf41a87ac536c638ea2f8f0c7edc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 08:30:28 -0700 Subject: [PATCH 162/204] F-2378 - Reject AES-CBC and AES-ECB data that is not block aligned --- src/crypto.c | 18 +++++++ tests/encrypt_decrypt_params_test.c | 74 +++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index 8482b0a2..73839edb 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -3239,6 +3239,11 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } + if ((ulDataLen % AES_BLOCK_SIZE) != 0) { + WP11_AesCbc_EncryptFinal(session); + WP11_Session_SetOpInitialized(session, 0); + return CKR_DATA_LEN_RANGE; + } encDataLen = (word32)ulDataLen; if (pEncryptedData == NULL) { @@ -3366,6 +3371,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } + if ((ulDataLen % AES_BLOCK_SIZE) != 0) { + WP11_Session_AbortOp(session); + return CKR_DATA_LEN_RANGE; + } encDataLen = (word32)ulDataLen; if (pEncryptedData == NULL) { @@ -4330,6 +4339,11 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } + if ((ulEncryptedDataLen % AES_BLOCK_SIZE) != 0) { + WP11_AesCbc_DecryptFinal(session); + WP11_Session_SetOpInitialized(session, 0); + return CKR_ENCRYPTED_DATA_LEN_RANGE; + } decDataLen = (word32)ulEncryptedDataLen; if (pData == NULL) { @@ -4468,6 +4482,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } + if ((ulEncryptedDataLen % AES_BLOCK_SIZE) != 0) { + WP11_Session_AbortOp(session); + return CKR_ENCRYPTED_DATA_LEN_RANGE; + } decDataLen = (word32)ulEncryptedDataLen; if (pData == NULL) { diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index da36118e..82696fa8 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -356,6 +356,77 @@ static void test_ctr_decrypt_size_query(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESECB)) +/* Unpadded block modes reject data that is not a whole number of blocks with + * the PKCS#11 length-range codes, and the failed call ends the operation. */ +static void check_block_alignment(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key, CK_MECHANISM* mech) +{ + CK_RV rv; + byte data[32]; + byte out[32]; + CK_ULONG outLen; + + XMEMSET(data, 0x5A, sizeof(data)); + + rv = funcList->C_EncryptInit(session, mech, key); + CHECK_RV(rv, "block align: C_EncryptInit", CKR_OK); + if (rv != CKR_OK) + return; + outLen = sizeof(out); + rv = funcList->C_Encrypt(session, data, 17, out, &outLen); + CHECK_RV(rv, "block align: C_Encrypt of 17 bytes", CKR_DATA_LEN_RANGE); + outLen = sizeof(out); + rv = funcList->C_Encrypt(session, data, 16, out, &outLen); + CHECK_RV(rv, "block align: encrypt operation ended after length error", + CKR_OPERATION_NOT_INITIALIZED); + + rv = funcList->C_DecryptInit(session, mech, key); + CHECK_RV(rv, "block align: C_DecryptInit", CKR_OK); + if (rv != CKR_OK) + return; + outLen = sizeof(out); + rv = funcList->C_Decrypt(session, data, 15, out, &outLen); + CHECK_RV(rv, "block align: C_Decrypt of 15 bytes", + CKR_ENCRYPTED_DATA_LEN_RANGE); + outLen = sizeof(out); + rv = funcList->C_Decrypt(session, data, 16, out, &outLen); + CHECK_RV(rv, "block align: decrypt operation ended after length error", + CKR_OPERATION_NOT_INITIALIZED); +} + +static void test_block_mode_alignment(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech; +#ifdef HAVE_AES_CBC + byte iv[16]; +#endif + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "block align: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + +#ifdef HAVE_AES_CBC + XMEMSET(iv, 0x12, sizeof(iv)); + mech.mechanism = CKM_AES_CBC; + mech.pParameter = iv; + mech.ulParameterLen = sizeof(iv); + printf("AES-CBC\n"); + check_block_alignment(session, key, &mech); +#endif +#ifdef HAVE_AESECB + mech.mechanism = CKM_AES_ECB; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + printf("AES-ECB\n"); + check_block_alignment(session, key, &mech); +#endif +} +#endif + static int run_test(void) { CK_RV rv; @@ -380,6 +451,9 @@ static int run_test(void) #endif #if !defined(NO_AES) && defined(HAVE_AESCTR) test_ctr_decrypt_size_query(session); +#endif +#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESECB)) + test_block_mode_alignment(session); #endif } From 0098666e7d32ee35f78a8bb01b440343601fee17 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 08:32:36 -0700 Subject: [PATCH 163/204] F-12459 - Compute AES check value from a single zero block --- src/internal.c | 27 +++----- tests/encrypt_decrypt_params_test.c | 95 +++++++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 24 deletions(-) diff --git a/src/internal.c b/src/internal.c index b8aee2fb..ce869f55 100644 --- a/src/internal.c +++ b/src/internal.c @@ -15008,10 +15008,9 @@ static int GetEcbCheckValue(WP11_Object* secret, byte* dataOut, CK_ULONG* outLen) { int ret; - byte* hash; - byte* input; - word32 inLen; - WP11_Data* key = secret->data.symmKey; + byte input[AES_BLOCK_SIZE]; + byte block[AES_BLOCK_SIZE]; + word32 blockLen = AES_BLOCK_SIZE; if (dataOut == NULL) { if (outLen != NULL) { @@ -15026,27 +15025,17 @@ static int GetEcbCheckValue(WP11_Object* secret, byte* dataOut, return BUFFER_E; } - hash = XMALLOC(key->len, NULL, DYNAMIC_TYPE_TMP_BUFFER); - if (!hash) - return MEMORY_E; - input = XMALLOC(key->len, NULL, DYNAMIC_TYPE_TMP_BUFFER); - if (input == NULL) { - XFREE(hash, NULL, DYNAMIC_TYPE_TMP_BUFFER); - return MEMORY_E; - } - inLen = key->len; - XMEMSET(input, 0, inLen); - - ret = WP11_AesEcb_Encrypt(input, inLen, hash, &inLen, secret, + XMEMSET(input, 0, sizeof(input)); + ret = WP11_AesEcb_Encrypt(input, sizeof(input), block, &blockLen, secret, secret->session); if (ret == 0) { - XMEMCPY(dataOut, hash, PKCS11_CHECK_VALUE_SIZE); + XMEMCPY(dataOut, block, PKCS11_CHECK_VALUE_SIZE); *outLen = PKCS11_CHECK_VALUE_SIZE; } - XFREE(hash, NULL, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(input, NULL, DYNAMIC_TYPE_TMP_BUFFER); + /* The full encrypted zero block is key material (e.g. the GHASH key). */ + wc_ForceZero(block, sizeof(block)); if (ret != 0) return CKR_FUNCTION_FAILED; diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index 82696fa8..ce4725cf 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -427,10 +427,87 @@ static void test_block_mode_alignment(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AESECB) +/* CKA_CHECK_VALUE of an AES key is the first three bytes of one zero block + * encrypted under the key, for every AES key size. */ +static void test_aes_check_value_key_sizes(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key; + CK_MECHANISM mech; + byte keyData[32]; + byte zero[16]; + byte enc[16]; + byte kcv[3]; + CK_ULONG encLen; + CK_ATTRIBUTE attr; + static const CK_ULONG keyLens[] = { 16, 24, 32 }; + int i; + + for (i = 0; i < (int)sizeof(keyData); i++) + keyData[i] = (byte)(0xA0 + i); + XMEMSET(zero, 0, sizeof(zero)); + mech.mechanism = CKM_AES_ECB; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + + for (i = 0; i < (int)(sizeof(keyLens) / sizeof(*keyLens)); i++) { + printf("check value: AES key length %lu\n", (unsigned long)keyLens[i]); + key = CK_INVALID_HANDLE; + rv = create_aes_key(session, keyData, keyLens[i], &key); + CHECK_RV(rv, "check value: create AES key", CKR_OK); + if (rv != CKR_OK) + continue; + + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "check value: C_EncryptInit", CKR_OK); + if (rv != CKR_OK) + continue; + encLen = sizeof(enc); + rv = funcList->C_Encrypt(session, zero, sizeof(zero), enc, &encLen); + CHECK_RV(rv, "check value: C_Encrypt zero block", CKR_OK); + if (rv != CKR_OK) + continue; + + XMEMSET(kcv, 0, sizeof(kcv)); + attr.type = CKA_CHECK_VALUE; + attr.pValue = kcv; + attr.ulValueLen = sizeof(kcv); + rv = funcList->C_GetAttributeValue(session, key, &attr, 1); + CHECK_RV(rv, "check value: C_GetAttributeValue", CKR_OK); + CHECK_TRUE(rv == CKR_OK && attr.ulValueLen == sizeof(kcv) && + XMEMCMP(kcv, enc, sizeof(kcv)) == 0, + "check value matches encrypted zero block"); + } +} +#endif + +typedef void (*test_fn)(CK_SESSION_HANDLE session); + +/* Each check runs in its own session so session objects and operation state + * do not carry over between checks. */ +static void run_in_session(CK_SLOT_ID slot, test_fn fn) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + CHECK_RV(rv, "open test session", CKR_OK); + if (rv == CKR_OK) { + fn(session); + funcList->C_CloseSession(session); + } +} + static int run_test(void) { CK_RV rv; CK_SESSION_HANDLE session = 0; + CK_SESSION_INFO info; + CK_SLOT_ID slot = 0; + + (void)run_in_session; rv = pkcs11_load(); CHECK_RV(rv, "load library", CKR_OK); @@ -440,20 +517,28 @@ static int run_test(void) rv = pkcs11_open_session(&session); CHECK_RV(rv, "open session", CKR_OK); if (rv == CKR_OK) { + rv = funcList->C_GetSessionInfo(session, &info); + CHECK_RV(rv, "C_GetSessionInfo", CKR_OK); + } + if (rv == CKR_OK) { + slot = info.slotID; #if !defined(NO_AES) && defined(HAVE_AES_CBC) - test_cbc_pad_encrypt_final_len(session); + run_in_session(slot, test_cbc_pad_encrypt_final_len); #endif #if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) - test_oaep_source_ptr_len(session); + run_in_session(slot, test_oaep_source_ptr_len); #endif #if !defined(NO_AES) && defined(HAVE_AESCCM) - test_ccm_nonce_len(session); + run_in_session(slot, test_ccm_nonce_len); #endif #if !defined(NO_AES) && defined(HAVE_AESCTR) - test_ctr_decrypt_size_query(session); + run_in_session(slot, test_ctr_decrypt_size_query); #endif #if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESECB)) - test_block_mode_alignment(session); + run_in_session(slot, test_block_mode_alignment); +#endif +#if !defined(NO_AES) && defined(HAVE_AESECB) + run_in_session(slot, test_aes_check_value_key_sizes); #endif } From 3f0a9cdfa310df677f7675ab412299f1069f6522 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 08:34:58 -0700 Subject: [PATCH 164/204] F-4963 - Reject out-of-range AES-GCM parameter lengths at init --- src/crypto.c | 10 +++ src/internal.c | 6 +- tests/encrypt_decrypt_params_test.c | 98 +++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 73839edb..c4b5dbd8 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -2993,6 +2993,11 @@ static CK_RV EncryptInit(CK_SESSION_HANDLE hSession, return CKR_MECHANISM_PARAM_INVALID; params = (CK_GCM_PARAMS*)pMechanism->pParameter; + if (params->ulIvLen > (CK_ULONG)INT_MAX || + params->ulAADLen > (CK_ULONG)INT_MAX || + params->ulTagBits > 128) { + return CKR_MECHANISM_PARAM_INVALID; + } ret = WP11_Session_SetGcmParams(session, params->pIv, (int)params->ulIvLen, params->pAAD, (int)params->ulAADLen, @@ -4072,6 +4077,11 @@ static CK_RV DecryptInit(CK_SESSION_HANDLE hSession, return CKR_MECHANISM_PARAM_INVALID; params = (CK_GCM_PARAMS*)pMechanism->pParameter; + if (params->ulIvLen > (CK_ULONG)INT_MAX || + params->ulAADLen > (CK_ULONG)INT_MAX || + params->ulTagBits > 128) { + return CKR_MECHANISM_PARAM_INVALID; + } ret = WP11_Session_SetGcmParams(session, params->pIv, (int)params->ulIvLen, params->pAAD, (int)params->ulAADLen, diff --git a/src/internal.c b/src/internal.c index ce869f55..b5fc66f8 100644 --- a/src/internal.c +++ b/src/internal.c @@ -11970,7 +11970,8 @@ int WP11_Session_SetAesWrapParams(WP11_Session* session, byte* iv, word32 ivLen, * @param aad [in] Additional authentication data. * @param aadSz [in] Length of additional authentication data. * @param tagBits [in] Number of bits to use as the authentication tag. - * @return BAD_FUNC_ARG if the IV/nonce or the tagBits are too big. + * @return BAD_FUNC_ARG if the IV/nonce, AAD length or tagBits are out of + * range. * Other -ve value on failure. * 0 on success. */ @@ -11981,7 +11982,8 @@ int WP11_Session_SetGcmParams(WP11_Session* session, unsigned char* iv, int ret = 0; WP11_GcmParams* gcm = &session->params.gcm; - if (tagBits > 128 || ivSz < 0 || ivSz > WP11_MAX_GCM_NONCE_SZ) + if (tagBits < 0 || tagBits > 128 || ivSz < 0 || + ivSz > WP11_MAX_GCM_NONCE_SZ || aadLen < 0) ret = BAD_FUNC_ARG; /* Caller-supplied IV pointer and length must agree: NULL pairs with 0 * and only with 0, and a non-NULL buffer must come with a positive diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index ce4725cf..b9bcbad1 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -500,6 +500,101 @@ static void run_in_session(CK_SLOT_ID slot, test_fn fn) } } +#if !defined(NO_AES) && defined(HAVE_AESGCM) +static void gcm_params_init(CK_GCM_PARAMS* params, CK_MECHANISM* mech, + byte* iv, byte* aad) +{ + XMEMSET(params, 0, sizeof(*params)); + params->pIv = iv; + params->ulIvLen = 12; + params->pAAD = aad; + params->ulAADLen = 16; + params->ulTagBits = 128; + mech->mechanism = CKM_AES_GCM; + mech->pParameter = params; + mech->ulParameterLen = sizeof(*params); +} + +static void check_gcm_params_rejected(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key, CK_MECHANISM* mech) +{ + CK_RV rv; + + rv = funcList->C_EncryptInit(session, mech, key); + CHECK_RV(rv, "GCM: C_EncryptInit rejects parameter", + CKR_MECHANISM_PARAM_INVALID); + rv = funcList->C_DecryptInit(session, mech, key); + CHECK_RV(rv, "GCM: C_DecryptInit rejects parameter", + CKR_MECHANISM_PARAM_INVALID); +} + +/* CK_GCM_PARAMS lengths outside what the token supports are rejected rather + * than narrowed. */ +static void test_gcm_param_ranges(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_GCM_PARAMS params; + CK_MECHANISM mech; + byte iv[12]; + byte aad[16]; + byte plain[16]; + byte enc[32]; + CK_ULONG encLen; + CK_ULONG wrap = ((CK_ULONG)1 << 16) << 16; + + XMEMSET(iv, 0x21, sizeof(iv)); + XMEMSET(aad, 0x43, sizeof(aad)); + XMEMSET(plain, 0x65, sizeof(plain)); + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "GCM: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + printf("GCM: tag bits 0xFFFFFFFF\n"); + gcm_params_init(¶ms, &mech, iv, aad); + params.ulTagBits = 0xFFFFFFFFUL; + check_gcm_params_rejected(session, key, &mech); + + printf("GCM: tag bits 0x80000000\n"); + gcm_params_init(¶ms, &mech, iv, aad); + params.ulTagBits = 0x80000000UL; + check_gcm_params_rejected(session, key, &mech); + + printf("GCM: AAD length 0x80000000\n"); + gcm_params_init(¶ms, &mech, iv, aad); + params.ulAADLen = 0x80000000UL; + check_gcm_params_rejected(session, key, &mech); + + if (wrap != 0) { + printf("GCM: tag bits 2^32 + 128\n"); + gcm_params_init(¶ms, &mech, iv, aad); + params.ulTagBits = wrap + 128; + check_gcm_params_rejected(session, key, &mech); + + printf("GCM: IV length 2^32 + 12\n"); + gcm_params_init(¶ms, &mech, iv, aad); + params.ulIvLen = wrap + sizeof(iv); + check_gcm_params_rejected(session, key, &mech); + + printf("GCM: AAD length 2^32 + 16\n"); + gcm_params_init(¶ms, &mech, iv, aad); + params.ulAADLen = wrap + sizeof(aad); + check_gcm_params_rejected(session, key, &mech); + } + + gcm_params_init(¶ms, &mech, iv, aad); + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "GCM: C_EncryptInit valid parameters", CKR_OK); + if (rv != CKR_OK) + return; + encLen = sizeof(enc); + rv = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encLen); + CHECK_RV(rv, "GCM: C_Encrypt valid parameters", CKR_OK); +} +#endif + static int run_test(void) { CK_RV rv; @@ -539,6 +634,9 @@ static int run_test(void) #endif #if !defined(NO_AES) && defined(HAVE_AESECB) run_in_session(slot, test_aes_check_value_key_sizes); +#endif +#if !defined(NO_AES) && defined(HAVE_AESGCM) + run_in_session(slot, test_gcm_param_ranges); #endif } From e5b779d50c7f5594b8cd39ab02583a646b95482c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:26:43 -0700 Subject: [PATCH 165/204] F-4959 - Reject out-of-range AES-CCM parameter and MAC lengths at init --- src/crypto.c | 12 ++++ src/internal.c | 7 ++- tests/encrypt_decrypt_params_test.c | 93 +++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index c4b5dbd8..de55f352 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -3021,6 +3021,12 @@ static CK_RV EncryptInit(CK_SESSION_HANDLE hSession, return CKR_MECHANISM_PARAM_INVALID; params = (CK_CCM_PARAMS*)pMechanism->pParameter; + if (params->ulDataLen > (CK_ULONG)INT_MAX || + params->ulIvLen > (CK_ULONG)INT_MAX || + params->ulAADLen > (CK_ULONG)INT_MAX || + params->ulMacLen > (CK_ULONG)INT_MAX) { + return CKR_MECHANISM_PARAM_INVALID; + } ret = WP11_Session_SetCcmParams(session, (int)params->ulDataLen, params->pIv, (int)params->ulIvLen, @@ -4104,6 +4110,12 @@ static CK_RV DecryptInit(CK_SESSION_HANDLE hSession, return CKR_MECHANISM_PARAM_INVALID; params = (CK_CCM_PARAMS*)pMechanism->pParameter; + if (params->ulDataLen > (CK_ULONG)INT_MAX || + params->ulIvLen > (CK_ULONG)INT_MAX || + params->ulAADLen > (CK_ULONG)INT_MAX || + params->ulMacLen > (CK_ULONG)INT_MAX) { + return CKR_MECHANISM_PARAM_INVALID; + } ret = WP11_Session_SetCcmParams(session, (int)params->ulDataLen, params->pIv, (int)params->ulIvLen, diff --git a/src/internal.c b/src/internal.c index b5fc66f8..1125494d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -12040,7 +12040,7 @@ int WP11_Session_SetGcmParams(WP11_Session* session, unsigned char* iv, * @param aad [in] Additional authentication data. * @param aadSz [in] Length of additional authentication data. * @param macSz [in] Length of the MAC in bytes. - * @return BAD_FUNC_ARG if the nonce is not 7 to 13 bytes long. + * @return BAD_FUNC_ARG if the nonce, MAC or a length is out of range. * Other -ve value on failure. * 0 on success. */ @@ -12054,6 +12054,11 @@ int WP11_Session_SetCcmParams(WP11_Session* session, int dataSz, if (ivSz < CCM_NONCE_MIN_SZ || ivSz > CCM_NONCE_MAX_SZ) ret = BAD_FUNC_ARG; + /* CCM tags are an even number of bytes from 4 to 16. */ + if (ret == 0 && (macSz < 4 || macSz > AES_BLOCK_SIZE || (macSz & 1))) + ret = BAD_FUNC_ARG; + if (ret == 0 && (dataSz < 0 || aadSz < 0)) + ret = BAD_FUNC_ARG; /* Caller-supplied IV pointer and length must agree: NULL pairs with 0 * and only with 0, and a non-NULL buffer must come with a positive * length. Either mismatch is a contract bug. */ diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index b9bcbad1..88fc285f 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -595,6 +595,96 @@ static void test_gcm_param_ranges(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AESCCM) +static void check_ccm_params_rejected(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key, CK_MECHANISM* mech) +{ + CK_RV rv; + + rv = funcList->C_EncryptInit(session, mech, key); + CHECK_RV(rv, "CCM: C_EncryptInit rejects parameter", + CKR_MECHANISM_PARAM_INVALID); + rv = funcList->C_DecryptInit(session, mech, key); + CHECK_RV(rv, "CCM: C_DecryptInit rejects parameter", + CKR_MECHANISM_PARAM_INVALID); +} + +/* CK_CCM_PARAMS lengths must be in range for CCM and are rejected rather than + * narrowed. */ +static void test_ccm_param_ranges(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_CCM_PARAMS params; + CK_MECHANISM mech; + byte iv[13]; + byte aad[16]; + byte plain[16]; + byte enc[32]; + CK_ULONG encLen; + CK_ULONG wrap = ((CK_ULONG)1 << 16) << 16; + static const CK_ULONG badMacLens[] = { 0, 2, 5, 15, 18 }; + int i; + + XMEMSET(iv, 0x31, sizeof(iv)); + XMEMSET(aad, 0x53, sizeof(aad)); + XMEMSET(plain, 0x75, sizeof(plain)); + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "CCM: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + for (i = 0; i < (int)(sizeof(badMacLens) / sizeof(*badMacLens)); i++) { + printf("CCM: MAC length %lu\n", (unsigned long)badMacLens[i]); + ccm_params_init(¶ms, &mech, iv, sizeof(iv)); + params.ulMacLen = badMacLens[i]; + check_ccm_params_rejected(session, key, &mech); + } + + printf("CCM: AAD length 0x80000000\n"); + ccm_params_init(¶ms, &mech, iv, sizeof(iv)); + params.pAAD = aad; + params.ulAADLen = 0x80000000UL; + check_ccm_params_rejected(session, key, &mech); + + printf("CCM: data length 0x80000000\n"); + ccm_params_init(¶ms, &mech, iv, sizeof(iv)); + params.ulDataLen = 0x80000000UL; + check_ccm_params_rejected(session, key, &mech); + + if (wrap != 0) { + printf("CCM: MAC length 2^32 + 4\n"); + ccm_params_init(¶ms, &mech, iv, sizeof(iv)); + params.ulMacLen = wrap + 4; + check_ccm_params_rejected(session, key, &mech); + + printf("CCM: AAD length 2^32 + 16\n"); + ccm_params_init(¶ms, &mech, iv, sizeof(iv)); + params.pAAD = aad; + params.ulAADLen = wrap + sizeof(aad); + check_ccm_params_rejected(session, key, &mech); + + printf("CCM: nonce length 2^32 + 13\n"); + ccm_params_init(¶ms, &mech, iv, wrap + sizeof(iv)); + check_ccm_params_rejected(session, key, &mech); + } + + ccm_params_init(¶ms, &mech, iv, sizeof(iv)); + params.pAAD = aad; + params.ulAADLen = sizeof(aad); + params.ulMacLen = 8; + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "CCM: C_EncryptInit valid parameters", CKR_OK); + if (rv != CKR_OK) + return; + encLen = sizeof(enc); + rv = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encLen); + CHECK_RV(rv, "CCM: C_Encrypt valid parameters", CKR_OK); + CHECK_TRUE(encLen == sizeof(plain) + 8, "CCM: output carries 8-byte MAC"); +} +#endif + static int run_test(void) { CK_RV rv; @@ -637,6 +727,9 @@ static int run_test(void) #endif #if !defined(NO_AES) && defined(HAVE_AESGCM) run_in_session(slot, test_gcm_param_ranges); +#endif +#if !defined(NO_AES) && defined(HAVE_AESCCM) + run_in_session(slot, test_ccm_param_ranges); #endif } From 2fe5e83e76076dcda820a4925684e4f72dbebf3f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 09:31:02 -0700 Subject: [PATCH 166/204] F-4735 - Reject OAEP source lengths that do not fit an int --- src/crypto.c | 4 ++ src/internal.c | 6 +- tests/encrypt_decrypt_params_test.c | 89 +++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index de55f352..befbf54b 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -2887,6 +2887,8 @@ static CK_RV EncryptInit(CK_SESSION_HANDLE hSession, params = (CK_RSA_PKCS_OAEP_PARAMS*)pMechanism->pParameter; if (params->source != CKZ_DATA_SPECIFIED) return CKR_MECHANISM_PARAM_INVALID; + if (params->ulSourceDataLen > (CK_ULONG)INT_MAX) + return CKR_MECHANISM_PARAM_INVALID; ret = WP11_Session_SetOaepParams(session, params->hashAlg, params->mgf, (byte*)params->pSourceData, @@ -4007,6 +4009,8 @@ static CK_RV DecryptInit(CK_SESSION_HANDLE hSession, params = (CK_RSA_PKCS_OAEP_PARAMS*)pMechanism->pParameter; if (params->source != CKZ_DATA_SPECIFIED) return CKR_MECHANISM_PARAM_INVALID; + if (params->ulSourceDataLen > (CK_ULONG)INT_MAX) + return CKR_MECHANISM_PARAM_INVALID; ret = WP11_Session_SetOaepParams(session, params->hashAlg, params->mgf, (byte*)params->pSourceData, diff --git a/src/internal.c b/src/internal.c index 1125494d..79a8995e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -11659,8 +11659,8 @@ static int wp11_mgf(CK_MECHANISM_TYPE mgfType, int *mgf) * @param labelSz [in] Length of data in bytes. * @return MEMORY_E when dynamic memory allocation fails. * BAD_FUNC_ARG when the digest algorithm id or the mask generation - * function id are not recognized, or label is NULL with a non-zero - * length. + * function id are not recognized, labelSz is negative, or label is + * NULL with a non-zero length. * 0 on success. */ int WP11_Session_SetOaepParams(WP11_Session* session, CK_MECHANISM_TYPE hashAlg, @@ -11674,7 +11674,7 @@ int WP11_Session_SetOaepParams(WP11_Session* session, CK_MECHANISM_TYPE hashAlg, ret = wp11_hash_type(hashAlg, &hashType); if (ret == 0) ret = wp11_mgf(mgf, &mgfType); - if (ret == 0 && label == NULL && labelSz != 0) + if (ret == 0 && (labelSz < 0 || (label == NULL && labelSz != 0))) ret = BAD_FUNC_ARG; if (ret != 0) return ret; diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index 88fc285f..3eeeb187 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -685,6 +685,92 @@ static void test_ccm_param_ranges(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) +static void check_oaep_params_rejected(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE pub, + CK_OBJECT_HANDLE priv, + CK_MECHANISM* mech) +{ + CK_RV rv; + + rv = funcList->C_EncryptInit(session, mech, pub); + CHECK_RV(rv, "OAEP: C_EncryptInit rejects source length", + CKR_MECHANISM_PARAM_INVALID); + rv = funcList->C_DecryptInit(session, mech, priv); + CHECK_RV(rv, "OAEP: C_DecryptInit rejects source length", + CKR_MECHANISM_PARAM_INVALID); +} + +/* An OAEP source length the token cannot represent is rejected rather than + * narrowed to a different label. */ +static void test_oaep_source_len_range(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_RSA_PKCS_OAEP_PARAMS params; + CK_MECHANISM mech; + byte label[16]; + byte plain[16]; + byte enc[256]; + byte dec[256]; + CK_ULONG encLen; + CK_ULONG decLen; + CK_ULONG wrap = ((CK_ULONG)1 << 16) << 16; + + XMEMSET(label, 0x4C, sizeof(label)); + XMEMSET(plain, 0x50, sizeof(plain)); + + rv = create_rsa_keys(session, &pub, &priv); + CHECK_RV(rv, "OAEP: create RSA keys", CKR_OK); + if (rv != CKR_OK) + return; + + printf("OAEP: source length 0x80000000\n"); + oaep_params_init(¶ms, &mech); + params.pSourceData = label; + params.ulSourceDataLen = 0x80000000UL; + check_oaep_params_rejected(session, pub, priv, &mech); + + if (wrap != 0) { + printf("OAEP: source length 2^32 + 16\n"); + oaep_params_init(¶ms, &mech); + params.pSourceData = label; + params.ulSourceDataLen = wrap + sizeof(label); + check_oaep_params_rejected(session, pub, priv, &mech); + + printf("OAEP: source length 2^32\n"); + oaep_params_init(¶ms, &mech); + params.pSourceData = label; + params.ulSourceDataLen = wrap; + check_oaep_params_rejected(session, pub, priv, &mech); + } + + oaep_params_init(¶ms, &mech); + params.pSourceData = label; + params.ulSourceDataLen = sizeof(label); + rv = funcList->C_EncryptInit(session, &mech, pub); + CHECK_RV(rv, "OAEP: C_EncryptInit with label", CKR_OK); + if (rv != CKR_OK) + return; + encLen = sizeof(enc); + rv = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encLen); + CHECK_RV(rv, "OAEP: C_Encrypt with label", CKR_OK); + if (rv != CKR_OK) + return; + rv = funcList->C_DecryptInit(session, &mech, priv); + CHECK_RV(rv, "OAEP: C_DecryptInit with label", CKR_OK); + if (rv != CKR_OK) + return; + decLen = sizeof(dec); + rv = funcList->C_Decrypt(session, enc, encLen, dec, &decLen); + CHECK_RV(rv, "OAEP: C_Decrypt with label", CKR_OK); + CHECK_TRUE(decLen == sizeof(plain) && + XMEMCMP(dec, plain, sizeof(plain)) == 0, + "OAEP: labelled ciphertext round-trips"); +} +#endif + static int run_test(void) { CK_RV rv; @@ -730,6 +816,9 @@ static int run_test(void) #endif #if !defined(NO_AES) && defined(HAVE_AESCCM) run_in_session(slot, test_ccm_param_ranges); +#endif +#if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) + run_in_session(slot, test_oaep_source_len_range); #endif } From d5f2876c0e503e6a2f7f8c0b3de2f12ec6e80ba8 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:08:12 -0700 Subject: [PATCH 167/204] F-6669 - Reject oversized AES-CTR and AES-CTS single-part lengths --- src/crypto.c | 16 ++++++ src/internal.c | 17 ++++++ tests/encrypt_decrypt_params_test.c | 86 +++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 1 + 4 files changed, 120 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index befbf54b..78220619 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -3308,6 +3308,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, case CKM_AES_CTR: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTR_ENC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulDataLen)) { + WP11_AesCtr_Final(session); + return CKR_DATA_LEN_RANGE; + } if (pEncryptedData == NULL) { *pulEncryptedDataLen = ulDataLen; @@ -3408,6 +3412,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, case CKM_AES_CTS: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTS_ENC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulDataLen)) { + WP11_AesCts_Final(session); + return CKR_DATA_LEN_RANGE; + } encDataLen = (word32)*pulEncryptedDataLen; if (pEncryptedData == NULL) { @@ -4432,6 +4440,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, case CKM_AES_CTR: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTR_DEC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) { + WP11_AesCtr_Final(session); + return CKR_ENCRYPTED_DATA_LEN_RANGE; + } if (pData == NULL) { *pulDataLen = ulEncryptedDataLen; @@ -4532,6 +4544,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, case CKM_AES_CTS: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTS_DEC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) { + WP11_AesCts_Final(session); + return CKR_ENCRYPTED_DATA_LEN_RANGE; + } decDataLen = (word32)*pulDataLen; if (pData == NULL) { diff --git a/src/internal.c b/src/internal.c index 79a8995e..f8a64b1c 100644 --- a/src/internal.c +++ b/src/internal.c @@ -18996,6 +18996,8 @@ int WP11_AesCtr_Final(WP11_Session* session) WP11_CtrParams* ctr = &session->params.ctr; wc_AesFree(&ctr->aes); + /* Older wolfSSL versions do not zeroize the key schedule in wc_AesFree. */ + wc_ForceZero(ctr, sizeof(*ctr)); session->init = 0; return 0; @@ -20132,6 +20134,21 @@ int WP11_AesCts_DecryptFinal(unsigned char* dec, word32* decSz, return ret; } + +/** + * Abandon an AES-CTS operation, releasing the AES object. + * + * @param session [in] Session object holding Aes object. + */ +void WP11_AesCts_Final(WP11_Session* session) +{ + WP11_CtsParams* cts = &session->params.cts; + + wc_AesFree(&cts->aes); + /* Older wolfSSL versions do not zeroize the key schedule in wc_AesFree. */ + wc_ForceZero(cts, sizeof(*cts)); + session->init = 0; +} #endif /* HAVE_AESCTS */ #ifdef HAVE_AESCMAC diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index 3eeeb187..050ffd7d 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -771,6 +771,89 @@ static void test_oaep_source_len_range(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && (defined(HAVE_AESCTR) || defined(HAVE_AESCTS)) +/* Single-part calls reject a data length the token cannot represent and end + * the operation instead of processing a shorter length. */ +static void check_single_part_len_range(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key, + CK_MECHANISM* mech) +{ + CK_RV rv; + byte data[32]; + byte out[32]; + CK_ULONG outLen; + CK_ULONG wrap = ((CK_ULONG)1 << 16) << 16; + + XMEMSET(data, 0x3C, sizeof(data)); + + rv = funcList->C_EncryptInit(session, mech, key); + CHECK_RV(rv, "single-part length: C_EncryptInit", CKR_OK); + if (rv != CKR_OK) + return; + outLen = wrap + sizeof(out); + rv = funcList->C_Encrypt(session, data, wrap + sizeof(data), out, &outLen); + CHECK_RV(rv, "single-part length: C_Encrypt rejects oversized data", + CKR_DATA_LEN_RANGE); + outLen = sizeof(out); + rv = funcList->C_Encrypt(session, data, sizeof(data), out, &outLen); + CHECK_RV(rv, "single-part length: encrypt operation ended", + CKR_OPERATION_NOT_INITIALIZED); + + rv = funcList->C_DecryptInit(session, mech, key); + CHECK_RV(rv, "single-part length: C_DecryptInit", CKR_OK); + if (rv != CKR_OK) + return; + outLen = wrap + sizeof(out); + rv = funcList->C_Decrypt(session, data, wrap + sizeof(data), out, &outLen); + CHECK_RV(rv, "single-part length: C_Decrypt rejects oversized data", + CKR_ENCRYPTED_DATA_LEN_RANGE); + outLen = sizeof(out); + rv = funcList->C_Decrypt(session, data, sizeof(data), out, &outLen); + CHECK_RV(rv, "single-part length: decrypt operation ended", + CKR_OPERATION_NOT_INITIALIZED); +} + +static void test_single_part_len_range(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech; +#ifdef HAVE_AESCTR + CK_AES_CTR_PARAMS ctrParams; +#endif +#ifdef HAVE_AESCTS + byte iv[16]; +#endif + + if ((((CK_ULONG)1 << 16) << 16) == 0) + return; + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "single-part length: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + +#ifdef HAVE_AESCTR + XMEMSET(&ctrParams, 0, sizeof(ctrParams)); + ctrParams.ulCounterBits = 32; + XMEMSET(ctrParams.cb, 0x18, sizeof(ctrParams.cb)); + mech.mechanism = CKM_AES_CTR; + mech.pParameter = &ctrParams; + mech.ulParameterLen = sizeof(ctrParams); + printf("AES-CTR\n"); + check_single_part_len_range(session, key, &mech); +#endif +#ifdef HAVE_AESCTS + XMEMSET(iv, 0x29, sizeof(iv)); + mech.mechanism = CKM_AES_CTS; + mech.pParameter = iv; + mech.ulParameterLen = sizeof(iv); + printf("AES-CTS\n"); + check_single_part_len_range(session, key, &mech); +#endif +} +#endif + static int run_test(void) { CK_RV rv; @@ -819,6 +902,9 @@ static int run_test(void) #endif #if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) run_in_session(slot, test_oaep_source_len_range); +#endif +#if !defined(NO_AES) && (defined(HAVE_AESCTR) || defined(HAVE_AESCTS)) + run_in_session(slot, test_single_part_len_range); #endif } diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index a4539bf0..df852574 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -815,6 +815,7 @@ WP11_LOCAL int WP11_AesCts_DecryptUpdate(unsigned char* enc, word32 encSz, WP11_Session* session); WP11_LOCAL int WP11_AesCts_DecryptFinal(unsigned char* dec, word32* decSz, WP11_Session* session); +WP11_LOCAL void WP11_AesCts_Final(WP11_Session* session); WP11_LOCAL int WP11_Aes_Cmac_Init(WP11_Object* secret, WP11_Session* session, word32 sigLen); From 11006c91b78a54a5e41d4e640e8d0e441c6df164 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:25:13 -0700 Subject: [PATCH 168/204] F-8671 - Reject oversized AES key unwrap ciphertext lengths --- src/crypto.c | 10 +++-- src/internal.c | 15 +++++++ tests/encrypt_decrypt_params_test.c | 65 +++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 1 + 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 78220619..ae2e58ca 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -4573,8 +4573,9 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, /* AES Key Wrap ciphertext is at least two semiblocks: one data * semiblock plus the 8-byte integrity check value. */ - if (ulEncryptedDataLen < 2 * KEYWRAP_BLOCK_SIZE) { - WP11_Session_AbortOp(session); + if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen) || + ulEncryptedDataLen < 2 * KEYWRAP_BLOCK_SIZE) { + WP11_AesKeyWrap_Final(session); return CKR_ENCRYPTED_DATA_LEN_RANGE; } decDataLen = (word32)(ulEncryptedDataLen - KEYWRAP_BLOCK_SIZE); @@ -4598,9 +4599,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, /* RFC 5649 ciphertext is at least two semiblocks and a multiple of * 8. The recovered length is encoded in the AIV and is known only * after unwrapping; the upper bound is ciphertext - 8. */ - if (ulEncryptedDataLen < 2 * KEYWRAP_BLOCK_SIZE || + if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen) || + ulEncryptedDataLen < 2 * KEYWRAP_BLOCK_SIZE || (ulEncryptedDataLen % KEYWRAP_BLOCK_SIZE) != 0) { - WP11_Session_AbortOp(session); + WP11_AesKeyWrap_Final(session); return CKR_ENCRYPTED_DATA_LEN_RANGE; } decDataLen = (word32)(ulEncryptedDataLen - KEYWRAP_BLOCK_SIZE); diff --git a/src/internal.c b/src/internal.c index f8a64b1c..c379b572 100644 --- a/src/internal.c +++ b/src/internal.c @@ -19740,6 +19740,21 @@ int WP11_AesKeyWrap_Decrypt(unsigned char* enc, word32 encSz, return 0; } +/** + * Abandon an AES key wrap operation, releasing the AES object. + * + * @param session [in] Session object holding Aes object. + */ +void WP11_AesKeyWrap_Final(WP11_Session* session) +{ + WP11_KeyWrapParams *wrap = &session->params.kw; + + wc_AesFree(&wrap->aes); + /* Older wolfSSL versions do not zeroize the key schedule in wc_AesFree. */ + wc_ForceZero(wrap, sizeof(*wrap)); + session->init = 0; +} + /* Single-block AES encrypt/decrypt. FIPS modules before v5.3 declare * wc_AesEncryptDirect/wc_AesDecryptDirect as returning void, so no result is * available there. The v5.2.3 and v5.2.4 modules return int for ARM assembly diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index 050ffd7d..b74946b8 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -854,6 +854,68 @@ static void test_single_part_len_range(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AES_KEYWRAP) +/* Key unwrap rejects a ciphertext length the token cannot represent and ends + * the operation instead of unwrapping a shorter length. */ +static void test_key_wrap_decrypt_len_range(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech; + byte plain[16]; + byte wrapped[24]; + byte out[32]; + CK_ULONG wrappedLen; + CK_ULONG outLen; + CK_ULONG wrap = ((CK_ULONG)1 << 16) << 16; + static const CK_MECHANISM_TYPE mechs[] = { + CKM_AES_KEY_WRAP, CKM_AES_KEY_WRAP_PAD + }; + int i; + + if (wrap == 0) + return; + + XMEMSET(plain, 0x6B, sizeof(plain)); + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "key unwrap length: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + for (i = 0; i < (int)(sizeof(mechs) / sizeof(*mechs)); i++) { + mech.mechanism = mechs[i]; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + printf("key unwrap length: mechanism 0x%lx\n", (unsigned long)mechs[i]); + + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "key unwrap length: C_EncryptInit", CKR_OK); + if (rv != CKR_OK) + continue; + wrappedLen = sizeof(wrapped); + rv = funcList->C_Encrypt(session, plain, sizeof(plain), wrapped, + &wrappedLen); + CHECK_RV(rv, "key unwrap length: C_Encrypt", CKR_OK); + if (rv != CKR_OK) + continue; + + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "key unwrap length: C_DecryptInit", CKR_OK); + if (rv != CKR_OK) + continue; + outLen = sizeof(out); + rv = funcList->C_Decrypt(session, wrapped, wrap + wrappedLen, out, + &outLen); + CHECK_RV(rv, "key unwrap length: C_Decrypt rejects oversized length", + CKR_ENCRYPTED_DATA_LEN_RANGE); + outLen = sizeof(out); + rv = funcList->C_Decrypt(session, wrapped, wrappedLen, out, &outLen); + CHECK_RV(rv, "key unwrap length: decrypt operation ended", + CKR_OPERATION_NOT_INITIALIZED); + } +} +#endif + static int run_test(void) { CK_RV rv; @@ -905,6 +967,9 @@ static int run_test(void) #endif #if !defined(NO_AES) && (defined(HAVE_AESCTR) || defined(HAVE_AESCTS)) run_in_session(slot, test_single_part_len_range); +#endif +#if !defined(NO_AES) && defined(HAVE_AES_KEYWRAP) + run_in_session(slot, test_key_wrap_decrypt_len_range); #endif } diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index df852574..15ec362d 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -799,6 +799,7 @@ WP11_LOCAL int WP11_AesKeyWrapPad_Encrypt(unsigned char* plain, word32 plainSz, unsigned char* enc, word32* encSz, WP11_Session* session); WP11_LOCAL int WP11_AesKeyWrapPad_Decrypt(unsigned char* enc, word32 encSz, unsigned char* dec, word32* decSz, WP11_Session* session); +WP11_LOCAL void WP11_AesKeyWrap_Final(WP11_Session* session); WP11_LOCAL int WP11_AesCts_Encrypt(unsigned char* plain, word32 plainSz, unsigned char* enc, word32* encSz, From f30bffc02835f9f5b5dd647f566bacacfbd77033 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:30:33 -0700 Subject: [PATCH 169/204] F-5156 - Compare the AES-GCM C_DecryptFinal buffer length without narrowing --- src/crypto.c | 2 +- tests/encrypt_decrypt_params_test.c | 64 +++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index ae2e58ca..5f94b0b3 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -5023,7 +5023,7 @@ static CK_RV wp11_C_DecryptFinal(CK_SESSION_HANDLE hSession, *pulLastPartLen = decPartLen; return CKR_OK; } - if (decPartLen > (word32)*pulLastPartLen) + if ((CK_ULONG)decPartLen > *pulLastPartLen) return CKR_BUFFER_TOO_SMALL; ret = WP11_AesGcm_DecryptFinal(pLastPart, &decPartLen, obj, diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index b74946b8..2ddd8f44 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -916,6 +916,67 @@ static void test_key_wrap_decrypt_len_range(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AESGCM) +/* C_DecryptFinal accepts any output buffer length that is large enough, + * including lengths that do not fit in 32 bits. */ +static void test_gcm_decrypt_final_large_len(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_GCM_PARAMS params; + CK_MECHANISM mech; + byte iv[12]; + byte aad[16]; + byte plain[16]; + byte enc[32]; + byte dec[32]; + CK_ULONG encLen; + CK_ULONG partLen; + CK_ULONG lastLen; + CK_ULONG wrap = ((CK_ULONG)1 << 16) << 16; + + if (wrap == 0) + return; + + XMEMSET(iv, 0x2D, sizeof(iv)); + XMEMSET(aad, 0x4E, sizeof(aad)); + XMEMSET(plain, 0x6F, sizeof(plain)); + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "GCM final length: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + gcm_params_init(¶ms, &mech, iv, aad); + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "GCM final length: C_EncryptInit", CKR_OK); + if (rv != CKR_OK) + return; + encLen = sizeof(enc); + rv = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encLen); + CHECK_RV(rv, "GCM final length: C_Encrypt", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "GCM final length: C_DecryptInit", CKR_OK); + if (rv != CKR_OK) + return; + partLen = sizeof(dec); + rv = funcList->C_DecryptUpdate(session, enc, encLen, dec, &partLen); + CHECK_RV(rv, "GCM final length: C_DecryptUpdate", CKR_OK); + if (rv != CKR_OK) + return; + lastLen = wrap; + rv = funcList->C_DecryptFinal(session, dec, &lastLen); + CHECK_RV(rv, "GCM final length: C_DecryptFinal with large buffer length", + CKR_OK); + CHECK_TRUE(rv == CKR_OK && lastLen == sizeof(plain) && + XMEMCMP(dec, plain, sizeof(plain)) == 0, + "GCM final length: plaintext recovered"); +} +#endif + static int run_test(void) { CK_RV rv; @@ -970,6 +1031,9 @@ static int run_test(void) #endif #if !defined(NO_AES) && defined(HAVE_AES_KEYWRAP) run_in_session(slot, test_key_wrap_decrypt_len_range); +#endif +#if !defined(NO_AES) && defined(HAVE_AESGCM) + run_in_session(slot, test_gcm_decrypt_final_large_len); #endif } From d064bb36be4da6f48aac2d388d41db7f00544d0b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:39:20 -0700 Subject: [PATCH 170/204] F-5153 - Compare C_EncryptFinal buffer lengths without narrowing --- src/crypto.c | 11 +++- tests/encrypt_decrypt_params_test.c | 92 +++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 5f94b0b3..50fb8c8c 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -3832,7 +3832,7 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, *pulLastEncryptedPartLen = encPartLen; return CKR_OK; } - if (encPartLen > (word32)*pulLastEncryptedPartLen) + if ((CK_ULONG)encPartLen > *pulLastEncryptedPartLen) return CKR_BUFFER_TOO_SMALL; ret = WP11_AesCbcPad_EncryptFinal(pLastEncryptedPart, &encPartLen, @@ -3872,7 +3872,7 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, *pulLastEncryptedPartLen = encPartLen; return CKR_OK; } - if (encPartLen > (word32)*pulLastEncryptedPartLen) + if ((CK_ULONG)encPartLen > *pulLastEncryptedPartLen) return CKR_BUFFER_TOO_SMALL; ret = WP11_AesGcm_EncryptFinal(pLastEncryptedPart, &encPartLen, @@ -3894,7 +3894,12 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, return CKR_OK; } - encPartLen = (word32)*pulLastEncryptedPartLen; + /* The final output is at most two blocks; cap the capacity to + * avoid truncating a large CK_ULONG. */ + encPartLen = (*pulLastEncryptedPartLen < + (CK_ULONG)(AES_BLOCK_SIZE * 2)) ? + (word32)*pulLastEncryptedPartLen : + (word32)(AES_BLOCK_SIZE * 2); ret = WP11_AesCts_EncryptFinal(pLastEncryptedPart, &encPartLen, session); if (ret == BUFFER_E) diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index 2ddd8f44..ad9baa68 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -977,6 +977,94 @@ static void test_gcm_decrypt_final_large_len(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESGCM) || \ + defined(HAVE_AESCTS)) +/* C_EncryptFinal accepts an output buffer length that does not fit in 32 bits + * and reports the bytes it wrote. */ +static void check_encrypt_final_large_len(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key, + CK_MECHANISM* mech, + CK_ULONG dataLen, CK_ULONG encTotal) +{ + CK_RV rv; + byte plain[32]; + byte enc[64]; + CK_ULONG updLen; + CK_ULONG lastLen; + CK_ULONG wrap = ((CK_ULONG)1 << 16) << 16; + + XMEMSET(plain, 0x7A, sizeof(plain)); + + rv = funcList->C_EncryptInit(session, mech, key); + CHECK_RV(rv, "encrypt final length: C_EncryptInit", CKR_OK); + if (rv != CKR_OK) + return; + updLen = sizeof(enc); + rv = funcList->C_EncryptUpdate(session, plain, dataLen, enc, &updLen); + CHECK_RV(rv, "encrypt final length: C_EncryptUpdate", CKR_OK); + if (rv != CKR_OK) + return; + lastLen = wrap; + rv = funcList->C_EncryptFinal(session, enc + updLen, &lastLen); + CHECK_RV(rv, "encrypt final length: C_EncryptFinal with large buffer " + "length", CKR_OK); + CHECK_TRUE(rv == CKR_OK && updLen + lastLen == encTotal, + "encrypt final length: reports bytes written"); + if (rv == CKR_BUFFER_TOO_SMALL) { + /* End the still-active operation so the next mechanism can start. */ + lastLen = sizeof(enc) - updLen; + (void)funcList->C_EncryptFinal(session, enc + updLen, &lastLen); + } +} + +static void test_encrypt_final_large_len(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech; +#if defined(HAVE_AES_CBC) || defined(HAVE_AESCTS) + byte iv[16]; +#endif +#ifdef HAVE_AESGCM + CK_GCM_PARAMS params; + byte gcmIv[12]; + byte aad[16]; +#endif + + if ((((CK_ULONG)1 << 16) << 16) == 0) + return; + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "encrypt final length: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + +#ifdef HAVE_AES_CBC + XMEMSET(iv, 0x13, sizeof(iv)); + mech.mechanism = CKM_AES_CBC_PAD; + mech.pParameter = iv; + mech.ulParameterLen = sizeof(iv); + printf("AES-CBC-PAD\n"); + check_encrypt_final_large_len(session, key, &mech, 20, 32); +#endif +#ifdef HAVE_AESGCM + XMEMSET(gcmIv, 0x24, sizeof(gcmIv)); + XMEMSET(aad, 0x35, sizeof(aad)); + gcm_params_init(¶ms, &mech, gcmIv, aad); + printf("AES-GCM\n"); + check_encrypt_final_large_len(session, key, &mech, 16, 32); +#endif +#ifdef HAVE_AESCTS + XMEMSET(iv, 0x46, sizeof(iv)); + mech.mechanism = CKM_AES_CTS; + mech.pParameter = iv; + mech.ulParameterLen = sizeof(iv); + printf("AES-CTS\n"); + check_encrypt_final_large_len(session, key, &mech, 20, 20); +#endif +} +#endif + static int run_test(void) { CK_RV rv; @@ -1034,6 +1122,10 @@ static int run_test(void) #endif #if !defined(NO_AES) && defined(HAVE_AESGCM) run_in_session(slot, test_gcm_decrypt_final_large_len); +#endif +#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESGCM) || \ + defined(HAVE_AESCTS)) + run_in_session(slot, test_encrypt_final_large_len); #endif } From 8c5c6755c687ade6d759489ebe8fefafcce54814 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:53:24 -0700 Subject: [PATCH 171/204] F-4801 - Reject oversized AES-CTR, AES-GCM and AES-CTS C_EncryptUpdate parts --- src/crypto.c | 12 ++++ src/internal.c | 32 ++++++++++ tests/encrypt_decrypt_params_test.c | 93 +++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 1 + 4 files changed, 138 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index 50fb8c8c..8500467a 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -3648,6 +3648,10 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, case CKM_AES_CTR: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTR_ENC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulPartLen)) { + WP11_AesCtr_Final(session); + return CKR_DATA_LEN_RANGE; + } if (pEncryptedPart == NULL) { *pulEncryptedPartLen = ulPartLen; @@ -3674,6 +3678,10 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, case CKM_AES_GCM: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_GCM_ENC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulPartLen)) { + WP11_AesGcm_Final(session); + return CKR_DATA_LEN_RANGE; + } encPartLen = (word32)ulPartLen; if (pEncryptedPart == NULL) { @@ -3697,6 +3705,10 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, case CKM_AES_CTS: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTS_ENC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulPartLen)) { + WP11_AesCts_Final(session); + return CKR_DATA_LEN_RANGE; + } if (pEncryptedPart == NULL) { *pulEncryptedPartLen = ulPartLen + AES_BLOCK_SIZE * 2; diff --git a/src/internal.c b/src/internal.c index c379b572..19e7a03b 100644 --- a/src/internal.c +++ b/src/internal.c @@ -19465,6 +19465,38 @@ int WP11_AesGcm_DecryptFinal(unsigned char* dec, word32* decSz, return ret; } + +/** + * Abandon an AES-GCM operation, releasing cached data and AES state. + * + * @param session [in] Session object holding GCM parameters. + */ +void WP11_AesGcm_Final(WP11_Session* session) +{ + WP11_GcmParams* gcm = &session->params.gcm; + + if (gcm->enc != NULL) { + wc_ForceZero(gcm->enc, (word32)gcm->encSz); + XFREE(gcm->enc, NULL, DYNAMIC_TYPE_TMP_BUFFER); + gcm->enc = NULL; + } + gcm->encSz = 0; + if (gcm->aad != NULL) { + wc_ForceZero(gcm->aad, (word32)gcm->aadSz); + XFREE(gcm->aad, NULL, DYNAMIC_TYPE_TMP_BUFFER); + gcm->aad = NULL; + } + gcm->aadSz = 0; +#ifdef WOLFSSL_AESGCM_STREAM + if (gcm->streamInit) { + wc_AesFree(&gcm->aes); + gcm->streamInit = 0; + } +#endif + /* Older wolfSSL versions do not zeroize the key schedule in wc_AesFree. */ + wc_ForceZero(gcm, sizeof(*gcm)); + session->init = 0; +} #endif /* HAVE_AESGCM */ #ifdef HAVE_AESCCM diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index ad9baa68..62152283 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -1065,6 +1065,95 @@ static void test_encrypt_final_large_len(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && (defined(HAVE_AESCTR) || defined(HAVE_AESGCM) || \ + defined(HAVE_AESCTS)) +/* Multi-part encryption rejects a part length the token cannot represent and + * ends the operation instead of encrypting a shorter part. */ +static void check_encrypt_update_len_range(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key, + CK_MECHANISM* mech) +{ + CK_RV rv; + byte data[32]; + byte out[64]; + CK_ULONG outLen; + CK_ULONG wrap = ((CK_ULONG)1 << 16) << 16; + + XMEMSET(data, 0x5D, sizeof(data)); + + rv = funcList->C_EncryptInit(session, mech, key); + CHECK_RV(rv, "encrypt update length: C_EncryptInit", CKR_OK); + if (rv != CKR_OK) + return; + outLen = wrap + sizeof(out); + rv = funcList->C_EncryptUpdate(session, data, wrap + sizeof(data), out, + &outLen); + CHECK_RV(rv, "encrypt update length: C_EncryptUpdate rejects oversized " + "part", CKR_DATA_LEN_RANGE); + outLen = sizeof(out); + rv = funcList->C_EncryptUpdate(session, data, sizeof(data), out, &outLen); + CHECK_RV(rv, "encrypt update length: operation ended", + CKR_OPERATION_NOT_INITIALIZED); + if (rv == CKR_OK) { + /* End the still-active operation so the next mechanism can start. */ + outLen = sizeof(out); + (void)funcList->C_EncryptFinal(session, out, &outLen); + } +} + +static void test_encrypt_update_len_range(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech; +#ifdef HAVE_AESCTR + CK_AES_CTR_PARAMS ctrParams; +#endif +#ifdef HAVE_AESGCM + CK_GCM_PARAMS gcmParams; + byte gcmIv[12]; + byte aad[16]; +#endif +#ifdef HAVE_AESCTS + byte iv[16]; +#endif + + if ((((CK_ULONG)1 << 16) << 16) == 0) + return; + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "encrypt update length: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + +#ifdef HAVE_AESCTR + XMEMSET(&ctrParams, 0, sizeof(ctrParams)); + ctrParams.ulCounterBits = 32; + XMEMSET(ctrParams.cb, 0x57, sizeof(ctrParams.cb)); + mech.mechanism = CKM_AES_CTR; + mech.pParameter = &ctrParams; + mech.ulParameterLen = sizeof(ctrParams); + printf("AES-CTR\n"); + check_encrypt_update_len_range(session, key, &mech); +#endif +#ifdef HAVE_AESGCM + XMEMSET(gcmIv, 0x68, sizeof(gcmIv)); + XMEMSET(aad, 0x79, sizeof(aad)); + gcm_params_init(&gcmParams, &mech, gcmIv, aad); + printf("AES-GCM\n"); + check_encrypt_update_len_range(session, key, &mech); +#endif +#ifdef HAVE_AESCTS + XMEMSET(iv, 0x8A, sizeof(iv)); + mech.mechanism = CKM_AES_CTS; + mech.pParameter = iv; + mech.ulParameterLen = sizeof(iv); + printf("AES-CTS\n"); + check_encrypt_update_len_range(session, key, &mech); +#endif +} +#endif + static int run_test(void) { CK_RV rv; @@ -1126,6 +1215,10 @@ static int run_test(void) #if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESGCM) || \ defined(HAVE_AESCTS)) run_in_session(slot, test_encrypt_final_large_len); +#endif +#if !defined(NO_AES) && (defined(HAVE_AESCTR) || defined(HAVE_AESGCM) || \ + defined(HAVE_AESCTS)) + run_in_session(slot, test_encrypt_update_len_range); #endif } diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 15ec362d..a148d587 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -775,6 +775,7 @@ WP11_LOCAL int WP11_AesGcm_DecryptUpdate(unsigned char* enc, word32 encSz, WP11_Session* session); WP11_LOCAL int WP11_AesGcm_DecryptFinal(unsigned char* dec, word32* decSz, WP11_Object* secret, WP11_Session* session); +WP11_LOCAL void WP11_AesGcm_Final(WP11_Session* session); WP11_LOCAL int WP11_AesCcm_DataLen(WP11_Session* session); WP11_LOCAL int WP11_AesCcm_GetMacLen(WP11_Session* session); From 2b14321e88f2033ee8f7d8238f51e58f00a6c5ec Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:01:35 -0700 Subject: [PATCH 172/204] F-5079 - Reject oversized AES-CTR C_DecryptUpdate parts --- src/crypto.c | 7 ++- tests/encrypt_decrypt_params_test.c | 69 +++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index 8500467a..0d382c7b 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -4805,6 +4805,10 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, case CKM_AES_CTR: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTR_DEC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulEncryptedPartLen)) { + WP11_AesCtr_Final(session); + return CKR_ENCRYPTED_DATA_LEN_RANGE; + } if (pPart == NULL) { *pulPartLen = ulEncryptedPartLen; @@ -4813,7 +4817,8 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, if (ulEncryptedPartLen > *pulPartLen) return CKR_BUFFER_TOO_SMALL; - decPartLen = (word32)*pulPartLen; + /* Output is the same length as the input, which fits the buffer. */ + decPartLen = (word32)ulEncryptedPartLen; ret = WP11_AesCtr_Update(pEncryptedPart, (word32)ulEncryptedPartLen, pPart, &decPartLen, session); if (ret == WP11_CTR_OVERFLOW_E) { diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index 62152283..91f38726 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -1154,6 +1154,72 @@ static void test_encrypt_update_len_range(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AESCTR) +/* Multi-part AES-CTR decryption rejects a part length the token cannot + * represent and accepts an output buffer length that does not fit in 32 bits. */ +static void test_ctr_decrypt_update_len(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_AES_CTR_PARAMS params; + CK_MECHANISM mech; + byte data[32]; + byte out[32]; + CK_ULONG partLen; + CK_ULONG wrap = ((CK_ULONG)1 << 16) << 16; + + if (wrap == 0) + return; + + XMEMSET(¶ms, 0, sizeof(params)); + params.ulCounterBits = 32; + XMEMSET(params.cb, 0x9B, sizeof(params.cb)); + XMEMSET(data, 0xAC, sizeof(data)); + mech.mechanism = CKM_AES_CTR; + mech.pParameter = ¶ms; + mech.ulParameterLen = sizeof(params); + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "CTR decrypt update: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "CTR decrypt update: C_DecryptInit", CKR_OK); + if (rv != CKR_OK) + return; + partLen = wrap + sizeof(out); + rv = funcList->C_DecryptUpdate(session, data, wrap + sizeof(data), out, + &partLen); + CHECK_RV(rv, "CTR decrypt update: rejects oversized part", + CKR_ENCRYPTED_DATA_LEN_RANGE); + partLen = sizeof(out); + rv = funcList->C_DecryptUpdate(session, data, sizeof(data), out, &partLen); + CHECK_RV(rv, "CTR decrypt update: operation ended", + CKR_OPERATION_NOT_INITIALIZED); + if (rv == CKR_OK) { + /* End the still-active operation so the next check can start. */ + partLen = sizeof(out); + (void)funcList->C_DecryptFinal(session, out, &partLen); + } + + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "CTR decrypt update: C_DecryptInit", CKR_OK); + if (rv != CKR_OK) + return; + partLen = wrap + 8; + rv = funcList->C_DecryptUpdate(session, data, 16, out, &partLen); + CHECK_RV(rv, "CTR decrypt update: large output buffer length", CKR_OK); + CHECK_TRUE(rv == CKR_OK && partLen == 16, + "CTR decrypt update: reports bytes written"); + if (rv == CKR_OK) { + partLen = sizeof(out); + rv = funcList->C_DecryptFinal(session, out, &partLen); + CHECK_RV(rv, "CTR decrypt update: C_DecryptFinal", CKR_OK); + } +} +#endif + static int run_test(void) { CK_RV rv; @@ -1219,6 +1285,9 @@ static int run_test(void) #if !defined(NO_AES) && (defined(HAVE_AESCTR) || defined(HAVE_AESGCM) || \ defined(HAVE_AESCTS)) run_in_session(slot, test_encrypt_update_len_range); +#endif +#if !defined(NO_AES) && defined(HAVE_AESCTR) + run_in_session(slot, test_ctr_decrypt_update_len); #endif } From 5b4dc1c11d2d50a3493d9e62479bee47faf8b5ee Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:11:07 -0700 Subject: [PATCH 173/204] F-4800 - Reject oversized and cumulative AES-GCM C_DecryptUpdate lengths --- src/crypto.c | 6 +++ src/internal.c | 4 ++ tests/encrypt_decrypt_params_test.c | 78 +++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index 0d382c7b..913f7f62 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -4836,6 +4836,12 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, case CKM_AES_GCM: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_GCM_DEC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulEncryptedPartLen) || + ulEncryptedPartLen > (CK_ULONG)(INT_MAX - + WP11_AesGcm_EncDataLen(session))) { + WP11_AesGcm_Final(session); + return CKR_ENCRYPTED_DATA_LEN_RANGE; + } *pulPartLen = 0; if (pPart == NULL) diff --git a/src/internal.c b/src/internal.c index 19e7a03b..67010a43 100644 --- a/src/internal.c +++ b/src/internal.c @@ -19410,6 +19410,10 @@ int WP11_AesGcm_DecryptUpdate(unsigned char* enc, word32 encSz, unsigned char* newEnc; WP11_GcmParams* gcm = &session->params.gcm; + /* The cached length is an int; never let it grow past INT_MAX. */ + if (encSz > (word32)(INT_MAX - gcm->encSz)) + return BAD_FUNC_ARG; + #ifdef XREALLOC newEnc = (unsigned char*)XREALLOC(gcm->enc, gcm->encSz + encSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index 91f38726..8713a68f 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -26,6 +26,7 @@ #include #endif +#include #include #include @@ -1220,6 +1221,80 @@ static void test_ctr_decrypt_update_len(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AESGCM) +/* Multi-part AES-GCM decryption rejects a part that does not fit in 32 bits or + * would grow the buffered ciphertext past INT_MAX, and ends the operation. */ +static void check_gcm_decrypt_update_rejected(CK_SESSION_HANDLE session, + byte* data, CK_ULONG dataLen, + CK_ULONG badLen) +{ + CK_RV rv; + byte out[64]; + CK_ULONG partLen; + + partLen = sizeof(out); + rv = funcList->C_DecryptUpdate(session, data, badLen, out, &partLen); + CHECK_RV(rv, "GCM decrypt update: rejects part length", + CKR_ENCRYPTED_DATA_LEN_RANGE); + partLen = sizeof(out); + rv = funcList->C_DecryptUpdate(session, data, dataLen, out, &partLen); + CHECK_RV(rv, "GCM decrypt update: operation ended", + CKR_OPERATION_NOT_INITIALIZED); + if (rv == CKR_OK) { + /* End the still-active operation so the next check can start. */ + partLen = sizeof(out); + (void)funcList->C_DecryptFinal(session, out, &partLen); + } +} + +static void test_gcm_decrypt_update_len_range(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_GCM_PARAMS params; + CK_MECHANISM mech; + byte iv[12]; + byte aad[16]; + byte data[32]; + byte out[32]; + CK_ULONG partLen; + CK_ULONG wrap = ((CK_ULONG)1 << 16) << 16; + + XMEMSET(iv, 0xBD, sizeof(iv)); + XMEMSET(aad, 0xCE, sizeof(aad)); + XMEMSET(data, 0xDF, sizeof(data)); + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "GCM decrypt update: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + gcm_params_init(¶ms, &mech, iv, aad); + + if (wrap != 0) { + printf("GCM decrypt update: part length 2^32 + 32\n"); + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "GCM decrypt update: C_DecryptInit", CKR_OK); + if (rv == CKR_OK) { + check_gcm_decrypt_update_rejected(session, data, sizeof(data), + wrap + sizeof(data)); + } + } + + printf("GCM decrypt update: buffered length past INT_MAX\n"); + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "GCM decrypt update: C_DecryptInit", CKR_OK); + if (rv != CKR_OK) + return; + partLen = sizeof(out); + rv = funcList->C_DecryptUpdate(session, data, sizeof(data), out, &partLen); + CHECK_RV(rv, "GCM decrypt update: first part", CKR_OK); + if (rv != CKR_OK) + return; + check_gcm_decrypt_update_rejected(session, data, sizeof(data), + (CK_ULONG)INT_MAX - sizeof(data) + 1); +} +#endif + static int run_test(void) { CK_RV rv; @@ -1288,6 +1363,9 @@ static int run_test(void) #endif #if !defined(NO_AES) && defined(HAVE_AESCTR) run_in_session(slot, test_ctr_decrypt_update_len); +#endif +#if !defined(NO_AES) && defined(HAVE_AESGCM) + run_in_session(slot, test_gcm_decrypt_update_len_range); #endif } From c79d8f8f39c69da9ae3a4f48f4edd7ab6b6aac5f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:22:49 -0700 Subject: [PATCH 174/204] F-10403 - Cap the buffered AES-GCM length at INT_MAX --- src/crypto.c | 4 +- src/internal.c | 4 ++ tests/encrypt_decrypt_params_test.c | 76 +++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index 913f7f62..1c60f487 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -3678,7 +3678,9 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, case CKM_AES_GCM: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_GCM_ENC)) return CKR_OPERATION_NOT_INITIALIZED; - if (!CK_ULONG_FITS_WORD32(ulPartLen)) { + if (!CK_ULONG_FITS_WORD32(ulPartLen) || + ulPartLen > (CK_ULONG)(INT_MAX - + WP11_AesGcm_EncDataLen(session))) { WP11_AesGcm_Final(session); return CKR_DATA_LEN_RANGE; } diff --git a/src/internal.c b/src/internal.c index 67010a43..81285185 100644 --- a/src/internal.c +++ b/src/internal.c @@ -19114,6 +19114,10 @@ static int wp11_AesGcm_BufferAppend(WP11_GcmParams* gcm, unsigned char* data, { unsigned char* newBuf; + /* The cached length is an int; never let it grow past INT_MAX. */ + if (dataSz > (word32)(INT_MAX - gcm->encSz)) + return BAD_FUNC_ARG; + #ifdef XREALLOC newBuf = (unsigned char*)XREALLOC(gcm->enc, gcm->encSz + dataSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index 8713a68f..95318e8f 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -1295,6 +1295,81 @@ static void test_gcm_decrypt_update_len_range(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AESGCM) +static void check_gcm_encrypt_update_rejected(CK_SESSION_HANDLE session, + byte* data, CK_ULONG dataLen, + CK_ULONG badLen) +{ + CK_RV rv; + byte out[64]; + CK_ULONG outLen; + + outLen = badLen; + rv = funcList->C_EncryptUpdate(session, data, badLen, out, &outLen); + CHECK_RV(rv, "GCM encrypt update: rejects part length", + CKR_DATA_LEN_RANGE); + outLen = sizeof(out); + rv = funcList->C_EncryptUpdate(session, data, dataLen, out, &outLen); + CHECK_RV(rv, "GCM encrypt update: operation ended", + CKR_OPERATION_NOT_INITIALIZED); + if (rv == CKR_OK) { + /* End the still-active operation so the next check can start. */ + outLen = sizeof(out); + (void)funcList->C_EncryptFinal(session, out, &outLen); + } +} + +/* Multi-part AES-GCM encryption rejects a part that would take the operation + * past INT_MAX bytes, and ends the operation. */ +static void test_gcm_encrypt_update_total_len(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_GCM_PARAMS params; + CK_MECHANISM mech; + byte iv[12]; + byte aad[16]; + byte data[32]; +#ifndef WOLFSSL_AESGCM_STREAM + byte out[32]; + CK_ULONG outLen; +#endif + + XMEMSET(iv, 0xE1, sizeof(iv)); + XMEMSET(aad, 0xF2, sizeof(aad)); + XMEMSET(data, 0x03, sizeof(data)); + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "GCM encrypt update: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + gcm_params_init(¶ms, &mech, iv, aad); + + printf("GCM encrypt update: part length 0x80000000\n"); + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "GCM encrypt update: C_EncryptInit", CKR_OK); + if (rv == CKR_OK) { + check_gcm_encrypt_update_rejected(session, data, sizeof(data), + (CK_ULONG)0x80000000UL); + } + +#ifndef WOLFSSL_AESGCM_STREAM + printf("GCM encrypt update: buffered length past INT_MAX\n"); + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "GCM encrypt update: C_EncryptInit", CKR_OK); + if (rv != CKR_OK) + return; + outLen = sizeof(out); + rv = funcList->C_EncryptUpdate(session, data, sizeof(data), out, &outLen); + CHECK_RV(rv, "GCM encrypt update: first part", CKR_OK); + if (rv != CKR_OK) + return; + check_gcm_encrypt_update_rejected(session, data, sizeof(data), + (CK_ULONG)INT_MAX - sizeof(data) + 1); +#endif +} +#endif + static int run_test(void) { CK_RV rv; @@ -1366,6 +1441,7 @@ static int run_test(void) #endif #if !defined(NO_AES) && defined(HAVE_AESGCM) run_in_session(slot, test_gcm_decrypt_update_len_range); + run_in_session(slot, test_gcm_encrypt_update_total_len); #endif } From 97f6e7ec904b1154a3c55d0858d2f8daddc86342 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:49 -0700 Subject: [PATCH 175/204] Use WP11_Session_AbortOp on AES length errors --- src/crypto.c | 14 ++++++------- src/internal.c | 47 ------------------------------------------- wolfpkcs11/internal.h | 2 -- 3 files changed, 7 insertions(+), 56 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 1c60f487..05c3edbe 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -3413,7 +3413,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTS_ENC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulDataLen)) { - WP11_AesCts_Final(session); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -3681,7 +3681,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, if (!CK_ULONG_FITS_WORD32(ulPartLen) || ulPartLen > (CK_ULONG)(INT_MAX - WP11_AesGcm_EncDataLen(session))) { - WP11_AesGcm_Final(session); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -3708,7 +3708,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTS_ENC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulPartLen)) { - WP11_AesCts_Final(session); + WP11_Session_AbortOp(session); return CKR_DATA_LEN_RANGE; } @@ -4564,7 +4564,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTS_DEC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) { - WP11_AesCts_Final(session); + WP11_Session_AbortOp(session); return CKR_ENCRYPTED_DATA_LEN_RANGE; } @@ -4594,7 +4594,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, * semiblock plus the 8-byte integrity check value. */ if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen) || ulEncryptedDataLen < 2 * KEYWRAP_BLOCK_SIZE) { - WP11_AesKeyWrap_Final(session); + WP11_Session_AbortOp(session); return CKR_ENCRYPTED_DATA_LEN_RANGE; } decDataLen = (word32)(ulEncryptedDataLen - KEYWRAP_BLOCK_SIZE); @@ -4621,7 +4621,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen) || ulEncryptedDataLen < 2 * KEYWRAP_BLOCK_SIZE || (ulEncryptedDataLen % KEYWRAP_BLOCK_SIZE) != 0) { - WP11_AesKeyWrap_Final(session); + WP11_Session_AbortOp(session); return CKR_ENCRYPTED_DATA_LEN_RANGE; } decDataLen = (word32)(ulEncryptedDataLen - KEYWRAP_BLOCK_SIZE); @@ -4841,7 +4841,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, if (!CK_ULONG_FITS_WORD32(ulEncryptedPartLen) || ulEncryptedPartLen > (CK_ULONG)(INT_MAX - WP11_AesGcm_EncDataLen(session))) { - WP11_AesGcm_Final(session); + WP11_Session_AbortOp(session); return CKR_ENCRYPTED_DATA_LEN_RANGE; } diff --git a/src/internal.c b/src/internal.c index 81285185..d98b3a9e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -19473,38 +19473,6 @@ int WP11_AesGcm_DecryptFinal(unsigned char* dec, word32* decSz, return ret; } - -/** - * Abandon an AES-GCM operation, releasing cached data and AES state. - * - * @param session [in] Session object holding GCM parameters. - */ -void WP11_AesGcm_Final(WP11_Session* session) -{ - WP11_GcmParams* gcm = &session->params.gcm; - - if (gcm->enc != NULL) { - wc_ForceZero(gcm->enc, (word32)gcm->encSz); - XFREE(gcm->enc, NULL, DYNAMIC_TYPE_TMP_BUFFER); - gcm->enc = NULL; - } - gcm->encSz = 0; - if (gcm->aad != NULL) { - wc_ForceZero(gcm->aad, (word32)gcm->aadSz); - XFREE(gcm->aad, NULL, DYNAMIC_TYPE_TMP_BUFFER); - gcm->aad = NULL; - } - gcm->aadSz = 0; -#ifdef WOLFSSL_AESGCM_STREAM - if (gcm->streamInit) { - wc_AesFree(&gcm->aes); - gcm->streamInit = 0; - } -#endif - /* Older wolfSSL versions do not zeroize the key schedule in wc_AesFree. */ - wc_ForceZero(gcm, sizeof(*gcm)); - session->init = 0; -} #endif /* HAVE_AESGCM */ #ifdef HAVE_AESCCM @@ -19780,21 +19748,6 @@ int WP11_AesKeyWrap_Decrypt(unsigned char* enc, word32 encSz, return 0; } -/** - * Abandon an AES key wrap operation, releasing the AES object. - * - * @param session [in] Session object holding Aes object. - */ -void WP11_AesKeyWrap_Final(WP11_Session* session) -{ - WP11_KeyWrapParams *wrap = &session->params.kw; - - wc_AesFree(&wrap->aes); - /* Older wolfSSL versions do not zeroize the key schedule in wc_AesFree. */ - wc_ForceZero(wrap, sizeof(*wrap)); - session->init = 0; -} - /* Single-block AES encrypt/decrypt. FIPS modules before v5.3 declare * wc_AesEncryptDirect/wc_AesDecryptDirect as returning void, so no result is * available there. The v5.2.3 and v5.2.4 modules return int for ARM assembly diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index a148d587..df852574 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -775,7 +775,6 @@ WP11_LOCAL int WP11_AesGcm_DecryptUpdate(unsigned char* enc, word32 encSz, WP11_Session* session); WP11_LOCAL int WP11_AesGcm_DecryptFinal(unsigned char* dec, word32* decSz, WP11_Object* secret, WP11_Session* session); -WP11_LOCAL void WP11_AesGcm_Final(WP11_Session* session); WP11_LOCAL int WP11_AesCcm_DataLen(WP11_Session* session); WP11_LOCAL int WP11_AesCcm_GetMacLen(WP11_Session* session); @@ -800,7 +799,6 @@ WP11_LOCAL int WP11_AesKeyWrapPad_Encrypt(unsigned char* plain, word32 plainSz, unsigned char* enc, word32* encSz, WP11_Session* session); WP11_LOCAL int WP11_AesKeyWrapPad_Decrypt(unsigned char* enc, word32 encSz, unsigned char* dec, word32* decSz, WP11_Session* session); -WP11_LOCAL void WP11_AesKeyWrap_Final(WP11_Session* session); WP11_LOCAL int WP11_AesCts_Encrypt(unsigned char* plain, word32 plainSz, unsigned char* enc, word32* encSz, From 5378f6de720275ff4b41c1d9401c5dee674308a3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:37:24 -0700 Subject: [PATCH 176/204] F-10392 - Reject a mismatched hash algorithm for hashed RSA-PSS mechanisms --- src/crypto.c | 39 +++++ tests/include.am | 7 + tests/sign_verify_mac_test.c | 278 +++++++++++++++++++++++++++++++++++ 3 files changed, 324 insertions(+) create mode 100644 tests/sign_verify_mac_test.c diff --git a/src/crypto.c b/src/crypto.c index 05c3edbe..9cf1de76 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -5545,6 +5545,39 @@ static int GetInitValue(CK_MECHANISM_TYPE mechanism) { } } +#if !defined(NO_RSA) && defined(WC_RSA_PSS) +/* A hashed RSA-PSS mechanism requires its own digest as the PSS hash. The MGF1 + * hash may differ (RFC 8017) and is validated when the parameters are set. */ +static CK_RV CheckPssMechParams(CK_MECHANISM_TYPE mechanism, + const CK_RSA_PKCS_PSS_PARAMS* params) +{ + CK_MECHANISM_TYPE hashAlg; + + switch (mechanism) { + case CKM_SHA1_RSA_PKCS_PSS: + hashAlg = CKM_SHA1; + break; + case CKM_SHA224_RSA_PKCS_PSS: + hashAlg = CKM_SHA224; + break; + case CKM_SHA256_RSA_PKCS_PSS: + hashAlg = CKM_SHA256; + break; + case CKM_SHA384_RSA_PKCS_PSS: + hashAlg = CKM_SHA384; + break; + case CKM_SHA512_RSA_PKCS_PSS: + hashAlg = CKM_SHA512; + break; + default: + return CKR_OK; + } + if (params->hashAlg != hashAlg) + return CKR_MECHANISM_PARAM_INVALID; + return CKR_OK; +} +#endif + /** * Initialize signing operation. * @@ -5696,6 +5729,9 @@ static CK_RV wp11_C_SignInit(CK_SESSION_HANDLE hSession, return CKR_MECHANISM_PARAM_INVALID; params = (CK_RSA_PKCS_PSS_PARAMS*)pMechanism->pParameter; + rv = CheckPssMechParams(pMechanism->mechanism, params); + if (rv != CKR_OK) + return rv; ret = WP11_Session_SetPssParams(session, params->hashAlg, params->mgf, (int)params->sLen); if (ret != 0) @@ -6869,6 +6905,9 @@ static CK_RV wp11_C_VerifyInit(CK_SESSION_HANDLE hSession, return CKR_MECHANISM_PARAM_INVALID; params = (CK_RSA_PKCS_PSS_PARAMS*)pMechanism->pParameter; + rv = CheckPssMechParams(pMechanism->mechanism, params); + if (rv != CKR_OK) + return rv; ret = WP11_Session_SetPssParams(session, params->hashAlg, params->mgf, (int)params->sLen); if (ret != 0) diff --git a/tests/include.am b/tests/include.am index 2e541b02..74492b51 100644 --- a/tests/include.am +++ b/tests/include.am @@ -370,6 +370,11 @@ noinst_PROGRAMS += tests/encrypt_decrypt_params_test tests_encrypt_decrypt_params_test_SOURCES = tests/encrypt_decrypt_params_test.c tests_encrypt_decrypt_params_test_LDADD = +check_PROGRAMS += tests/sign_verify_mac_test +noinst_PROGRAMS += tests/sign_verify_mac_test +tests_sign_verify_mac_test_SOURCES = tests/sign_verify_mac_test.c +tests_sign_verify_mac_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -445,6 +450,7 @@ tests_attribute_validation_test_LDADD += src/libwolfpkcs11.la tests_key_material_update_test_LDADD += src/libwolfpkcs11.la tests_object_create_getattr_test_LDADD += src/libwolfpkcs11.la tests_encrypt_decrypt_params_test_LDADD += src/libwolfpkcs11.la +tests_sign_verify_mac_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -512,6 +518,7 @@ tests_attribute_validation_test_LDADD += src/libwolfpkcs11.la tests_key_material_update_test_LDADD += src/libwolfpkcs11.la tests_object_create_getattr_test_LDADD += src/libwolfpkcs11.la tests_encrypt_decrypt_params_test_LDADD += src/libwolfpkcs11.la +tests_sign_verify_mac_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c new file mode 100644 index 00000000..75197d8d --- /dev/null +++ b/tests/sign_verify_mac_test.c @@ -0,0 +1,278 @@ +/* sign_verify_mac_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Sign, verify and MAC operations must honour the mechanism definitions: + * parameters are validated against the mechanism, output lengths match the + * mechanism and verification compares the whole value. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#define TEST_DIR "./store/sign_verify_mac_test" + +static const char* soPin = "password123456"; +static const char* userPin = "wolfpkcs11-test"; +static const char tokenLabel[] = "sign-verify-mac"; + +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; + +#ifndef NO_RSA +static CK_OBJECT_CLASS privKeyClass = CKO_PRIVATE_KEY; +static CK_OBJECT_CLASS pubKeyClass = CKO_PUBLIC_KEY; +static CK_KEY_TYPE rsaKeyType = CKK_RSA; + +static CK_RV create_rsa_keys(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* priv, + CK_OBJECT_HANDLE* pub) +{ + CK_RV rv; + CK_ATTRIBUTE privTmpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, + { CKA_SIGN, &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 pubTmpl[] = { + { CKA_CLASS, &pubKeyClass, sizeof(pubKeyClass) }, + { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + + rv = funcList->C_CreateObject(session, privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), priv); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), pub); + } + return rv; +} +#endif + +#if !defined(NO_RSA) && defined(WC_RSA_PSS) && !defined(NO_SHA256) +/* A hashed RSA-PSS mechanism fixes the PSS hash to its own digest; any valid + * MGF1 hash is accepted. */ +static void pss_hash_binding_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_RSA_PKCS_PSS_PARAMS params; + CK_MECHANISM mech; + byte data[32]; + byte sig[2048 / 8]; + CK_ULONG sigLen = sizeof(sig); + + XMEMSET(data, 0x5a, sizeof(data)); + rv = create_rsa_keys(session, &priv, &pub); + CHECK_RV(rv, "create RSA key pair", CKR_OK); + if (rv != CKR_OK) + return; + + mech.mechanism = CKM_SHA256_RSA_PKCS_PSS; + mech.pParameter = ¶ms; + mech.ulParameterLen = sizeof(params); + + params.hashAlg = CKM_SHA1; + params.mgf = CKG_MGF1_SHA256; + params.sLen = 32; + rv = funcList->C_SignInit(session, &mech, priv); + CHECK_RV(rv, "C_SignInit(SHA256 PSS, different hashAlg)", + CKR_MECHANISM_PARAM_INVALID); + rv = funcList->C_VerifyInit(session, &mech, pub); + CHECK_RV(rv, "C_VerifyInit(SHA256 PSS, different hashAlg)", + CKR_MECHANISM_PARAM_INVALID); + + params.hashAlg = CKM_SHA256; + params.mgf = (CK_RSA_PKCS_MGF_TYPE)0x7FFFFFFFUL; + rv = funcList->C_SignInit(session, &mech, priv); + CHECK_RV(rv, "C_SignInit(SHA256 PSS, unknown MGF)", + CKR_MECHANISM_PARAM_INVALID); + rv = funcList->C_VerifyInit(session, &mech, pub); + CHECK_RV(rv, "C_VerifyInit(SHA256 PSS, unknown MGF)", + CKR_MECHANISM_PARAM_INVALID); + + params.mgf = CKG_MGF1_SHA1; + rv = funcList->C_SignInit(session, &mech, priv); + CHECK_RV(rv, "C_SignInit(SHA256 PSS, different MGF)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Sign(session, data, sizeof(data), sig, &sigLen); + CHECK_RV(rv, "C_Sign(SHA256 PSS, different MGF)", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_VerifyInit(session, &mech, pub); + CHECK_RV(rv, "C_VerifyInit(SHA256 PSS, different MGF)", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_Verify(session, data, sizeof(data), sig, sigLen); + CHECK_RV(rv, "C_Verify(SHA256 PSS, different MGF)", CKR_OK); + } + + sigLen = sizeof(sig); + params.mgf = CKG_MGF1_SHA256; + rv = funcList->C_SignInit(session, &mech, priv); + CHECK_RV(rv, "C_SignInit(SHA256 PSS, matching params)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Sign(session, data, sizeof(data), sig, &sigLen); + CHECK_RV(rv, "C_Sign(SHA256 PSS)", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_VerifyInit(session, &mech, pub); + CHECK_RV(rv, "C_VerifyInit(SHA256 PSS, matching params)", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_Verify(session, data, sizeof(data), sig, sigLen); + CHECK_RV(rv, "C_Verify(SHA256 PSS)", CKR_OK); + } + + funcList->C_DestroyObject(session, priv); + funcList->C_DestroyObject(session, pub); +} +#endif + +static CK_RV token_init(CK_SLOT_ID* slot) +{ + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + CK_SESSION_HANDLE soSession = 0; + unsigned char label[32]; + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + if (rv != CKR_OK) + return rv; + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv != CKR_OK) + return rv; + if (slotCount == 0) + return CKR_TOKEN_NOT_PRESENT; + *slot = slotList[0]; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenLabel, XSTRLEN(tokenLabel)); + rv = funcList->C_InitToken(*slot, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin), label); + if (rv != CKR_OK) + return rv; + + rv = funcList->C_OpenSession(*slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &soSession); + if (rv != CKR_OK) + return rv; + rv = funcList->C_Login(soSession, CKU_SO, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin)); + if (rv == CKR_OK) { + rv = funcList->C_InitPIN(soSession, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + } + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + return rv; +} + +static int run_test(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = 0; + + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv != CKR_OK) + return -1; + + rv = token_init(&slot); + CHECK_RV(rv, "token init", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + CHECK_RV(rv, "open session", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + CHECK_RV(rv, "user login", CKR_OK); + } + if (rv == CKR_OK) { +#if !defined(NO_RSA) && defined(WC_RSA_PSS) && !defined(NO_SHA256) + pss_hash_binding_test(session); +#endif + } + + if (session != 0) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + funcList->C_Finalize(NULL); + pkcs11_unload(); + return 0; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + (void)ckTrue; + (void)ckFalse; + +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + + printf("=== wolfPKCS11 sign, verify and MAC test ===\n"); + run_test(); + return pkcs11_test_summary(); +} From 69781eae86ef506f501b4bb56d88f935327b2772 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 11:51:59 -0700 Subject: [PATCH 177/204] F-8674 - Produce a full AES block tag for CKM_AES_CMAC --- src/crypto.c | 4 +- tests/pkcs11test.c | 11 +++-- tests/sign_verify_mac_test.c | 92 ++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 9cf1de76..46649e57 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -5891,7 +5891,7 @@ static CK_RV wp11_C_SignInit(CK_SESSION_HANDLE hSession, pMechanism->ulParameterLen != 0) { return CKR_MECHANISM_PARAM_INVALID; } - ret = WP11_Aes_Cmac_Init(obj, session, AES_BLOCK_SIZE/2); + ret = WP11_Aes_Cmac_Init(obj, session, AES_BLOCK_SIZE); if (ret != 0) return CKR_FUNCTION_FAILED; init = WP11_INIT_AES_CMAC_SIGN; @@ -7083,7 +7083,7 @@ static CK_RV wp11_C_VerifyInit(CK_SESSION_HANDLE hSession, pMechanism->ulParameterLen != 0) { return CKR_MECHANISM_PARAM_INVALID; } - ret = WP11_Aes_Cmac_Init(obj, session, AES_BLOCK_SIZE/2); + ret = WP11_Aes_Cmac_Init(obj, session, AES_BLOCK_SIZE); if (ret != 0) return CKR_FUNCTION_FAILED; init = WP11_INIT_AES_CMAC_VERIFY; diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index 208bcf0b..8a20ddff 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -13032,7 +13032,7 @@ static CK_RV test_aes_cmac_one_shot(CK_SESSION_HANDLE session, unsigned char* exp, int expLen, CK_OBJECT_HANDLE key) { CK_RV ret = CKR_OK; - byte data[72], out[8]; + byte data[72], out[16]; CK_ULONG dataSz, outSz; CK_MECHANISM mech; @@ -13052,7 +13052,7 @@ static CK_RV test_aes_cmac_one_shot(CK_SESSION_HANDLE session, CHECK_CKR(ret, "AES-CMAC Sign no out"); } if (ret == CKR_OK) { - CHECK_COND(outSz == 8, ret, "AES-CMAC Sign out size"); + CHECK_COND(outSz == 16, ret, "AES-CMAC Sign out size"); } if (ret == CKR_OK) { outSz = 0; @@ -13100,7 +13100,7 @@ static CK_RV test_aes_cmac_update(CK_SESSION_HANDLE session, unsigned char* exp, int expLen, CK_OBJECT_HANDLE key) { CK_RV ret = CKR_OK; - byte data[72], out[8]; + byte data[72], out[16]; CK_ULONG dataSz, outSz; CK_MECHANISM mech; int i; @@ -13127,7 +13127,7 @@ static CK_RV test_aes_cmac_update(CK_SESSION_HANDLE session, unsigned char* exp, CHECK_CKR(ret, "AES-CMAC Sign Final no out"); } if (ret == CKR_OK) { - CHECK_COND(outSz == 8, ret, "AES-CMAC Sign Final out size"); + CHECK_COND(outSz == 16, ret, "AES-CMAC Sign Final out size"); } if (ret == CKR_OK) { outSz = sizeof(out); @@ -13191,7 +13191,8 @@ static CK_RV test_aes_cmac(void* args) CK_RV ret; CK_OBJECT_HANDLE key; static unsigned char exp[] = { - 0x81, 0x4f, 0x6c, 0xe5, 0x04, 0x97, 0xf9, 0x26 + 0x81, 0x4f, 0x6c, 0xe5, 0x04, 0x97, 0xf9, 0x26, + 0x9b, 0x5b, 0xa0, 0x87, 0xe4, 0x64, 0xf4, 0xf9 }; ret = get_aes_128_key(session, NULL, 0, &key); diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index 75197d8d..1770c21f 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -180,6 +180,95 @@ static void pss_hash_binding_test(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AESCMAC) +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_KEY_TYPE aesKeyType = CKK_AES; + +/* RFC 4493 AES-CMAC example 2. */ +static byte cmacKey[16] = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c +}; +static byte cmacMsg[16] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a +}; +static byte cmacTag[16] = { + 0x07, 0x0a, 0x16, 0xb4, 0x6b, 0x4d, 0x41, 0x44, + 0xf7, 0x9b, 0xdd, 0x9d, 0xd0, 0x4a, 0x28, 0x7c +}; + +/* CKM_AES_CMAC produces and checks the full AES block as its MAC. */ +static void aes_cmac_full_block_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_AES_CMAC, NULL, 0 }; + byte mac[32]; + CK_ULONG macLen; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, cmacKey, sizeof(cmacKey) }, + }; + + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &key); + CHECK_RV(rv, "create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(AES-CMAC)", CKR_OK); + if (rv == CKR_OK) { + macLen = 0; + rv = funcList->C_Sign(session, cmacMsg, sizeof(cmacMsg), NULL, &macLen); + CHECK_RV(rv, "C_Sign(AES-CMAC, length query)", CKR_OK); + CHECK_TRUE(macLen == sizeof(cmacTag), "AES-CMAC length is one block"); + macLen = sizeof(mac); + rv = funcList->C_Sign(session, cmacMsg, sizeof(cmacMsg), mac, &macLen); + CHECK_RV(rv, "C_Sign(AES-CMAC)", CKR_OK); + CHECK_TRUE(macLen == sizeof(cmacTag) && + XMEMCMP(mac, cmacTag, sizeof(cmacTag)) == 0, + "AES-CMAC matches the full known-answer tag"); + } + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(AES-CMAC multi-part)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SignUpdate(session, cmacMsg, sizeof(cmacMsg)); + CHECK_RV(rv, "C_SignUpdate(AES-CMAC)", CKR_OK); + macLen = sizeof(mac); + rv = funcList->C_SignFinal(session, mac, &macLen); + CHECK_RV(rv, "C_SignFinal(AES-CMAC)", CKR_OK); + CHECK_TRUE(macLen == sizeof(cmacTag) && + XMEMCMP(mac, cmacTag, sizeof(cmacTag)) == 0, + "multi-part AES-CMAC matches the full known-answer tag"); + } + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(AES-CMAC)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Verify(session, cmacMsg, sizeof(cmacMsg), cmacTag, + sizeof(cmacTag)); + CHECK_RV(rv, "C_Verify(AES-CMAC, full tag)", CKR_OK); + } + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(AES-CMAC, half tag)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Verify(session, cmacMsg, sizeof(cmacMsg), cmacTag, + sizeof(cmacTag) / 2); + CHECK_TRUE(rv != CKR_OK, "C_Verify(AES-CMAC) rejects a half-block tag"); + } + + funcList->C_DestroyObject(session, key); +} +#endif + static CK_RV token_init(CK_SLOT_ID* slot) { CK_RV rv; @@ -249,6 +338,9 @@ static int run_test(void) if (rv == CKR_OK) { #if !defined(NO_RSA) && defined(WC_RSA_PSS) && !defined(NO_SHA256) pss_hash_binding_test(session); +#endif +#if !defined(NO_AES) && defined(HAVE_AESCMAC) + aes_cmac_full_block_test(session); #endif } From ec87e9632a6b7321e037c8f7099361328fc41808 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:01:08 -0700 Subject: [PATCH 178/204] F-13817 - Keep a TLS MAC sign active when C_SignFinal gets a short buffer --- src/crypto.c | 2 + tests/sign_verify_mac_test.c | 83 ++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index 46649e57..3436989b 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -6603,6 +6603,8 @@ static CK_RV wp11_C_SignFinal(CK_SESSION_HANDLE hSession, *pulSignatureLen = (CK_ULONG)WP11_TLS_MAC_get_len(session); return CKR_OK; } + if ((CK_ULONG)WP11_TLS_MAC_get_len(session) > *pulSignatureLen) + return CKR_BUFFER_TOO_SMALL; WP11_Session_GetData(session, &data, &dataLen); ret = (int)C_Sign(hSession, data, dataLen, pSignature, diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index 1770c21f..090dcb2c 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -269,6 +269,86 @@ static void aes_cmac_full_block_test(CK_SESSION_HANDLE session) } #endif +#if defined(WOLFSSL_HAVE_PRF) && !defined(NO_SHA256) +static CK_OBJECT_CLASS tlsKeyClass = CKO_SECRET_KEY; +static CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; +static byte tlsSecret[48] = { 0x42 }; +static byte tlsHandshakeHash[32] = { 0x17 }; + +static CK_RV create_generic_key(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key) +{ + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &tlsKeyClass, sizeof(tlsKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, tlsSecret, sizeof(tlsSecret) }, + }; + + return funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), key); +} + +static void tls_mac_params(CK_TLS_MAC_PARAMS* params, CK_MECHANISM* mech) +{ + params->prfHashMechanism = CKM_SHA256; + params->ulMacLength = 12; + params->ulServerOrClient = 1; + mech->mechanism = CKM_TLS_MAC; + mech->pParameter = params; + mech->ulParameterLen = sizeof(*params); +} + +/* A short output buffer leaves a multi-part TLS MAC active for a retry. */ +static void tls_mac_sign_final_retry_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_TLS_MAC_PARAMS params; + CK_MECHANISM mech; + byte expMac[12]; + byte mac[12]; + CK_ULONG macLen; + + rv = create_generic_key(session, &key); + CHECK_RV(rv, "create TLS secret", CKR_OK); + if (rv != CKR_OK) + return; + tls_mac_params(¶ms, &mech); + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(TLS MAC single-part)", CKR_OK); + if (rv == CKR_OK) { + macLen = sizeof(expMac); + rv = funcList->C_Sign(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash), expMac, &macLen); + CHECK_RV(rv, "C_Sign(TLS MAC)", CKR_OK); + } + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(TLS MAC multi-part)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SignUpdate(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash)); + CHECK_RV(rv, "C_SignUpdate(TLS MAC)", CKR_OK); + macLen = 4; + rv = funcList->C_SignFinal(session, mac, &macLen); + CHECK_RV(rv, "C_SignFinal(TLS MAC, short buffer)", + CKR_BUFFER_TOO_SMALL); + macLen = sizeof(mac); + rv = funcList->C_SignFinal(session, mac, &macLen); + CHECK_RV(rv, "C_SignFinal(TLS MAC, retry)", CKR_OK); + CHECK_TRUE(rv == CKR_OK && macLen == sizeof(expMac) && + XMEMCMP(mac, expMac, sizeof(expMac)) == 0, + "retried TLS MAC matches the single-part result"); + } + + funcList->C_DestroyObject(session, key); +} +#endif + static CK_RV token_init(CK_SLOT_ID* slot) { CK_RV rv; @@ -341,6 +421,9 @@ static int run_test(void) #endif #if !defined(NO_AES) && defined(HAVE_AESCMAC) aes_cmac_full_block_test(session); +#endif +#if defined(WOLFSSL_HAVE_PRF) && !defined(NO_SHA256) + tls_mac_sign_final_retry_test(session); #endif } From 5f4e4a64fcba7f9621a94f37196b09b7e140f4fe Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 12:47:27 -0700 Subject: [PATCH 179/204] F-13247 - Support multi-part CKM_TLS_MAC verification --- src/crypto.c | 47 ++++++++++++ src/internal.c | 2 + tests/sign_verify_mac_test.c | 136 +++++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index 3436989b..ecc801bc 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -7439,8 +7439,17 @@ static CK_RV wp11_C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, case CKM_NSS_TLS_PRF_GENERAL_SHA256: #endif { + byte* data = NULL; + word32 dataLen = 0; + if (!WP11_Session_IsOpInitialized(session, WP11_INIT_TLS_MAC_VERIFY)) return CKR_OPERATION_NOT_INITIALIZED; + /* C_Verify cannot finish a multi-part verify. */ + WP11_Session_GetData(session, &data, &dataLen); + if (data != NULL || dataLen != 0) { + WP11_Session_FreeData(session); + return CKR_FUNCTION_FAILED; + } ret = WP11_TLS_MAC_verify(pData, (word32)ulDataLen, pSignature, (word32)ulSignatureLen, &stat, session); @@ -7580,6 +7589,22 @@ static CK_RV wp11_C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, ret = WP11_Aes_Cmac_Sign_Update(pPart, (word32)ulPartLen, session); break; #endif +#endif +#ifdef WOLFSSL_HAVE_PRF + case CKM_TLS_MAC: + if (!WP11_Session_IsOpInitialized(session, + WP11_INIT_TLS_MAC_VERIFY)) { + return CKR_OPERATION_NOT_INITIALIZED; + } + if (!CK_ULONG_FITS_WORD32(ulPartLen)) { + WP11_Session_FreeData(session); + return CKR_DATA_LEN_RANGE; + } + + ret = WP11_Session_UpdateData(session, pPart, (word32)ulPartLen); + if (ret < 0) + WP11_Session_FreeData(session); + break; #endif default: (void)ulPartLen; @@ -7715,6 +7740,28 @@ static CK_RV wp11_C_VerifyFinal(CK_SESSION_HANDLE hSession, &stat, session); break; #endif +#endif +#ifdef WOLFSSL_HAVE_PRF + case CKM_TLS_MAC: + { + byte* data = NULL; + word32 dataLen = 0; + + if (!WP11_Session_IsOpInitialized(session, + WP11_INIT_TLS_MAC_VERIFY)) { + return CKR_OPERATION_NOT_INITIALIZED; + } + if (!CK_ULONG_FITS_WORD32(ulSignatureLen)) { + WP11_Session_FreeData(session); + return CKR_SIGNATURE_LEN_RANGE; + } + + WP11_Session_GetData(session, &data, &dataLen); + ret = WP11_TLS_MAC_verify(data, dataLen, pSignature, + (word32)ulSignatureLen, &stat, session); + WP11_Session_FreeData(session); + break; + } #endif default: (void)ulSignatureLen; diff --git a/src/internal.c b/src/internal.c index d98b3a9e..c3170918 100644 --- a/src/internal.c +++ b/src/internal.c @@ -20744,6 +20744,8 @@ int WP11_TLS_MAC_init(unsigned long hashType, unsigned long macLen, byte server, if (hashType != CKM_TLS_PRF && MechToMac(hashType) == no_mac) return BAD_FUNC_ARG; WP11_Session_AbortOp(session); + /* Multi-part data from an earlier operation must not carry over. */ + WP11_Session_FreeData(session); XMEMSET(mac, 0, sizeof(*mac)); if (hashType == CKM_TLS_PRF) { mac->isTlsPrf = 1; diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index 090dcb2c..e17cca90 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -347,6 +347,140 @@ static void tls_mac_sign_final_retry_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, key); } + +/* A TLS MAC verify supports the multi-part update and final calls. */ +static void tls_mac_verify_multipart_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_TLS_MAC_PARAMS params; + CK_MECHANISM mech; + byte mac[12]; + CK_ULONG macLen = sizeof(mac); + CK_ULONG half = sizeof(tlsHandshakeHash) / 2; + + rv = create_generic_key(session, &key); + CHECK_RV(rv, "create TLS secret", CKR_OK); + if (rv != CKR_OK) + return; + tls_mac_params(¶ms, &mech); + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(TLS MAC)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Sign(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash), mac, &macLen); + CHECK_RV(rv, "C_Sign(TLS MAC)", CKR_OK); + } + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(TLS MAC multi-part)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_VerifyUpdate(session, tlsHandshakeHash, half); + CHECK_RV(rv, "C_VerifyUpdate(TLS MAC, first part)", CKR_OK); + rv = funcList->C_VerifyUpdate(session, tlsHandshakeHash + half, + sizeof(tlsHandshakeHash) - half); + CHECK_RV(rv, "C_VerifyUpdate(TLS MAC, second part)", CKR_OK); + rv = funcList->C_VerifyFinal(session, mac, macLen); + CHECK_RV(rv, "C_VerifyFinal(TLS MAC)", CKR_OK); + } + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(TLS MAC, other data)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_VerifyUpdate(session, tlsHandshakeHash, half); + CHECK_RV(rv, "C_VerifyUpdate(TLS MAC, partial data)", CKR_OK); + rv = funcList->C_VerifyFinal(session, mac, macLen); + CHECK_RV(rv, "C_VerifyFinal(TLS MAC, other data)", + CKR_SIGNATURE_INVALID); + } + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(TLS MAC, mixed calls)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_VerifyUpdate(session, tlsHandshakeHash, half); + CHECK_RV(rv, "C_VerifyUpdate(TLS MAC, before single-part)", CKR_OK); + rv = funcList->C_Verify(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash), mac, macLen); + CHECK_TRUE(rv != CKR_OK, + "C_Verify(TLS MAC) does not finish a multi-part verify"); + } + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(TLS MAC, mixed calls)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_SignUpdate(session, tlsHandshakeHash, half); + CHECK_RV(rv, "C_SignUpdate(TLS MAC, before single-part)", CKR_OK); + macLen = sizeof(mac); + rv = funcList->C_Sign(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash), mac, &macLen); + CHECK_RV(rv, "C_Sign(TLS MAC, after update)", CKR_OK); + } + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(TLS MAC, after sign)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_VerifyUpdate(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash)); + CHECK_RV(rv, "C_VerifyUpdate(TLS MAC, after sign)", CKR_OK); + rv = funcList->C_VerifyFinal(session, mac, macLen); + CHECK_RV(rv, "C_VerifyFinal(TLS MAC, after sign)", CKR_OK); + } + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(TLS MAC, after mixed calls)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Verify(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash), mac, macLen); + CHECK_RV(rv, "C_Verify(TLS MAC, after mixed calls)", CKR_OK); + } + + funcList->C_DestroyObject(session, key); +} + +/* Closing a session discards any multi-part TLS MAC data it held. */ +static void tls_mac_verify_session_close_test(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_TLS_MAC_PARAMS params; + CK_MECHANISM mech; + byte mac[12]; + CK_ULONG macLen = sizeof(mac); + int i; + + tls_mac_params(¶ms, &mech); + for (i = 0; i < 2; i++) { + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + CHECK_RV(rv, "C_OpenSession(TLS MAC close)", CKR_OK); + if (rv != CKR_OK) + return; + rv = create_generic_key(session, &key); + CHECK_RV(rv, "create TLS secret (TLS MAC close)", CKR_OK); + if (rv == CKR_OK && i == 0) { + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(TLS MAC, abandoned)", CKR_OK); + rv = funcList->C_VerifyUpdate(session, tlsHandshakeHash, 8); + CHECK_RV(rv, "C_VerifyUpdate(TLS MAC, abandoned)", CKR_OK); + } + else if (rv == CKR_OK) { + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(TLS MAC, new session)", CKR_OK); + rv = funcList->C_Sign(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash), mac, &macLen); + CHECK_RV(rv, "C_Sign(TLS MAC, new session)", CKR_OK); + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(TLS MAC, new session)", CKR_OK); + rv = funcList->C_VerifyUpdate(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash)); + CHECK_RV(rv, "C_VerifyUpdate(TLS MAC, new session)", CKR_OK); + rv = funcList->C_VerifyFinal(session, mac, macLen); + CHECK_RV(rv, "C_VerifyFinal(TLS MAC, new session)", CKR_OK); + } + funcList->C_CloseSession(session); + } +} #endif static CK_RV token_init(CK_SLOT_ID* slot) @@ -424,6 +558,8 @@ static int run_test(void) #endif #if defined(WOLFSSL_HAVE_PRF) && !defined(NO_SHA256) tls_mac_sign_final_retry_test(session); + tls_mac_verify_multipart_test(session); + tls_mac_verify_session_close_test(slot); #endif } From 47a097b3d1faa1ca3e3b36d7af048a971f77f83e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:00:41 -0700 Subject: [PATCH 180/204] F-8655 - Return CKR_SIGNATURE_LEN_RANGE for a wrong-length ECDSA signature --- src/crypto.c | 4 +++ tests/pkcs11mtt.c | 2 +- tests/pkcs11test.c | 4 +-- tests/sign_verify_mac_test.c | 60 ++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index ecc801bc..ac5f532b 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -7333,6 +7333,10 @@ static CK_RV wp11_C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, return CKR_OPERATION_NOT_INITIALIZED; if (!WP11_Session_IsHashOpInitialized(session, (int)mechanism)) return CKR_OPERATION_NOT_INITIALIZED; + if (ulSignatureLen != (CK_ULONG)WP11_Ec_SigLen(obj)) { + WP11_Session_AbortOp(session); + return CKR_SIGNATURE_LEN_RANGE; + } if (hash_type != WC_HASH_TYPE_NONE) { if (wc_Hash(hash_type, pData, (word32)ulDataLen, digest, diff --git a/tests/pkcs11mtt.c b/tests/pkcs11mtt.c index 0d3a4788..056f0e3c 100644 --- a/tests/pkcs11mtt.c +++ b/tests/pkcs11mtt.c @@ -3942,7 +3942,7 @@ static CK_RV ecdsa_test(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE privKey, if (ret == CKR_OK) { outSz = 1; ret = funcList->C_Verify(session, hash, hashSz, out, outSz); - CHECK_CKR_FAIL(ret, CKR_FUNCTION_FAILED, "ECDSA Verify bad sig"); + CHECK_CKR_FAIL(ret, CKR_SIGNATURE_LEN_RANGE, "ECDSA Verify bad sig"); } return ret; diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index 8a20ddff..b55b0702 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -9512,7 +9512,7 @@ static CK_RV ecdsa_test(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE privKey, if (ret == CKR_OK) { outSz = 1; ret = funcList->C_Verify(session, hash, hashSz, out, outSz); - CHECK_CKR_FAIL(ret, CKR_FUNCTION_FAILED, "ECDSA Verify bad sig"); + CHECK_CKR_FAIL(ret, CKR_SIGNATURE_LEN_RANGE, "ECDSA Verify bad sig"); } /* Test digests */ @@ -9578,7 +9578,7 @@ static CK_RV ecdsa_test(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE privKey, if (ret == CKR_OK) { outSz = 1; ret = funcList->C_Verify(session, data, dataSz, out, outSz); - CHECK_CKR_FAIL(ret, CKR_FUNCTION_FAILED, "ECDSA Verify bad sig"); + CHECK_CKR_FAIL(ret, CKR_SIGNATURE_LEN_RANGE, "ECDSA Verify bad sig"); } } diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index e17cca90..21319e49 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -483,6 +483,63 @@ static void tls_mac_verify_session_close_test(CK_SLOT_ID slot) } #endif +#ifdef HAVE_ECC +static CK_OBJECT_CLASS ecPubClass = CKO_PUBLIC_KEY; +static CK_KEY_TYPE ecKeyType = CKK_EC; + +/* An ECDSA signature whose length is not 2 * order size is out of range. */ +static void ecdsa_sig_len_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_ECDSA, NULL, 0 }; + byte hash[32]; + byte sig[65]; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_CLASS, &ecPubClass, sizeof(ecPubClass) }, + { CKA_KEY_TYPE, &ecKeyType, sizeof(ecKeyType) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_EC_POINT, ecc_p256_pub, sizeof(ecc_p256_pub) }, + }; + + XMEMSET(hash, 0x3c, sizeof(hash)); + XMEMSET(sig, 0x01, sizeof(sig)); + rv = funcList->C_CreateObject(session, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), &pub); + CHECK_RV(rv, "create EC public key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_VerifyInit(session, &mech, pub); + CHECK_RV(rv, "C_VerifyInit(ECDSA, short signature)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Verify(session, hash, sizeof(hash), sig, 63); + CHECK_RV(rv, "C_Verify(ECDSA, short signature)", + CKR_SIGNATURE_LEN_RANGE); + } + + rv = funcList->C_VerifyInit(session, &mech, pub); + CHECK_RV(rv, "C_VerifyInit(ECDSA, long signature)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Verify(session, hash, sizeof(hash), sig, 65); + CHECK_RV(rv, "C_Verify(ECDSA, long signature)", + CKR_SIGNATURE_LEN_RANGE); + } + + rv = funcList->C_VerifyInit(session, &mech, pub); + CHECK_RV(rv, "C_VerifyInit(ECDSA, wrong signature)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Verify(session, hash, sizeof(hash), sig, 64); + CHECK_RV(rv, "C_Verify(ECDSA, wrong signature)", + CKR_SIGNATURE_INVALID); + } + + funcList->C_DestroyObject(session, pub); +} +#endif + static CK_RV token_init(CK_SLOT_ID* slot) { CK_RV rv; @@ -560,6 +617,9 @@ static int run_test(void) tls_mac_sign_final_retry_test(session); tls_mac_verify_multipart_test(session); tls_mac_verify_session_close_test(slot); +#endif +#ifdef HAVE_ECC + ecdsa_sig_len_test(session); #endif } From 4000f0556c8787e8b01fb87e930cc5622b7a11e8 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:07:49 -0700 Subject: [PATCH 181/204] F-5872 - Reject an out of range CKM_AES_CMAC_GENERAL MAC length --- src/crypto.c | 8 ++++++++ tests/sign_verify_mac_test.c | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index ac5f532b..88c0bdc0 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -5876,6 +5876,10 @@ static CK_RV wp11_C_SignInit(CK_SESSION_HANDLE hSession, pMechanism->ulParameterLen != sizeof(CK_MAC_GENERAL_PARAMS)) { return CKR_MECHANISM_PARAM_INVALID; } + if (!CK_ULONG_FITS_WORD32( + *(CK_MAC_GENERAL_PARAMS*)pMechanism->pParameter)) { + return CKR_MECHANISM_PARAM_INVALID; + } ret = WP11_Aes_Cmac_Init(obj, session, (word32)*((CK_MAC_GENERAL_PARAMS*)pMechanism->pParameter)); if (ret == BAD_FUNC_ARG) @@ -7070,6 +7074,10 @@ static CK_RV wp11_C_VerifyInit(CK_SESSION_HANDLE hSession, pMechanism->ulParameterLen != sizeof(CK_MAC_GENERAL_PARAMS)) { return CKR_MECHANISM_PARAM_INVALID; } + if (!CK_ULONG_FITS_WORD32( + *(CK_MAC_GENERAL_PARAMS*)pMechanism->pParameter)) { + return CKR_MECHANISM_PARAM_INVALID; + } ret = WP11_Aes_Cmac_Init(obj, session, (word32)*((CK_MAC_GENERAL_PARAMS*)pMechanism->pParameter)); if (ret == BAD_FUNC_ARG) diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index 21319e49..d3c32876 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -267,6 +267,43 @@ static void aes_cmac_full_block_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, key); } + +/* The CKM_AES_CMAC_GENERAL length is validated as the full CK_ULONG value. */ +static void aes_cmac_general_len_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MAC_GENERAL_PARAMS macLen = 16; + CK_MECHANISM mech = { CKM_AES_CMAC_GENERAL, &macLen, sizeof(macLen) }; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, cmacKey, sizeof(cmacKey) }, + }; + + if (sizeof(CK_ULONG) <= sizeof(word32)) + return; + /* Out of range, but equal to a valid length in the low 32 bits. */ + macLen |= ((CK_ULONG)1 << 16) << 16; + + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &key); + CHECK_RV(rv, "create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(AES-CMAC-GENERAL, out of range length)", + CKR_MECHANISM_PARAM_INVALID); + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(AES-CMAC-GENERAL, out of range length)", + CKR_MECHANISM_PARAM_INVALID); + + funcList->C_DestroyObject(session, key); +} #endif #if defined(WOLFSSL_HAVE_PRF) && !defined(NO_SHA256) @@ -612,6 +649,7 @@ static int run_test(void) #endif #if !defined(NO_AES) && defined(HAVE_AESCMAC) aes_cmac_full_block_test(session); + aes_cmac_general_len_test(session); #endif #if defined(WOLFSSL_HAVE_PRF) && !defined(NO_SHA256) tls_mac_sign_final_retry_test(session); From 716fb43b20055c0c14dbc57aeee561d6b804d66f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:16:07 -0700 Subject: [PATCH 182/204] F-4802 - Reject a CKM_TLS_MAC length that does not fit in 32 bits --- src/crypto.c | 2 ++ tests/sign_verify_mac_test.c | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index 88c0bdc0..5f17fc2a 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -5429,6 +5429,8 @@ static int CKM_TLS_MAC_init(CK_KEY_TYPE type, CK_MECHANISM_PTR pMechanism, return CKR_MECHANISM_PARAM_INVALID; } params = (CK_TLS_MAC_PARAMS*)pMechanism->pParameter; + if (!CK_ULONG_FITS_WORD32(params->ulMacLength)) + return CKR_MECHANISM_PARAM_INVALID; if (params->prfHashMechanism == CKM_TLS_PRF) { if (params->ulMacLength != 12) return CKR_MECHANISM_PARAM_INVALID; diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index d3c32876..65c7d488 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -474,6 +474,44 @@ static void tls_mac_verify_multipart_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, key); } +/* The TLS MAC length is validated as the full CK_ULONG value. */ +static void tls_mac_len_param_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_TLS_MAC_PARAMS params; + CK_MECHANISM mech; + CK_ULONG high; + + if (sizeof(CK_ULONG) <= sizeof(word32)) + return; + high = ((CK_ULONG)1 << 16) << 16; + + rv = create_generic_key(session, &key); + CHECK_RV(rv, "create TLS secret", CKR_OK); + if (rv != CKR_OK) + return; + tls_mac_params(¶ms, &mech); + + params.ulMacLength = high; + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(TLS MAC, length above 32 bits)", + CKR_MECHANISM_PARAM_INVALID); + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(TLS MAC, length above 32 bits)", + CKR_MECHANISM_PARAM_INVALID); + + params.ulMacLength = high | 12; + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(TLS MAC, length above 32 bits plus 12)", + CKR_MECHANISM_PARAM_INVALID); + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(TLS MAC, length above 32 bits plus 12)", + CKR_MECHANISM_PARAM_INVALID); + + funcList->C_DestroyObject(session, key); +} + /* Closing a session discards any multi-part TLS MAC data it held. */ static void tls_mac_verify_session_close_test(CK_SLOT_ID slot) { @@ -655,6 +693,7 @@ static int run_test(void) tls_mac_sign_final_retry_test(session); tls_mac_verify_multipart_test(session); tls_mac_verify_session_close_test(slot); + tls_mac_len_param_test(session); #endif #ifdef HAVE_ECC ecdsa_sig_len_test(session); From e6ff232b2e1b3c92ea82c365aab40a39b64d477d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:23:42 -0700 Subject: [PATCH 183/204] F-4960 - Reject an RSA-PSS salt length larger than the maximum modulus --- src/crypto.c | 4 ++-- src/internal.c | 7 +++++-- tests/sign_verify_mac_test.c | 38 ++++++++++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 2 +- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 5f17fc2a..2969b959 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -5735,7 +5735,7 @@ static CK_RV wp11_C_SignInit(CK_SESSION_HANDLE hSession, if (rv != CKR_OK) return rv; ret = WP11_Session_SetPssParams(session, params->hashAlg, - params->mgf, (int)params->sLen); + params->mgf, params->sLen); if (ret != 0) return CKR_MECHANISM_PARAM_INVALID; init |= WP11_INIT_RSA_PKCS_PSS_SIGN; @@ -6917,7 +6917,7 @@ static CK_RV wp11_C_VerifyInit(CK_SESSION_HANDLE hSession, if (rv != CKR_OK) return rv; ret = WP11_Session_SetPssParams(session, params->hashAlg, - params->mgf, (int)params->sLen); + params->mgf, params->sLen); if (ret != 0) return CKR_MECHANISM_PARAM_INVALID; init |= WP11_INIT_RSA_PKCS_PSS_VERIFY; diff --git a/src/internal.c b/src/internal.c index c3170918..a6774724 100644 --- a/src/internal.c +++ b/src/internal.c @@ -11717,7 +11717,7 @@ int WP11_Session_SetOaepParams(WP11_Session* session, CK_MECHANISM_TYPE hashAlg, * 0 on success. */ int WP11_Session_SetPssParams(WP11_Session* session, CK_MECHANISM_TYPE hashAlg, - CK_MECHANISM_TYPE mgf, int sLen) + CK_MECHANISM_TYPE mgf, CK_ULONG sLen) { int ret; WP11_PssParams* pss = &session->params.pss; @@ -11727,12 +11727,15 @@ int WP11_Session_SetPssParams(WP11_Session* session, CK_MECHANISM_TYPE hashAlg, ret = wp11_hash_type(hashAlg, &hashType); if (ret == 0) ret = wp11_mgf(mgf, &mgfType); + /* Larger values would alias wolfCrypt's negative salt length markers. */ + if (ret == 0 && sLen > (CK_ULONG)(RSA_MAX_SIZE / 8)) + ret = BAD_FUNC_ARG; if (ret == 0) { WP11_Session_AbortOp(session); XMEMSET(pss, 0, sizeof(*pss)); pss->hashType = hashType; pss->mgf = mgfType; - pss->saltLen = sLen; + pss->saltLen = (int)sLen; } return ret; diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index 65c7d488..44512ff9 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -178,6 +178,43 @@ static void pss_hash_binding_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, priv); funcList->C_DestroyObject(session, pub); } + +/* A PSS salt length larger than any RSA modulus is rejected, not remapped. */ +static void pss_salt_len_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_RSA_PKCS_PSS_PARAMS params; + CK_MECHANISM mech; + CK_ULONG badLen[2]; + int i; + + badLen[0] = (CK_ULONG)0xFFFFFFFFUL; + badLen[1] = (CK_ULONG)0xFFFFFFFEUL; + rv = create_rsa_keys(session, &priv, &pub); + CHECK_RV(rv, "create RSA key pair", CKR_OK); + if (rv != CKR_OK) + return; + + mech.mechanism = CKM_RSA_PKCS_PSS; + mech.pParameter = ¶ms; + mech.ulParameterLen = sizeof(params); + params.hashAlg = CKM_SHA256; + params.mgf = CKG_MGF1_SHA256; + for (i = 0; i < 2; i++) { + params.sLen = badLen[i]; + rv = funcList->C_SignInit(session, &mech, priv); + CHECK_RV(rv, "C_SignInit(RSA PSS, oversized salt length)", + CKR_MECHANISM_PARAM_INVALID); + rv = funcList->C_VerifyInit(session, &mech, pub); + CHECK_RV(rv, "C_VerifyInit(RSA PSS, oversized salt length)", + CKR_MECHANISM_PARAM_INVALID); + } + + funcList->C_DestroyObject(session, priv); + funcList->C_DestroyObject(session, pub); +} #endif #if !defined(NO_AES) && defined(HAVE_AESCMAC) @@ -684,6 +721,7 @@ static int run_test(void) if (rv == CKR_OK) { #if !defined(NO_RSA) && defined(WC_RSA_PSS) && !defined(NO_SHA256) pss_hash_binding_test(session); + pss_salt_len_test(session); #endif #if !defined(NO_AES) && defined(HAVE_AESCMAC) aes_cmac_full_block_test(session); diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index df852574..2c6f27d3 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -494,7 +494,7 @@ WP11_LOCAL CK_MECHANISM_TYPE WP11_Session_GetMechanism(WP11_Session* session); WP11_LOCAL void WP11_Session_SetMechanism(WP11_Session* session, CK_MECHANISM_TYPE mechanism); WP11_LOCAL int WP11_Session_SetPssParams(WP11_Session* session, CK_MECHANISM_TYPE hashAlg, - CK_MECHANISM_TYPE mgf, int sLen); + CK_MECHANISM_TYPE mgf, CK_ULONG sLen); WP11_LOCAL int WP11_Session_SetOaepParams(WP11_Session* session, CK_MECHANISM_TYPE hashAlg, CK_MECHANISM_TYPE mgf, byte* label, int labelSz); WP11_LOCAL int WP11_Session_SetCbcParams(WP11_Session* session, unsigned char* iv, int enc, From da754bec446bd8f2c5467ac5222efae50963f072 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:31:01 -0700 Subject: [PATCH 184/204] F-5873 - Compare the full HMAC length parameter against the digest size --- src/internal.c | 2 +- tests/sign_verify_mac_test.c | 53 ++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index a6774724..ef0a78b7 100644 --- a/src/internal.c +++ b/src/internal.c @@ -20585,7 +20585,7 @@ int WP11_Hmac_Init(CK_MECHANISM_TYPE mechanism, WP11_Object* secret, ret = wp11_hmac_hash_type(mechanism, &hashType); if (ret == 0 && digestSize != 0 && - (word32)wc_HmacSizeByType(hashType) != (word32)digestSize) + (CK_ULONG)wc_HmacSizeByType(hashType) != digestSize) ret = BAD_FUNC_ARG; if (ret == 0) { WP11_Session_AbortOp(session); diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index 44512ff9..f0feaa47 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -595,6 +595,56 @@ static void tls_mac_verify_session_close_test(CK_SLOT_ID slot) } #endif +#if !defined(NO_HMAC) && !defined(NO_SHA256) +static CK_OBJECT_CLASS hmacKeyClass = CKO_SECRET_KEY; +static CK_KEY_TYPE hmacKeyType = CKK_GENERIC_SECRET; + +/* The HMAC output length parameter is compared as the full CK_ULONG value. */ +static void hmac_len_param_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_ULONG digestLen = 32; + CK_MECHANISM mech = { CKM_SHA256_HMAC, &digestLen, sizeof(digestLen) }; + byte keyData[32]; + byte data[16]; + byte mac[32]; + CK_ULONG macLen = sizeof(mac); + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &hmacKeyClass, sizeof(hmacKeyClass) }, + { CKA_KEY_TYPE, &hmacKeyType, sizeof(hmacKeyType) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + }; + + XMEMSET(keyData, 0x0b, sizeof(keyData)); + XMEMSET(data, 0x61, sizeof(data)); + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &key); + CHECK_RV(rv, "create HMAC key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(SHA256 HMAC, matching length)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "C_Sign(SHA256 HMAC)", CKR_OK); + } + + if (sizeof(CK_ULONG) > sizeof(word32)) { + /* Out of range, but equal to the digest size in the low 32 bits. */ + digestLen |= ((CK_ULONG)1 << 16) << 16; + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(SHA256 HMAC, length above 32 bits)", + CKR_MECHANISM_PARAM_INVALID); + } + + funcList->C_DestroyObject(session, key); +} +#endif + #ifdef HAVE_ECC static CK_OBJECT_CLASS ecPubClass = CKO_PUBLIC_KEY; static CK_KEY_TYPE ecKeyType = CKK_EC; @@ -735,6 +785,9 @@ static int run_test(void) #endif #ifdef HAVE_ECC ecdsa_sig_len_test(session); +#endif +#if !defined(NO_HMAC) && !defined(NO_SHA256) + hmac_len_param_test(session); #endif } From 146be2baaabb98872962e5845f6e024eb78f0eb4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:38:39 -0700 Subject: [PATCH 185/204] F-6665 - Check the whole recovered block in CKM_RSA_X_509 verify --- src/internal.c | 11 +++++++-- tests/sign_verify_mac_test.c | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index ef0a78b7..aad4ce71 100644 --- a/src/internal.c +++ b/src/internal.c @@ -16790,6 +16790,8 @@ int WP11_Rsa_Verify(unsigned char* sig, word32 sigLen, unsigned char* in, { byte decSig[RSA_MAX_SIZE / 8]; word32 decSigLen; + word32 i; + byte lead = 0; int ret = 0; *stat = 0; @@ -16811,8 +16813,13 @@ int WP11_Rsa_Verify(unsigned char* sig, word32 sigLen, unsigned char* in, if (ret > 0) ret = 0; - if (ret == 0) - *stat = WP11_ConstantCompare(in, decSig + (decSigLen - inLen), inLen); + if (ret == 0) { + /* The recovered block must be the data left padded with zeros. */ + for (i = 0; i < decSigLen - inLen; i++) + lead |= decSig[i]; + *stat = WP11_ConstantCompare(in, decSig + (decSigLen - inLen), inLen) & + (lead == 0); + } return ret; } diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index f0feaa47..3d0a83b5 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -99,6 +99,49 @@ static CK_RV create_rsa_keys(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* priv, } #endif +#if !defined(NO_RSA) && defined(WC_RSA_DIRECT) +/* A raw RSA verify compares the whole recovered block with the data. */ +static void rsa_x509_verify_block_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_RSA_X_509, NULL, 0 }; + byte data[32]; + byte sig[2048 / 8]; + CK_ULONG sigLen = sizeof(sig); + + XMEMSET(data, 0x5a, sizeof(data)); + rv = create_rsa_keys(session, &priv, &pub); + CHECK_RV(rv, "create RSA key pair", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, priv); + CHECK_RV(rv, "C_SignInit(RSA X.509)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Sign(session, data, sizeof(data), sig, &sigLen); + CHECK_RV(rv, "C_Sign(RSA X.509)", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_VerifyInit(session, &mech, pub); + CHECK_RV(rv, "C_VerifyInit(RSA X.509)", CKR_OK); + rv = funcList->C_Verify(session, data, sizeof(data), sig, sigLen); + CHECK_RV(rv, "C_Verify(RSA X.509, signed data)", CKR_OK); + + rv = funcList->C_VerifyInit(session, &mech, pub); + CHECK_RV(rv, "C_VerifyInit(RSA X.509, shorter data)", CKR_OK); + rv = funcList->C_Verify(session, data + 1, sizeof(data) - 1, sig, + sigLen); + CHECK_RV(rv, "C_Verify(RSA X.509, trailing part of signed data)", + CKR_SIGNATURE_INVALID); + } + + funcList->C_DestroyObject(session, priv); + funcList->C_DestroyObject(session, pub); +} +#endif + #if !defined(NO_RSA) && defined(WC_RSA_PSS) && !defined(NO_SHA256) /* A hashed RSA-PSS mechanism fixes the PSS hash to its own digest; any valid * MGF1 hash is accepted. */ @@ -773,6 +816,9 @@ static int run_test(void) pss_hash_binding_test(session); pss_salt_len_test(session); #endif +#if !defined(NO_RSA) && defined(WC_RSA_DIRECT) + rsa_x509_verify_block_test(session); +#endif #if !defined(NO_AES) && defined(HAVE_AESCMAC) aes_cmac_full_block_test(session); aes_cmac_general_len_test(session); From ba2852a0ccd9a51bbaba4465a25fa6dad6392359 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 13:53:01 -0700 Subject: [PATCH 186/204] F-10401 - Reject an RSA key larger than the raw sign buffer --- src/internal.c | 5 ++++ tests/sign_verify_mac_test.c | 54 ++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/internal.c b/src/internal.c index aad4ce71..2aa60cd1 100644 --- a/src/internal.c +++ b/src/internal.c @@ -16659,6 +16659,11 @@ int WP11_Rsa_Sign(unsigned char* in, word32 inLen, unsigned char* sig, WP11_Lock_LockRO(priv->lock); keyLen = wc_RsaEncryptSize(priv->data.rsaKey); + if (keyLen > sizeof(data)) { + if (priv->onToken) + WP11_Lock_UnlockRO(priv->lock); + return BUFFER_E; + } if (inLen < keyLen) { XMEMSET(data, 0, keyLen - inLen); XMEMCPY(data + keyLen - inLen, in, inLen); diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index 3d0a83b5..66aba8d3 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -35,6 +35,9 @@ #endif #include #include +#ifndef NO_RSA + #include +#endif #ifndef WOLFPKCS11_USER_SETTINGS #include @@ -140,6 +143,56 @@ static void rsa_x509_verify_block_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, priv); funcList->C_DestroyObject(session, pub); } + +/* Larger than the maximum RSA modulus wolfCrypt is built for. */ +#define BIG_MOD_SZ ((RSA_MAX_SIZE / 8) + 64) +static byte bigMod[BIG_MOD_SZ]; +static byte bigHalf[BIG_MOD_SZ / 2]; +static byte bigSig[BIG_MOD_SZ]; + +/* Raw RSA fails cleanly for a key larger than the supported modulus size. */ +static void rsa_x509_big_modulus_sign_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_RSA_X_509, NULL, 0 }; + byte data[32]; + CK_ULONG sigLen = sizeof(bigSig); + CK_ATTRIBUTE privTmpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_MODULUS, bigMod, sizeof(bigMod) }, + { CKA_PRIVATE_EXPONENT, bigMod, sizeof(bigMod) }, + { CKA_PRIME_1, bigHalf, sizeof(bigHalf) }, + { CKA_PRIME_2, bigHalf, sizeof(bigHalf) }, + { CKA_EXPONENT_1, bigHalf, sizeof(bigHalf) }, + { CKA_EXPONENT_2, bigHalf, sizeof(bigHalf) }, + { CKA_COEFFICIENT, bigHalf, sizeof(bigHalf) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + + XMEMSET(bigMod, 0xa5, sizeof(bigMod)); + XMEMSET(bigHalf, 0xc3, sizeof(bigHalf)); + XMEMSET(data, 0x11, sizeof(data)); + rv = funcList->C_CreateObject(session, privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), &priv); + if (rv != CKR_OK) { + CHECK_TRUE(1, "oversized RSA private key rejected at import"); + return; + } + + rv = funcList->C_SignInit(session, &mech, priv); + if (rv == CKR_OK) { + rv = funcList->C_Sign(session, data, sizeof(data), bigSig, &sigLen); + CHECK_TRUE(rv != CKR_OK, "C_Sign(RSA X.509) fails for oversized key"); + } + else { + CHECK_TRUE(1, "C_SignInit(RSA X.509) rejects oversized key"); + } + funcList->C_DestroyObject(session, priv); +} #endif #if !defined(NO_RSA) && defined(WC_RSA_PSS) && !defined(NO_SHA256) @@ -818,6 +871,7 @@ static int run_test(void) #endif #if !defined(NO_RSA) && defined(WC_RSA_DIRECT) rsa_x509_verify_block_test(session); + rsa_x509_big_modulus_sign_test(session); #endif #if !defined(NO_AES) && defined(HAVE_AESCMAC) aes_cmac_full_block_test(session); From 63a48f9ab7d4f40afafab229e9d7b0f32cd29a66 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:08:09 -0700 Subject: [PATCH 187/204] F-10402 - Bound the raw RSA verify output by the recovery buffer size --- src/internal.c | 2 +- tests/sign_verify_mac_test.c | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 2aa60cd1..0960571c 100644 --- a/src/internal.c +++ b/src/internal.c @@ -16804,7 +16804,7 @@ int WP11_Rsa_Verify(unsigned char* sig, word32 sigLen, unsigned char* in, if (pub->onToken) WP11_Lock_LockRO(pub->lock); decSigLen = wc_RsaEncryptSize(pub->data.rsaKey); - if (inLen > decSigLen) { + if (inLen > decSigLen || decSigLen > sizeof(decSig)) { if(pub->onToken) WP11_Lock_UnlockRO(pub->lock); return BUFFER_E; diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index 66aba8d3..7b15840a 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -149,6 +149,8 @@ static void rsa_x509_verify_block_test(CK_SESSION_HANDLE session) static byte bigMod[BIG_MOD_SZ]; static byte bigHalf[BIG_MOD_SZ / 2]; static byte bigSig[BIG_MOD_SZ]; +/* Just over the maximum, so the math library can still use the key. */ +#define BIG_VERIFY_MOD_SZ ((RSA_MAX_SIZE / 8) + 8) /* Raw RSA fails cleanly for a key larger than the supported modulus size. */ static void rsa_x509_big_modulus_sign_test(CK_SESSION_HANDLE session) @@ -193,6 +195,45 @@ static void rsa_x509_big_modulus_sign_test(CK_SESSION_HANDLE session) } funcList->C_DestroyObject(session, priv); } + +/* Raw RSA verify fails cleanly for a key larger than the supported size. */ +static void rsa_x509_big_modulus_verify_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_RSA_X_509, NULL, 0 }; + byte data[32]; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_CLASS, &pubKeyClass, sizeof(pubKeyClass) }, + { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_MODULUS, bigMod, BIG_VERIFY_MOD_SZ }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + + XMEMSET(bigMod, 0xa5, sizeof(bigMod)); + XMEMSET(bigSig, 0x01, sizeof(bigSig)); + XMEMSET(data, 0x11, sizeof(data)); + rv = funcList->C_CreateObject(session, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), &pub); + if (rv != CKR_OK) { + CHECK_TRUE(1, "oversized RSA public key rejected at import"); + return; + } + + rv = funcList->C_VerifyInit(session, &mech, pub); + if (rv == CKR_OK) { + rv = funcList->C_Verify(session, data, sizeof(data), bigSig, + BIG_VERIFY_MOD_SZ); + CHECK_TRUE(rv != CKR_OK, + "C_Verify(RSA X.509) fails for oversized key"); + } + else { + CHECK_TRUE(1, "C_VerifyInit(RSA X.509) rejects oversized key"); + } + funcList->C_DestroyObject(session, pub); +} #endif #if !defined(NO_RSA) && defined(WC_RSA_PSS) && !defined(NO_SHA256) @@ -872,6 +913,7 @@ static int run_test(void) #if !defined(NO_RSA) && defined(WC_RSA_DIRECT) rsa_x509_verify_block_test(session); rsa_x509_big_modulus_sign_test(session); + rsa_x509_big_modulus_verify_test(session); #endif #if !defined(NO_AES) && defined(HAVE_AESCMAC) aes_cmac_full_block_test(session); From 61b537f690615d65d7e6be732f403085a1bfe5f6 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:19:24 -0700 Subject: [PATCH 188/204] F-6962 - Zeroize the padded input buffer after a raw RSA sign --- src/internal.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/internal.c b/src/internal.c index 0960571c..82c0dda6 100644 --- a/src/internal.c +++ b/src/internal.c @@ -16695,6 +16695,7 @@ int WP11_Rsa_Sign(unsigned char* in, word32 inLen, unsigned char* sig, if (priv->onToken) WP11_Lock_UnlockRO(priv->lock); + wc_ForceZero(data, sizeof(data)); return ret; } From ded7d8837951ff0be37e598ed38d11645c35ddd4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:37:06 -0700 Subject: [PATCH 189/204] F-8639 - Refuse CKM_RSA_X_509 sign and verify at init without WC_RSA_DIRECT --- src/crypto.c | 6 +++++ src/slot.c | 4 ++++ tests/sign_verify_mac_test.c | 45 ++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index 2969b959..f3810fcc 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -5671,6 +5671,7 @@ static CK_RV wp11_C_SignInit(CK_SESSION_HANDLE hSession, init = GetInitValue(pMechanism->mechanism); switch (pMechanism->mechanism) { #ifndef NO_RSA + #ifdef WC_RSA_DIRECT case CKM_RSA_X_509: if (type != CKK_RSA) return CKR_KEY_TYPE_INCONSISTENT; @@ -5680,6 +5681,7 @@ static CK_RV wp11_C_SignInit(CK_SESSION_HANDLE hSession, } init = WP11_INIT_RSA_X_509_SIGN; break; + #endif #ifndef NO_SHA case CKM_SHA1_RSA_PKCS: #endif @@ -6853,6 +6855,7 @@ static CK_RV wp11_C_VerifyInit(CK_SESSION_HANDLE hSession, init = GetInitValue(pMechanism->mechanism); switch (pMechanism->mechanism) { #ifndef NO_RSA + #ifdef WC_RSA_DIRECT case CKM_RSA_X_509: if (type != CKK_RSA) return CKR_KEY_TYPE_INCONSISTENT; @@ -6862,6 +6865,7 @@ static CK_RV wp11_C_VerifyInit(CK_SESSION_HANDLE hSession, } init = WP11_INIT_RSA_X_509_VERIFY; break; + #endif #ifndef NO_SHA case CKM_SHA1_RSA_PKCS: #endif @@ -7863,9 +7867,11 @@ static CK_RV wp11_C_VerifyRecoverInit(CK_SESSION_HANDLE hSession, case CKM_RSA_PKCS: init = WP11_INIT_RSA_PKCS_VERIFY_RECOVER; break; +#if !defined(NO_RSA) && defined(WC_RSA_DIRECT) case CKM_RSA_X_509: init = WP11_INIT_RSA_X_509_VERIFY_RECOVER; break; +#endif default: return CKR_MECHANISM_INVALID; } diff --git a/src/slot.c b/src/slot.c index 4544d84f..665adcbd 100644 --- a/src/slot.c +++ b/src/slot.c @@ -594,8 +594,12 @@ static CK_MECHANISM_INFO rsaKgMechInfo = { #endif /* Info on RSA X.509 mechanism. */ static CK_MECHANISM_INFO rsaX509MechInfo = { +#ifdef WC_RSA_DIRECT 1024, 4096, CKF_ENCRYPT | CKF_DECRYPT | CKF_SIGN | CKF_VERIFY | CKF_VERIFY_RECOVER +#else + 1024, 4096, CKF_ENCRYPT | CKF_DECRYPT +#endif }; /* Info on RSA PKCS#1.5 mechanism. */ static CK_MECHANISM_INFO rsaPkcsMechInfo = { diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index 7b15840a..c722b6f1 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -236,6 +236,48 @@ static void rsa_x509_big_modulus_verify_test(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_RSA) && !defined(WC_RSA_DIRECT) +/* Without raw RSA support, raw RSA sign and verify are refused at init. */ +static void rsa_x509_unsupported_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_RSA_X_509, NULL, 0 }; + CK_MECHANISM_INFO info; + CK_SESSION_INFO sessInfo; + + rv = create_rsa_keys(session, &priv, &pub); + CHECK_RV(rv, "create RSA key pair", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, priv); + CHECK_RV(rv, "C_SignInit(RSA X.509) without raw RSA", + CKR_MECHANISM_INVALID); + rv = funcList->C_VerifyInit(session, &mech, pub); + CHECK_RV(rv, "C_VerifyInit(RSA X.509) without raw RSA", + CKR_MECHANISM_INVALID); + rv = funcList->C_VerifyRecoverInit(session, &mech, pub); + CHECK_RV(rv, "C_VerifyRecoverInit(RSA X.509) without raw RSA", + CKR_MECHANISM_INVALID); + + rv = funcList->C_GetSessionInfo(session, &sessInfo); + CHECK_RV(rv, "C_GetSessionInfo", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_GetMechanismInfo(sessInfo.slotID, CKM_RSA_X_509, + &info); + CHECK_RV(rv, "C_GetMechanismInfo(RSA X.509)", CKR_OK); + CHECK_TRUE(rv == CKR_OK && (info.flags & (CKF_SIGN | CKF_VERIFY | + CKF_VERIFY_RECOVER)) == 0, + "RSA X.509 does not advertise sign or verify"); + } + + funcList->C_DestroyObject(session, priv); + funcList->C_DestroyObject(session, pub); +} +#endif + #if !defined(NO_RSA) && defined(WC_RSA_PSS) && !defined(NO_SHA256) /* A hashed RSA-PSS mechanism fixes the PSS hash to its own digest; any valid * MGF1 hash is accepted. */ @@ -915,6 +957,9 @@ static int run_test(void) rsa_x509_big_modulus_sign_test(session); rsa_x509_big_modulus_verify_test(session); #endif +#if !defined(NO_RSA) && !defined(WC_RSA_DIRECT) + rsa_x509_unsupported_test(session); +#endif #if !defined(NO_AES) && defined(HAVE_AESCMAC) aes_cmac_full_block_test(session); aes_cmac_general_len_test(session); From ee13e3fdb4f542e12de620a7c3677158504d131f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 14:46:58 -0700 Subject: [PATCH 190/204] F-5075 - Require a user login to sign with the MAXQ10xx device key --- src/crypto.c | 6 ++++++ tests/sign_verify_mac_test.c | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index f3810fcc..20f6c068 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -5638,6 +5638,12 @@ static CK_RV wp11_C_SignInit(CK_SESSION_HANDLE hSession, if (pMechanism->pParameter != NULL || pMechanism->ulParameterLen != 0) { return CKR_MECHANISM_PARAM_INVALID; } + /* The pre-provisioned key is private, so require a user login. */ + if (!WP11_Slot_IsUserLoggedIn(WP11_Session_GetSlot(session))) { + rv = CKR_USER_NOT_LOGGED_IN; + WOLFPKCS11_LEAVE("C_SignInit", rv); + return rv; + } if (WP11_Session_IsOpCategoryActive(session, WP11_OP_SIGN)) { rv = CKR_OPERATION_ACTIVE; diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index c722b6f1..c107917c 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -881,6 +881,23 @@ static void ecdsa_sig_len_test(CK_SESSION_HANDLE session) } #endif +#ifdef WOLFSSL_MAXQ10XX_CRYPTO +/* Signing with the pre-provisioned device key requires a user login. */ +static void maxq_device_key_login_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_MECHANISM mech = { CKM_ECDSA, NULL, 0 }; + + rv = funcList->C_Logout(session); + CHECK_RV(rv, "C_Logout", CKR_OK); + rv = funcList->C_SignInit(session, &mech, 0); + CHECK_RV(rv, "C_SignInit(device key, no login)", CKR_USER_NOT_LOGGED_IN); + rv = funcList->C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + CHECK_RV(rv, "user login again", CKR_OK); +} +#endif + static CK_RV token_init(CK_SLOT_ID* slot) { CK_RV rv; @@ -975,6 +992,9 @@ static int run_test(void) #endif #if !defined(NO_HMAC) && !defined(NO_SHA256) hmac_len_param_test(session); +#endif +#ifdef WOLFSSL_MAXQ10XX_CRYPTO + maxq_device_key_login_test(session); #endif } From f8dc13faa7189260dd70aa6196651ffe2c94736c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:53 -0700 Subject: [PATCH 191/204] Accept the NSS TLS PRF mechanism in multi-part MAC verify --- src/crypto.c | 42 +++++++++++--- src/internal.c | 3 +- tests/sign_verify_mac_test.c | 104 ++++++++++++++++++++++++++++++++++- 3 files changed, 137 insertions(+), 12 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 20f6c068..700f4a65 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -6296,8 +6296,18 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, #ifdef WOLFPKCS11_NSS case CKM_NSS_TLS_PRF_GENERAL_SHA256: #endif + { + byte* data = NULL; + word32 dataLen = 0; + if (!WP11_Session_IsOpInitialized(session, WP11_INIT_TLS_MAC_SIGN)) return CKR_OPERATION_NOT_INITIALIZED; + /* C_Sign cannot finish a multi-part sign. */ + WP11_Session_GetData(session, &data, &dataLen); + if (data != NULL || dataLen != 0) { + WP11_Session_AbortOp(session); + return CKR_FUNCTION_FAILED; + } if (pSignature == NULL) { *pulSignatureLen = (CK_ULONG)WP11_TLS_MAC_get_len(session); @@ -6311,6 +6321,7 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, &sigLen, session); *pulSignatureLen = sigLen; break; + } #endif default: (void)sigLen; @@ -6621,14 +6632,13 @@ static CK_RV wp11_C_SignFinal(CK_SESSION_HANDLE hSession, return CKR_BUFFER_TOO_SMALL; WP11_Session_GetData(session, &data, &dataLen); - ret = (int)C_Sign(hSession, data, dataLen, pSignature, - pulSignatureLen); + sigLen = (word32)*pulSignatureLen; + ret = WP11_TLS_MAC_sign(data, dataLen, pSignature, &sigLen, + session); WP11_Session_FreeData(session); - if (ret != CKR_OK) { - WP11_Session_SetOpInitialized(session, 0); - return ret; - } - + if (ret != 0) + break; + *pulSignatureLen = sigLen; break; } #endif @@ -7471,9 +7481,14 @@ static CK_RV wp11_C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, /* C_Verify cannot finish a multi-part verify. */ WP11_Session_GetData(session, &data, &dataLen); if (data != NULL || dataLen != 0) { - WP11_Session_FreeData(session); + WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; } + if (WP11_TLS_MAC_get_len(session) != 0 && + ulSignatureLen != (CK_ULONG)WP11_TLS_MAC_get_len(session)) { + WP11_Session_AbortOp(session); + return CKR_SIGNATURE_LEN_RANGE; + } ret = WP11_TLS_MAC_verify(pData, (word32)ulDataLen, pSignature, (word32)ulSignatureLen, &stat, session); @@ -7616,6 +7631,9 @@ static CK_RV wp11_C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, #endif #ifdef WOLFSSL_HAVE_PRF case CKM_TLS_MAC: +#ifdef WOLFPKCS11_NSS + case CKM_NSS_TLS_PRF_GENERAL_SHA256: +#endif if (!WP11_Session_IsOpInitialized(session, WP11_INIT_TLS_MAC_VERIFY)) { return CKR_OPERATION_NOT_INITIALIZED; @@ -7767,6 +7785,9 @@ static CK_RV wp11_C_VerifyFinal(CK_SESSION_HANDLE hSession, #endif #ifdef WOLFSSL_HAVE_PRF case CKM_TLS_MAC: +#ifdef WOLFPKCS11_NSS + case CKM_NSS_TLS_PRF_GENERAL_SHA256: +#endif { byte* data = NULL; word32 dataLen = 0; @@ -7779,6 +7800,11 @@ static CK_RV wp11_C_VerifyFinal(CK_SESSION_HANDLE hSession, WP11_Session_FreeData(session); return CKR_SIGNATURE_LEN_RANGE; } + if (WP11_TLS_MAC_get_len(session) != 0 && + ulSignatureLen != (CK_ULONG)WP11_TLS_MAC_get_len(session)) { + WP11_Session_AbortOp(session); + return CKR_SIGNATURE_LEN_RANGE; + } WP11_Session_GetData(session, &data, &dataLen); ret = WP11_TLS_MAC_verify(data, dataLen, pSignature, diff --git a/src/internal.c b/src/internal.c index 82c0dda6..9827adfe 100644 --- a/src/internal.c +++ b/src/internal.c @@ -20845,7 +20845,8 @@ int WP11_TLS_MAC_verify(byte* data, word32 dataLen, byte* sig, word32 sigLen, word32 genSigLen = sigLen; WP11_TlsMacParams *mac = &session->params.tlsMac; - if (sigLen != mac->macSz) + /* An unspecified MAC length takes the length of the signature. */ + if (mac->macSz != 0 && sigLen != mac->macSz) return BUFFER_E; genSig = (byte*)XMALLOC(genSigLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/tests/sign_verify_mac_test.c b/tests/sign_verify_mac_test.c index c107917c..987c9327 100644 --- a/tests/sign_verify_mac_test.c +++ b/tests/sign_verify_mac_test.c @@ -601,6 +601,66 @@ static void tls_mac_sign_final_retry_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, key); } +#ifdef WOLFPKCS11_NSS +/* The NSS TLS PRF mechanism verifies in multi-part calls like CKM_TLS_MAC. */ +static void nss_tls_prf_verify_multipart_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_NSS_TLS_PRF_GENERAL_SHA256, NULL, 0 }; + byte mac[12]; + CK_ULONG macLen = sizeof(mac); + CK_ULONG half = sizeof(tlsHandshakeHash) / 2; + + rv = create_generic_key(session, &key); + CHECK_RV(rv, "create TLS secret", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(NSS TLS PRF)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Sign(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash), mac, &macLen); + CHECK_RV(rv, "C_Sign(NSS TLS PRF)", CKR_OK); + CHECK_TRUE(macLen == sizeof(mac), + "C_Sign(NSS TLS PRF) fills the buffer"); + } + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(NSS TLS PRF multi-part)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_VerifyUpdate(session, tlsHandshakeHash, half); + CHECK_RV(rv, "C_VerifyUpdate(NSS TLS PRF, first part)", CKR_OK); + rv = funcList->C_VerifyUpdate(session, tlsHandshakeHash + half, + sizeof(tlsHandshakeHash) - half); + CHECK_RV(rv, "C_VerifyUpdate(NSS TLS PRF, second part)", CKR_OK); + rv = funcList->C_VerifyFinal(session, mac, macLen); + CHECK_RV(rv, "C_VerifyFinal(NSS TLS PRF)", CKR_OK); + } + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(NSS TLS PRF single-part)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Verify(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash), mac, macLen); + CHECK_RV(rv, "C_Verify(NSS TLS PRF)", CKR_OK); + } + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(NSS TLS PRF, other data)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_VerifyUpdate(session, tlsHandshakeHash, half); + CHECK_RV(rv, "C_VerifyUpdate(NSS TLS PRF, partial data)", CKR_OK); + rv = funcList->C_VerifyFinal(session, mac, macLen); + CHECK_RV(rv, "C_VerifyFinal(NSS TLS PRF, other data)", + CKR_SIGNATURE_INVALID); + } + + funcList->C_DestroyObject(session, key); +} +#endif + /* A TLS MAC verify supports the multi-part update and final calls. */ static void tls_mac_verify_multipart_test(CK_SESSION_HANDLE session) { @@ -655,8 +715,11 @@ static void tls_mac_verify_multipart_test(CK_SESSION_HANDLE session) CHECK_RV(rv, "C_VerifyUpdate(TLS MAC, before single-part)", CKR_OK); rv = funcList->C_Verify(session, tlsHandshakeHash, sizeof(tlsHandshakeHash), mac, macLen); - CHECK_TRUE(rv != CKR_OK, - "C_Verify(TLS MAC) does not finish a multi-part verify"); + CHECK_RV(rv, "C_Verify(TLS MAC) does not finish a multi-part verify", + CKR_FUNCTION_FAILED); + rv = funcList->C_VerifyFinal(session, mac, macLen); + CHECK_RV(rv, "C_Verify(TLS MAC) failure ends the operation", + CKR_OPERATION_NOT_INITIALIZED); } rv = funcList->C_SignInit(session, &mech, key); @@ -667,7 +730,39 @@ static void tls_mac_verify_multipart_test(CK_SESSION_HANDLE session) macLen = sizeof(mac); rv = funcList->C_Sign(session, tlsHandshakeHash, sizeof(tlsHandshakeHash), mac, &macLen); - CHECK_RV(rv, "C_Sign(TLS MAC, after update)", CKR_OK); + CHECK_RV(rv, "C_Sign(TLS MAC) does not finish a multi-part sign", + CKR_FUNCTION_FAILED); + macLen = sizeof(mac); + rv = funcList->C_SignFinal(session, mac, &macLen); + CHECK_RV(rv, "C_Sign(TLS MAC) failure ends the operation", + CKR_OPERATION_NOT_INITIALIZED); + } + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(TLS MAC, regenerate)", CKR_OK); + if (rv == CKR_OK) { + macLen = sizeof(mac); + rv = funcList->C_Sign(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash), mac, &macLen); + CHECK_RV(rv, "C_Sign(TLS MAC, regenerate)", CKR_OK); + } + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(TLS MAC, wrong length)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_Verify(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash), mac, macLen - 1); + CHECK_RV(rv, "C_Verify(TLS MAC, short signature)", + CKR_SIGNATURE_LEN_RANGE); + } + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(TLS MAC, wrong length multi-part)", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_VerifyUpdate(session, tlsHandshakeHash, + sizeof(tlsHandshakeHash)); + CHECK_RV(rv, "C_VerifyUpdate(TLS MAC, wrong length)", CKR_OK); + rv = funcList->C_VerifyFinal(session, mac, macLen + 1); + CHECK_RV(rv, "C_VerifyFinal(TLS MAC, long signature)", + CKR_SIGNATURE_LEN_RANGE); } rv = funcList->C_VerifyInit(session, &mech, key); CHECK_RV(rv, "C_VerifyInit(TLS MAC, after sign)", CKR_OK); @@ -983,6 +1078,9 @@ static int run_test(void) #endif #if defined(WOLFSSL_HAVE_PRF) && !defined(NO_SHA256) tls_mac_sign_final_retry_test(session); +#ifdef WOLFPKCS11_NSS + nss_tls_prf_verify_multipart_test(session); +#endif tls_mac_verify_multipart_test(session); tls_mac_verify_session_close_test(slot); tls_mac_len_param_test(session); From e761fb26091ae098997b322cd8e7afbb2e372f6b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 15:57:53 -0700 Subject: [PATCH 192/204] F-2040 - Require an active digest operation in C_Digest --- src/crypto.c | 2 + tests/digest_length_test.c | 227 +++++++++++++++++++++++++++++++++++++ tests/include.am | 7 ++ 3 files changed, 236 insertions(+) create mode 100644 tests/digest_length_test.c diff --git a/src/crypto.c b/src/crypto.c index 700f4a65..aaed0d6a 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -5228,6 +5228,8 @@ CK_RV C_Digest(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, return CKR_SESSION_HANDLE_INVALID; if (pData == NULL || ulDataLen == 0 || pulDigestLen == NULL) return CKR_ARGUMENTS_BAD; + if (!WP11_Session_IsOpInitialized(session, WP11_INIT_DIGEST)) + return CKR_OPERATION_NOT_INITIALIZED; hashLen = (word32)*pulDigestLen; ret = WP11_Digest_Single(pData, (word32)ulDataLen, pDigest, &hashLen, diff --git a/tests/digest_length_test.c b/tests/digest_length_test.c new file mode 100644 index 00000000..b65a8607 --- /dev/null +++ b/tests/digest_length_test.c @@ -0,0 +1,227 @@ +/* digest_length_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Digest, sign, verify, encrypt and decrypt must honour the PKCS#11 length + * conventions: input lengths are taken in full, output buffer lengths are + * compared in full and a short buffer reports the length that is needed. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#define TEST_DIR "./store/digest_length_test" + +static const char* soPin = "password123456"; +static const char* userPin = "wolfpkcs11-test"; +static const char tokenLabel[] = "digest-length"; + +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; + +#ifndef NO_SHA256 +static CK_MECHANISM sha256Mech = { CKM_SHA256, NULL, 0 }; + +#if !defined(NO_HMAC) +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; + +static CK_RV create_hmac_key(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* key) +{ + static byte keyData[32] = { 0x0b }; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + }; + + return funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), key); +} +#endif + +/* C_Digest only runs inside an operation started by C_DigestInit. */ +static void digest_requires_init_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + byte data[16]; + byte hash[32]; + CK_ULONG hashLen = sizeof(hash); +#if !defined(NO_HMAC) + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM hmacMech = { CKM_SHA256_HMAC, NULL, 0 }; + byte mac[32]; + CK_ULONG macLen = sizeof(mac); +#endif + + XMEMSET(data, 0x61, sizeof(data)); + rv = funcList->C_Digest(session, data, sizeof(data), hash, &hashLen); + CHECK_RV(rv, "C_Digest without C_DigestInit", + CKR_OPERATION_NOT_INITIALIZED); + + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA256)", CKR_OK); + hashLen = sizeof(hash); + rv = funcList->C_Digest(session, data, sizeof(data), hash, &hashLen); + CHECK_RV(rv, "C_Digest(SHA256)", CKR_OK); + hashLen = sizeof(hash); + rv = funcList->C_Digest(session, data, sizeof(data), hash, &hashLen); + CHECK_RV(rv, "C_Digest after the digest operation finished", + CKR_OPERATION_NOT_INITIALIZED); + +#if !defined(NO_HMAC) + rv = create_hmac_key(session, &key); + CHECK_RV(rv, "create HMAC key", CKR_OK); + if (rv != CKR_OK) + return; + rv = funcList->C_SignInit(session, &hmacMech, key); + CHECK_RV(rv, "C_SignInit(SHA256 HMAC)", CKR_OK); + hashLen = sizeof(hash); + rv = funcList->C_Digest(session, data, sizeof(data), hash, &hashLen); + CHECK_RV(rv, "C_Digest during a sign operation", + CKR_OPERATION_NOT_INITIALIZED); + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "sign operation still active after C_Digest", CKR_OK); + funcList->C_DestroyObject(session, key); +#endif +} +#endif /* !NO_SHA256 */ + +static CK_RV token_init(CK_SLOT_ID* slot) +{ + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + CK_SESSION_HANDLE soSession = 0; + unsigned char label[32]; + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + if (rv != CKR_OK) + return rv; + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv != CKR_OK) + return rv; + if (slotCount == 0) + return CKR_TOKEN_NOT_PRESENT; + *slot = slotList[0]; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenLabel, XSTRLEN(tokenLabel)); + rv = funcList->C_InitToken(*slot, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin), label); + if (rv != CKR_OK) + return rv; + + rv = funcList->C_OpenSession(*slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &soSession); + if (rv != CKR_OK) + return rv; + rv = funcList->C_Login(soSession, CKU_SO, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin)); + if (rv == CKR_OK) { + rv = funcList->C_InitPIN(soSession, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + } + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + return rv; +} + +static int run_test(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = 0; + + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv != CKR_OK) + return -1; + + rv = token_init(&slot); + CHECK_RV(rv, "token init", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &session); + CHECK_RV(rv, "open session", CKR_OK); + } + if (rv == CKR_OK) { + rv = funcList->C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + CHECK_RV(rv, "user login", CKR_OK); + } + if (rv == CKR_OK) { +#ifndef NO_SHA256 + digest_requires_init_test(session); +#endif + } + + if (session != 0) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + funcList->C_Finalize(NULL); + pkcs11_unload(); + return 0; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + (void)ckTrue; + (void)ckFalse; + +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + + printf("=== wolfPKCS11 digest and length convention test ===\n"); + run_test(); + return pkcs11_test_summary(); +} diff --git a/tests/include.am b/tests/include.am index 74492b51..0d758768 100644 --- a/tests/include.am +++ b/tests/include.am @@ -375,6 +375,11 @@ noinst_PROGRAMS += tests/sign_verify_mac_test tests_sign_verify_mac_test_SOURCES = tests/sign_verify_mac_test.c tests_sign_verify_mac_test_LDADD = +check_PROGRAMS += tests/digest_length_test +noinst_PROGRAMS += tests/digest_length_test +tests_digest_length_test_SOURCES = tests/digest_length_test.c +tests_digest_length_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -451,6 +456,7 @@ tests_key_material_update_test_LDADD += src/libwolfpkcs11.la tests_object_create_getattr_test_LDADD += src/libwolfpkcs11.la tests_encrypt_decrypt_params_test_LDADD += src/libwolfpkcs11.la tests_sign_verify_mac_test_LDADD += src/libwolfpkcs11.la +tests_digest_length_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -519,6 +525,7 @@ tests_key_material_update_test_LDADD += src/libwolfpkcs11.la tests_object_create_getattr_test_LDADD += src/libwolfpkcs11.la tests_encrypt_decrypt_params_test_LDADD += src/libwolfpkcs11.la tests_sign_verify_mac_test_LDADD += src/libwolfpkcs11.la +tests_digest_length_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ From 5223c3b62638dce3554fb62d469125b26a5a0171 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 16:20:16 -0700 Subject: [PATCH 193/204] F-2786 - Reject digest, sign, verify and RSA lengths above 32 bits --- src/crypto.c | 61 ++++++++++ tests/digest_length_test.c | 224 +++++++++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index aaed0d6a..430530c0 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -66,6 +66,8 @@ #define CK_ULONG_MAX_OVERHEAD ((CK_ULONG)64) #define CK_ULONG_FITS_WORD32(v) \ ((v) <= (CK_ULONG)0xFFFFFFFF - CK_ULONG_MAX_OVERHEAD) +/* For lengths passed straight to a word32 parameter with no overhead added. */ +#define CK_ULONG_IS_WORD32(v) ((CK_ULONG)(word32)(v) == (v)) /* RFC 5869 limits HKDF-Expand output to 255 * HashLen octets. Cap the * requested output length to the largest such value across supported digests @@ -3184,6 +3186,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, case CKM_RSA_X_509: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_RSA_X_509_ENC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulDataLen)) { + WP11_Session_AbortOp(session); + return CKR_DATA_LEN_RANGE; + } encDataLen = WP11_Rsa_KeyLen(obj); if (pEncryptedData == NULL) { @@ -3203,6 +3209,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, case CKM_RSA_PKCS: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_RSA_PKCS_ENC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulDataLen)) { + WP11_Session_AbortOp(session); + return CKR_DATA_LEN_RANGE; + } encDataLen = WP11_Rsa_KeyLen(obj); if (pEncryptedData == NULL) { @@ -3225,6 +3235,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, WP11_INIT_RSA_PKCS_OAEP_ENC)) { return CKR_OPERATION_NOT_INITIALIZED; } + if (!CK_ULONG_FITS_WORD32(ulDataLen)) { + WP11_Session_AbortOp(session); + return CKR_DATA_LEN_RANGE; + } encDataLen = WP11_Rsa_KeyLen(obj); if (pEncryptedData == NULL) { @@ -4322,6 +4336,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, case CKM_RSA_X_509: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_RSA_X_509_DEC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) { + WP11_Session_AbortOp(session); + return CKR_ENCRYPTED_DATA_LEN_RANGE; + } decDataLen = WP11_Rsa_KeyLen(obj); if (pData == NULL) { @@ -4342,6 +4360,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, case CKM_RSA_PKCS: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_RSA_PKCS_DEC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) { + WP11_Session_AbortOp(session); + return CKR_ENCRYPTED_DATA_LEN_RANGE; + } decDataLen = WP11_Rsa_KeyLen(obj); if (pData == NULL) { @@ -4365,6 +4387,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, WP11_INIT_RSA_PKCS_OAEP_DEC)) { return CKR_OPERATION_NOT_INITIALIZED; } + if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) { + WP11_Session_AbortOp(session); + return CKR_ENCRYPTED_DATA_LEN_RANGE; + } decDataLen = WP11_Rsa_KeyLen(obj); if (pData == NULL) { @@ -5230,6 +5256,10 @@ CK_RV C_Digest(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, return CKR_ARGUMENTS_BAD; if (!WP11_Session_IsOpInitialized(session, WP11_INIT_DIGEST)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_IS_WORD32(ulDataLen)) { + WP11_Session_AbortOp(session); + return CKR_DATA_LEN_RANGE; + } hashLen = (word32)*pulDigestLen; ret = WP11_Digest_Single(pData, (word32)ulDataLen, pDigest, &hashLen, @@ -5283,6 +5313,10 @@ CK_RV C_DigestUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, return CKR_ARGUMENTS_BAD; if (!WP11_Session_IsOpInitialized(session, WP11_INIT_DIGEST)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_IS_WORD32(ulPartLen)) { + WP11_Session_AbortOp(session); + return CKR_DATA_LEN_RANGE; + } ret = WP11_Digest_Update(pPart, (word32)ulPartLen, session); @@ -6009,6 +6043,12 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, WP11_Session_GetObject(session, &obj); if (obj == NULL) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulDataLen)) { + if (!WP11_Session_IsOpCategoryActive(session, WP11_OP_SIGN)) + return CKR_OPERATION_NOT_INITIALIZED; + WP11_Session_AbortOp(session); + return CKR_DATA_LEN_RANGE; + } mechanism = WP11_Session_GetMechanism(session); switch (mechanism) { @@ -6396,6 +6436,12 @@ static CK_RV wp11_C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, WP11_Session_GetObject(session, &obj); if (obj == NULL) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulPartLen)) { + if (!WP11_Session_IsOpCategoryActive(session, WP11_OP_SIGN)) + return CKR_OPERATION_NOT_INITIALIZED; + WP11_Session_AbortOp(session); + return CKR_DATA_LEN_RANGE; + } mechanism = WP11_Session_GetMechanism(session); switch (mechanism) { @@ -7222,6 +7268,15 @@ static CK_RV wp11_C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, WP11_Session_GetObject(session, &obj); if (obj == NULL) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulDataLen) || + !CK_ULONG_FITS_WORD32(ulSignatureLen)) { + if (!WP11_Session_IsOpCategoryActive(session, WP11_OP_VERIFY)) + return CKR_OPERATION_NOT_INITIALIZED; + WP11_Session_AbortOp(session); + if (!CK_ULONG_FITS_WORD32(ulDataLen)) + return CKR_DATA_LEN_RANGE; + return CKR_SIGNATURE_LEN_RANGE; + } mechanism = WP11_Session_GetMechanism(session); switch (mechanism) { @@ -7575,6 +7630,12 @@ static CK_RV wp11_C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, WP11_Session_GetObject(session, &obj); if (obj == NULL) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulPartLen)) { + if (!WP11_Session_IsOpCategoryActive(session, WP11_OP_VERIFY)) + return CKR_OPERATION_NOT_INITIALIZED; + WP11_Session_AbortOp(session); + return CKR_DATA_LEN_RANGE; + } mechanism = WP11_Session_GetMechanism(session); switch (mechanism) { diff --git a/tests/digest_length_test.c b/tests/digest_length_test.c index b65a8607..d819c218 100644 --- a/tests/digest_length_test.c +++ b/tests/digest_length_test.c @@ -58,6 +58,59 @@ static const char tokenLabel[] = "digest-length"; static CK_BBOOL ckTrue = CK_TRUE; static CK_BBOOL ckFalse = CK_FALSE; +#if !defined(NO_SHA256) || !defined(NO_RSA) || \ + (!defined(NO_AES) && defined(HAVE_AESCMAC)) +/* A length above the 32-bit range whose low 32 bits equal small. */ +static CK_ULONG big_len(CK_ULONG small) +{ + return (((CK_ULONG)1 << 16) << 16) | small; +} +#endif + +#ifndef NO_RSA +static CK_OBJECT_CLASS privKeyClass = CKO_PRIVATE_KEY; +static CK_OBJECT_CLASS pubKeyClass = CKO_PUBLIC_KEY; +static CK_KEY_TYPE rsaKeyType = CKK_RSA; + +static CK_RV create_rsa_keys(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* priv, + CK_OBJECT_HANDLE* pub) +{ + CK_RV rv; + CK_ATTRIBUTE privTmpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &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 pubTmpl[] = { + { CKA_CLASS, &pubKeyClass, sizeof(pubKeyClass) }, + { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, + { CKA_PUBLIC_EXPONENT, rsa_2048_pub_exp, sizeof(rsa_2048_pub_exp) }, + }; + + rv = funcList->C_CreateObject(session, privTmpl, + sizeof(privTmpl) / sizeof(*privTmpl), priv); + if (rv == CKR_OK) { + rv = funcList->C_CreateObject(session, pubTmpl, + sizeof(pubTmpl) / sizeof(*pubTmpl), pub); + } + return rv; +} +#endif + #ifndef NO_SHA256 static CK_MECHANISM sha256Mech = { CKM_SHA256, NULL, 0 }; @@ -127,8 +180,168 @@ static void digest_requires_init_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, key); #endif } + +/* Digest input lengths that do not fit in 32 bits are rejected. */ +static void digest_input_len_range_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + byte data[16]; + byte hash[32]; + CK_ULONG hashLen = sizeof(hash); + + XMEMSET(data, 0x62, sizeof(data)); + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA256)", CKR_OK); + rv = funcList->C_Digest(session, data, big_len(sizeof(data)), hash, + &hashLen); + CHECK_RV(rv, "C_Digest rejects a length above 32 bits", + CKR_DATA_LEN_RANGE); + hashLen = sizeof(hash); + rv = funcList->C_Digest(session, data, sizeof(data), hash, &hashLen); + CHECK_RV(rv, "C_Digest length error ends the operation", + CKR_OPERATION_NOT_INITIALIZED); + + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA256) for update", CKR_OK); + rv = funcList->C_DigestUpdate(session, data, big_len(sizeof(data))); + CHECK_RV(rv, "C_DigestUpdate rejects a length above 32 bits", + CKR_DATA_LEN_RANGE); + hashLen = sizeof(hash); + rv = funcList->C_DigestFinal(session, hash, &hashLen); + CHECK_RV(rv, "C_DigestUpdate length error ends the operation", + CKR_OPERATION_NOT_INITIALIZED); +} + +#if !defined(NO_HMAC) +/* MAC input and signature lengths that do not fit in 32 bits are rejected. */ +static void hmac_input_len_range_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_SHA256_HMAC, NULL, 0 }; + byte data[16]; + byte mac[32]; + CK_ULONG macLen = sizeof(mac); + + XMEMSET(data, 0x63, sizeof(data)); + rv = create_hmac_key(session, &key); + CHECK_RV(rv, "create HMAC key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(SHA256 HMAC)", CKR_OK); + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "C_Sign(SHA256 HMAC)", CKR_OK); + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(SHA256 HMAC) for long data", CKR_OK); + macLen = sizeof(mac); + rv = funcList->C_Sign(session, data, big_len(sizeof(data)), mac, &macLen); + CHECK_RV(rv, "C_Sign rejects a length above 32 bits", CKR_DATA_LEN_RANGE); + macLen = sizeof(mac); + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "C_Sign length error ends the operation", + CKR_OPERATION_NOT_INITIALIZED); + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(SHA256 HMAC) for update", CKR_OK); + rv = funcList->C_SignUpdate(session, data, big_len(sizeof(data))); + CHECK_RV(rv, "C_SignUpdate rejects a length above 32 bits", + CKR_DATA_LEN_RANGE); + macLen = sizeof(mac); + rv = funcList->C_SignFinal(session, mac, &macLen); + CHECK_RV(rv, "C_SignUpdate length error ends the operation", + CKR_OPERATION_NOT_INITIALIZED); + + macLen = sizeof(mac); + rv = funcList->C_SignInit(session, &mech, key); + if (rv == CKR_OK) + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "C_Sign(SHA256 HMAC) reference MAC", CKR_OK); + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(SHA256 HMAC)", CKR_OK); + rv = funcList->C_Verify(session, data, big_len(sizeof(data)), mac, macLen); + CHECK_RV(rv, "C_Verify rejects a data length above 32 bits", + CKR_DATA_LEN_RANGE); + rv = funcList->C_Verify(session, data, sizeof(data), mac, macLen); + CHECK_RV(rv, "C_Verify length error ends the operation", + CKR_OPERATION_NOT_INITIALIZED); + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(SHA256 HMAC) for long signature", CKR_OK); + rv = funcList->C_Verify(session, data, sizeof(data), mac, big_len(macLen)); + CHECK_RV(rv, "C_Verify rejects a signature length above 32 bits", + CKR_SIGNATURE_LEN_RANGE); + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(SHA256 HMAC) for update", CKR_OK); + rv = funcList->C_VerifyUpdate(session, data, big_len(sizeof(data))); + CHECK_RV(rv, "C_VerifyUpdate rejects a length above 32 bits", + CKR_DATA_LEN_RANGE); + rv = funcList->C_VerifyFinal(session, mac, macLen); + CHECK_RV(rv, "C_VerifyUpdate length error ends the operation", + CKR_OPERATION_NOT_INITIALIZED); + + funcList->C_DestroyObject(session, key); +} +#endif #endif /* !NO_SHA256 */ +#ifndef NO_RSA +/* RSA encrypt and decrypt input lengths that do not fit in 32 bits are + * rejected. */ +static void rsa_input_len_range_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_RSA_PKCS, NULL, 0 }; + byte data[16]; + byte enc[2048 / 8]; + byte dec[2048 / 8]; + CK_ULONG encLen = sizeof(enc); + CK_ULONG decLen = sizeof(dec); + + XMEMSET(data, 0x64, sizeof(data)); + rv = create_rsa_keys(session, &priv, &pub); + CHECK_RV(rv, "create RSA key pair", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_EncryptInit(session, &mech, pub); + CHECK_RV(rv, "C_EncryptInit(RSA PKCS)", CKR_OK); + rv = funcList->C_Encrypt(session, data, big_len(sizeof(data)), enc, + &encLen); + CHECK_RV(rv, "C_Encrypt(RSA PKCS) rejects a length above 32 bits", + CKR_DATA_LEN_RANGE); + encLen = sizeof(enc); + rv = funcList->C_Encrypt(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_Encrypt(RSA PKCS) length error ends the operation", + CKR_OPERATION_NOT_INITIALIZED); + + encLen = sizeof(enc); + rv = funcList->C_EncryptInit(session, &mech, pub); + if (rv == CKR_OK) + rv = funcList->C_Encrypt(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_Encrypt(RSA PKCS)", CKR_OK); + + rv = funcList->C_DecryptInit(session, &mech, priv); + CHECK_RV(rv, "C_DecryptInit(RSA PKCS)", CKR_OK); + rv = funcList->C_Decrypt(session, enc, big_len(encLen), dec, &decLen); + CHECK_RV(rv, "C_Decrypt(RSA PKCS) rejects a length above 32 bits", + CKR_ENCRYPTED_DATA_LEN_RANGE); + decLen = sizeof(dec); + rv = funcList->C_Decrypt(session, enc, encLen, dec, &decLen); + CHECK_RV(rv, "C_Decrypt(RSA PKCS) length error ends the operation", + CKR_OPERATION_NOT_INITIALIZED); + + funcList->C_DestroyObject(session, priv); + funcList->C_DestroyObject(session, pub); +} +#endif + static CK_RV token_init(CK_SLOT_ID* slot) { CK_RV rv; @@ -199,6 +412,17 @@ static int run_test(void) #ifndef NO_SHA256 digest_requires_init_test(session); #endif + if (sizeof(CK_ULONG) > sizeof(word32)) { +#ifndef NO_SHA256 + digest_input_len_range_test(session); +#if !defined(NO_HMAC) + hmac_input_len_range_test(session); +#endif +#endif +#ifndef NO_RSA + rsa_input_len_range_test(session); +#endif + } } if (session != 0) { From 3ee80def7fe1d4e5ed75db8e9e6b325f759d5806 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 16:32:15 -0700 Subject: [PATCH 194/204] F-2787 - Reject a C_VerifyFinal signature length above 32 bits --- src/crypto.c | 6 +++ tests/digest_length_test.c | 88 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index 430530c0..40c0baab 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -7788,6 +7788,12 @@ static CK_RV wp11_C_VerifyFinal(CK_SESSION_HANDLE hSession, WP11_Session_GetObject(session, &obj); if (obj == NULL) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulSignatureLen)) { + if (!WP11_Session_IsOpCategoryActive(session, WP11_OP_VERIFY)) + return CKR_OPERATION_NOT_INITIALIZED; + WP11_Session_AbortOp(session); + return CKR_SIGNATURE_LEN_RANGE; + } mechanism = WP11_Session_GetMechanism(session); switch (mechanism) { diff --git a/tests/digest_length_test.c b/tests/digest_length_test.c index d819c218..4f138f71 100644 --- a/tests/digest_length_test.c +++ b/tests/digest_length_test.c @@ -286,6 +286,41 @@ static void hmac_input_len_range_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, key); } + +/* A multi-part MAC verify takes the signature length in full. */ +static void verify_final_sig_len_range_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_SHA256_HMAC, NULL, 0 }; + byte data[16]; + byte mac[32]; + CK_ULONG macLen = sizeof(mac); + + XMEMSET(data, 0x65, sizeof(data)); + rv = create_hmac_key(session, &key); + CHECK_RV(rv, "create HMAC key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, key); + if (rv == CKR_OK) + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "C_Sign(SHA256 HMAC) reference MAC", CKR_OK); + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(SHA256 HMAC)", CKR_OK); + rv = funcList->C_VerifyUpdate(session, data, sizeof(data)); + CHECK_RV(rv, "C_VerifyUpdate(SHA256 HMAC)", CKR_OK); + rv = funcList->C_VerifyFinal(session, mac, big_len(macLen)); + CHECK_RV(rv, "C_VerifyFinal(HMAC) rejects a length above 32 bits", + CKR_SIGNATURE_LEN_RANGE); + rv = funcList->C_VerifyFinal(session, mac, macLen); + CHECK_RV(rv, "C_VerifyFinal(HMAC) length error ends the operation", + CKR_OPERATION_NOT_INITIALIZED); + + funcList->C_DestroyObject(session, key); +} #endif #endif /* !NO_SHA256 */ @@ -342,6 +377,55 @@ static void rsa_input_len_range_test(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AESCMAC) +static CK_OBJECT_CLASS aesKeyClass = CKO_SECRET_KEY; +static CK_KEY_TYPE aesKeyType = CKK_AES; + +/* A multi-part CMAC verify takes the signature length in full. */ +static void cmac_verify_final_sig_len_range_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_AES_CMAC, NULL, 0 }; + byte data[16]; + byte mac[16]; + CK_ULONG macLen = sizeof(mac); + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &aesKeyClass, sizeof(aesKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, + }; + + XMEMSET(data, 0x66, sizeof(data)); + rv = funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), &key); + CHECK_RV(rv, "create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, key); + if (rv == CKR_OK) + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "C_Sign(AES CMAC) reference MAC", CKR_OK); + + rv = funcList->C_VerifyInit(session, &mech, key); + CHECK_RV(rv, "C_VerifyInit(AES CMAC)", CKR_OK); + rv = funcList->C_VerifyUpdate(session, data, sizeof(data)); + CHECK_RV(rv, "C_VerifyUpdate(AES CMAC)", CKR_OK); + rv = funcList->C_VerifyFinal(session, mac, big_len(macLen)); + CHECK_RV(rv, "C_VerifyFinal(CMAC) rejects a length above 32 bits", + CKR_SIGNATURE_LEN_RANGE); + rv = funcList->C_VerifyFinal(session, mac, macLen); + CHECK_RV(rv, "C_VerifyFinal(CMAC) length error ends the operation", + CKR_OPERATION_NOT_INITIALIZED); + + funcList->C_DestroyObject(session, key); +} +#endif + static CK_RV token_init(CK_SLOT_ID* slot) { CK_RV rv; @@ -417,10 +501,14 @@ static int run_test(void) digest_input_len_range_test(session); #if !defined(NO_HMAC) hmac_input_len_range_test(session); + verify_final_sig_len_range_test(session); #endif #endif #ifndef NO_RSA rsa_input_len_range_test(session); +#endif +#if !defined(NO_AES) && defined(HAVE_AESCMAC) + cmac_verify_final_sig_len_range_test(session); #endif } } From deec67c4195ad9170b2197f6339d5ee0db306f01 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 16:53:29 -0700 Subject: [PATCH 195/204] F-5155 - Compare sign output buffer lengths without narrowing --- src/crypto.c | 26 ++++---- tests/digest_length_test.c | 132 ++++++++++++++++++++++++++++++++++--- 2 files changed, 137 insertions(+), 21 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 40c0baab..b88e2c06 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -68,6 +68,10 @@ ((v) <= (CK_ULONG)0xFFFFFFFF - CK_ULONG_MAX_OVERHEAD) /* For lengths passed straight to a word32 parameter with no overhead added. */ #define CK_ULONG_IS_WORD32(v) ((CK_ULONG)(word32)(v) == (v)) +/* A caller buffer length as a word32 capacity that saturates, not wraps. */ +#define CK_ULONG_CAP_WORD32(v) \ + (CK_ULONG_FITS_WORD32(v) ? (word32)(v) : \ + (word32)(0xFFFFFFFF - CK_ULONG_MAX_OVERHEAD)) /* RFC 5869 limits HKDF-Expand output to 255 * HashLen octets. Cap the * requested output length to the largest such value across supported digests @@ -6065,7 +6069,7 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = sigLen; return CKR_OK; } - if (sigLen > (word32)*pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) return CKR_BUFFER_TOO_SMALL; ret = WP11_Rsa_Sign(pData, (int)ulDataLen, pSignature, &sigLen, obj, @@ -6104,7 +6108,7 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = sigLen; return CKR_OK; } - if (sigLen > (word32)*pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) return CKR_BUFFER_TOO_SMALL; if (hash_type != WC_HASH_TYPE_NONE) { @@ -6173,7 +6177,7 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = sigLen; return CKR_OK; } - if (sigLen > (word32)*pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) return CKR_BUFFER_TOO_SMALL; if (hash_type != WC_HASH_TYPE_NONE) { @@ -6225,7 +6229,7 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = sigLen; return CKR_OK; } - if (sigLen > (word32)*pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) return CKR_BUFFER_TOO_SMALL; if (hash_type != WC_HASH_TYPE_NONE) { @@ -6255,7 +6259,7 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = sigLen; return CKR_OK; } - if (sigLen > (word32)*pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) return CKR_BUFFER_TOO_SMALL; ret = WP11_Mldsa_Sign(pData, (int)ulDataLen, pSignature, @@ -6307,7 +6311,7 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = sigLen; return CKR_OK; } - if (sigLen > (word32)*pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) return CKR_BUFFER_TOO_SMALL; ret = WP11_Hmac_Sign(pData, (int)ulDataLen, pSignature, &sigLen, @@ -6326,7 +6330,7 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, if (ret != CKR_OK || pSignature == NULL) return ret; - sigLen = (word32)*pulSignatureLen; + sigLen = CK_ULONG_CAP_WORD32(*pulSignatureLen); ret = WP11_Aes_Cmac_Sign(pData, (word32)ulDataLen, pSignature, &sigLen, session); *pulSignatureLen = sigLen; @@ -6355,10 +6359,10 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = (CK_ULONG)WP11_TLS_MAC_get_len(session); return CKR_OK; } - if (WP11_TLS_MAC_get_len(session) > (word32)*pulSignatureLen) + if ((CK_ULONG)WP11_TLS_MAC_get_len(session) > *pulSignatureLen) return CKR_BUFFER_TOO_SMALL; - sigLen = (word32)*pulSignatureLen; + sigLen = CK_ULONG_CAP_WORD32(*pulSignatureLen); ret = WP11_TLS_MAC_sign(pData, (word32)ulDataLen, pSignature, &sigLen, session); *pulSignatureLen = sigLen; @@ -6636,7 +6640,7 @@ static CK_RV wp11_C_SignFinal(CK_SESSION_HANDLE hSession, *pulSignatureLen = sigLen; return CKR_OK; } - if (sigLen > (word32)*pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) return CKR_BUFFER_TOO_SMALL; ret = WP11_Hmac_SignFinal(pSignature, &sigLen, session); @@ -6654,7 +6658,7 @@ static CK_RV wp11_C_SignFinal(CK_SESSION_HANDLE hSession, if (ret != CKR_OK || pSignature == NULL) return ret; - sigLen = (word32)*pulSignatureLen; + sigLen = CK_ULONG_CAP_WORD32(*pulSignatureLen); ret = WP11_Aes_Cmac_Sign_Final(pSignature, &sigLen, session); *pulSignatureLen = sigLen; break; diff --git a/tests/digest_length_test.c b/tests/digest_length_test.c index 4f138f71..7d5da9fc 100644 --- a/tests/digest_length_test.c +++ b/tests/digest_length_test.c @@ -321,6 +321,43 @@ static void verify_final_sig_len_range_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, key); } + +/* Signing accepts an output buffer length above 32 bits. */ +static void sign_output_capacity_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_SHA256_HMAC, NULL, 0 }; + byte data[16]; + byte mac[32]; + CK_ULONG macLen; + + XMEMSET(data, 0x68, sizeof(data)); + rv = create_hmac_key(session, &key); + CHECK_RV(rv, "create HMAC key", CKR_OK); + if (rv != CKR_OK) + return; + + macLen = big_len(0); + rv = funcList->C_SignInit(session, &mech, key); + if (rv == CKR_OK) + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "C_Sign(HMAC) with a buffer length above 32 bits", CKR_OK); + CHECK_TRUE(macLen == sizeof(mac), "C_Sign(HMAC) reports the MAC length"); + + macLen = big_len(0); + rv = funcList->C_SignInit(session, &mech, key); + if (rv == CKR_OK) + rv = funcList->C_SignUpdate(session, data, sizeof(data)); + if (rv == CKR_OK) + rv = funcList->C_SignFinal(session, mac, &macLen); + CHECK_RV(rv, "C_SignFinal(HMAC) with a buffer length above 32 bits", + CKR_OK); + CHECK_TRUE(macLen == sizeof(mac), + "C_SignFinal(HMAC) reports the MAC length"); + + funcList->C_DestroyObject(session, key); +} #endif #endif /* !NO_SHA256 */ @@ -377,19 +414,41 @@ static void rsa_input_len_range_test(CK_SESSION_HANDLE session) } #endif +#ifndef NO_RSA +/* An RSA sign accepts an output buffer length above 32 bits. */ +static void rsa_sign_output_capacity_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_RSA_PKCS, NULL, 0 }; + byte data[16]; + byte sig[2048 / 8]; + CK_ULONG sigLen = big_len(0); + + XMEMSET(data, 0x69, sizeof(data)); + rv = create_rsa_keys(session, &priv, &pub); + CHECK_RV(rv, "create RSA key pair", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, priv); + if (rv == CKR_OK) + rv = funcList->C_Sign(session, data, sizeof(data), sig, &sigLen); + CHECK_RV(rv, "C_Sign(RSA) with a buffer length above 32 bits", CKR_OK); + CHECK_TRUE(sigLen == sizeof(sig), "C_Sign(RSA) reports the length"); + + funcList->C_DestroyObject(session, priv); + funcList->C_DestroyObject(session, pub); +} +#endif + #if !defined(NO_AES) && defined(HAVE_AESCMAC) static CK_OBJECT_CLASS aesKeyClass = CKO_SECRET_KEY; static CK_KEY_TYPE aesKeyType = CKK_AES; -/* A multi-part CMAC verify takes the signature length in full. */ -static void cmac_verify_final_sig_len_range_test(CK_SESSION_HANDLE session) +static CK_RV create_cmac_key(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* key) { - CK_RV rv; - CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; - CK_MECHANISM mech = { CKM_AES_CMAC, NULL, 0 }; - byte data[16]; - byte mac[16]; - CK_ULONG macLen = sizeof(mac); CK_ATTRIBUTE keyTmpl[] = { { CKA_CLASS, &aesKeyClass, sizeof(aesKeyClass) }, { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, @@ -399,9 +458,22 @@ static void cmac_verify_final_sig_len_range_test(CK_SESSION_HANDLE session) { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, }; + return funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), key); +} + +/* A multi-part CMAC verify takes the signature length in full. */ +static void cmac_verify_final_sig_len_range_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_AES_CMAC, NULL, 0 }; + byte data[16]; + byte mac[16]; + CK_ULONG macLen = sizeof(mac); + XMEMSET(data, 0x66, sizeof(data)); - rv = funcList->C_CreateObject(session, keyTmpl, - sizeof(keyTmpl) / sizeof(*keyTmpl), &key); + rv = create_cmac_key(session, &key); CHECK_RV(rv, "create AES key", CKR_OK); if (rv != CKR_OK) return; @@ -424,6 +496,43 @@ static void cmac_verify_final_sig_len_range_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, key); } + +/* A CMAC sign accepts an output buffer length above 32 bits. */ +static void cmac_sign_output_capacity_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_AES_CMAC, NULL, 0 }; + byte data[16]; + byte mac[16]; + CK_ULONG macLen; + + XMEMSET(data, 0x67, sizeof(data)); + rv = create_cmac_key(session, &key); + CHECK_RV(rv, "create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + macLen = big_len(0); + rv = funcList->C_SignInit(session, &mech, key); + if (rv == CKR_OK) + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "C_Sign(CMAC) with a buffer length above 32 bits", CKR_OK); + CHECK_TRUE(macLen == sizeof(mac), "C_Sign(CMAC) reports the MAC length"); + + macLen = big_len(0); + rv = funcList->C_SignInit(session, &mech, key); + if (rv == CKR_OK) + rv = funcList->C_SignUpdate(session, data, sizeof(data)); + if (rv == CKR_OK) + rv = funcList->C_SignFinal(session, mac, &macLen); + CHECK_RV(rv, "C_SignFinal(CMAC) with a buffer length above 32 bits", + CKR_OK); + CHECK_TRUE(macLen == sizeof(mac), + "C_SignFinal(CMAC) reports the MAC length"); + + funcList->C_DestroyObject(session, key); +} #endif static CK_RV token_init(CK_SLOT_ID* slot) @@ -502,13 +611,16 @@ static int run_test(void) #if !defined(NO_HMAC) hmac_input_len_range_test(session); verify_final_sig_len_range_test(session); + sign_output_capacity_test(session); #endif #endif #ifndef NO_RSA rsa_input_len_range_test(session); + rsa_sign_output_capacity_test(session); #endif #if !defined(NO_AES) && defined(HAVE_AESCMAC) cmac_verify_final_sig_len_range_test(session); + cmac_sign_output_capacity_test(session); #endif } } From 2179b85d034fa1e1873359f57ac01b2650bd30f5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 6 Oct 2026 17:09:58 -0700 Subject: [PATCH 196/204] F-3639 - Report the required length when an output buffer is too small --- src/crypto.c | 185 +++++++++++++++++------ src/internal.c | 4 +- tests/digest_length_test.c | 291 +++++++++++++++++++++++++++++++++++++ 3 files changed, 434 insertions(+), 46 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index b88e2c06..ea8dea33 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -3200,8 +3200,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) + if (encDataLen > (word32)*pulEncryptedDataLen) { + *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_Rsa_PublicEncrypt(pData, (int)ulDataLen, pEncryptedData, &encDataLen, obj, @@ -3223,8 +3225,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) + if (encDataLen > (word32)*pulEncryptedDataLen) { + *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_RsaPkcs15_PublicEncrypt(pData, (int)ulDataLen, pEncryptedData, &encDataLen, obj, @@ -3249,8 +3253,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) + if (encDataLen > (word32)*pulEncryptedDataLen) { + *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_RsaOaep_PublicEncrypt(pData, (int)ulDataLen, pEncryptedData, &encDataLen, obj, @@ -3281,8 +3287,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) + if (encDataLen > (word32)*pulEncryptedDataLen) { + *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesCbc_Encrypt(pData, (int)ulDataLen, pEncryptedData, &encDataLen, session); @@ -3312,8 +3320,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) + if (encDataLen > (word32)*pulEncryptedDataLen) { + *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesCbcPad_Encrypt(pData, (int)ulDataLen, pEncryptedData, &encDataLen, session); @@ -3335,8 +3345,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = ulDataLen; return CKR_OK; } - if (ulDataLen > *pulEncryptedDataLen) + if (ulDataLen > *pulEncryptedDataLen) { + *pulEncryptedDataLen = ulDataLen; return CKR_BUFFER_TOO_SMALL; + } encDataLen = (word32)*pulEncryptedDataLen; ret = WP11_AesCtr_Do(pData, (word32)ulDataLen, pEncryptedData, @@ -3363,8 +3375,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) + if (encDataLen > (word32)*pulEncryptedDataLen) { + *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesGcm_Encrypt(pData, (int)ulDataLen, pEncryptedData, &encDataLen, obj, session); @@ -3388,8 +3402,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) + if (encDataLen > (word32)*pulEncryptedDataLen) { + *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesCcm_Encrypt(pData, (int)ulDataLen, pEncryptedData, &encDataLen, obj, session); @@ -3416,8 +3432,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) + if (encDataLen > (word32)*pulEncryptedDataLen) { + *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesEcb_Encrypt(pData, (int)ulDataLen, pEncryptedData, &encDataLen, obj, session); @@ -3440,8 +3458,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = ulDataLen; return CKR_OK; } - if (ulDataLen > *pulEncryptedDataLen) + if (ulDataLen > *pulEncryptedDataLen) { + *pulEncryptedDataLen = ulDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesCts_Encrypt(pData, (int)ulDataLen, pEncryptedData, &encDataLen, session); @@ -3465,8 +3485,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) + if (encDataLen > (word32)*pulEncryptedDataLen) { + *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesKeyWrap_Encrypt(pData, (word32)ulDataLen, pEncryptedData, &encDataLen, session); @@ -3493,8 +3515,10 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) + if (encDataLen > (word32)*pulEncryptedDataLen) { + *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesKeyWrapPad_Encrypt(pData, (word32)ulDataLen, pEncryptedData, &encDataLen, session); @@ -3618,6 +3642,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, return rv; } if (encPartLen > (word32)*pulEncryptedPartLen) { + *pulEncryptedPartLen = encPartLen; rv = CKR_BUFFER_TOO_SMALL; WOLFPKCS11_LEAVE("C_EncryptUpdate", rv); return rv; @@ -3650,8 +3675,10 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, *pulEncryptedPartLen = encPartLen; return CKR_OK; } - if (encPartLen > (word32)*pulEncryptedPartLen) + if (encPartLen > (word32)*pulEncryptedPartLen) { + *pulEncryptedPartLen = encPartLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesCbcPad_EncryptUpdate(pPart, (int)ulPartLen, pEncryptedPart, &encPartLen, session); @@ -3675,8 +3702,10 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, *pulEncryptedPartLen = ulPartLen; return CKR_OK; } - if (ulPartLen > *pulEncryptedPartLen) + if (ulPartLen > *pulEncryptedPartLen) { + *pulEncryptedPartLen = ulPartLen; return CKR_BUFFER_TOO_SMALL; + } encPartLen = (word32)*pulEncryptedPartLen; ret = WP11_AesCtr_Update(pPart, (int)ulPartLen, pEncryptedPart, @@ -3708,8 +3737,10 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, *pulEncryptedPartLen = encPartLen; return CKR_OK; } - if (encPartLen > (word32)*pulEncryptedPartLen) + if (encPartLen > (word32)*pulEncryptedPartLen) { + *pulEncryptedPartLen = encPartLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesGcm_EncryptUpdate(pPart, (int)ulPartLen, pEncryptedPart, &encPartLen, obj, @@ -3738,8 +3769,10 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, encPartLen = (word32)*pulEncryptedPartLen; ret = WP11_AesCts_EncryptUpdate(pPart, (word32)ulPartLen, pEncryptedPart, &encPartLen, session); - if (ret == BUFFER_E) + if (ret == BUFFER_E) { + *pulEncryptedPartLen = ulPartLen + AES_BLOCK_SIZE * 2; return CKR_BUFFER_TOO_SMALL; + } if (ret < 0) { WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; @@ -3864,8 +3897,10 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, *pulLastEncryptedPartLen = encPartLen; return CKR_OK; } - if ((CK_ULONG)encPartLen > *pulLastEncryptedPartLen) + if ((CK_ULONG)encPartLen > *pulLastEncryptedPartLen) { + *pulLastEncryptedPartLen = encPartLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesCbcPad_EncryptFinal(pLastEncryptedPart, &encPartLen, session); @@ -3904,8 +3939,10 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, *pulLastEncryptedPartLen = encPartLen; return CKR_OK; } - if ((CK_ULONG)encPartLen > *pulLastEncryptedPartLen) + if ((CK_ULONG)encPartLen > *pulLastEncryptedPartLen) { + *pulLastEncryptedPartLen = encPartLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesGcm_EncryptFinal(pLastEncryptedPart, &encPartLen, obj, session); @@ -3934,8 +3971,10 @@ static CK_RV wp11_C_EncryptFinal(CK_SESSION_HANDLE hSession, (word32)(AES_BLOCK_SIZE * 2); ret = WP11_AesCts_EncryptFinal(pLastEncryptedPart, &encPartLen, session); - if (ret == BUFFER_E) + if (ret == BUFFER_E) { + *pulLastEncryptedPartLen = AES_BLOCK_SIZE * 2; return CKR_BUFFER_TOO_SMALL; + } if (ret < 0) { WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; @@ -4350,8 +4389,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) + if (decDataLen > (word32)*pulDataLen) { + *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_Rsa_PrivateDecrypt(pEncryptedData, (int)ulEncryptedDataLen, pData, @@ -4374,8 +4415,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) + if (decDataLen > (word32)*pulDataLen) { + *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_RsaPkcs15_PrivateDecrypt(pEncryptedData, (int)ulEncryptedDataLen, pData, @@ -4401,8 +4444,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) + if (decDataLen > (word32)*pulDataLen) { + *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_RsaOaep_PrivateDecrypt(pEncryptedData, (int)ulEncryptedDataLen, pData, @@ -4433,8 +4478,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) + if (decDataLen > (word32)*pulDataLen) { + *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesCbc_Decrypt(pEncryptedData, (int)ulEncryptedDataLen, pData, &decDataLen, session); @@ -4498,8 +4545,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = ulEncryptedDataLen; return CKR_OK; } - if (ulEncryptedDataLen > *pulDataLen) + if (ulEncryptedDataLen > *pulDataLen) { + *pulDataLen = ulEncryptedDataLen; return CKR_BUFFER_TOO_SMALL; + } decDataLen = (word32)*pulDataLen; ret = WP11_AesCtr_Do(pEncryptedData, @@ -4526,8 +4575,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) + if (decDataLen > (word32)*pulDataLen) { + *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesGcm_Decrypt(pEncryptedData, (int)ulEncryptedDataLen, pData, &decDataLen, obj, session); @@ -4551,8 +4602,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) + if (decDataLen > (word32)*pulDataLen) { + *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesCcm_Decrypt(pEncryptedData, (int)ulEncryptedDataLen, pData, &decDataLen, obj, session); @@ -4579,8 +4632,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) + if (decDataLen > (word32)*pulDataLen) { + *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesEcb_Decrypt(pEncryptedData, (int)ulEncryptedDataLen, pData, &decDataLen, obj, session); @@ -4603,13 +4658,17 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = ulEncryptedDataLen; return CKR_OK; } - if (ulEncryptedDataLen > *pulDataLen) + if (ulEncryptedDataLen > *pulDataLen) { + *pulDataLen = ulEncryptedDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesCts_Decrypt(pEncryptedData, (int)ulEncryptedDataLen, pData, &decDataLen, session); - if (ret == BUFFER_E) + if (ret == BUFFER_E) { + *pulDataLen = ulEncryptedDataLen; return CKR_BUFFER_TOO_SMALL; + } if (ret < 0) break; *pulDataLen = decDataLen; @@ -4632,8 +4691,10 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) + if (decDataLen > (word32)*pulDataLen) { + *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesKeyWrap_Decrypt(pEncryptedData, (word32)ulEncryptedDataLen, pData, &decDataLen, session); @@ -4782,8 +4843,10 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, *pulPartLen = decPartLen; return CKR_OK; } - if (decPartLen > (word32)*pulPartLen) + if (decPartLen > (word32)*pulPartLen) { + *pulPartLen = decPartLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesCbc_DecryptUpdate(pEncryptedPart, (int)ulEncryptedPartLen, pPart, @@ -4815,8 +4878,10 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, *pulPartLen = decPartLen; return CKR_OK; } - if (decPartLen > (word32)*pulPartLen) + if (decPartLen > (word32)*pulPartLen) { + *pulPartLen = decPartLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesCbcPad_DecryptUpdate(pEncryptedPart, (int)ulEncryptedPartLen, pPart, @@ -4846,8 +4911,10 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, *pulPartLen = ulEncryptedPartLen; return CKR_OK; } - if (ulEncryptedPartLen > *pulPartLen) + if (ulEncryptedPartLen > *pulPartLen) { + *pulPartLen = ulEncryptedPartLen; return CKR_BUFFER_TOO_SMALL; + } /* Output is the same length as the input, which fits the buffer. */ decPartLen = (word32)ulEncryptedPartLen; @@ -4891,6 +4958,10 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, case CKM_AES_CTS: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTS_DEC)) return CKR_OPERATION_NOT_INITIALIZED; + if (!CK_ULONG_FITS_WORD32(ulEncryptedPartLen)) { + WP11_AesCts_Final(session); + return CKR_ENCRYPTED_DATA_LEN_RANGE; + } if (pPart == NULL) { *pulPartLen = ulEncryptedPartLen + AES_BLOCK_SIZE * 2; @@ -4900,8 +4971,10 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, decPartLen = (word32)*pulPartLen; ret = WP11_AesCts_DecryptUpdate(pEncryptedPart, (word32)ulEncryptedPartLen, pPart, &decPartLen, session); - if (ret == BUFFER_E) + if (ret == BUFFER_E) { + *pulPartLen = ulEncryptedPartLen + AES_BLOCK_SIZE * 2; return CKR_BUFFER_TOO_SMALL; + } if (ret < 0) { WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; @@ -5083,8 +5156,10 @@ static CK_RV wp11_C_DecryptFinal(CK_SESSION_HANDLE hSession, *pulLastPartLen = decPartLen; return CKR_OK; } - if ((CK_ULONG)decPartLen > *pulLastPartLen) + if ((CK_ULONG)decPartLen > *pulLastPartLen) { + *pulLastPartLen = decPartLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_AesGcm_DecryptFinal(pLastPart, &decPartLen, obj, session); @@ -5107,8 +5182,10 @@ static CK_RV wp11_C_DecryptFinal(CK_SESSION_HANDLE hSession, decPartLen = (word32)*pulLastPartLen; ret = WP11_AesCts_DecryptFinal(pLastPart, &decPartLen, session); - if (ret == BUFFER_E) + if (ret == BUFFER_E) { + *pulLastPartLen = AES_BLOCK_SIZE * 2; return CKR_BUFFER_TOO_SMALL; + } if (ret < 0) { WP11_Session_AbortOp(session); return CKR_FUNCTION_FAILED; @@ -6069,8 +6146,10 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = sigLen; return CKR_OK; } - if ((CK_ULONG)sigLen > *pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) { + *pulSignatureLen = sigLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_Rsa_Sign(pData, (int)ulDataLen, pSignature, &sigLen, obj, WP11_Session_GetSlot(session)); @@ -6108,8 +6187,10 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = sigLen; return CKR_OK; } - if ((CK_ULONG)sigLen > *pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) { + *pulSignatureLen = sigLen; return CKR_BUFFER_TOO_SMALL; + } if (hash_type != WC_HASH_TYPE_NONE) { if (wc_Hash(hash_type, pData, (word32)ulDataLen, @@ -6177,8 +6258,10 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = sigLen; return CKR_OK; } - if ((CK_ULONG)sigLen > *pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) { + *pulSignatureLen = sigLen; return CKR_BUFFER_TOO_SMALL; + } if (hash_type != WC_HASH_TYPE_NONE) { if (wc_Hash(hash_type, pData, (word32)ulDataLen, @@ -6229,8 +6312,10 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = sigLen; return CKR_OK; } - if ((CK_ULONG)sigLen > *pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) { + *pulSignatureLen = sigLen; return CKR_BUFFER_TOO_SMALL; + } if (hash_type != WC_HASH_TYPE_NONE) { if (wc_Hash(hash_type, pData, (word32)ulDataLen, digest, @@ -6259,8 +6344,10 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = sigLen; return CKR_OK; } - if ((CK_ULONG)sigLen > *pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) { + *pulSignatureLen = sigLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_Mldsa_Sign(pData, (int)ulDataLen, pSignature, &sigLen, obj, session); @@ -6311,8 +6398,10 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = sigLen; return CKR_OK; } - if ((CK_ULONG)sigLen > *pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) { + *pulSignatureLen = sigLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_Hmac_Sign(pData, (int)ulDataLen, pSignature, &sigLen, session); @@ -6359,8 +6448,10 @@ static CK_RV wp11_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulSignatureLen = (CK_ULONG)WP11_TLS_MAC_get_len(session); return CKR_OK; } - if ((CK_ULONG)WP11_TLS_MAC_get_len(session) > *pulSignatureLen) + if ((CK_ULONG)WP11_TLS_MAC_get_len(session) > *pulSignatureLen) { + *pulSignatureLen = WP11_TLS_MAC_get_len(session); return CKR_BUFFER_TOO_SMALL; + } sigLen = CK_ULONG_CAP_WORD32(*pulSignatureLen); ret = WP11_TLS_MAC_sign(pData, (word32)ulDataLen, pSignature, @@ -6640,8 +6731,10 @@ static CK_RV wp11_C_SignFinal(CK_SESSION_HANDLE hSession, *pulSignatureLen = sigLen; return CKR_OK; } - if ((CK_ULONG)sigLen > *pulSignatureLen) + if ((CK_ULONG)sigLen > *pulSignatureLen) { + *pulSignatureLen = sigLen; return CKR_BUFFER_TOO_SMALL; + } ret = WP11_Hmac_SignFinal(pSignature, &sigLen, session); *pulSignatureLen = sigLen; @@ -6680,8 +6773,10 @@ static CK_RV wp11_C_SignFinal(CK_SESSION_HANDLE hSession, *pulSignatureLen = (CK_ULONG)WP11_TLS_MAC_get_len(session); return CKR_OK; } - if ((CK_ULONG)WP11_TLS_MAC_get_len(session) > *pulSignatureLen) + if ((CK_ULONG)WP11_TLS_MAC_get_len(session) > *pulSignatureLen) { + *pulSignatureLen = WP11_TLS_MAC_get_len(session); return CKR_BUFFER_TOO_SMALL; + } WP11_Session_GetData(session, &data, &dataLen); sigLen = (word32)*pulSignatureLen; diff --git a/src/internal.c b/src/internal.c index 9827adfe..224121e2 100644 --- a/src/internal.c +++ b/src/internal.c @@ -20207,8 +20207,10 @@ int WP11_Aes_Cmac_Check_Len(CK_BYTE_PTR pSignature, *pulSignatureLen = cmac->sigLen; return CKR_OK; } - if (*pulSignatureLen < cmac->sigLen) + if (*pulSignatureLen < cmac->sigLen) { + *pulSignatureLen = cmac->sigLen; return CKR_BUFFER_TOO_SMALL; + } return CKR_OK; } diff --git a/tests/digest_length_test.c b/tests/digest_length_test.c index 7d5da9fc..5c3d0696 100644 --- a/tests/digest_length_test.c +++ b/tests/digest_length_test.c @@ -322,6 +322,48 @@ static void verify_final_sig_len_range_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, key); } +/* A short signature buffer reports the length that is needed and leaves the + * operation active. */ +static void sign_buffer_too_small_len_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_SHA256_HMAC, NULL, 0 }; + byte data[16]; + byte mac[32]; + CK_ULONG macLen; + + XMEMSET(data, 0x6b, sizeof(data)); + rv = create_hmac_key(session, &key); + CHECK_RV(rv, "create HMAC key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(HMAC)", CKR_OK); + macLen = sizeof(mac) - 1; + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "C_Sign(HMAC) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(macLen == sizeof(mac), + "C_Sign(HMAC) short buffer reports the length"); + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "C_Sign(HMAC) retry with the reported length", CKR_OK); + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(HMAC) multi-part", CKR_OK); + rv = funcList->C_SignUpdate(session, data, sizeof(data)); + CHECK_RV(rv, "C_SignUpdate(HMAC)", CKR_OK); + macLen = 1; + rv = funcList->C_SignFinal(session, mac, &macLen); + CHECK_RV(rv, "C_SignFinal(HMAC) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(macLen == sizeof(mac), + "C_SignFinal(HMAC) short buffer reports the length"); + rv = funcList->C_SignFinal(session, mac, &macLen); + CHECK_RV(rv, "C_SignFinal(HMAC) retry with the reported length", CKR_OK); + + funcList->C_DestroyObject(session, key); +} + /* Signing accepts an output buffer length above 32 bits. */ static void sign_output_capacity_test(CK_SESSION_HANDLE session) { @@ -414,6 +456,65 @@ static void rsa_input_len_range_test(CK_SESSION_HANDLE session) } #endif +#ifndef NO_RSA +/* A short RSA output buffer reports the length that is needed. */ +static void rsa_buffer_too_small_len_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_RSA_PKCS, NULL, 0 }; + byte data[16]; + byte out[2048 / 8]; + byte dec[2048 / 8]; + CK_ULONG outLen; + CK_ULONG decLen; + + XMEMSET(data, 0x6c, sizeof(data)); + rv = create_rsa_keys(session, &priv, &pub); + CHECK_RV(rv, "create RSA key pair", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, priv); + CHECK_RV(rv, "C_SignInit(RSA PKCS)", CKR_OK); + outLen = sizeof(out) - 1; + rv = funcList->C_Sign(session, data, sizeof(data), out, &outLen); + CHECK_RV(rv, "C_Sign(RSA PKCS) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(outLen == sizeof(out), + "C_Sign(RSA PKCS) short buffer reports the length"); + rv = funcList->C_Sign(session, data, sizeof(data), out, &outLen); + CHECK_RV(rv, "C_Sign(RSA PKCS) retry with the reported length", CKR_OK); + + rv = funcList->C_EncryptInit(session, &mech, pub); + CHECK_RV(rv, "C_EncryptInit(RSA PKCS)", CKR_OK); + outLen = sizeof(out) - 1; + rv = funcList->C_Encrypt(session, data, sizeof(data), out, &outLen); + CHECK_RV(rv, "C_Encrypt(RSA PKCS) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(outLen == sizeof(out), + "C_Encrypt(RSA PKCS) short buffer reports the length"); + rv = funcList->C_Encrypt(session, data, sizeof(data), out, &outLen); + CHECK_RV(rv, "C_Encrypt(RSA PKCS) retry with the reported length", + CKR_OK); + + rv = funcList->C_DecryptInit(session, &mech, priv); + CHECK_RV(rv, "C_DecryptInit(RSA PKCS)", CKR_OK); + decLen = sizeof(data); + rv = funcList->C_Decrypt(session, out, outLen, dec, &decLen); + CHECK_RV(rv, "C_Decrypt(RSA PKCS) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(decLen == sizeof(dec), + "C_Decrypt(RSA PKCS) short buffer reports the length"); + rv = funcList->C_Decrypt(session, out, outLen, dec, &decLen); + CHECK_RV(rv, "C_Decrypt(RSA PKCS) retry with the reported length", + CKR_OK); + CHECK_TRUE(decLen == sizeof(data) && XMEMCMP(dec, data, decLen) == 0, + "C_Decrypt(RSA PKCS) recovers the data"); + + funcList->C_DestroyObject(session, priv); + funcList->C_DestroyObject(session, pub); +} +#endif + #ifndef NO_RSA /* An RSA sign accepts an output buffer length above 32 bits. */ static void rsa_sign_output_capacity_test(CK_SESSION_HANDLE session) @@ -497,6 +598,35 @@ static void cmac_verify_final_sig_len_range_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, key); } +/* A short CMAC buffer reports the length that is needed. */ +static void cmac_buffer_too_small_len_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_AES_CMAC, NULL, 0 }; + byte data[16]; + byte mac[16]; + CK_ULONG macLen; + + XMEMSET(data, 0x6d, sizeof(data)); + rv = create_cmac_key(session, &key); + CHECK_RV(rv, "create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_SignInit(session, &mech, key); + CHECK_RV(rv, "C_SignInit(CMAC)", CKR_OK); + macLen = sizeof(mac) - 1; + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "C_Sign(CMAC) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(macLen == sizeof(mac), + "C_Sign(CMAC) short buffer reports the length"); + rv = funcList->C_Sign(session, data, sizeof(data), mac, &macLen); + CHECK_RV(rv, "C_Sign(CMAC) retry with the reported length", CKR_OK); + + funcList->C_DestroyObject(session, key); +} + /* A CMAC sign accepts an output buffer length above 32 bits. */ static void cmac_sign_output_capacity_test(CK_SESSION_HANDLE session) { @@ -535,6 +665,155 @@ static void cmac_sign_output_capacity_test(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESGCM)) +static CK_OBJECT_CLASS aesEncKeyClass = CKO_SECRET_KEY; +static CK_KEY_TYPE aesEncKeyType = CKK_AES; + +static CK_RV create_aes_enc_key(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key) +{ + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &aesEncKeyClass, sizeof(aesEncKeyClass) }, + { CKA_KEY_TYPE, &aesEncKeyType, sizeof(aesEncKeyType) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, + }; + + return funcList->C_CreateObject(session, keyTmpl, + sizeof(keyTmpl) / sizeof(*keyTmpl), key); +} + +/* A short encrypt or decrypt buffer reports the length that is needed and + * leaves the operation active. */ +static void aes_buffer_too_small_len_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + byte iv[16]; + byte data[32]; + byte enc[48]; + byte dec[48]; + CK_ULONG encLen; + CK_ULONG decLen; +#ifdef HAVE_AES_CBC + CK_MECHANISM cbcMech; +#endif +#ifdef HAVE_AESGCM + CK_GCM_PARAMS gcmParams; + CK_MECHANISM gcmMech; +#endif + + XMEMSET(iv, 0x01, sizeof(iv)); + XMEMSET(data, 0x6a, sizeof(data)); + rv = create_aes_enc_key(session, &key); + CHECK_RV(rv, "create AES key", CKR_OK); + if (rv != CKR_OK) + return; + +#ifdef HAVE_AES_CBC + cbcMech.mechanism = CKM_AES_CBC; + cbcMech.pParameter = iv; + cbcMech.ulParameterLen = sizeof(iv); + + rv = funcList->C_EncryptInit(session, &cbcMech, key); + CHECK_RV(rv, "C_EncryptInit(AES CBC)", CKR_OK); + encLen = sizeof(data) - 1; + rv = funcList->C_Encrypt(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_Encrypt(AES CBC) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(encLen == sizeof(data), + "C_Encrypt(AES CBC) short buffer reports the length"); + rv = funcList->C_Encrypt(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_Encrypt(AES CBC) retry with the reported length", CKR_OK); + + rv = funcList->C_DecryptInit(session, &cbcMech, key); + CHECK_RV(rv, "C_DecryptInit(AES CBC)", CKR_OK); + decLen = 1; + rv = funcList->C_Decrypt(session, enc, encLen, dec, &decLen); + CHECK_RV(rv, "C_Decrypt(AES CBC) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(decLen == encLen, + "C_Decrypt(AES CBC) short buffer reports the length"); + rv = funcList->C_Decrypt(session, enc, encLen, dec, &decLen); + CHECK_RV(rv, "C_Decrypt(AES CBC) retry with the reported length", CKR_OK); + + rv = funcList->C_EncryptInit(session, &cbcMech, key); + CHECK_RV(rv, "C_EncryptInit(AES CBC) multi-part", CKR_OK); + encLen = 16; + rv = funcList->C_EncryptUpdate(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_EncryptUpdate(AES CBC) short buffer", + CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(encLen == sizeof(data), + "C_EncryptUpdate(AES CBC) short buffer reports the length"); + rv = funcList->C_EncryptUpdate(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_EncryptUpdate(AES CBC) retry", CKR_OK); + decLen = sizeof(dec); + rv = funcList->C_EncryptFinal(session, dec, &decLen); + CHECK_RV(rv, "C_EncryptFinal(AES CBC)", CKR_OK); + + rv = funcList->C_DecryptInit(session, &cbcMech, key); + CHECK_RV(rv, "C_DecryptInit(AES CBC) multi-part", CKR_OK); + decLen = 16; + rv = funcList->C_DecryptUpdate(session, enc, encLen, dec, &decLen); + CHECK_RV(rv, "C_DecryptUpdate(AES CBC) short buffer", + CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(decLen == encLen, + "C_DecryptUpdate(AES CBC) short buffer reports the length"); + rv = funcList->C_DecryptUpdate(session, enc, encLen, dec, &decLen); + CHECK_RV(rv, "C_DecryptUpdate(AES CBC) retry", CKR_OK); + encLen = sizeof(enc); + rv = funcList->C_DecryptFinal(session, enc, &encLen); + CHECK_RV(rv, "C_DecryptFinal(AES CBC)", CKR_OK); +#endif + +#ifdef HAVE_AESGCM + XMEMSET(&gcmParams, 0, sizeof(gcmParams)); + gcmParams.pIv = iv; + gcmParams.ulIvLen = 12; + gcmParams.ulIvBits = 96; + gcmParams.ulTagBits = 128; + gcmMech.mechanism = CKM_AES_GCM; + gcmMech.pParameter = &gcmParams; + gcmMech.ulParameterLen = sizeof(gcmParams); + + rv = funcList->C_EncryptInit(session, &gcmMech, key); + CHECK_RV(rv, "C_EncryptInit(AES GCM)", CKR_OK); + encLen = sizeof(data); + rv = funcList->C_Encrypt(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_Encrypt(AES GCM) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(encLen == sizeof(data) + 16, + "C_Encrypt(AES GCM) short buffer reports the length"); + rv = funcList->C_Encrypt(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_Encrypt(AES GCM) retry with the reported length", CKR_OK); + + rv = funcList->C_DecryptInit(session, &gcmMech, key); + CHECK_RV(rv, "C_DecryptInit(AES GCM)", CKR_OK); + decLen = 1; + rv = funcList->C_Decrypt(session, enc, encLen, dec, &decLen); + CHECK_RV(rv, "C_Decrypt(AES GCM) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(decLen == sizeof(data), + "C_Decrypt(AES GCM) short buffer reports the length"); + rv = funcList->C_Decrypt(session, enc, encLen, dec, &decLen); + CHECK_RV(rv, "C_Decrypt(AES GCM) retry with the reported length", CKR_OK); + + rv = funcList->C_EncryptInit(session, &gcmMech, key); + CHECK_RV(rv, "C_EncryptInit(AES GCM) multi-part", CKR_OK); + encLen = sizeof(enc); + rv = funcList->C_EncryptUpdate(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_EncryptUpdate(AES GCM)", CKR_OK); + decLen = 15; + rv = funcList->C_EncryptFinal(session, dec, &decLen); + CHECK_RV(rv, "C_EncryptFinal(AES GCM) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(decLen == 16, + "C_EncryptFinal(AES GCM) short buffer reports the length"); + rv = funcList->C_EncryptFinal(session, dec, &decLen); + CHECK_RV(rv, "C_EncryptFinal(AES GCM) retry", CKR_OK); +#endif + + funcList->C_DestroyObject(session, key); +} +#endif + static CK_RV token_init(CK_SLOT_ID* slot) { CK_RV rv; @@ -604,6 +883,18 @@ static int run_test(void) if (rv == CKR_OK) { #ifndef NO_SHA256 digest_requires_init_test(session); +#endif +#if !defined(NO_SHA256) && !defined(NO_HMAC) + sign_buffer_too_small_len_test(session); +#endif +#ifndef NO_RSA + rsa_buffer_too_small_len_test(session); +#endif +#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESGCM)) + aes_buffer_too_small_len_test(session); +#endif +#if !defined(NO_AES) && defined(HAVE_AESCMAC) + cmac_buffer_too_small_len_test(session); #endif if (sizeof(CK_ULONG) > sizeof(word32)) { #ifndef NO_SHA256 From 0f8b840bbf7730fc9d0446dd3290dc94c577ef90 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 08:35:32 -0700 Subject: [PATCH 197/204] F-3836 - Report the digest length when the C_Digest buffer is too small --- src/internal.c | 1 + tests/digest_length_test.c | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/internal.c b/src/internal.c index 224121e2..ce19aa3d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -20490,6 +20490,7 @@ int WP11_Digest_Single(unsigned char* data, word32 dataLen, return CKR_OK; } if (*dataOutLen < (word32)blockLen) { + *dataOutLen = (word32)blockLen; return BUFFER_E; } ret = wc_Hash(digest->hashType, data, dataLen, dataOut, *dataOutLen); diff --git a/tests/digest_length_test.c b/tests/digest_length_test.c index 5c3d0696..3350a172 100644 --- a/tests/digest_length_test.c +++ b/tests/digest_length_test.c @@ -181,6 +181,26 @@ static void digest_requires_init_test(CK_SESSION_HANDLE session) #endif } +/* A short C_Digest buffer reports the digest length and leaves the operation + * active. */ +static void digest_single_too_small_len_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + byte data[16]; + byte hash[32]; + CK_ULONG hashLen = sizeof(hash) - 1; + + XMEMSET(data, 0x6e, sizeof(data)); + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA256)", CKR_OK); + rv = funcList->C_Digest(session, data, sizeof(data), hash, &hashLen); + CHECK_RV(rv, "C_Digest short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(hashLen == sizeof(hash), + "C_Digest short buffer reports the digest length"); + rv = funcList->C_Digest(session, data, sizeof(data), hash, &hashLen); + CHECK_RV(rv, "C_Digest retry with the reported length", CKR_OK); +} + /* Digest input lengths that do not fit in 32 bits are rejected. */ static void digest_input_len_range_test(CK_SESSION_HANDLE session) { @@ -883,6 +903,7 @@ static int run_test(void) if (rv == CKR_OK) { #ifndef NO_SHA256 digest_requires_init_test(session); + digest_single_too_small_len_test(session); #endif #if !defined(NO_SHA256) && !defined(NO_HMAC) sign_buffer_too_small_len_test(session); From afeec54bc379a81e1f6a479fdd9aa7f5bc54a807 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 08:43:09 -0700 Subject: [PATCH 198/204] F-3837 - Report the digest length when the C_DigestFinal buffer is too small --- src/internal.c | 1 + tests/digest_length_test.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/internal.c b/src/internal.c index ce19aa3d..a2715795 100644 --- a/src/internal.c +++ b/src/internal.c @@ -20448,6 +20448,7 @@ int WP11_Digest_Final(unsigned char* data, word32* dataLen, } if (*dataLen < (word32)blockLen) { + *dataLen = (word32)blockLen; return BUFFER_E; } diff --git a/tests/digest_length_test.c b/tests/digest_length_test.c index 3350a172..03b74a9b 100644 --- a/tests/digest_length_test.c +++ b/tests/digest_length_test.c @@ -201,6 +201,38 @@ static void digest_single_too_small_len_test(CK_SESSION_HANDLE session) CHECK_RV(rv, "C_Digest retry with the reported length", CKR_OK); } +/* A short C_DigestFinal buffer reports the digest length and leaves the + * operation active. */ +static void digest_final_too_small_len_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + byte data[16]; + byte hash[32]; + byte single[32]; + CK_ULONG hashLen = 1; + CK_ULONG singleLen = sizeof(single); + + XMEMSET(data, 0x6f, sizeof(data)); + rv = funcList->C_DigestInit(session, &sha256Mech); + if (rv == CKR_OK) + rv = funcList->C_Digest(session, data, sizeof(data), single, + &singleLen); + CHECK_RV(rv, "C_Digest(SHA256) reference", CKR_OK); + + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA256)", CKR_OK); + rv = funcList->C_DigestUpdate(session, data, sizeof(data)); + CHECK_RV(rv, "C_DigestUpdate(SHA256)", CKR_OK); + rv = funcList->C_DigestFinal(session, hash, &hashLen); + CHECK_RV(rv, "C_DigestFinal short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(hashLen == sizeof(hash), + "C_DigestFinal short buffer reports the digest length"); + rv = funcList->C_DigestFinal(session, hash, &hashLen); + CHECK_RV(rv, "C_DigestFinal retry with the reported length", CKR_OK); + CHECK_TRUE(hashLen == singleLen && XMEMCMP(hash, single, hashLen) == 0, + "C_DigestFinal retry gives the full digest"); +} + /* Digest input lengths that do not fit in 32 bits are rejected. */ static void digest_input_len_range_test(CK_SESSION_HANDLE session) { @@ -904,6 +936,7 @@ static int run_test(void) #ifndef NO_SHA256 digest_requires_init_test(session); digest_single_too_small_len_test(session); + digest_final_too_small_len_test(session); #endif #if !defined(NO_SHA256) && !defined(NO_HMAC) sign_buffer_too_small_len_test(session); From 08952d96dcfdf2ece669bbf3a23df6ccef4b9719 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 08:52:27 -0700 Subject: [PATCH 199/204] F-5154 - Compare digest output buffer lengths without narrowing --- src/crypto.c | 4 ++-- tests/digest_length_test.c | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index ea8dea33..87a1772d 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -5342,7 +5342,7 @@ CK_RV C_Digest(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, return CKR_DATA_LEN_RANGE; } - hashLen = (word32)*pulDigestLen; + hashLen = CK_ULONG_CAP_WORD32(*pulDigestLen); ret = WP11_Digest_Single(pData, (word32)ulDataLen, pDigest, &hashLen, session); *pulDigestLen = hashLen; @@ -5518,7 +5518,7 @@ CK_RV C_DigestFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pDigest, return CKR_ARGUMENTS_BAD; if (!WP11_Session_IsOpInitialized(session, WP11_INIT_DIGEST)) return CKR_OPERATION_NOT_INITIALIZED; - hashLen = (word32)*pulDigestLen; + hashLen = CK_ULONG_CAP_WORD32(*pulDigestLen); ret = WP11_Digest_Final(pDigest, &hashLen, session); *pulDigestLen = hashLen; diff --git a/tests/digest_length_test.c b/tests/digest_length_test.c index 03b74a9b..ae74dfa6 100644 --- a/tests/digest_length_test.c +++ b/tests/digest_length_test.c @@ -264,6 +264,32 @@ static void digest_input_len_range_test(CK_SESSION_HANDLE session) CKR_OPERATION_NOT_INITIALIZED); } +/* Digesting accepts an output buffer length above 32 bits. */ +static void digest_output_capacity_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + byte data[16]; + byte hash[32]; + CK_ULONG hashLen = big_len(0); + + XMEMSET(data, 0x70, sizeof(data)); + rv = funcList->C_DigestInit(session, &sha256Mech); + if (rv == CKR_OK) + rv = funcList->C_Digest(session, data, sizeof(data), hash, &hashLen); + CHECK_RV(rv, "C_Digest with a buffer length above 32 bits", CKR_OK); + CHECK_TRUE(hashLen == sizeof(hash), "C_Digest reports the digest length"); + + hashLen = big_len(0); + rv = funcList->C_DigestInit(session, &sha256Mech); + if (rv == CKR_OK) + rv = funcList->C_DigestUpdate(session, data, sizeof(data)); + if (rv == CKR_OK) + rv = funcList->C_DigestFinal(session, hash, &hashLen); + CHECK_RV(rv, "C_DigestFinal with a buffer length above 32 bits", CKR_OK); + CHECK_TRUE(hashLen == sizeof(hash), + "C_DigestFinal reports the digest length"); +} + #if !defined(NO_HMAC) /* MAC input and signature lengths that do not fit in 32 bits are rejected. */ static void hmac_input_len_range_test(CK_SESSION_HANDLE session) @@ -953,6 +979,7 @@ static int run_test(void) if (sizeof(CK_ULONG) > sizeof(word32)) { #ifndef NO_SHA256 digest_input_len_range_test(session); + digest_output_capacity_test(session); #if !defined(NO_HMAC) hmac_input_len_range_test(session); verify_final_sig_len_range_test(session); From b41998bf649fb19a4d95dcccae8451c9b89a7812 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:02:07 -0700 Subject: [PATCH 200/204] F-2377 - Accept zero-length input in C_Digest and C_DigestUpdate --- src/crypto.c | 8 +++--- tests/digest_length_test.c | 59 ++++++++++++++++++++++++++++++++++++++ tests/pkcs11mtt.c | 7 +++-- tests/pkcs11test.c | 7 +++-- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 87a1772d..99af8d5b 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -5306,7 +5306,7 @@ CK_RV C_DigestInit(CK_SESSION_HANDLE hSession, * On out, length of the digest data in bytes. * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SESSION_HANDLE_INVALID when session handle is not valid. - * CKR_ARGUMENTS_BAD when pData, ulDataLen or pulDigestLen is NULL. + * CKR_ARGUMENTS_BAD when pData or pulDigestLen is NULL. * CKR_OPERATION_NOT_INITIALIZED when C_DigestInit has not been * successfully called. */ @@ -5333,7 +5333,7 @@ CK_RV C_Digest(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, } if (WP11_Session_Get(hSession, &session) != 0) return CKR_SESSION_HANDLE_INVALID; - if (pData == NULL || ulDataLen == 0 || pulDigestLen == NULL) + if (pData == NULL || pulDigestLen == NULL) return CKR_ARGUMENTS_BAD; if (!WP11_Session_IsOpInitialized(session, WP11_INIT_DIGEST)) return CKR_OPERATION_NOT_INITIALIZED; @@ -5365,7 +5365,7 @@ CK_RV C_Digest(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, * @param ulPartLen [in] Length of data in bytes. * @return CKR_CRYPTOKI_NOT_INITIALIZED when library not initialized. * CKR_SESSION_HANDLE_INVALID when session handle is not valid. - * CKR_ARGUMENTS_BAD when pPart is NULL or ulPartLen is 0. + * CKR_ARGUMENTS_BAD when pPart is NULL. * CKR_OPERATION_NOT_INITIALIZED when C_DigestInit has not been * successfully called. */ @@ -5390,7 +5390,7 @@ CK_RV C_DigestUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, } if (WP11_Session_Get(hSession, &session) != 0) return CKR_SESSION_HANDLE_INVALID; - if (pPart == NULL || ulPartLen == 0) + if (pPart == NULL) return CKR_ARGUMENTS_BAD; if (!WP11_Session_IsOpInitialized(session, WP11_INIT_DIGEST)) return CKR_OPERATION_NOT_INITIALIZED; diff --git a/tests/digest_length_test.c b/tests/digest_length_test.c index ae74dfa6..1f19d5ed 100644 --- a/tests/digest_length_test.c +++ b/tests/digest_length_test.c @@ -233,6 +233,64 @@ static void digest_final_too_small_len_test(CK_SESSION_HANDLE session) "C_DigestFinal retry gives the full digest"); } +/* The empty message digests to the standard value, and a zero-length update + * leaves a multi-part digest unchanged. */ +static void digest_empty_input_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + static const byte emptySha256[32] = { + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, + 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, + 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 + }; + byte data[16]; + byte hash[32]; + byte single[32]; + CK_ULONG hashLen = sizeof(hash); + CK_ULONG singleLen = sizeof(single); + + XMEMSET(data, 0x71, sizeof(data)); + rv = funcList->C_DigestInit(session, &sha256Mech); + if (rv == CKR_OK) + rv = funcList->C_Digest(session, data, 0, hash, &hashLen); + CHECK_RV(rv, "C_Digest of the empty message", CKR_OK); + CHECK_TRUE(hashLen == sizeof(emptySha256) && + XMEMCMP(hash, emptySha256, sizeof(emptySha256)) == 0, + "C_Digest of the empty message matches SHA-256(\"\")"); + + rv = funcList->C_DigestInit(session, &sha256Mech); + if (rv == CKR_OK) + rv = funcList->C_Digest(session, data, sizeof(data), single, + &singleLen); + CHECK_RV(rv, "C_Digest(SHA256) reference", CKR_OK); + + hashLen = sizeof(hash); + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA256) multi-part", CKR_OK); + rv = funcList->C_DigestUpdate(session, data, 8); + CHECK_RV(rv, "C_DigestUpdate first part", CKR_OK); + rv = funcList->C_DigestUpdate(session, data + 8, 0); + CHECK_RV(rv, "C_DigestUpdate zero-length part", CKR_OK); + rv = funcList->C_DigestUpdate(session, data + 8, sizeof(data) - 8); + CHECK_RV(rv, "C_DigestUpdate last part", CKR_OK); + rv = funcList->C_DigestFinal(session, hash, &hashLen); + CHECK_RV(rv, "C_DigestFinal multi-part", CKR_OK); + CHECK_TRUE(hashLen == singleLen && XMEMCMP(hash, single, hashLen) == 0, + "zero-length update leaves the digest unchanged"); + + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA256) for NULL data", CKR_OK); + hashLen = sizeof(hash); + rv = funcList->C_Digest(session, NULL, sizeof(data), hash, &hashLen); + CHECK_RV(rv, "C_Digest rejects NULL data with a length", + CKR_ARGUMENTS_BAD); + rv = funcList->C_DigestUpdate(session, NULL, sizeof(data)); + CHECK_RV(rv, "C_DigestUpdate rejects NULL data with a length", + CKR_ARGUMENTS_BAD); + (void)funcList->C_DigestFinal(session, hash, &hashLen); +} + /* Digest input lengths that do not fit in 32 bits are rejected. */ static void digest_input_len_range_test(CK_SESSION_HANDLE session) { @@ -963,6 +1021,7 @@ static int run_test(void) digest_requires_init_test(session); digest_single_too_small_len_test(session); digest_final_too_small_len_test(session); + digest_empty_input_test(session); #endif #if !defined(NO_SHA256) && !defined(NO_HMAC) sign_buffer_too_small_len_test(session); diff --git a/tests/pkcs11mtt.c b/tests/pkcs11mtt.c index 056f0e3c..78c60c95 100644 --- a/tests/pkcs11mtt.c +++ b/tests/pkcs11mtt.c @@ -1271,7 +1271,8 @@ static CK_RV test_digest(void* args) } if (ret == CKR_OK) { ret = funcList->C_Digest(session, data, 0, hash, &hashSz); - CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "Digest zero data length"); + CHECK_CKR_FAIL(ret, CKR_OPERATION_NOT_INITIALIZED, + "Digest zero data length without init"); } if (ret == CKR_OK) { ret = funcList->C_Digest(session, data, dataSz, hash, NULL); @@ -1288,8 +1289,8 @@ static CK_RV test_digest(void* args) } if (ret == CKR_OK) { ret = funcList->C_DigestUpdate(session, data, 0); - CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, - "Digest Update zero data length"); + CHECK_CKR_FAIL(ret, CKR_OPERATION_NOT_INITIALIZED, + "Digest Update zero data length without init"); } if (ret == CKR_OK) { ret = funcList->C_DigestKey(CK_INVALID_HANDLE, key); diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index b55b0702..91e4f616 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -5408,7 +5408,8 @@ static CK_RV test_digest_fail(void* args) } if (ret == CKR_OK) { ret = funcList->C_Digest(session, data, 0, hash, &hashSz); - CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, "Digest zero data length"); + CHECK_CKR_FAIL(ret, CKR_OPERATION_NOT_INITIALIZED, + "Digest zero data length without init"); } if (ret == CKR_OK) { ret = funcList->C_Digest(session, data, dataSz, hash, NULL); @@ -5425,8 +5426,8 @@ static CK_RV test_digest_fail(void* args) } if (ret == CKR_OK) { ret = funcList->C_DigestUpdate(session, data, 0); - CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, - "Digest Update zero data length"); + CHECK_CKR_FAIL(ret, CKR_OPERATION_NOT_INITIALIZED, + "Digest Update zero data length without init"); } if (ret == CKR_OK) { ret = funcList->C_DigestKey(CK_INVALID_HANDLE, key); From aa23cd1da546abfdeac03dc6c3d694d45570c5f9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 7 Oct 2026 09:29:16 -0700 Subject: [PATCH 201/204] F-2782 - Digest the secret key value in C_DigestKey --- src/internal.c | 21 +++++- tests/digest_length_test.c | 133 +++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 3 deletions(-) diff --git a/src/internal.c b/src/internal.c index a2715795..5661f9f4 100644 --- a/src/internal.c +++ b/src/internal.c @@ -20405,7 +20405,8 @@ int WP11_Digest_Update(unsigned char* data, word32 dataLen, * * @param key [in] Key to be digested. * @param session [in] Session object with the Digest object. -* @return -ve on failure. +* @return CKR_KEY_INDIGESTIBLE when the key is not a secret key value. +* -ve on failure. * 0 on success. */ int WP11_Digest_Key(WP11_Object* key, WP11_Session* session) @@ -20413,8 +20414,22 @@ int WP11_Digest_Key(WP11_Object* key, WP11_Session* session) int ret; #ifndef WOLFPKCS11_NO_STORE WP11_Digest* digest = &session->params.digest; - ret = wc_HashUpdate(&digest->hash, digest->hashType, key->keyData, - key->keyDataLen); + + if (key->objClass != CKO_SECRET_KEY || (key->type != CKK_AES && + key->type != CKK_GENERIC_SECRET && key->type != CKK_HKDF) || + key->data.symmKey == NULL) { + return (int)CKR_KEY_INDIGESTIBLE; + } + if (key->encoded) + return BAD_FUNC_ARG; + if (key->onToken) { + WP11_Lock_LockRO(key->lock); + } + ret = wc_HashUpdate(&digest->hash, digest->hashType, + key->data.symmKey->data, key->data.symmKey->len); + if (key->onToken) { + WP11_Lock_UnlockRO(key->lock); + } #else (void) session; (void) key; diff --git a/tests/digest_length_test.c b/tests/digest_length_test.c index 1f19d5ed..71cbb7f7 100644 --- a/tests/digest_length_test.c +++ b/tests/digest_length_test.c @@ -680,6 +680,136 @@ static void rsa_sign_output_capacity_test(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_SHA256) && !defined(WOLFPKCS11_NO_STORE) +/* Digest of a secret key through C_DigestKey for comparison with the value. */ +static CK_RV digest_key(CK_SESSION_HANDLE session, const byte* prefix, + CK_ULONG prefixLen, CK_OBJECT_HANDLE key, byte* hash, + CK_ULONG* hashLen) +{ + CK_RV rv; + + rv = funcList->C_DigestInit(session, &sha256Mech); + if (rv == CKR_OK && prefixLen > 0) + rv = funcList->C_DigestUpdate(session, (CK_BYTE_PTR)prefix, prefixLen); + if (rv == CKR_OK) + rv = funcList->C_DigestKey(session, key); + if (rv == CKR_OK) + rv = funcList->C_DigestFinal(session, hash, hashLen); + return rv; +} + +/* C_DigestKey digests the value of a secret key, whatever its storage or + * sensitivity, and refuses other object classes. */ +static void digest_key_value_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE sessKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE tokenKey = CK_INVALID_HANDLE; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_GENERIC_SECRET; + byte value[32]; + byte prefixed[8 + 32]; + byte expect[32]; + byte expectPrefixed[32]; + byte hash[32]; + CK_ULONG expectLen = sizeof(expect); + CK_ULONG expectPrefixedLen = sizeof(expectPrefixed); + CK_ULONG hashLen; + CK_ATTRIBUTE sessTmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, value, sizeof(value) }, + }; + CK_ATTRIBUTE tokenTmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, value, sizeof(value) }, + }; +#ifndef NO_RSA + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; +#endif + + XMEMSET(value, 0x72, sizeof(value)); + XMEMSET(prefixed, 0x73, 8); + XMEMCPY(prefixed + 8, value, sizeof(value)); + + rv = funcList->C_DigestInit(session, &sha256Mech); + if (rv == CKR_OK) + rv = funcList->C_Digest(session, value, sizeof(value), expect, + &expectLen); + CHECK_RV(rv, "C_Digest of the key value", CKR_OK); + rv = funcList->C_DigestInit(session, &sha256Mech); + if (rv == CKR_OK) + rv = funcList->C_Digest(session, prefixed, sizeof(prefixed), + expectPrefixed, &expectPrefixedLen); + CHECK_RV(rv, "C_Digest of data followed by the key value", CKR_OK); + + rv = funcList->C_CreateObject(session, sessTmpl, + sizeof(sessTmpl) / sizeof(*sessTmpl), + &sessKey); + CHECK_RV(rv, "create session secret key", CKR_OK); + if (rv == CKR_OK) { + hashLen = sizeof(hash); + rv = digest_key(session, NULL, 0, sessKey, hash, &hashLen); + CHECK_RV(rv, "C_DigestKey(session key)", CKR_OK); + CHECK_TRUE(hashLen == expectLen && + XMEMCMP(hash, expect, expectLen) == 0, + "C_DigestKey(session key) digests the key value"); + + hashLen = sizeof(hash); + rv = digest_key(session, prefixed, 8, sessKey, hash, &hashLen); + CHECK_RV(rv, "C_DigestUpdate then C_DigestKey", CKR_OK); + CHECK_TRUE(hashLen == expectPrefixedLen && + XMEMCMP(hash, expectPrefixed, expectPrefixedLen) == 0, + "C_DigestKey continues a multi-part digest"); + funcList->C_DestroyObject(session, sessKey); + } + + rv = funcList->C_CreateObject(session, tokenTmpl, + sizeof(tokenTmpl) / sizeof(*tokenTmpl), + &tokenKey); + CHECK_RV(rv, "create sensitive token secret key", CKR_OK); + if (rv == CKR_OK) { + hashLen = sizeof(hash); + rv = digest_key(session, NULL, 0, tokenKey, hash, &hashLen); + CHECK_RV(rv, "C_DigestKey(sensitive token key)", CKR_OK); + CHECK_TRUE(hashLen == expectLen && + XMEMCMP(hash, expect, expectLen) == 0, + "C_DigestKey(sensitive token key) digests the key value"); + funcList->C_DestroyObject(session, tokenKey); + } + +#ifndef NO_RSA + rv = create_rsa_keys(session, &priv, &pub); + CHECK_RV(rv, "create RSA key pair", CKR_OK); + if (rv == CKR_OK) { + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA256) for a public key", CKR_OK); + rv = funcList->C_DigestKey(session, pub); + CHECK_RV(rv, "C_DigestKey rejects a public key", CKR_KEY_INDIGESTIBLE); + hashLen = sizeof(hash); + rv = funcList->C_DigestFinal(session, hash, &hashLen); + CHECK_RV(rv, "C_DigestKey rejection ends the operation", + CKR_OPERATION_NOT_INITIALIZED); + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA256) for a private key", CKR_OK); + rv = funcList->C_DigestKey(session, priv); + CHECK_RV(rv, "C_DigestKey rejects a private key", + CKR_KEY_INDIGESTIBLE); + funcList->C_DestroyObject(session, priv); + funcList->C_DestroyObject(session, pub); + } +#endif +} +#endif + #if !defined(NO_AES) && defined(HAVE_AESCMAC) static CK_OBJECT_CLASS aesKeyClass = CKO_SECRET_KEY; static CK_KEY_TYPE aesKeyType = CKK_AES; @@ -1022,6 +1152,9 @@ static int run_test(void) digest_single_too_small_len_test(session); digest_final_too_small_len_test(session); digest_empty_input_test(session); +#ifndef WOLFPKCS11_NO_STORE + digest_key_value_test(session); +#endif #endif #if !defined(NO_SHA256) && !defined(NO_HMAC) sign_buffer_too_small_len_test(session); From 63fd30151c255881ef9aad0204ec062df2e48618 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:55 -0700 Subject: [PATCH 202/204] Fix output length checks and the CTS short buffer retry --- src/crypto.c | 79 ++++++----- src/internal.c | 31 ++--- tests/digest_length_test.c | 194 ++++++++++++++++++++++++++- tests/encrypt_decrypt_params_test.c | 195 ++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 1 - 5 files changed, 440 insertions(+), 60 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 99af8d5b..885ce5f9 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -3200,7 +3200,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) { + if ((CK_ULONG)encDataLen > *pulEncryptedDataLen) { *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -3225,7 +3225,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) { + if ((CK_ULONG)encDataLen > *pulEncryptedDataLen) { *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -3253,7 +3253,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) { + if ((CK_ULONG)encDataLen > *pulEncryptedDataLen) { *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -3287,7 +3287,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) { + if ((CK_ULONG)encDataLen > *pulEncryptedDataLen) { *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -3320,7 +3320,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) { + if ((CK_ULONG)encDataLen > *pulEncryptedDataLen) { *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -3350,7 +3350,8 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, return CKR_BUFFER_TOO_SMALL; } - encDataLen = (word32)*pulEncryptedDataLen; + /* Output is the same length as the input, which fits the buffer. */ + encDataLen = (word32)ulDataLen; ret = WP11_AesCtr_Do(pData, (word32)ulDataLen, pEncryptedData, &encDataLen, session); if (ret == WP11_CTR_OVERFLOW_E) @@ -3375,7 +3376,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) { + if ((CK_ULONG)encDataLen > *pulEncryptedDataLen) { *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -3402,7 +3403,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) { + if ((CK_ULONG)encDataLen > *pulEncryptedDataLen) { *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -3432,7 +3433,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) { + if ((CK_ULONG)encDataLen > *pulEncryptedDataLen) { *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -3453,7 +3454,6 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, return CKR_DATA_LEN_RANGE; } - encDataLen = (word32)*pulEncryptedDataLen; if (pEncryptedData == NULL) { *pulEncryptedDataLen = ulDataLen; return CKR_OK; @@ -3463,6 +3463,8 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, return CKR_BUFFER_TOO_SMALL; } + /* Output is the same length as the input, which fits the buffer. */ + encDataLen = (word32)ulDataLen; ret = WP11_AesCts_Encrypt(pData, (int)ulDataLen, pEncryptedData, &encDataLen, session); if (ret < 0) @@ -3485,7 +3487,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) { + if ((CK_ULONG)encDataLen > *pulEncryptedDataLen) { *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -3515,7 +3517,7 @@ static CK_RV wp11_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, *pulEncryptedDataLen = encDataLen; return CKR_OK; } - if (encDataLen > (word32)*pulEncryptedDataLen) { + if ((CK_ULONG)encDataLen > *pulEncryptedDataLen) { *pulEncryptedDataLen = encDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -3641,7 +3643,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, WOLFPKCS11_LEAVE("C_EncryptUpdate", rv); return rv; } - if (encPartLen > (word32)*pulEncryptedPartLen) { + if ((CK_ULONG)encPartLen > *pulEncryptedPartLen) { *pulEncryptedPartLen = encPartLen; rv = CKR_BUFFER_TOO_SMALL; WOLFPKCS11_LEAVE("C_EncryptUpdate", rv); @@ -3675,7 +3677,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, *pulEncryptedPartLen = encPartLen; return CKR_OK; } - if (encPartLen > (word32)*pulEncryptedPartLen) { + if ((CK_ULONG)encPartLen > *pulEncryptedPartLen) { *pulEncryptedPartLen = encPartLen; return CKR_BUFFER_TOO_SMALL; } @@ -3707,7 +3709,8 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, return CKR_BUFFER_TOO_SMALL; } - encPartLen = (word32)*pulEncryptedPartLen; + /* Output is the same length as the input, which fits the buffer. */ + encPartLen = (word32)ulPartLen; ret = WP11_AesCtr_Update(pPart, (int)ulPartLen, pEncryptedPart, &encPartLen, session); if (ret == WP11_CTR_OVERFLOW_E) { @@ -3737,7 +3740,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, *pulEncryptedPartLen = encPartLen; return CKR_OK; } - if (encPartLen > (word32)*pulEncryptedPartLen) { + if ((CK_ULONG)encPartLen > *pulEncryptedPartLen) { *pulEncryptedPartLen = encPartLen; return CKR_BUFFER_TOO_SMALL; } @@ -3766,7 +3769,7 @@ static CK_RV wp11_C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, return CKR_OK; } - encPartLen = (word32)*pulEncryptedPartLen; + encPartLen = CK_ULONG_CAP_WORD32(*pulEncryptedPartLen); ret = WP11_AesCts_EncryptUpdate(pPart, (word32)ulPartLen, pEncryptedPart, &encPartLen, session); if (ret == BUFFER_E) { @@ -4389,7 +4392,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) { + if ((CK_ULONG)decDataLen > *pulDataLen) { *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -4415,7 +4418,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) { + if ((CK_ULONG)decDataLen > *pulDataLen) { *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -4444,7 +4447,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) { + if ((CK_ULONG)decDataLen > *pulDataLen) { *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -4478,7 +4481,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) { + if ((CK_ULONG)decDataLen > *pulDataLen) { *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -4518,7 +4521,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, * this capacity and returns BUFFER_E (mapped to * CKR_BUFFER_TOO_SMALL below) rather than overflowing a buffer * that is too small for the recovered plaintext. */ - decDataLen = (word32)*pulDataLen; + decDataLen = CK_ULONG_CAP_WORD32(*pulDataLen); ret = WP11_AesCbcPad_Decrypt(pEncryptedData, (int)ulEncryptedDataLen, pData, &decDataLen, session); @@ -4550,7 +4553,8 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, return CKR_BUFFER_TOO_SMALL; } - decDataLen = (word32)*pulDataLen; + /* Output is the same length as the input, which fits the buffer. */ + decDataLen = (word32)ulEncryptedDataLen; ret = WP11_AesCtr_Do(pEncryptedData, (word32)ulEncryptedDataLen, pData, &decDataLen, session); if (ret == WP11_CTR_OVERFLOW_E) @@ -4575,7 +4579,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) { + if ((CK_ULONG)decDataLen > *pulDataLen) { *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -4602,7 +4606,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) { + if ((CK_ULONG)decDataLen > *pulDataLen) { *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -4632,7 +4636,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) { + if ((CK_ULONG)decDataLen > *pulDataLen) { *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -4653,7 +4657,6 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, return CKR_ENCRYPTED_DATA_LEN_RANGE; } - decDataLen = (word32)*pulDataLen; if (pData == NULL) { *pulDataLen = ulEncryptedDataLen; return CKR_OK; @@ -4663,6 +4666,8 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, return CKR_BUFFER_TOO_SMALL; } + /* Output is the same length as the input, which fits the buffer. */ + decDataLen = (word32)ulEncryptedDataLen; ret = WP11_AesCts_Decrypt(pEncryptedData, (int)ulEncryptedDataLen, pData, &decDataLen, session); if (ret == BUFFER_E) { @@ -4691,7 +4696,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, *pulDataLen = decDataLen; return CKR_OK; } - if (decDataLen > (word32)*pulDataLen) { + if ((CK_ULONG)decDataLen > *pulDataLen) { *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; } @@ -4727,7 +4732,7 @@ static CK_RV wp11_C_Decrypt(CK_SESSION_HANDLE hSession, * do not pre-reject against the upper bound. Unwrap into the caller * buffer; if it does not fit, the unwrap reports the exact required * size via decDataLen. */ - decDataLen = (word32)*pulDataLen; + decDataLen = CK_ULONG_CAP_WORD32(*pulDataLen); ret = WP11_AesKeyWrapPad_Decrypt(pEncryptedData, (word32)ulEncryptedDataLen, pData, &decDataLen, session); if (ret == BUFFER_E) { @@ -4843,7 +4848,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, *pulPartLen = decPartLen; return CKR_OK; } - if (decPartLen > (word32)*pulPartLen) { + if ((CK_ULONG)decPartLen > *pulPartLen) { *pulPartLen = decPartLen; return CKR_BUFFER_TOO_SMALL; } @@ -4878,7 +4883,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, *pulPartLen = decPartLen; return CKR_OK; } - if (decPartLen > (word32)*pulPartLen) { + if ((CK_ULONG)decPartLen > *pulPartLen) { *pulPartLen = decPartLen; return CKR_BUFFER_TOO_SMALL; } @@ -4959,7 +4964,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CTS_DEC)) return CKR_OPERATION_NOT_INITIALIZED; if (!CK_ULONG_FITS_WORD32(ulEncryptedPartLen)) { - WP11_AesCts_Final(session); + WP11_Session_AbortOp(session); return CKR_ENCRYPTED_DATA_LEN_RANGE; } @@ -4968,7 +4973,7 @@ static CK_RV wp11_C_DecryptUpdate(CK_SESSION_HANDLE hSession, return CKR_OK; } - decPartLen = (word32)*pulPartLen; + decPartLen = CK_ULONG_CAP_WORD32(*pulPartLen); ret = WP11_AesCts_DecryptUpdate(pEncryptedPart, (word32)ulEncryptedPartLen, pPart, &decPartLen, session); if (ret == BUFFER_E) { @@ -5180,7 +5185,11 @@ static CK_RV wp11_C_DecryptFinal(CK_SESSION_HANDLE hSession, return CKR_OK; } - decPartLen = (word32)*pulLastPartLen; + /* The final output is at most two blocks; cap the capacity to + * avoid truncating a large CK_ULONG. */ + decPartLen = (*pulLastPartLen < (CK_ULONG)(AES_BLOCK_SIZE * 2)) ? + (word32)*pulLastPartLen : + (word32)(AES_BLOCK_SIZE * 2); ret = WP11_AesCts_DecryptFinal(pLastPart, &decPartLen, session); if (ret == BUFFER_E) { *pulLastPartLen = AES_BLOCK_SIZE * 2; @@ -8221,7 +8230,7 @@ static CK_RV wp11_C_VerifyRecover(CK_SESSION_HANDLE hSession, } if (!CK_ULONG_FITS_WORD32(*pulDataLen)) return CKR_ARGUMENTS_BAD; - if (decDataLen > (word32)*pulDataLen) { + if ((CK_ULONG)decDataLen > *pulDataLen) { /* Report the required size as mandated by PKCS#11. */ *pulDataLen = decDataLen; return CKR_BUFFER_TOO_SMALL; diff --git a/src/internal.c b/src/internal.c index 5661f9f4..8e6e62ea 100644 --- a/src/internal.c +++ b/src/internal.c @@ -20072,9 +20072,11 @@ int WP11_AesCts_EncryptFinal(unsigned char* enc, word32* encSz, WP11_CtsParams* cts = &session->params.cts; ret = wc_AesCtsEncryptFinal(&cts->aes, enc, encSz); - - wc_AesFree(&cts->aes); - session->init = 0; + /* A short buffer leaves the state untouched so the caller can retry. */ + if (ret != BUFFER_E) { + wc_AesFree(&cts->aes); + session->init = 0; + } return ret; } @@ -20152,27 +20154,14 @@ int WP11_AesCts_DecryptFinal(unsigned char* dec, word32* decSz, WP11_CtsParams* cts = &session->params.cts; ret = wc_AesCtsDecryptFinal(&cts->aes, dec, decSz); - - wc_AesFree(&cts->aes); - session->init = 0; + /* A short buffer leaves the state untouched so the caller can retry. */ + if (ret != BUFFER_E) { + wc_AesFree(&cts->aes); + session->init = 0; + } return ret; } - -/** - * Abandon an AES-CTS operation, releasing the AES object. - * - * @param session [in] Session object holding Aes object. - */ -void WP11_AesCts_Final(WP11_Session* session) -{ - WP11_CtsParams* cts = &session->params.cts; - - wc_AesFree(&cts->aes); - /* Older wolfSSL versions do not zeroize the key schedule in wc_AesFree. */ - wc_ForceZero(cts, sizeof(*cts)); - session->init = 0; -} #endif /* HAVE_AESCTS */ #ifdef HAVE_AESCMAC diff --git a/tests/digest_length_test.c b/tests/digest_length_test.c index 71cbb7f7..0a574ba7 100644 --- a/tests/digest_length_test.c +++ b/tests/digest_length_test.c @@ -59,7 +59,8 @@ static CK_BBOOL ckTrue = CK_TRUE; static CK_BBOOL ckFalse = CK_FALSE; #if !defined(NO_SHA256) || !defined(NO_RSA) || \ - (!defined(NO_AES) && defined(HAVE_AESCMAC)) + (!defined(NO_AES) && (defined(HAVE_AESCMAC) || defined(HAVE_AES_CBC) || \ + defined(HAVE_AESCTS))) /* A length above the 32-bit range whose low 32 bits equal small. */ static CK_ULONG big_len(CK_ULONG small) { @@ -678,6 +679,44 @@ static void rsa_sign_output_capacity_test(CK_SESSION_HANDLE session) funcList->C_DestroyObject(session, priv); funcList->C_DestroyObject(session, pub); } + +/* RSA encrypt and decrypt accept an output buffer length above 32 bits. */ +static void rsa_encrypt_output_capacity_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_RSA_PKCS, NULL, 0 }; + byte data[16]; + byte enc[2048 / 8]; + byte dec[2048 / 8]; + CK_ULONG encLen = big_len(0); + CK_ULONG decLen = big_len(0); + + XMEMSET(data, 0x6d, sizeof(data)); + rv = create_rsa_keys(session, &priv, &pub); + CHECK_RV(rv, "create RSA key pair", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_EncryptInit(session, &mech, pub); + if (rv == CKR_OK) + rv = funcList->C_Encrypt(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_Encrypt(RSA) with a buffer length above 32 bits", CKR_OK); + CHECK_TRUE(encLen == sizeof(enc), "C_Encrypt(RSA) reports the length"); + + if (rv == CKR_OK) + rv = funcList->C_DecryptInit(session, &mech, priv); + if (rv == CKR_OK) + rv = funcList->C_Decrypt(session, enc, encLen, dec, &decLen); + CHECK_RV(rv, "C_Decrypt(RSA) with a buffer length above 32 bits", CKR_OK); + CHECK_TRUE(rv == CKR_OK && decLen == sizeof(data) && + XMEMCMP(dec, data, sizeof(data)) == 0, + "C_Decrypt(RSA) recovers the data"); + + funcList->C_DestroyObject(session, priv); + funcList->C_DestroyObject(session, pub); +} #endif #if !defined(NO_SHA256) && !defined(WOLFPKCS11_NO_STORE) @@ -931,7 +970,8 @@ static void cmac_sign_output_capacity_test(CK_SESSION_HANDLE session) } #endif -#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESGCM)) +#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESGCM) || \ + defined(HAVE_AESCTS)) static CK_OBJECT_CLASS aesEncKeyClass = CKO_SECRET_KEY; static CK_KEY_TYPE aesEncKeyType = CKK_AES; @@ -970,6 +1010,11 @@ static void aes_buffer_too_small_len_test(CK_SESSION_HANDLE session) CK_GCM_PARAMS gcmParams; CK_MECHANISM gcmMech; #endif +#ifdef HAVE_AESCTS + CK_MECHANISM ctsMech; + byte single[48]; + CK_ULONG singleLen; +#endif XMEMSET(iv, 0x01, sizeof(iv)); XMEMSET(data, 0x6a, sizeof(data)); @@ -1076,6 +1121,144 @@ static void aes_buffer_too_small_len_test(CK_SESSION_HANDLE session) CHECK_RV(rv, "C_EncryptFinal(AES GCM) retry", CKR_OK); #endif +#ifdef HAVE_AESCTS + ctsMech.mechanism = CKM_AES_CTS; + ctsMech.pParameter = iv; + ctsMech.ulParameterLen = sizeof(iv); + + rv = funcList->C_EncryptInit(session, &ctsMech, key); + CHECK_RV(rv, "C_EncryptInit(AES CTS)", CKR_OK); + singleLen = sizeof(data) - 1; + rv = funcList->C_Encrypt(session, data, sizeof(data), single, &singleLen); + CHECK_RV(rv, "C_Encrypt(AES CTS) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(singleLen == sizeof(data), + "C_Encrypt(AES CTS) short buffer reports the length"); + rv = funcList->C_Encrypt(session, data, sizeof(data), single, &singleLen); + CHECK_RV(rv, "C_Encrypt(AES CTS) retry with the reported length", CKR_OK); + + rv = funcList->C_DecryptInit(session, &ctsMech, key); + CHECK_RV(rv, "C_DecryptInit(AES CTS)", CKR_OK); + decLen = 1; + rv = funcList->C_Decrypt(session, single, singleLen, dec, &decLen); + CHECK_RV(rv, "C_Decrypt(AES CTS) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(decLen == singleLen, + "C_Decrypt(AES CTS) short buffer reports the length"); + rv = funcList->C_Decrypt(session, single, singleLen, dec, &decLen); + CHECK_RV(rv, "C_Decrypt(AES CTS) retry with the reported length", CKR_OK); + CHECK_TRUE(decLen == sizeof(data) && XMEMCMP(dec, data, decLen) == 0, + "C_Decrypt(AES CTS) recovers the data"); + + /* Two blocks of input are held back for the final call. */ + rv = funcList->C_EncryptInit(session, &ctsMech, key); + CHECK_RV(rv, "C_EncryptInit(AES CTS) multi-part", CKR_OK); + encLen = sizeof(enc); + rv = funcList->C_EncryptUpdate(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_EncryptUpdate(AES CTS)", CKR_OK); + CHECK_TRUE(encLen == 0, "C_EncryptUpdate(AES CTS) holds back the tail"); + decLen = 16; + rv = funcList->C_EncryptFinal(session, enc, &decLen); + CHECK_RV(rv, "C_EncryptFinal(AES CTS) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(decLen == 32, + "C_EncryptFinal(AES CTS) short buffer reports the length"); + rv = funcList->C_EncryptFinal(session, enc, &decLen); + CHECK_RV(rv, "C_EncryptFinal(AES CTS) retry", CKR_OK); + CHECK_TRUE(rv == CKR_OK && decLen == singleLen && + XMEMCMP(enc, single, singleLen) == 0, + "C_EncryptFinal(AES CTS) retry matches the single-part output"); + + rv = funcList->C_DecryptInit(session, &ctsMech, key); + CHECK_RV(rv, "C_DecryptInit(AES CTS) multi-part", CKR_OK); + decLen = sizeof(dec); + rv = funcList->C_DecryptUpdate(session, single, singleLen, dec, &decLen); + CHECK_RV(rv, "C_DecryptUpdate(AES CTS)", CKR_OK); + CHECK_TRUE(decLen == 0, "C_DecryptUpdate(AES CTS) holds back the tail"); + decLen = 16; + rv = funcList->C_DecryptFinal(session, dec, &decLen); + CHECK_RV(rv, "C_DecryptFinal(AES CTS) short buffer", CKR_BUFFER_TOO_SMALL); + CHECK_TRUE(decLen == 32, + "C_DecryptFinal(AES CTS) short buffer reports the length"); + rv = funcList->C_DecryptFinal(session, dec, &decLen); + CHECK_RV(rv, "C_DecryptFinal(AES CTS) retry", CKR_OK); + CHECK_TRUE(rv == CKR_OK && decLen == sizeof(data) && + XMEMCMP(dec, data, sizeof(data)) == 0, + "C_DecryptFinal(AES CTS) retry recovers the data"); +#endif + + funcList->C_DestroyObject(session, key); +} +#endif + +#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESCTS)) +/* Encrypt and decrypt accept an output buffer length above 32 bits. */ +static void aes_output_capacity_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + byte iv[16]; + byte data[32]; + byte enc[48]; + byte dec[48]; + CK_ULONG encLen; + CK_ULONG decLen; + CK_MECHANISM mech; + + XMEMSET(iv, 0x02, sizeof(iv)); + XMEMSET(data, 0x6b, sizeof(data)); + rv = create_aes_enc_key(session, &key); + CHECK_RV(rv, "create AES key", CKR_OK); + if (rv != CKR_OK) + return; + +#ifdef HAVE_AES_CBC + mech.mechanism = CKM_AES_CBC; +#else + mech.mechanism = CKM_AES_CTS; +#endif + mech.pParameter = iv; + mech.ulParameterLen = sizeof(iv); + + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "C_EncryptInit(AES)", CKR_OK); + encLen = big_len(0); + rv = funcList->C_Encrypt(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_Encrypt(AES) with a buffer length above 32 bits", CKR_OK); + CHECK_TRUE(encLen == sizeof(data), "C_Encrypt(AES) reports the length"); + + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "C_DecryptInit(AES)", CKR_OK); + decLen = big_len(0); + rv = funcList->C_Decrypt(session, enc, encLen, dec, &decLen); + CHECK_RV(rv, "C_Decrypt(AES) with a buffer length above 32 bits", CKR_OK); + CHECK_TRUE(decLen == sizeof(data) && XMEMCMP(dec, data, decLen) == 0, + "C_Decrypt(AES) recovers the data"); + + rv = funcList->C_EncryptInit(session, &mech, key); + CHECK_RV(rv, "C_EncryptInit(AES) multi-part", CKR_OK); + encLen = big_len(0); + rv = funcList->C_EncryptUpdate(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "C_EncryptUpdate(AES) with a buffer length above 32 bits", + CKR_OK); + decLen = big_len(0); + rv = funcList->C_EncryptFinal(session, enc + encLen, &decLen); + CHECK_RV(rv, "C_EncryptFinal(AES) with a buffer length above 32 bits", + CKR_OK); + CHECK_TRUE(encLen + decLen == sizeof(data), + "C_EncryptUpdate(AES) and C_EncryptFinal report the length"); + + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "C_DecryptInit(AES) multi-part", CKR_OK); + decLen = big_len(0); + rv = funcList->C_DecryptUpdate(session, enc, sizeof(data), dec, &decLen); + CHECK_RV(rv, "C_DecryptUpdate(AES) with a buffer length above 32 bits", + CKR_OK); + encLen = big_len(0); + rv = funcList->C_DecryptFinal(session, dec + decLen, &encLen); + CHECK_RV(rv, "C_DecryptFinal(AES) with a buffer length above 32 bits", + CKR_OK); + CHECK_TRUE(decLen + encLen == sizeof(data) && + XMEMCMP(dec, data, sizeof(data)) == 0, + "C_DecryptUpdate(AES) and C_DecryptFinal recover the data"); + funcList->C_DestroyObject(session, key); } #endif @@ -1162,7 +1345,8 @@ static int run_test(void) #ifndef NO_RSA rsa_buffer_too_small_len_test(session); #endif -#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESGCM)) +#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESGCM) || \ + defined(HAVE_AESCTS)) aes_buffer_too_small_len_test(session); #endif #if !defined(NO_AES) && defined(HAVE_AESCMAC) @@ -1181,6 +1365,10 @@ static int run_test(void) #ifndef NO_RSA rsa_input_len_range_test(session); rsa_sign_output_capacity_test(session); + rsa_encrypt_output_capacity_test(session); +#endif +#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(HAVE_AESCTS)) + aes_output_capacity_test(session); #endif #if !defined(NO_AES) && defined(HAVE_AESCMAC) cmac_verify_final_sig_len_range_test(session); diff --git a/tests/encrypt_decrypt_params_test.c b/tests/encrypt_decrypt_params_test.c index 95318e8f..2ffdc422 100644 --- a/tests/encrypt_decrypt_params_test.c +++ b/tests/encrypt_decrypt_params_test.c @@ -1221,6 +1221,195 @@ static void test_ctr_decrypt_update_len(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_AES) && defined(HAVE_AESCTS) +/* Multi-part AES-CTS decryption rejects a part length the token cannot + * represent and accepts output buffer lengths that do not fit in 32 bits. */ +static void test_cts_decrypt_update_len(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech; + byte iv[16]; + byte data[32]; + byte out[64]; + CK_ULONG partLen; + CK_ULONG lastLen; + CK_ULONG wrap = ((CK_ULONG)1 << 16) << 16; + + if (wrap == 0) + return; + + XMEMSET(iv, 0xBD, sizeof(iv)); + XMEMSET(data, 0xCE, sizeof(data)); + mech.mechanism = CKM_AES_CTS; + mech.pParameter = iv; + mech.ulParameterLen = sizeof(iv); + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "CTS decrypt update: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "CTS decrypt update: C_DecryptInit", CKR_OK); + if (rv != CKR_OK) + return; + partLen = wrap + sizeof(out); + rv = funcList->C_DecryptUpdate(session, data, wrap + sizeof(data), out, + &partLen); + CHECK_RV(rv, "CTS decrypt update: rejects oversized part", + CKR_ENCRYPTED_DATA_LEN_RANGE); + partLen = sizeof(out); + rv = funcList->C_DecryptUpdate(session, data, sizeof(data), out, &partLen); + CHECK_RV(rv, "CTS decrypt update: operation ended", + CKR_OPERATION_NOT_INITIALIZED); + if (rv == CKR_OK) { + /* End the still-active operation so the next check can start. */ + partLen = sizeof(out); + (void)funcList->C_DecryptFinal(session, out, &partLen); + } + + rv = funcList->C_DecryptInit(session, &mech, key); + CHECK_RV(rv, "CTS decrypt update: C_DecryptInit", CKR_OK); + if (rv != CKR_OK) + return; + partLen = wrap + 8; + rv = funcList->C_DecryptUpdate(session, data, sizeof(data), out, &partLen); + CHECK_RV(rv, "CTS decrypt update: large output buffer length", CKR_OK); + CHECK_TRUE(rv == CKR_OK && partLen == 0, + "CTS decrypt update: holds back the last two blocks"); + if (rv == CKR_OK) { + lastLen = wrap + 8; + rv = funcList->C_DecryptFinal(session, out, &lastLen); + CHECK_RV(rv, "CTS decrypt update: C_DecryptFinal with large buffer " + "length", CKR_OK); + CHECK_TRUE(rv == CKR_OK && lastLen == sizeof(data), + "CTS decrypt update: C_DecryptFinal reports bytes written"); + } +} +#endif + +#if !defined(NO_AES) && (defined(HAVE_AESCTR) || defined(HAVE_AESCTS)) +/* Single-part and multi-part calls accept an output buffer length that does + * not fit in 32 bits and use the whole buffer. */ +static void check_stream_output_capacity(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key, + CK_MECHANISM* mech) +{ + CK_RV rv; + byte data[32]; + byte enc[64]; + byte out[64]; + CK_ULONG encLen; + CK_ULONG outLen; + CK_ULONG lastLen; + CK_ULONG wrap = ((CK_ULONG)1 << 16) << 16; + + XMEMSET(data, 0xDF, sizeof(data)); + + rv = funcList->C_EncryptInit(session, mech, key); + CHECK_RV(rv, "output capacity: C_EncryptInit", CKR_OK); + if (rv != CKR_OK) + return; + encLen = wrap + 8; + rv = funcList->C_Encrypt(session, data, sizeof(data), enc, &encLen); + CHECK_RV(rv, "output capacity: C_Encrypt with large buffer length", + CKR_OK); + CHECK_TRUE(rv == CKR_OK && encLen == sizeof(data), + "output capacity: C_Encrypt reports bytes written"); + if (rv != CKR_OK) + return; + + rv = funcList->C_DecryptInit(session, mech, key); + CHECK_RV(rv, "output capacity: C_DecryptInit", CKR_OK); + if (rv != CKR_OK) + return; + outLen = wrap + 8; + rv = funcList->C_Decrypt(session, enc, encLen, out, &outLen); + CHECK_RV(rv, "output capacity: C_Decrypt with large buffer length", + CKR_OK); + CHECK_TRUE(rv == CKR_OK && outLen == sizeof(data) && + XMEMCMP(out, data, sizeof(data)) == 0, + "output capacity: C_Decrypt round-trips"); + + rv = funcList->C_EncryptInit(session, mech, key); + CHECK_RV(rv, "output capacity: C_EncryptInit multi-part", CKR_OK); + if (rv != CKR_OK) + return; + outLen = wrap + 8; + rv = funcList->C_EncryptUpdate(session, data, sizeof(data), out, &outLen); + CHECK_RV(rv, "output capacity: C_EncryptUpdate with large buffer length", + CKR_OK); + if (rv != CKR_OK) + return; + lastLen = wrap + 8; + rv = funcList->C_EncryptFinal(session, out + outLen, &lastLen); + CHECK_RV(rv, "output capacity: C_EncryptFinal with large buffer length", + CKR_OK); + CHECK_TRUE(rv == CKR_OK && outLen + lastLen == encLen && + XMEMCMP(out, enc, encLen) == 0, + "output capacity: multi-part output matches single-part"); + + rv = funcList->C_DecryptInit(session, mech, key); + CHECK_RV(rv, "output capacity: C_DecryptInit multi-part", CKR_OK); + if (rv != CKR_OK) + return; + outLen = wrap + 8; + rv = funcList->C_DecryptUpdate(session, enc, encLen, out, &outLen); + CHECK_RV(rv, "output capacity: C_DecryptUpdate with large buffer length", + CKR_OK); + if (rv != CKR_OK) + return; + lastLen = wrap + 8; + rv = funcList->C_DecryptFinal(session, out + outLen, &lastLen); + CHECK_RV(rv, "output capacity: C_DecryptFinal with large buffer length", + CKR_OK); + CHECK_TRUE(rv == CKR_OK && outLen + lastLen == sizeof(data) && + XMEMCMP(out, data, sizeof(data)) == 0, + "output capacity: multi-part decrypt round-trips"); +} + +static void test_stream_output_capacity(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech; +#ifdef HAVE_AESCTR + CK_AES_CTR_PARAMS ctrParams; +#endif +#ifdef HAVE_AESCTS + byte iv[16]; +#endif + + if ((((CK_ULONG)1 << 16) << 16) == 0) + return; + + rv = create_aes_key(session, aes_128_key, sizeof(aes_128_key), &key); + CHECK_RV(rv, "output capacity: create AES key", CKR_OK); + if (rv != CKR_OK) + return; + +#ifdef HAVE_AESCTR + XMEMSET(&ctrParams, 0, sizeof(ctrParams)); + ctrParams.ulCounterBits = 32; + XMEMSET(ctrParams.cb, 0xE0, sizeof(ctrParams.cb)); + mech.mechanism = CKM_AES_CTR; + mech.pParameter = &ctrParams; + mech.ulParameterLen = sizeof(ctrParams); + printf("AES-CTR\n"); + check_stream_output_capacity(session, key, &mech); +#endif +#ifdef HAVE_AESCTS + XMEMSET(iv, 0xF1, sizeof(iv)); + mech.mechanism = CKM_AES_CTS; + mech.pParameter = iv; + mech.ulParameterLen = sizeof(iv); + printf("AES-CTS\n"); + check_stream_output_capacity(session, key, &mech); +#endif +} +#endif + #if !defined(NO_AES) && defined(HAVE_AESGCM) /* Multi-part AES-GCM decryption rejects a part that does not fit in 32 bits or * would grow the buffered ciphertext past INT_MAX, and ends the operation. */ @@ -1439,6 +1628,12 @@ static int run_test(void) #if !defined(NO_AES) && defined(HAVE_AESCTR) run_in_session(slot, test_ctr_decrypt_update_len); #endif +#if !defined(NO_AES) && defined(HAVE_AESCTS) + run_in_session(slot, test_cts_decrypt_update_len); +#endif +#if !defined(NO_AES) && (defined(HAVE_AESCTR) || defined(HAVE_AESCTS)) + run_in_session(slot, test_stream_output_capacity); +#endif #if !defined(NO_AES) && defined(HAVE_AESGCM) run_in_session(slot, test_gcm_decrypt_update_len_range); run_in_session(slot, test_gcm_encrypt_update_total_len); diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 2c6f27d3..dc677aca 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -815,7 +815,6 @@ WP11_LOCAL int WP11_AesCts_DecryptUpdate(unsigned char* enc, word32 encSz, WP11_Session* session); WP11_LOCAL int WP11_AesCts_DecryptFinal(unsigned char* dec, word32* decSz, WP11_Session* session); -WP11_LOCAL void WP11_AesCts_Final(WP11_Session* session); WP11_LOCAL int WP11_Aes_Cmac_Init(WP11_Object* secret, WP11_Session* session, word32 sigLen); From 8707b0af3f94e0b117cf5c196122717cab6d77d0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:55 -0700 Subject: [PATCH 203/204] Cap the sign output capacity passed to word32 helpers --- src/crypto.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto.c b/src/crypto.c index 885ce5f9..a16b057f 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -6788,7 +6788,7 @@ static CK_RV wp11_C_SignFinal(CK_SESSION_HANDLE hSession, } WP11_Session_GetData(session, &data, &dataLen); - sigLen = (word32)*pulSignatureLen; + sigLen = CK_ULONG_CAP_WORD32(*pulSignatureLen); ret = WP11_TLS_MAC_sign(data, dataLen, pSignature, &sigLen, session); WP11_Session_FreeData(session); From 036b2d2d6f4921444454b5b8d1da24f08b46f44c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:38:56 -0700 Subject: [PATCH 204/204] Refuse to save digest state after C_DigestKey --- src/internal.c | 13 +++++++ tests/digest_length_test.c | 77 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/internal.c b/src/internal.c index 8e6e62ea..d360929d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -449,6 +449,7 @@ typedef struct WP11_Hmac { typedef struct WP11_Digest { wc_HashAlg hash; enum wc_HashType hashType; + byte keyDigested; /* Key material fed by C_DigestKey */ } WP11_Digest; #ifdef HAVE_AESCMAC @@ -20363,6 +20364,7 @@ int WP11_Digest_Init(CK_MECHANISM_TYPE mechanism, WP11_Session* session) if (ret == 0) { WP11_Session_AbortOp(session); digest->hashType = (enum wc_HashType)hashType; + digest->keyDigested = 0; ret = wc_HashInit(&digest->hash, (enum wc_HashType)hashType); } @@ -20419,6 +20421,8 @@ int WP11_Digest_Key(WP11_Object* key, WP11_Session* session) if (key->onToken) { WP11_Lock_UnlockRO(key->lock); } + /* The hash context now holds key bytes: it must not leave the token. */ + digest->keyDigested = 1; #else (void) session; (void) key; @@ -20459,6 +20463,8 @@ int WP11_Digest_Final(unsigned char* data, word32* dataLen, ret = wc_HashFinal(&digest->hash, digest->hashType, data); *dataLen = blockLen; wc_HashFree(&digest->hash, digest->hashType); + wc_ForceZero(&digest->hash, sizeof(digest->hash)); + digest->keyDigested = 0; return ret; } @@ -20486,6 +20492,7 @@ int WP11_Digest_Single(unsigned char* data, word32 dataLen, blockLen = wc_HashGetDigestSize(digest->hashType); if (blockLen < 0) { wc_HashFree(&digest->hash, digest->hashType); + wc_ForceZero(&digest->hash, sizeof(digest->hash)); session->init = 0; return CKR_FUNCTION_FAILED; } @@ -20502,6 +20509,8 @@ int WP11_Digest_Single(unsigned char* data, word32 dataLen, *dataOutLen = (word32)blockLen; wc_HashFree(&digest->hash, digest->hashType); + wc_ForceZero(&digest->hash, sizeof(digest->hash)); + digest->keyDigested = 0; return ret; } @@ -20929,6 +20938,9 @@ int WP11_GetOperationState(WP11_Session* session, unsigned char* stateData, return CKR_OPERATION_NOT_INITIALIZED; if (!WP11_Session_IsOpInitialized(session, WP11_INIT_DIGEST)) return CKR_STATE_UNSAVEABLE; + /* The saved hash context would carry the bytes of a digested key. */ + if (session->params.digest.keyDigested) + return CKR_STATE_UNSAVEABLE; switch (session->mechanism) { #ifndef NO_MD5 @@ -21085,6 +21097,7 @@ int WP11_SetOperationState(WP11_Session* session, unsigned char* stateData, session->mechanism = mechanism; session->params.digest.hashType = (enum wc_HashType)hashType; + session->params.digest.keyDigested = 0; ret = wc_HashInit(&session->params.digest.hash, (enum wc_HashType)hashType); diff --git a/tests/digest_length_test.c b/tests/digest_length_test.c index 0a574ba7..b8cf6747 100644 --- a/tests/digest_length_test.c +++ b/tests/digest_length_test.c @@ -849,6 +849,80 @@ static void digest_key_value_test(CK_SESSION_HANDLE session) } #endif +#if !defined(NO_SHA256) && !defined(WOLFPKCS11_NO_STORE) && !defined(NO_AES) +/* Once C_DigestKey has fed key material into the hash, the operation state + * cannot be saved: it would hand out a sensitive key in the clear. */ +static void digest_key_state_unsaveable_test(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_AES; + byte value[16]; + byte state[512]; + byte hash[32]; + CK_ULONG stateLen; + CK_ULONG hashLen; + CK_ULONG i; + int found = 0; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_VALUE, value, sizeof(value) }, + }; + CK_ATTRIBUTE valueAttr = { CKA_VALUE, hash, sizeof(hash) }; + + XMEMSET(value, 0x5d, sizeof(value)); + rv = funcList->C_CreateObject(session, tmpl, sizeof(tmpl) / sizeof(*tmpl), + &key); + CHECK_RV(rv, "create sensitive non-extractable AES key", CKR_OK); + if (rv != CKR_OK) + return; + rv = funcList->C_GetAttributeValue(session, key, &valueAttr, 1); + CHECK_RV(rv, "C_GetAttributeValue refuses the sensitive key value", + CKR_ATTRIBUTE_SENSITIVE); + + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA256) before C_DigestKey", CKR_OK); + rv = funcList->C_DigestKey(session, key); + CHECK_RV(rv, "C_DigestKey(sensitive AES key)", CKR_OK); + stateLen = 0; + rv = funcList->C_GetOperationState(session, NULL, &stateLen); + CHECK_RV(rv, "C_GetOperationState length query after C_DigestKey", + CKR_STATE_UNSAVEABLE); + stateLen = sizeof(state); + XMEMSET(state, 0, sizeof(state)); + rv = funcList->C_GetOperationState(session, state, &stateLen); + CHECK_RV(rv, "C_GetOperationState after C_DigestKey", + CKR_STATE_UNSAVEABLE); + for (i = 0; i + sizeof(value) <= sizeof(state); i++) { + if (XMEMCMP(state + i, value, sizeof(value)) == 0) + found = 1; + } + CHECK_TRUE(!found, "saved operation state does not hold the key value"); + hashLen = sizeof(hash); + rv = funcList->C_DigestFinal(session, hash, &hashLen); + CHECK_RV(rv, "C_DigestFinal after a refused state save", CKR_OK); + + rv = funcList->C_DigestInit(session, &sha256Mech); + CHECK_RV(rv, "C_DigestInit(SHA256) without C_DigestKey", CKR_OK); + rv = funcList->C_DigestUpdate(session, value, sizeof(value)); + CHECK_RV(rv, "C_DigestUpdate without C_DigestKey", CKR_OK); + stateLen = sizeof(state); + rv = funcList->C_GetOperationState(session, state, &stateLen); + CHECK_RV(rv, "C_GetOperationState without C_DigestKey", CKR_OK); + hashLen = sizeof(hash); + rv = funcList->C_DigestFinal(session, hash, &hashLen); + CHECK_RV(rv, "C_DigestFinal after a state save", CKR_OK); + + funcList->C_DestroyObject(session, key); +} +#endif + #if !defined(NO_AES) && defined(HAVE_AESCMAC) static CK_OBJECT_CLASS aesKeyClass = CKO_SECRET_KEY; static CK_KEY_TYPE aesKeyType = CKK_AES; @@ -1337,6 +1411,9 @@ static int run_test(void) digest_empty_input_test(session); #ifndef WOLFPKCS11_NO_STORE digest_key_value_test(session); +#ifndef NO_AES + digest_key_state_unsaveable_test(session); +#endif #endif #endif #if !defined(NO_SHA256) && !defined(NO_HMAC)