From 57fc045cdf24cfedaa8395720776addeb8cce865 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 19 Jun 2026 09:42:59 +0000 Subject: [PATCH 1/9] Add AES-GCM C_Decrypt minimum ciphertext length test (F-3850) C_Decrypt with CKM_AES_GCM rejects ciphertext shorter than the auth tag (tagBits/8) with CKR_ENCRYPTED_DATA_LEN_RANGE, preventing the decDataLen length subtraction from underflowing. This path had no test coverage. Add a standalone regression test that initializes AES-GCM decrypt with ulTagBits=128 and asserts CKR_ENCRYPTED_DATA_LEN_RANGE for ciphertext lengths 0, 1 and 15, plus a boundary case at 16 bytes that is accepted by the length check and proceeds to authentication. --- tests/aes_gcm_decrypt_len_test.c | 465 +++++++++++++++++++++++++++++++ tests/include.am | 7 + 2 files changed, 472 insertions(+) create mode 100644 tests/aes_gcm_decrypt_len_test.c diff --git a/tests/aes_gcm_decrypt_len_test.c b/tests/aes_gcm_decrypt_len_test.c new file mode 100644 index 00000000..3ece4840 --- /dev/null +++ b/tests/aes_gcm_decrypt_len_test.c @@ -0,0 +1,465 @@ +/* aes_gcm_decrypt_len_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 + * + * Regression test for issue F-3850: C_Decrypt with CKM_AES_GCM must reject + * ciphertext shorter than the authentication tag with + * CKR_ENCRYPTED_DATA_LEN_RANGE. Without that guard the length subtraction + * (decDataLen = ulEncryptedDataLen - tagBits/8) underflows. + */ + +#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" + +#if !defined(NO_AES) && defined(HAVE_AESGCM) + +#define GCM_LEN_TEST_DIR "./store/aes_gcm_decrypt_len_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +/* tagBits = 128 -> minimum ciphertext length is 16 bytes */ +#define GCM_TAG_BITS 128 +#define GCM_MIN_CT_LEN (GCM_TAG_BITS / 8) + +static int test_passed = 0; +static int test_failed = 0; + +#define CHECK_CKR(rv, op, expected) do { \ + if (rv != expected) { \ + fprintf(stderr, "FAIL: %s: expected %ld, got %ld\n", op, (long)expected, (long)rv); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while(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*)"someUserPin"; +static int userPinLen = 11; + +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_KEY_TYPE aesKeyType = CKK_AES; + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_INFO info; + 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) { + fprintf(stderr, "Failed to get function list function\n"); + dlclose(dlib); + return -1; + } + + ret = func(&funcList); + if (ret != CKR_OK) { + fprintf(stderr, "Failed to get function list: 0x%lx\n", + (unsigned long)ret); + dlclose(dlib); + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) { + fprintf(stderr, "Failed to get function list: 0x%lx\n", + (unsigned long)ret); + 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_GetInfo(&info); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + + if (slotCount > 0) { + slot = slotList[0]; + } else { + fprintf(stderr, "No slots available\n"); + return CKR_GENERAL_ERROR; + } + + 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_init_token(void) +{ + unsigned char label[32]; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + + return funcList->C_InitToken(slot, soPin, soPinLen, label); +} + +static CK_RV pkcs11_open_session(CK_SESSION_HANDLE* session) +{ + CK_RV ret; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, session); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_Login(*session, CKU_USER, userPin, userPinLen); + if (ret != CKR_OK) { + funcList->C_CloseSession(*session); + return ret; + } + + return CKR_OK; +} + +static CK_RV pkcs11_close_session(CK_SESSION_HANDLE session) +{ + funcList->C_Logout(session); + return funcList->C_CloseSession(session); +} + +static void cleanup_test_files(const char* dir) +{ + char filepath[512]; + + snprintf(filepath, sizeof(filepath), "%s" PATH_SEP "%s", dir, + WOLFPKCS11_TOKEN_FILENAME); + (void)remove(filepath); +} + +static CK_RV create_aes_128_key(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + }; + CK_ULONG tmplCnt = sizeof(tmpl) / sizeof(*tmpl); + + return funcList->C_CreateObject(session, tmpl, tmplCnt, key); +} + +static void gcm_mech_init(CK_MECHANISM* mech, CK_GCM_PARAMS* gcmParams, + byte* iv, CK_ULONG ivLen) +{ + XMEMSET(iv, 9, ivLen); + gcmParams->pIv = iv; + gcmParams->ulIvLen = ivLen; + gcmParams->pAAD = NULL; + gcmParams->ulAADLen = 0; + gcmParams->ulTagBits = GCM_TAG_BITS; + + mech->mechanism = CKM_AES_GCM; + mech->ulParameterLen = sizeof(*gcmParams); + mech->pParameter = gcmParams; +} + +/* + * Test 1: C_Decrypt with ciphertext shorter than the tag (0, 1, 15 bytes) must + * be rejected with CKR_ENCRYPTED_DATA_LEN_RANGE. The operation is deinitialized + * on rejection, so it is re-initialized before each attempt. + */ +static int test_gcm_short_ciphertext(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key) +{ + CK_RV ret; + CK_MECHANISM mech; + CK_GCM_PARAMS gcmParams; + byte iv[12]; + byte encBuf[GCM_MIN_CT_LEN]; + byte dec[32]; + CK_ULONG decSz; + CK_ULONG shortLens[3]; + int i; + int result = 0; + + shortLens[0] = 0; + shortLens[1] = 1; + shortLens[2] = GCM_MIN_CT_LEN - 1; /* 15 */ + XMEMSET(encBuf, 0, sizeof(encBuf)); + + for (i = 0; i < 3; i++) { + gcm_mech_init(&mech, &gcmParams, iv, sizeof(iv)); + ret = funcList->C_DecryptInit(session, &mech, key); + CHECK_CKR(ret, "Test1: C_DecryptInit (short ciphertext)", CKR_OK); + + decSz = sizeof(dec); + ret = funcList->C_Decrypt(session, encBuf, shortLens[i], dec, &decSz); + CHECK_CKR(ret, "Test1: C_Decrypt short ciphertext rejected", + CKR_ENCRYPTED_DATA_LEN_RANGE); + } + +cleanup: + return result; +} + +/* + * Test 2: Boundary case. Ciphertext of exactly tagBits/8 (16) bytes must NOT be + * rejected by the length check (catches a '<' -> '<=' mutation). A size query + * (pData == NULL) returns CKR_OK with a derived length of 0; the subsequent + * real decrypt of a 16-byte all-zero buffer fails authentication and returns + * CKR_ENCRYPTED_DATA_INVALID, proving the call reached actual decryption. + */ +static int test_gcm_boundary_ciphertext(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key) +{ + CK_RV ret; + CK_MECHANISM mech; + CK_GCM_PARAMS gcmParams; + byte iv[12]; + byte encBuf[GCM_MIN_CT_LEN]; + byte dec[32]; + CK_ULONG decSz; + int result = 0; + + XMEMSET(encBuf, 0, sizeof(encBuf)); + + gcm_mech_init(&mech, &gcmParams, iv, sizeof(iv)); + ret = funcList->C_DecryptInit(session, &mech, key); + CHECK_CKR(ret, "Test2: C_DecryptInit (boundary)", CKR_OK); + + decSz = 0; + ret = funcList->C_Decrypt(session, encBuf, GCM_MIN_CT_LEN, NULL, &decSz); + CHECK_CKR(ret, "Test2: C_Decrypt boundary len=16 not length-rejected", + CKR_OK); + + if (decSz != 0) { + fprintf(stderr, "FAIL: Test2: expected derived length 0, got %lu\n", + (unsigned long)decSz); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: Test2: boundary derived length is 0\n"); + test_passed++; + + decSz = sizeof(dec); + ret = funcList->C_Decrypt(session, encBuf, GCM_MIN_CT_LEN, dec, &decSz); + CHECK_CKR(ret, "Test2: C_Decrypt boundary reaches authentication", + CKR_ENCRYPTED_DATA_INVALID); + +cleanup: + return result; +} + +static int aes_gcm_decrypt_len_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE key; + int result = 0; + + printf("\n=== Testing AES-GCM C_Decrypt minimum ciphertext length ===\n"); + + cleanup_test_files(GCM_LEN_TEST_DIR); + + ret = pkcs11_init(); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: pkcs11_init: 0x%lx\n", (unsigned long)ret); + test_failed++; + return -1; + } + + ret = pkcs11_init_token(); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_InitToken: 0x%lx\n", (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + /* Set user PIN via SO session */ + { + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_OpenSession (SO): 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_Login (SO): 0x%lx\n", + (unsigned long)ret); + test_failed++; + funcList->C_CloseSession(soSession); + pkcs11_final(); + return -1; + } + + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_InitPIN: 0x%lx\n", (unsigned long)ret); + test_failed++; + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + pkcs11_final(); + return -1; + } + + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + } + + ret = pkcs11_open_session(&session); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: pkcs11_open_session: 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + ret = create_aes_128_key(session, &key); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: create_aes_128_key: 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_close_session(session); + pkcs11_final(); + return -1; + } + + if (test_gcm_short_ciphertext(session, key) != 0) + result = -1; + if (test_gcm_boundary_ciphertext(session, key) != 0) + result = -1; + + pkcs11_close_session(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", GCM_LEN_TEST_DIR, 1); +#endif + + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 AES-GCM Decrypt Length Range Test ===\n"); + + (void)aes_gcm_decrypt_len_test(); + + print_results(); + + return (test_failed == 0) ? 0 : 1; +} + +#else /* NO_AES || !HAVE_AESGCM */ + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + + printf("AES-GCM not available, skipping decrypt length range test\n"); + return 0; +} + +#endif /* !NO_AES && HAVE_AESGCM */ diff --git a/tests/include.am b/tests/include.am index ac9b98a3..11260f29 100644 --- a/tests/include.am +++ b/tests/include.am @@ -76,6 +76,11 @@ noinst_PROGRAMS += tests/pbkdf2_keygen_attrs_test tests_pbkdf2_keygen_attrs_test_SOURCES = tests/pbkdf2_keygen_attrs_test.c tests_pbkdf2_keygen_attrs_test_LDADD = +check_PROGRAMS += tests/aes_gcm_decrypt_len_test +noinst_PROGRAMS += tests/aes_gcm_decrypt_len_test +tests_aes_gcm_decrypt_len_test_SOURCES = tests/aes_gcm_decrypt_len_test.c +tests_aes_gcm_decrypt_len_test_LDADD = + check_PROGRAMS += tests/pkcs11v3test noinst_PROGRAMS += tests/pkcs11v3test tests_pkcs11v3test_SOURCES = tests/pkcs11v3test.c @@ -167,6 +172,7 @@ tests_ecb_check_value_error_test_LDADD += src/libwolfpkcs11.la tests_operation_active_test_LDADD += src/libwolfpkcs11.la tests_aes_keygen_attrs_test_LDADD += src/libwolfpkcs11.la tests_pbkdf2_keygen_attrs_test_LDADD += src/libwolfpkcs11.la +tests_aes_gcm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_pkcs11v3test_LDADD += src/libwolfpkcs11.la tests_rsa_exponent_test_LDADD += src/libwolfpkcs11.la tests_pkcs11_compliance_test_LDADD += src/libwolfpkcs11.la @@ -192,6 +198,7 @@ tests_ecb_check_value_error_test_LDADD += src/libwolfpkcs11.la tests_operation_active_test_LDADD += src/libwolfpkcs11.la tests_aes_keygen_attrs_test_LDADD += src/libwolfpkcs11.la tests_pbkdf2_keygen_attrs_test_LDADD += src/libwolfpkcs11.la +tests_aes_gcm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_pkcs11_compliance_test_LDADD += src/libwolfpkcs11.la tests_verify_recover_badkey_test_LDADD += src/libwolfpkcs11.la tests_login_pin_len_range_test_LDADD += src/libwolfpkcs11.la From a41ad9a3c3a4cb2963777e59915b75d7f4a6fae7 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 19 Jun 2026 09:46:39 +0000 Subject: [PATCH 2/9] Add AES-CCM C_Decrypt minimum ciphertext length test (F-3851) C_Decrypt with CKM_AES_CCM rejects ciphertext shorter than the configured MAC length with CKR_ENCRYPTED_DATA_LEN_RANGE, preventing the decDataLen length subtraction from underflowing. This path had no test coverage. Add a standalone regression test that initializes AES-CCM decrypt with a 16-byte MAC and asserts CKR_ENCRYPTED_DATA_LEN_RANGE for ciphertext lengths 0, 1 and 15, plus a boundary case at 16 bytes that is accepted by the length check and proceeds to authentication. --- tests/aes_ccm_decrypt_len_test.c | 465 +++++++++++++++++++++++++++++++ tests/include.am | 7 + 2 files changed, 472 insertions(+) create mode 100644 tests/aes_ccm_decrypt_len_test.c diff --git a/tests/aes_ccm_decrypt_len_test.c b/tests/aes_ccm_decrypt_len_test.c new file mode 100644 index 00000000..af36cf05 --- /dev/null +++ b/tests/aes_ccm_decrypt_len_test.c @@ -0,0 +1,465 @@ +/* aes_ccm_decrypt_len_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 + * + * Regression test for issue F-3851: C_Decrypt with CKM_AES_CCM must reject + * ciphertext shorter than the configured MAC length with + * CKR_ENCRYPTED_DATA_LEN_RANGE. Without that guard the length subtraction + * (decDataLen = ulEncryptedDataLen - macLen) underflows. + */ + +#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" + +#if !defined(NO_AES) && defined(HAVE_AESCCM) + +#define CCM_LEN_TEST_DIR "./store/aes_ccm_decrypt_len_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +/* MAC length configured for the operation -> minimum ciphertext length */ +#define CCM_MAC_LEN 16 + +static int test_passed = 0; +static int test_failed = 0; + +#define CHECK_CKR(rv, op, expected) do { \ + if (rv != expected) { \ + fprintf(stderr, "FAIL: %s: expected %ld, got %ld\n", op, (long)expected, (long)rv); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while(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*)"someUserPin"; +static int userPinLen = 11; + +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_KEY_TYPE aesKeyType = CKK_AES; + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_INFO info; + 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) { + fprintf(stderr, "Failed to get function list function\n"); + dlclose(dlib); + return -1; + } + + ret = func(&funcList); + if (ret != CKR_OK) { + fprintf(stderr, "Failed to get function list: 0x%lx\n", + (unsigned long)ret); + dlclose(dlib); + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) { + fprintf(stderr, "Failed to get function list: 0x%lx\n", + (unsigned long)ret); + 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_GetInfo(&info); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + + if (slotCount > 0) { + slot = slotList[0]; + } else { + fprintf(stderr, "No slots available\n"); + return CKR_GENERAL_ERROR; + } + + 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_init_token(void) +{ + unsigned char label[32]; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + + return funcList->C_InitToken(slot, soPin, soPinLen, label); +} + +static CK_RV pkcs11_open_session(CK_SESSION_HANDLE* session) +{ + CK_RV ret; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, session); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_Login(*session, CKU_USER, userPin, userPinLen); + if (ret != CKR_OK) { + funcList->C_CloseSession(*session); + return ret; + } + + return CKR_OK; +} + +static CK_RV pkcs11_close_session(CK_SESSION_HANDLE session) +{ + funcList->C_Logout(session); + return funcList->C_CloseSession(session); +} + +static void cleanup_test_files(const char* dir) +{ + char filepath[512]; + + snprintf(filepath, sizeof(filepath), "%s" PATH_SEP "%s", dir, + WOLFPKCS11_TOKEN_FILENAME); + (void)remove(filepath); +} + +static CK_RV create_aes_128_key(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + }; + CK_ULONG tmplCnt = sizeof(tmpl) / sizeof(*tmpl); + + return funcList->C_CreateObject(session, tmpl, tmplCnt, key); +} + +static void ccm_mech_init(CK_MECHANISM* mech, CK_CCM_PARAMS* ccmParams, + byte* iv, CK_ULONG ivLen) +{ + XMEMSET(iv, 9, ivLen); + ccmParams->ulDataLen = 0; + ccmParams->pIv = iv; + ccmParams->ulIvLen = ivLen; + ccmParams->pAAD = NULL; + ccmParams->ulAADLen = 0; + ccmParams->ulMacLen = CCM_MAC_LEN; + + mech->mechanism = CKM_AES_CCM; + mech->ulParameterLen = sizeof(*ccmParams); + mech->pParameter = ccmParams; +} + +/* + * Test 1: C_Decrypt with ciphertext shorter than the MAC (0, 1, 15 bytes) must + * be rejected with CKR_ENCRYPTED_DATA_LEN_RANGE. The operation is deinitialized + * on rejection, so it is re-initialized before each attempt. + */ +static int test_ccm_short_ciphertext(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key) +{ + CK_RV ret; + CK_MECHANISM mech; + CK_CCM_PARAMS ccmParams; + byte iv[13]; + byte encBuf[CCM_MAC_LEN]; + byte dec[32]; + CK_ULONG decSz; + CK_ULONG shortLens[3]; + int i; + int result = 0; + + shortLens[0] = 0; + shortLens[1] = 1; + shortLens[2] = CCM_MAC_LEN - 1; /* 15 */ + XMEMSET(encBuf, 0, sizeof(encBuf)); + + for (i = 0; i < 3; i++) { + ccm_mech_init(&mech, &ccmParams, iv, sizeof(iv)); + ret = funcList->C_DecryptInit(session, &mech, key); + CHECK_CKR(ret, "Test1: C_DecryptInit (short ciphertext)", CKR_OK); + + decSz = sizeof(dec); + ret = funcList->C_Decrypt(session, encBuf, shortLens[i], dec, &decSz); + CHECK_CKR(ret, "Test1: C_Decrypt short ciphertext rejected", + CKR_ENCRYPTED_DATA_LEN_RANGE); + } + +cleanup: + return result; +} + +/* + * Test 2: Boundary case. Ciphertext of exactly macLen (16) bytes must NOT be + * rejected by the length check (catches a '<' -> '<=' mutation). A size query + * (pData == NULL) returns CKR_OK with a derived length of 0; the subsequent + * real decrypt of a 16-byte all-zero buffer fails authentication and returns + * CKR_ENCRYPTED_DATA_INVALID, proving the call reached actual decryption. + */ +static int test_ccm_boundary_ciphertext(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key) +{ + CK_RV ret; + CK_MECHANISM mech; + CK_CCM_PARAMS ccmParams; + byte iv[13]; + byte encBuf[CCM_MAC_LEN]; + byte dec[32]; + CK_ULONG decSz; + int result = 0; + + XMEMSET(encBuf, 0, sizeof(encBuf)); + + ccm_mech_init(&mech, &ccmParams, iv, sizeof(iv)); + ret = funcList->C_DecryptInit(session, &mech, key); + CHECK_CKR(ret, "Test2: C_DecryptInit (boundary)", CKR_OK); + + decSz = 0; + ret = funcList->C_Decrypt(session, encBuf, CCM_MAC_LEN, NULL, &decSz); + CHECK_CKR(ret, "Test2: C_Decrypt boundary len=16 not length-rejected", + CKR_OK); + + if (decSz != 0) { + fprintf(stderr, "FAIL: Test2: expected derived length 0, got %lu\n", + (unsigned long)decSz); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: Test2: boundary derived length is 0\n"); + test_passed++; + + decSz = sizeof(dec); + ret = funcList->C_Decrypt(session, encBuf, CCM_MAC_LEN, dec, &decSz); + CHECK_CKR(ret, "Test2: C_Decrypt boundary reaches authentication", + CKR_ENCRYPTED_DATA_INVALID); + +cleanup: + return result; +} + +static int aes_ccm_decrypt_len_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE key; + int result = 0; + + printf("\n=== Testing AES-CCM C_Decrypt minimum ciphertext length ===\n"); + + cleanup_test_files(CCM_LEN_TEST_DIR); + + ret = pkcs11_init(); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: pkcs11_init: 0x%lx\n", (unsigned long)ret); + test_failed++; + return -1; + } + + ret = pkcs11_init_token(); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_InitToken: 0x%lx\n", (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + /* Set user PIN via SO session */ + { + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_OpenSession (SO): 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_Login (SO): 0x%lx\n", + (unsigned long)ret); + test_failed++; + funcList->C_CloseSession(soSession); + pkcs11_final(); + return -1; + } + + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_InitPIN: 0x%lx\n", (unsigned long)ret); + test_failed++; + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + pkcs11_final(); + return -1; + } + + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + } + + ret = pkcs11_open_session(&session); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: pkcs11_open_session: 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + ret = create_aes_128_key(session, &key); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: create_aes_128_key: 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_close_session(session); + pkcs11_final(); + return -1; + } + + if (test_ccm_short_ciphertext(session, key) != 0) + result = -1; + if (test_ccm_boundary_ciphertext(session, key) != 0) + result = -1; + + pkcs11_close_session(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", CCM_LEN_TEST_DIR, 1); +#endif + + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 AES-CCM Decrypt Length Range Test ===\n"); + + (void)aes_ccm_decrypt_len_test(); + + print_results(); + + return (test_failed == 0) ? 0 : 1; +} + +#else /* NO_AES || !HAVE_AESCCM */ + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + + printf("AES-CCM not available, skipping decrypt length range test\n"); + return 0; +} + +#endif /* !NO_AES && HAVE_AESCCM */ diff --git a/tests/include.am b/tests/include.am index 11260f29..c9a42c45 100644 --- a/tests/include.am +++ b/tests/include.am @@ -81,6 +81,11 @@ noinst_PROGRAMS += tests/aes_gcm_decrypt_len_test tests_aes_gcm_decrypt_len_test_SOURCES = tests/aes_gcm_decrypt_len_test.c tests_aes_gcm_decrypt_len_test_LDADD = +check_PROGRAMS += tests/aes_ccm_decrypt_len_test +noinst_PROGRAMS += tests/aes_ccm_decrypt_len_test +tests_aes_ccm_decrypt_len_test_SOURCES = tests/aes_ccm_decrypt_len_test.c +tests_aes_ccm_decrypt_len_test_LDADD = + check_PROGRAMS += tests/pkcs11v3test noinst_PROGRAMS += tests/pkcs11v3test tests_pkcs11v3test_SOURCES = tests/pkcs11v3test.c @@ -173,6 +178,7 @@ tests_operation_active_test_LDADD += src/libwolfpkcs11.la tests_aes_keygen_attrs_test_LDADD += src/libwolfpkcs11.la tests_pbkdf2_keygen_attrs_test_LDADD += src/libwolfpkcs11.la tests_aes_gcm_decrypt_len_test_LDADD += src/libwolfpkcs11.la +tests_aes_ccm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_pkcs11v3test_LDADD += src/libwolfpkcs11.la tests_rsa_exponent_test_LDADD += src/libwolfpkcs11.la tests_pkcs11_compliance_test_LDADD += src/libwolfpkcs11.la @@ -199,6 +205,7 @@ tests_operation_active_test_LDADD += src/libwolfpkcs11.la tests_aes_keygen_attrs_test_LDADD += src/libwolfpkcs11.la tests_pbkdf2_keygen_attrs_test_LDADD += src/libwolfpkcs11.la tests_aes_gcm_decrypt_len_test_LDADD += src/libwolfpkcs11.la +tests_aes_ccm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_pkcs11_compliance_test_LDADD += src/libwolfpkcs11.la tests_verify_recover_badkey_test_LDADD += src/libwolfpkcs11.la tests_login_pin_len_range_test_LDADD += src/libwolfpkcs11.la From 45e89e2b0be9ffed3277f15060509d3d14240d72 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 19 Jun 2026 09:49:48 +0000 Subject: [PATCH 3/9] Add AES-KEY-WRAP C_Decrypt minimum ciphertext length test (F-3852) C_Decrypt with CKM_AES_KEY_WRAP and CKM_AES_KEY_WRAP_PAD rejects ciphertext shorter than two 8-byte semiblocks (RFC 3394 minimum, 16 bytes) with CKR_ENCRYPTED_DATA_LEN_RANGE, preventing the decDataLen length subtraction from underflowing. This path had no test coverage. Add a standalone regression test that asserts CKR_ENCRYPTED_DATA_LEN_RANGE for ciphertext lengths 0, 8 and 15 for both mechanisms, plus a boundary case at 16 bytes that is accepted by the length check and proceeds to the unwrap integrity check. --- tests/aes_keywrap_decrypt_len_test.c | 438 +++++++++++++++++++++++++++ tests/include.am | 7 + 2 files changed, 445 insertions(+) create mode 100644 tests/aes_keywrap_decrypt_len_test.c diff --git a/tests/aes_keywrap_decrypt_len_test.c b/tests/aes_keywrap_decrypt_len_test.c new file mode 100644 index 00000000..99452175 --- /dev/null +++ b/tests/aes_keywrap_decrypt_len_test.c @@ -0,0 +1,438 @@ +/* aes_keywrap_decrypt_len_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 + * + * Regression test for issue F-3852: C_Decrypt with CKM_AES_KEY_WRAP and + * CKM_AES_KEY_WRAP_PAD must reject ciphertext shorter than two semiblocks + * (2 * 8 = 16 bytes, RFC 3394) with CKR_ENCRYPTED_DATA_LEN_RANGE. Without that + * guard the length subtraction (decDataLen = ulEncryptedDataLen - 8) underflows + * and an undersized ciphertext reaches WP11_AesKeyWrap_Decrypt. + */ + +#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" + +#if !defined(NO_AES) && defined(HAVE_AES_KEYWRAP) + +#define KW_LEN_TEST_DIR "./store/aes_keywrap_decrypt_len_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +/* RFC 3394 minimum wrapped length: two 8-byte semiblocks */ +#define KW_MIN_CT_LEN 16 + +static int test_passed = 0; +static int test_failed = 0; + +#define CHECK_CKR(rv, op, expected) do { \ + if (rv != expected) { \ + fprintf(stderr, "FAIL: %s: expected %ld, got %ld\n", op, (long)expected, (long)rv); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while(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*)"someUserPin"; +static int userPinLen = 11; + +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_KEY_TYPE aesKeyType = CKK_AES; + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_INFO info; + 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) { + fprintf(stderr, "Failed to get function list function\n"); + dlclose(dlib); + return -1; + } + + ret = func(&funcList); + if (ret != CKR_OK) { + fprintf(stderr, "Failed to get function list: 0x%lx\n", + (unsigned long)ret); + dlclose(dlib); + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) { + fprintf(stderr, "Failed to get function list: 0x%lx\n", + (unsigned long)ret); + 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_GetInfo(&info); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + + if (slotCount > 0) { + slot = slotList[0]; + } else { + fprintf(stderr, "No slots available\n"); + return CKR_GENERAL_ERROR; + } + + 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_init_token(void) +{ + unsigned char label[32]; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + + return funcList->C_InitToken(slot, soPin, soPinLen, label); +} + +static CK_RV pkcs11_open_session(CK_SESSION_HANDLE* session) +{ + CK_RV ret; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, session); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_Login(*session, CKU_USER, userPin, userPinLen); + if (ret != CKR_OK) { + funcList->C_CloseSession(*session); + return ret; + } + + return CKR_OK; +} + +static CK_RV pkcs11_close_session(CK_SESSION_HANDLE session) +{ + funcList->C_Logout(session); + return funcList->C_CloseSession(session); +} + +static void cleanup_test_files(const char* dir) +{ + char filepath[512]; + + snprintf(filepath, sizeof(filepath), "%s" PATH_SEP "%s", dir, + WOLFPKCS11_TOKEN_FILENAME); + (void)remove(filepath); +} + +static CK_RV create_aes_128_key(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + }; + CK_ULONG tmplCnt = sizeof(tmpl) / sizeof(*tmpl); + + return funcList->C_CreateObject(session, tmpl, tmplCnt, key); +} + +/* + * For a given key-wrap mechanism, verify: + * - ciphertext shorter than 2 semiblocks (0, 8, 15 bytes) is rejected with + * CKR_ENCRYPTED_DATA_LEN_RANGE (operation re-initialized between attempts); + * - boundary length 16 is NOT rejected by the length check (catches a + * '<' -> '<=' mutation): a size query returns CKR_OK with a derived length + * of 8, and the real decrypt of garbage reaches WP11_AesKeyWrap_Decrypt and + * fails the integrity check with CKR_ENCRYPTED_DATA_INVALID. + */ +static int test_keywrap_mech(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE key, + CK_MECHANISM_TYPE mechType, const char* name) +{ + CK_RV ret; + CK_MECHANISM mech; + byte encBuf[KW_MIN_CT_LEN]; + byte dec[32]; + CK_ULONG decSz; + CK_ULONG shortLens[3]; + int i; + int result = 0; + char msg[128]; + + mech.mechanism = mechType; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + + shortLens[0] = 0; + shortLens[1] = 8; + shortLens[2] = KW_MIN_CT_LEN - 1; /* 15 */ + XMEMSET(encBuf, 0, sizeof(encBuf)); + + for (i = 0; i < 3; i++) { + snprintf(msg, sizeof(msg), "%s: C_DecryptInit (short ciphertext)", + name); + ret = funcList->C_DecryptInit(session, &mech, key); + CHECK_CKR(ret, msg, CKR_OK); + + snprintf(msg, sizeof(msg), "%s: C_Decrypt short ciphertext rejected", + name); + decSz = sizeof(dec); + ret = funcList->C_Decrypt(session, encBuf, shortLens[i], dec, &decSz); + CHECK_CKR(ret, msg, CKR_ENCRYPTED_DATA_LEN_RANGE); + } + + snprintf(msg, sizeof(msg), "%s: C_DecryptInit (boundary)", name); + ret = funcList->C_DecryptInit(session, &mech, key); + CHECK_CKR(ret, msg, CKR_OK); + + snprintf(msg, sizeof(msg), "%s: C_Decrypt boundary len=16 not " + "length-rejected", name); + decSz = 0; + ret = funcList->C_Decrypt(session, encBuf, KW_MIN_CT_LEN, NULL, &decSz); + CHECK_CKR(ret, msg, CKR_OK); + + if (decSz != KW_MIN_CT_LEN - 8) { + fprintf(stderr, "FAIL: %s: expected derived length 8, got %lu\n", + name, (unsigned long)decSz); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: %s: boundary derived length is 8\n", name); + test_passed++; + + snprintf(msg, sizeof(msg), "%s: C_Decrypt boundary reaches unwrap", name); + decSz = sizeof(dec); + ret = funcList->C_Decrypt(session, encBuf, KW_MIN_CT_LEN, dec, &decSz); + CHECK_CKR(ret, msg, CKR_ENCRYPTED_DATA_INVALID); + +cleanup: + return result; +} + +static int aes_keywrap_decrypt_len_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE key; + int result = 0; + + printf("\n=== Testing AES-KEY-WRAP C_Decrypt minimum ciphertext length " + "===\n"); + + cleanup_test_files(KW_LEN_TEST_DIR); + + ret = pkcs11_init(); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: pkcs11_init: 0x%lx\n", (unsigned long)ret); + test_failed++; + return -1; + } + + ret = pkcs11_init_token(); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_InitToken: 0x%lx\n", (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + /* Set user PIN via SO session */ + { + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_OpenSession (SO): 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_Login (SO): 0x%lx\n", + (unsigned long)ret); + test_failed++; + funcList->C_CloseSession(soSession); + pkcs11_final(); + return -1; + } + + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_InitPIN: 0x%lx\n", (unsigned long)ret); + test_failed++; + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + pkcs11_final(); + return -1; + } + + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + } + + ret = pkcs11_open_session(&session); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: pkcs11_open_session: 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + ret = create_aes_128_key(session, &key); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: create_aes_128_key: 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_close_session(session); + pkcs11_final(); + return -1; + } + + if (test_keywrap_mech(session, key, CKM_AES_KEY_WRAP, + "AES_KEY_WRAP") != 0) + result = -1; + if (test_keywrap_mech(session, key, CKM_AES_KEY_WRAP_PAD, + "AES_KEY_WRAP_PAD") != 0) + result = -1; + + pkcs11_close_session(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", KW_LEN_TEST_DIR, 1); +#endif + + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 AES-KEY-WRAP Decrypt Length Range Test ===\n"); + + (void)aes_keywrap_decrypt_len_test(); + + print_results(); + + return (test_failed == 0) ? 0 : 1; +} + +#else /* NO_AES || !HAVE_AES_KEYWRAP */ + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + + printf("AES key wrap not available, skipping decrypt length range test\n"); + return 0; +} + +#endif /* !NO_AES && HAVE_AES_KEYWRAP */ diff --git a/tests/include.am b/tests/include.am index c9a42c45..70f4afc9 100644 --- a/tests/include.am +++ b/tests/include.am @@ -86,6 +86,11 @@ noinst_PROGRAMS += tests/aes_ccm_decrypt_len_test tests_aes_ccm_decrypt_len_test_SOURCES = tests/aes_ccm_decrypt_len_test.c tests_aes_ccm_decrypt_len_test_LDADD = +check_PROGRAMS += tests/aes_keywrap_decrypt_len_test +noinst_PROGRAMS += tests/aes_keywrap_decrypt_len_test +tests_aes_keywrap_decrypt_len_test_SOURCES = tests/aes_keywrap_decrypt_len_test.c +tests_aes_keywrap_decrypt_len_test_LDADD = + check_PROGRAMS += tests/pkcs11v3test noinst_PROGRAMS += tests/pkcs11v3test tests_pkcs11v3test_SOURCES = tests/pkcs11v3test.c @@ -179,6 +184,7 @@ tests_aes_keygen_attrs_test_LDADD += src/libwolfpkcs11.la tests_pbkdf2_keygen_attrs_test_LDADD += src/libwolfpkcs11.la tests_aes_gcm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_aes_ccm_decrypt_len_test_LDADD += src/libwolfpkcs11.la +tests_aes_keywrap_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_pkcs11v3test_LDADD += src/libwolfpkcs11.la tests_rsa_exponent_test_LDADD += src/libwolfpkcs11.la tests_pkcs11_compliance_test_LDADD += src/libwolfpkcs11.la @@ -206,6 +212,7 @@ tests_aes_keygen_attrs_test_LDADD += src/libwolfpkcs11.la tests_pbkdf2_keygen_attrs_test_LDADD += src/libwolfpkcs11.la tests_aes_gcm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_aes_ccm_decrypt_len_test_LDADD += src/libwolfpkcs11.la +tests_aes_keywrap_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_pkcs11_compliance_test_LDADD += src/libwolfpkcs11.la tests_verify_recover_badkey_test_LDADD += src/libwolfpkcs11.la tests_login_pin_len_range_test_LDADD += src/libwolfpkcs11.la From 11e172b01f6ef77439c0bc8c369f7badaf957a8a Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 19 Jun 2026 10:01:07 +0000 Subject: [PATCH 4/9] Add ML-KEM private key sensitive CKA_VALUE test (F-4532) MlKemObject_GetAttr withholds an ML-KEM private key's CKA_VALUE when noPriv = (CKA_SENSITIVE != 0) || (CKA_EXTRACTABLE == 0), returning CKR_ATTRIBUTE_SENSITIVE with ulValueLen = CK_UNAVAILABLE_INFORMATION. This guard had no test coverage (the existing ML-KEM test only used a non-sensitive, extractable key). Add a regression test that generates ML-KEM-512 key pairs and reads CKA_VALUE on the private key for: sensitive=TRUE/extractable=TRUE and sensitive=FALSE/extractable=FALSE (both withheld), and the sensitive=FALSE/extractable=TRUE control (key bytes returned). The two withheld cases exercise both arms of the noPriv disjunction. --- tests/include.am | 8 + tests/mlkem_sensitive_attr_test.c | 451 ++++++++++++++++++++++++++++++ 2 files changed, 459 insertions(+) create mode 100644 tests/mlkem_sensitive_attr_test.c diff --git a/tests/include.am b/tests/include.am index 70f4afc9..234b96c0 100644 --- a/tests/include.am +++ b/tests/include.am @@ -91,6 +91,12 @@ noinst_PROGRAMS += tests/aes_keywrap_decrypt_len_test tests_aes_keywrap_decrypt_len_test_SOURCES = tests/aes_keywrap_decrypt_len_test.c tests_aes_keywrap_decrypt_len_test_LDADD = + +check_PROGRAMS += tests/mlkem_sensitive_attr_test +noinst_PROGRAMS += tests/mlkem_sensitive_attr_test +tests_mlkem_sensitive_attr_test_SOURCES = tests/mlkem_sensitive_attr_test.c +tests_mlkem_sensitive_attr_test_LDADD = + check_PROGRAMS += tests/pkcs11v3test noinst_PROGRAMS += tests/pkcs11v3test tests_pkcs11v3test_SOURCES = tests/pkcs11v3test.c @@ -185,6 +191,7 @@ tests_pbkdf2_keygen_attrs_test_LDADD += src/libwolfpkcs11.la tests_aes_gcm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_aes_ccm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_aes_keywrap_decrypt_len_test_LDADD += src/libwolfpkcs11.la +tests_mlkem_sensitive_attr_test_LDADD += src/libwolfpkcs11.la tests_pkcs11v3test_LDADD += src/libwolfpkcs11.la tests_rsa_exponent_test_LDADD += src/libwolfpkcs11.la tests_pkcs11_compliance_test_LDADD += src/libwolfpkcs11.la @@ -213,6 +220,7 @@ tests_pbkdf2_keygen_attrs_test_LDADD += src/libwolfpkcs11.la tests_aes_gcm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_aes_ccm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_aes_keywrap_decrypt_len_test_LDADD += src/libwolfpkcs11.la +tests_mlkem_sensitive_attr_test_LDADD += src/libwolfpkcs11.la tests_pkcs11_compliance_test_LDADD += src/libwolfpkcs11.la tests_verify_recover_badkey_test_LDADD += src/libwolfpkcs11.la tests_login_pin_len_range_test_LDADD += src/libwolfpkcs11.la diff --git a/tests/mlkem_sensitive_attr_test.c b/tests/mlkem_sensitive_attr_test.c new file mode 100644 index 00000000..a8960f65 --- /dev/null +++ b/tests/mlkem_sensitive_attr_test.c @@ -0,0 +1,451 @@ +/* mlkem_sensitive_attr_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 + * + * Regression test for issue F-4532: MlKemObject_GetAttr must not disclose an + * ML-KEM private key's CKA_VALUE when the key is sensitive or not extractable. + * Reading CKA_VALUE must return CKR_ATTRIBUTE_SENSITIVE with + * ulValueLen = CK_UNAVAILABLE_INFORMATION when + * noPriv = (SENSITIVE != 0) || (EXTRACTABLE == 0) is true, and the real key + * bytes otherwise. + */ + +#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" + +#ifdef WOLFPKCS11_MLKEM + +#define MLKEM_SENS_TEST_DIR "./store/mlkem_sensitive_attr_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +static int test_passed = 0; +static int test_failed = 0; + +#define CHECK_CKR(rv, op, expected) do { \ + if (rv != expected) { \ + fprintf(stderr, "FAIL: %s: expected %ld, got %ld\n", op, (long)expected, (long)rv); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while(0) + +#define CHECK_COND(cond, op) do { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL: %s\n", op); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while(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*)"someUserPin"; +static int userPinLen = 11; + +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_INFO info; + 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) { + fprintf(stderr, "Failed to get function list function\n"); + dlclose(dlib); + return -1; + } + + ret = func(&funcList); + if (ret != CKR_OK) { + fprintf(stderr, "Failed to get function list: 0x%lx\n", + (unsigned long)ret); + dlclose(dlib); + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) { + fprintf(stderr, "Failed to get function list: 0x%lx\n", + (unsigned long)ret); + 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_GetInfo(&info); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + + if (slotCount > 0) { + slot = slotList[0]; + } else { + fprintf(stderr, "No slots available\n"); + return CKR_GENERAL_ERROR; + } + + 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_init_token(void) +{ + unsigned char label[32]; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + + return funcList->C_InitToken(slot, soPin, soPinLen, label); +} + +static CK_RV pkcs11_open_session(CK_SESSION_HANDLE* session) +{ + CK_RV ret; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, session); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_Login(*session, CKU_USER, userPin, userPinLen); + if (ret != CKR_OK) { + funcList->C_CloseSession(*session); + return ret; + } + + return CKR_OK; +} + +static CK_RV pkcs11_close_session(CK_SESSION_HANDLE session) +{ + funcList->C_Logout(session); + return funcList->C_CloseSession(session); +} + +static void cleanup_test_files(const char* dir) +{ + char filepath[512]; + + snprintf(filepath, sizeof(filepath), "%s" PATH_SEP "%s", dir, + WOLFPKCS11_TOKEN_FILENAME); + (void)remove(filepath); +} + +/* Generate an ML-KEM-512 key pair with the requested CKA_SENSITIVE and + * CKA_EXTRACTABLE values on the private key. */ +static CK_RV gen_mlkem_keypair(CK_SESSION_HANDLE session, CK_BBOOL* sensitive, + CK_BBOOL* extractable, CK_OBJECT_HANDLE* priv, + CK_OBJECT_HANDLE* pub) +{ + CK_MECHANISM mech = { CKM_ML_KEM_KEY_PAIR_GEN, NULL, 0 }; + CK_ML_KEM_PARAMETER_SET_TYPE paramSet = CKP_ML_KEM_512; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + { CKA_ENCAPSULATE, &ckTrue, sizeof(ckTrue) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + CK_ULONG pubCnt = sizeof(pubTmpl) / sizeof(*pubTmpl); + CK_ATTRIBUTE privTmpl[] = { + { CKA_DECAPSULATE, &ckTrue, sizeof(ckTrue) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_SENSITIVE, sensitive, sizeof(*sensitive) }, + { CKA_EXTRACTABLE, extractable, sizeof(*extractable) }, + }; + CK_ULONG privCnt = sizeof(privTmpl) / sizeof(*privTmpl); + + return funcList->C_GenerateKeyPair(session, &mech, pubTmpl, pubCnt, + privTmpl, privCnt, pub, priv); +} + +/* Read CKA_VALUE on a private key expected to be protected; assert + * CKR_ATTRIBUTE_SENSITIVE and ulValueLen == CK_UNAVAILABLE_INFORMATION. */ +static int check_value_protected(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE priv, const char* label) +{ + CK_RV ret; + CK_BYTE buf[4096]; + CK_ATTRIBUTE getTmpl[] = { { CKA_VALUE, buf, sizeof(buf) } }; + char msg[160]; + int result = 0; + + XMEMSET(buf, 0, sizeof(buf)); + ret = funcList->C_GetAttributeValue(session, priv, getTmpl, 1); + snprintf(msg, sizeof(msg), "%s: C_GetAttributeValue(CKA_VALUE) sensitive", + label); + CHECK_CKR(ret, msg, CKR_ATTRIBUTE_SENSITIVE); + + snprintf(msg, sizeof(msg), "%s: ulValueLen is CK_UNAVAILABLE_INFORMATION", + label); + CHECK_COND(getTmpl[0].ulValueLen == CK_UNAVAILABLE_INFORMATION, msg); + +cleanup: + return result; +} + +static int mlkem_sensitive_attr_test(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE, pub = CK_INVALID_HANDLE; + CK_BYTE buf[4096]; + CK_ATTRIBUTE getVal[] = { { CKA_VALUE, NULL, 0 } }; + int result = 0; + + /* Case 1: sensitive=TRUE, extractable=TRUE -> protected (noPriv true via + * the SENSITIVE term). */ + ret = gen_mlkem_keypair(session, &ckTrue, &ckTrue, &priv, &pub); + CHECK_CKR(ret, "Case1: ML-KEM keygen (sensitive=TRUE)", CKR_OK); + if (check_value_protected(session, priv, "Case1 sensitive") != 0) + result = -1; + funcList->C_DestroyObject(session, priv); + funcList->C_DestroyObject(session, pub); + priv = pub = CK_INVALID_HANDLE; + + /* Case 2: sensitive=FALSE, extractable=FALSE -> protected (noPriv true via + * the EXTRACTABLE term). */ + ret = gen_mlkem_keypair(session, &ckFalse, &ckFalse, &priv, &pub); + CHECK_CKR(ret, "Case2: ML-KEM keygen (extractable=FALSE)", CKR_OK); + if (check_value_protected(session, priv, "Case2 unextractable") != 0) + result = -1; + funcList->C_DestroyObject(session, priv); + funcList->C_DestroyObject(session, pub); + priv = pub = CK_INVALID_HANDLE; + + /* Case 3 (positive control): sensitive=FALSE, extractable=TRUE -> the real + * private key bytes are returned (noPriv false). */ + ret = gen_mlkem_keypair(session, &ckFalse, &ckTrue, &priv, &pub); + CHECK_CKR(ret, "Case3: ML-KEM keygen (sensitive=FALSE,extractable=TRUE)", + CKR_OK); + + ret = funcList->C_GetAttributeValue(session, priv, getVal, 1); + CHECK_CKR(ret, "Case3: C_GetAttributeValue(CKA_VALUE) size query", CKR_OK); + CHECK_COND(getVal[0].ulValueLen != CK_UNAVAILABLE_INFORMATION && + getVal[0].ulValueLen > 0 && + getVal[0].ulValueLen <= sizeof(buf), + "Case3: size query returns a real length"); + + getVal[0].pValue = buf; + XMEMSET(buf, 0, sizeof(buf)); + ret = funcList->C_GetAttributeValue(session, priv, getVal, 1); + CHECK_CKR(ret, "Case3: C_GetAttributeValue(CKA_VALUE) returns key bytes", + CKR_OK); + CHECK_COND(getVal[0].ulValueLen != CK_UNAVAILABLE_INFORMATION && + getVal[0].ulValueLen > 0, + "Case3: private key bytes available"); + +cleanup: + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + return result; +} + +static int run_mlkem_sensitive_attr_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = 0; + int result = 0; + + printf("\n=== Testing ML-KEM private key CKA_VALUE sensitivity ===\n"); + + cleanup_test_files(MLKEM_SENS_TEST_DIR); + + ret = pkcs11_init(); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: pkcs11_init: 0x%lx\n", (unsigned long)ret); + test_failed++; + return -1; + } + + ret = pkcs11_init_token(); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_InitToken: 0x%lx\n", (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + /* Set user PIN via SO session */ + { + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_OpenSession (SO): 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_Login (SO): 0x%lx\n", + (unsigned long)ret); + test_failed++; + funcList->C_CloseSession(soSession); + pkcs11_final(); + return -1; + } + + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_InitPIN: 0x%lx\n", (unsigned long)ret); + test_failed++; + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + pkcs11_final(); + return -1; + } + + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + } + + ret = pkcs11_open_session(&session); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: pkcs11_open_session: 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + if (mlkem_sensitive_attr_test(session) != 0) + result = -1; + + pkcs11_close_session(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", MLKEM_SENS_TEST_DIR, 1); +#endif + + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 ML-KEM Sensitive CKA_VALUE Test ===\n"); + + (void)run_mlkem_sensitive_attr_test(); + + print_results(); + + return (test_failed == 0) ? 0 : 1; +} + +#else /* !WOLFPKCS11_MLKEM */ + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + + printf("ML-KEM not available, skipping sensitive CKA_VALUE test\n"); + return 0; +} + +#endif /* WOLFPKCS11_MLKEM */ From 8edcf14ec08435c12f0ebec09223e52e73164675 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 19 Jun 2026 10:05:20 +0000 Subject: [PATCH 5/9] Add C_UnwrapKey read-only token-object gate test (F-5522) C_UnwrapKey from a read-only session rejects an unwrap template with CKA_TOKEN=CK_TRUE with CKR_SESSION_READ_ONLY, preventing token-object creation from an R/O session. Only the allowed session-object case was tested; the rejection branch had no coverage. Add a regression test that wraps an AES secret, opens an R/O session and asserts C_UnwrapKey with CKA_TOKEN=CK_TRUE returns CKR_SESSION_READ_ONLY, with CKA_TOKEN=CK_FALSE succeeding as the positive counterpart. --- tests/include.am | 7 + tests/unwrapkey_ro_token_test.c | 427 ++++++++++++++++++++++++++++++++ 2 files changed, 434 insertions(+) create mode 100644 tests/unwrapkey_ro_token_test.c diff --git a/tests/include.am b/tests/include.am index 234b96c0..dccc48d1 100644 --- a/tests/include.am +++ b/tests/include.am @@ -97,6 +97,11 @@ noinst_PROGRAMS += tests/mlkem_sensitive_attr_test tests_mlkem_sensitive_attr_test_SOURCES = tests/mlkem_sensitive_attr_test.c tests_mlkem_sensitive_attr_test_LDADD = +check_PROGRAMS += tests/unwrapkey_ro_token_test +noinst_PROGRAMS += tests/unwrapkey_ro_token_test +tests_unwrapkey_ro_token_test_SOURCES = tests/unwrapkey_ro_token_test.c +tests_unwrapkey_ro_token_test_LDADD = + check_PROGRAMS += tests/pkcs11v3test noinst_PROGRAMS += tests/pkcs11v3test tests_pkcs11v3test_SOURCES = tests/pkcs11v3test.c @@ -192,6 +197,7 @@ tests_aes_gcm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_aes_ccm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_aes_keywrap_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_mlkem_sensitive_attr_test_LDADD += src/libwolfpkcs11.la +tests_unwrapkey_ro_token_test_LDADD += src/libwolfpkcs11.la tests_pkcs11v3test_LDADD += src/libwolfpkcs11.la tests_rsa_exponent_test_LDADD += src/libwolfpkcs11.la tests_pkcs11_compliance_test_LDADD += src/libwolfpkcs11.la @@ -221,6 +227,7 @@ tests_aes_gcm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_aes_ccm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_aes_keywrap_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_mlkem_sensitive_attr_test_LDADD += src/libwolfpkcs11.la +tests_unwrapkey_ro_token_test_LDADD += src/libwolfpkcs11.la tests_pkcs11_compliance_test_LDADD += src/libwolfpkcs11.la tests_verify_recover_badkey_test_LDADD += src/libwolfpkcs11.la tests_login_pin_len_range_test_LDADD += src/libwolfpkcs11.la diff --git a/tests/unwrapkey_ro_token_test.c b/tests/unwrapkey_ro_token_test.c new file mode 100644 index 00000000..936b3c43 --- /dev/null +++ b/tests/unwrapkey_ro_token_test.c @@ -0,0 +1,427 @@ +/* unwrapkey_ro_token_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 + * + * Regression test for issue F-5522: C_UnwrapKey from a read-only session must + * reject an unwrap template requesting a token object (CKA_TOKEN=CK_TRUE) with + * CKR_SESSION_READ_ONLY, while still allowing creation of a session object + * (CKA_TOKEN=CK_FALSE). + */ + +#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" + +/* NSS treats every session as read/write (WP11_Session_IsRW always returns + * true), so the read-only token-object gate does not apply to NSS builds. */ +#if !defined(NO_AES) && defined(HAVE_AES_KEYWRAP) && \ + !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_NSS) + +#define UNWRAP_RO_TEST_DIR "./store/unwrapkey_ro_token_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +static int test_passed = 0; +static int test_failed = 0; + +#define CHECK_CKR(rv, op, expected) do { \ + if (rv != expected) { \ + fprintf(stderr, "FAIL: %s: expected %ld, got %ld\n", op, (long)expected, (long)rv); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while(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*)"someUserPin"; +static int userPinLen = 11; + +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_KEY_TYPE aesKeyType = CKK_AES; +static CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_INFO info; + 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) { + fprintf(stderr, "Failed to get function list function\n"); + dlclose(dlib); + return -1; + } + + ret = func(&funcList); + if (ret != CKR_OK) { + fprintf(stderr, "Failed to get function list: 0x%lx\n", + (unsigned long)ret); + dlclose(dlib); + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) { + fprintf(stderr, "Failed to get function list: 0x%lx\n", + (unsigned long)ret); + 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_GetInfo(&info); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + + if (slotCount > 0) { + slot = slotList[0]; + } else { + fprintf(stderr, "No slots available\n"); + return CKR_GENERAL_ERROR; + } + + 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_init_token(void) +{ + unsigned char label[32]; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + + return funcList->C_InitToken(slot, soPin, soPinLen, label); +} + +/* Open a READ-ONLY session (no CKF_RW_SESSION) and log in as user. */ +static CK_RV pkcs11_open_ro_session(CK_SESSION_HANDLE* session) +{ + CK_RV ret; + int sessFlags = CKF_SERIAL_SESSION; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, session); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_Login(*session, CKU_USER, userPin, userPinLen); + if (ret != CKR_OK) { + funcList->C_CloseSession(*session); + return ret; + } + + return CKR_OK; +} + +static CK_RV pkcs11_close_session(CK_SESSION_HANDLE session) +{ + funcList->C_Logout(session); + return funcList->C_CloseSession(session); +} + +static void cleanup_test_files(const char* dir) +{ + char filepath[512]; + + snprintf(filepath, sizeof(filepath), "%s" PATH_SEP "%s", dir, + WOLFPKCS11_TOKEN_FILENAME); + (void)remove(filepath); +} + +/* AES-128 wrapping key (session object) with CKA_WRAP and CKA_UNWRAP. */ +static CK_RV create_wrapping_key(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_WRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_UNWRAP, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, + }; + CK_ULONG tmplCnt = sizeof(tmpl) / sizeof(*tmpl); + + return funcList->C_CreateObject(session, tmpl, tmplCnt, key); +} + +/* Extractable generic secret (session object) to be wrapped. */ +static CK_RV create_secret_to_wrap(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key) +{ + byte keyData[32]; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + }; + CK_ULONG tmplCnt = sizeof(tmpl) / sizeof(*tmpl); + + XMEMSET(keyData, 7, sizeof(keyData)); + return funcList->C_CreateObject(session, tmpl, tmplCnt, key); +} + +static int unwrapkey_ro_token_test(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_MECHANISM mech = { CKM_AES_KEY_WRAP, NULL, 0 }; + CK_OBJECT_HANDLE wrappingKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE secretKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE unwrapped = CK_INVALID_HANDLE; + byte wrappedKey[64]; + CK_ULONG wrappedKeyLen = sizeof(wrappedKey); + int result = 0; + + CK_ATTRIBUTE tokenTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + }; + CK_ATTRIBUTE sessionTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + + ret = create_wrapping_key(session, &wrappingKey); + CHECK_CKR(ret, "Create wrapping key", CKR_OK); + ret = create_secret_to_wrap(session, &secretKey); + CHECK_CKR(ret, "Create secret key to wrap", CKR_OK); + + ret = funcList->C_WrapKey(session, &mech, wrappingKey, secretKey, + wrappedKey, &wrappedKeyLen); + CHECK_CKR(ret, "C_WrapKey", CKR_OK); + + /* Negative: R/O session must reject CKA_TOKEN=TRUE in the unwrap template */ + ret = funcList->C_UnwrapKey(session, &mech, wrappingKey, wrappedKey, + wrappedKeyLen, tokenTmpl, + sizeof(tokenTmpl) / sizeof(*tokenTmpl), + &unwrapped); + CHECK_CKR(ret, "C_UnwrapKey CKA_TOKEN=TRUE in R/O session rejected", + CKR_SESSION_READ_ONLY); + + /* Positive: R/O session may create a session object (CKA_TOKEN=FALSE) */ + ret = funcList->C_UnwrapKey(session, &mech, wrappingKey, wrappedKey, + wrappedKeyLen, sessionTmpl, + sizeof(sessionTmpl) / sizeof(*sessionTmpl), + &unwrapped); + CHECK_CKR(ret, "C_UnwrapKey CKA_TOKEN=FALSE in R/O session allowed", + CKR_OK); + +cleanup: + if (unwrapped != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, unwrapped); + if (secretKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, secretKey); + if (wrappingKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, wrappingKey); + return result; +} + +static int run_unwrapkey_ro_token_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = 0; + int result = 0; + + printf("\n=== Testing C_UnwrapKey read-only token-object gate ===\n"); + + cleanup_test_files(UNWRAP_RO_TEST_DIR); + + ret = pkcs11_init(); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: pkcs11_init: 0x%lx\n", (unsigned long)ret); + test_failed++; + return -1; + } + + ret = pkcs11_init_token(); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_InitToken: 0x%lx\n", (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + /* Set user PIN via SO session */ + { + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_OpenSession (SO): 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_Login (SO): 0x%lx\n", + (unsigned long)ret); + test_failed++; + funcList->C_CloseSession(soSession); + pkcs11_final(); + return -1; + } + + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_InitPIN: 0x%lx\n", (unsigned long)ret); + test_failed++; + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + pkcs11_final(); + return -1; + } + + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + } + + ret = pkcs11_open_ro_session(&session); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: pkcs11_open_ro_session: 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + if (unwrapkey_ro_token_test(session) != 0) + result = -1; + + pkcs11_close_session(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", UNWRAP_RO_TEST_DIR, 1); +#endif + + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 C_UnwrapKey Read-Only Token Gate Test ===\n"); + + (void)run_unwrapkey_ro_token_test(); + + print_results(); + + return (test_failed == 0) ? 0 : 1; +} + +#else /* NO_AES || !HAVE_AES_KEYWRAP || WOLFPKCS11_NO_STORE || WOLFPKCS11_NSS */ + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + + printf("AES key wrap not available, skipping C_UnwrapKey R/O token test\n"); + return 0; +} + +#endif /* !NO_AES && HAVE_AES_KEYWRAP && !WOLFPKCS11_NO_STORE && !WOLFPKCS11_NSS */ From 8be2b75453f1a1d358a9106253896e60664516d1 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 19 Jun 2026 10:08:35 +0000 Subject: [PATCH 6/9] Add AES-CTR ulCounterBits range validation test (F-5523) WP11_Session_SetCtrParams rejects CK_AES_CTR_PARAMS with ulCounterBits == 0 or > 128, which C_EncryptInit and C_DecryptInit surface as CKR_MECHANISM_PARAM_INVALID. This range check had no negative test coverage. Add a regression test asserting CKR_MECHANISM_PARAM_INVALID for ulCounterBits 0 and 129 in both C_EncryptInit and C_DecryptInit, with a control at 128 that initializes and encrypts/decrypts successfully. --- tests/aes_ctr_counterbits_test.c | 444 +++++++++++++++++++++++++++++++ tests/include.am | 7 + 2 files changed, 451 insertions(+) create mode 100644 tests/aes_ctr_counterbits_test.c diff --git a/tests/aes_ctr_counterbits_test.c b/tests/aes_ctr_counterbits_test.c new file mode 100644 index 00000000..e587efc8 --- /dev/null +++ b/tests/aes_ctr_counterbits_test.c @@ -0,0 +1,444 @@ +/* aes_ctr_counterbits_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 + * + * Regression test for issue F-5523: AES-CTR setup must reject CK_AES_CTR_PARAMS + * with ulCounterBits == 0 or > 128 (CKR_MECHANISM_PARAM_INVALID) in both + * C_EncryptInit and C_DecryptInit, while accepting the valid value 128. + */ + +#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" + +#if !defined(NO_AES) && defined(HAVE_AESCTR) + +#define CTR_BITS_TEST_DIR "./store/aes_ctr_counterbits_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +static int test_passed = 0; +static int test_failed = 0; + +#define CHECK_CKR(rv, op, expected) do { \ + if (rv != expected) { \ + fprintf(stderr, "FAIL: %s: expected %ld, got %ld\n", op, (long)expected, (long)rv); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while(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*)"someUserPin"; +static int userPinLen = 11; + +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_KEY_TYPE aesKeyType = CKK_AES; + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_INFO info; + 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) { + fprintf(stderr, "Failed to get function list function\n"); + dlclose(dlib); + return -1; + } + + ret = func(&funcList); + if (ret != CKR_OK) { + fprintf(stderr, "Failed to get function list: 0x%lx\n", + (unsigned long)ret); + dlclose(dlib); + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) { + fprintf(stderr, "Failed to get function list: 0x%lx\n", + (unsigned long)ret); + 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_GetInfo(&info); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + + if (slotCount > 0) { + slot = slotList[0]; + } else { + fprintf(stderr, "No slots available\n"); + return CKR_GENERAL_ERROR; + } + + 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_init_token(void) +{ + unsigned char label[32]; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + + return funcList->C_InitToken(slot, soPin, soPinLen, label); +} + +static CK_RV pkcs11_open_session(CK_SESSION_HANDLE* session) +{ + CK_RV ret; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, session); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_Login(*session, CKU_USER, userPin, userPinLen); + if (ret != CKR_OK) { + funcList->C_CloseSession(*session); + return ret; + } + + return CKR_OK; +} + +static CK_RV pkcs11_close_session(CK_SESSION_HANDLE session) +{ + funcList->C_Logout(session); + return funcList->C_CloseSession(session); +} + +static void cleanup_test_files(const char* dir) +{ + char filepath[512]; + + snprintf(filepath, sizeof(filepath), "%s" PATH_SEP "%s", dir, + WOLFPKCS11_TOKEN_FILENAME); + (void)remove(filepath); +} + +static CK_RV create_aes_128_key(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType) }, + { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + }; + CK_ULONG tmplCnt = sizeof(tmpl) / sizeof(*tmpl); + + return funcList->C_CreateObject(session, tmpl, tmplCnt, key); +} + +static void ctr_mech_init(CK_MECHANISM* mech, CK_AES_CTR_PARAMS* ctrParams, + CK_ULONG counterBits) +{ + XMEMSET(ctrParams->cb, 0, sizeof(ctrParams->cb)); + ctrParams->ulCounterBits = counterBits; + + mech->mechanism = CKM_AES_CTR; + mech->pParameter = ctrParams; + mech->ulParameterLen = sizeof(*ctrParams); +} + +/* + * C_EncryptInit: ulCounterBits of 0 and 129 are rejected with + * CKR_MECHANISM_PARAM_INVALID; 128 is accepted (and a block is encrypted to + * leave the session clean). + */ +static int test_ctr_encrypt_init(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key) +{ + CK_RV ret; + CK_MECHANISM mech; + CK_AES_CTR_PARAMS ctrParams; + byte plain[16], enc[16]; + CK_ULONG encSz; + int result = 0; + + ctr_mech_init(&mech, &ctrParams, 0); + ret = funcList->C_EncryptInit(session, &mech, key); + CHECK_CKR(ret, "C_EncryptInit ulCounterBits=0 rejected", + CKR_MECHANISM_PARAM_INVALID); + + ctr_mech_init(&mech, &ctrParams, 129); + ret = funcList->C_EncryptInit(session, &mech, key); + CHECK_CKR(ret, "C_EncryptInit ulCounterBits=129 rejected", + CKR_MECHANISM_PARAM_INVALID); + + ctr_mech_init(&mech, &ctrParams, 128); + ret = funcList->C_EncryptInit(session, &mech, key); + CHECK_CKR(ret, "C_EncryptInit ulCounterBits=128 accepted", CKR_OK); + + XMEMSET(plain, 9, sizeof(plain)); + encSz = sizeof(enc); + ret = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encSz); + CHECK_CKR(ret, "C_Encrypt ulCounterBits=128", CKR_OK); + +cleanup: + return result; +} + +/* + * C_DecryptInit: ulCounterBits of 0 and 129 are rejected with + * CKR_MECHANISM_PARAM_INVALID; 128 is accepted (and a block is decrypted to + * leave the session clean). + */ +static int test_ctr_decrypt_init(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key) +{ + CK_RV ret; + CK_MECHANISM mech; + CK_AES_CTR_PARAMS ctrParams; + byte enc[16], dec[16]; + CK_ULONG decSz; + int result = 0; + + ctr_mech_init(&mech, &ctrParams, 0); + ret = funcList->C_DecryptInit(session, &mech, key); + CHECK_CKR(ret, "C_DecryptInit ulCounterBits=0 rejected", + CKR_MECHANISM_PARAM_INVALID); + + ctr_mech_init(&mech, &ctrParams, 129); + ret = funcList->C_DecryptInit(session, &mech, key); + CHECK_CKR(ret, "C_DecryptInit ulCounterBits=129 rejected", + CKR_MECHANISM_PARAM_INVALID); + + ctr_mech_init(&mech, &ctrParams, 128); + ret = funcList->C_DecryptInit(session, &mech, key); + CHECK_CKR(ret, "C_DecryptInit ulCounterBits=128 accepted", CKR_OK); + + XMEMSET(enc, 9, sizeof(enc)); + decSz = sizeof(dec); + ret = funcList->C_Decrypt(session, enc, sizeof(enc), dec, &decSz); + CHECK_CKR(ret, "C_Decrypt ulCounterBits=128", CKR_OK); + +cleanup: + return result; +} + +static int run_aes_ctr_counterbits_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE key; + int result = 0; + + printf("\n=== Testing AES-CTR ulCounterBits range validation ===\n"); + + cleanup_test_files(CTR_BITS_TEST_DIR); + + ret = pkcs11_init(); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: pkcs11_init: 0x%lx\n", (unsigned long)ret); + test_failed++; + return -1; + } + + ret = pkcs11_init_token(); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_InitToken: 0x%lx\n", (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + /* Set user PIN via SO session */ + { + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_OpenSession (SO): 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_Login (SO): 0x%lx\n", + (unsigned long)ret); + test_failed++; + funcList->C_CloseSession(soSession); + pkcs11_final(); + return -1; + } + + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: C_InitPIN: 0x%lx\n", (unsigned long)ret); + test_failed++; + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + pkcs11_final(); + return -1; + } + + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + } + + ret = pkcs11_open_session(&session); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: pkcs11_open_session: 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_final(); + return -1; + } + + ret = create_aes_128_key(session, &key); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: create_aes_128_key: 0x%lx\n", + (unsigned long)ret); + test_failed++; + pkcs11_close_session(session); + pkcs11_final(); + return -1; + } + + if (test_ctr_encrypt_init(session, key) != 0) + result = -1; + if (test_ctr_decrypt_init(session, key) != 0) + result = -1; + + pkcs11_close_session(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", CTR_BITS_TEST_DIR, 1); +#endif + + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 AES-CTR Counter Bits Range Test ===\n"); + + (void)run_aes_ctr_counterbits_test(); + + print_results(); + + return (test_failed == 0) ? 0 : 1; +} + +#else /* NO_AES || !HAVE_AESCTR */ + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + + printf("AES-CTR not available, skipping counter bits range test\n"); + return 0; +} + +#endif /* !NO_AES && HAVE_AESCTR */ diff --git a/tests/include.am b/tests/include.am index dccc48d1..6e1c81da 100644 --- a/tests/include.am +++ b/tests/include.am @@ -102,6 +102,11 @@ noinst_PROGRAMS += tests/unwrapkey_ro_token_test tests_unwrapkey_ro_token_test_SOURCES = tests/unwrapkey_ro_token_test.c tests_unwrapkey_ro_token_test_LDADD = +check_PROGRAMS += tests/aes_ctr_counterbits_test +noinst_PROGRAMS += tests/aes_ctr_counterbits_test +tests_aes_ctr_counterbits_test_SOURCES = tests/aes_ctr_counterbits_test.c +tests_aes_ctr_counterbits_test_LDADD = + check_PROGRAMS += tests/pkcs11v3test noinst_PROGRAMS += tests/pkcs11v3test tests_pkcs11v3test_SOURCES = tests/pkcs11v3test.c @@ -198,6 +203,7 @@ tests_aes_ccm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_aes_keywrap_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_mlkem_sensitive_attr_test_LDADD += src/libwolfpkcs11.la tests_unwrapkey_ro_token_test_LDADD += src/libwolfpkcs11.la +tests_aes_ctr_counterbits_test_LDADD += src/libwolfpkcs11.la tests_pkcs11v3test_LDADD += src/libwolfpkcs11.la tests_rsa_exponent_test_LDADD += src/libwolfpkcs11.la tests_pkcs11_compliance_test_LDADD += src/libwolfpkcs11.la @@ -228,6 +234,7 @@ tests_aes_ccm_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_aes_keywrap_decrypt_len_test_LDADD += src/libwolfpkcs11.la tests_mlkem_sensitive_attr_test_LDADD += src/libwolfpkcs11.la tests_unwrapkey_ro_token_test_LDADD += src/libwolfpkcs11.la +tests_aes_ctr_counterbits_test_LDADD += src/libwolfpkcs11.la tests_pkcs11_compliance_test_LDADD += src/libwolfpkcs11.la tests_verify_recover_badkey_test_LDADD += src/libwolfpkcs11.la tests_login_pin_len_range_test_LDADD += src/libwolfpkcs11.la From 68766632c0e6f0f3d022fe7636a5ae67a44fcf48 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 19 Jun 2026 10:11:14 +0000 Subject: [PATCH 7/9] Ignore examples/nss_pkcs12_pbe_example build artifact The NSS-only example binary was missing from .gitignore while every other example binary is listed. Add it so NSS builds leave a clean working tree. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6942eb1f..6dbdb9e6 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ examples/mech_info examples/obj_list examples/slot_info examples/token_info +examples/nss_pkcs12_pbe_example store/* test/* *.gcda From 3b9764c21c9a6825b682ebe0b6e9b9270e29da04 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 19 Jun 2026 14:04:20 +0000 Subject: [PATCH 8/9] Initialize CK_GCM_PARAMS.ulIvBits in AES-GCM decrypt length test Copilot review: gcm_mech_init() left ulIvBits uninitialized. Passing an uninitialized mechanism parameter field across the PKCS#11 API is non-deterministic under sanitizers and brittle if ulIvBits is later validated. Set it to 0 (use ulIvLen bytes). --- tests/aes_gcm_decrypt_len_test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/aes_gcm_decrypt_len_test.c b/tests/aes_gcm_decrypt_len_test.c index 3ece4840..76e622ca 100644 --- a/tests/aes_gcm_decrypt_len_test.c +++ b/tests/aes_gcm_decrypt_len_test.c @@ -230,6 +230,7 @@ static void gcm_mech_init(CK_MECHANISM* mech, CK_GCM_PARAMS* gcmParams, XMEMSET(iv, 9, ivLen); gcmParams->pIv = iv; gcmParams->ulIvLen = ivLen; + gcmParams->ulIvBits = 0; gcmParams->pAAD = NULL; gcmParams->ulAADLen = 0; gcmParams->ulTagBits = GCM_TAG_BITS; From 594864032df6cfcdc615c4370f24e4f08c0db6aa Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 19 Jun 2026 14:27:20 +0000 Subject: [PATCH 9/9] Use a 14+ character user PIN in new tests for FIPS FIPS requires the user PIN to be at least 14 characters (it is used for PBKDF2 key derivation). The new tests set an 11-character userPin, so C_InitPIN/C_Login failed under --enable-fips=v5. The AES-GCM/CCM/CTR tests (those not skipped by feature guards in the FIPS build) therefore failed. Use the 15-character 'wolfpkcs11-test' PIN, matching pkcs11test.c and the other test programs. --- tests/aes_ccm_decrypt_len_test.c | 4 ++-- tests/aes_ctr_counterbits_test.c | 4 ++-- tests/aes_gcm_decrypt_len_test.c | 4 ++-- tests/aes_keywrap_decrypt_len_test.c | 4 ++-- tests/mlkem_sensitive_attr_test.c | 4 ++-- tests/unwrapkey_ro_token_test.c | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/aes_ccm_decrypt_len_test.c b/tests/aes_ccm_decrypt_len_test.c index af36cf05..6919c44e 100644 --- a/tests/aes_ccm_decrypt_len_test.c +++ b/tests/aes_ccm_decrypt_len_test.c @@ -78,8 +78,8 @@ static CK_SLOT_ID slot = 0; static const char* tokenName = "wolfpkcs11"; static byte* soPin = (byte*)"password123456"; static int soPinLen = 14; -static byte* userPin = (byte*)"someUserPin"; -static int userPinLen = 11; +static byte* userPin = (byte*)"wolfpkcs11-test"; +static int userPinLen = 15; static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; static CK_BBOOL ckTrue = CK_TRUE; diff --git a/tests/aes_ctr_counterbits_test.c b/tests/aes_ctr_counterbits_test.c index e587efc8..df357d1d 100644 --- a/tests/aes_ctr_counterbits_test.c +++ b/tests/aes_ctr_counterbits_test.c @@ -74,8 +74,8 @@ static CK_SLOT_ID slot = 0; static const char* tokenName = "wolfpkcs11"; static byte* soPin = (byte*)"password123456"; static int soPinLen = 14; -static byte* userPin = (byte*)"someUserPin"; -static int userPinLen = 11; +static byte* userPin = (byte*)"wolfpkcs11-test"; +static int userPinLen = 15; static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; static CK_BBOOL ckTrue = CK_TRUE; diff --git a/tests/aes_gcm_decrypt_len_test.c b/tests/aes_gcm_decrypt_len_test.c index 76e622ca..86fb90b3 100644 --- a/tests/aes_gcm_decrypt_len_test.c +++ b/tests/aes_gcm_decrypt_len_test.c @@ -79,8 +79,8 @@ static CK_SLOT_ID slot = 0; static const char* tokenName = "wolfpkcs11"; static byte* soPin = (byte*)"password123456"; static int soPinLen = 14; -static byte* userPin = (byte*)"someUserPin"; -static int userPinLen = 11; +static byte* userPin = (byte*)"wolfpkcs11-test"; +static int userPinLen = 15; static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; static CK_BBOOL ckTrue = CK_TRUE; diff --git a/tests/aes_keywrap_decrypt_len_test.c b/tests/aes_keywrap_decrypt_len_test.c index 99452175..5a167689 100644 --- a/tests/aes_keywrap_decrypt_len_test.c +++ b/tests/aes_keywrap_decrypt_len_test.c @@ -79,8 +79,8 @@ static CK_SLOT_ID slot = 0; static const char* tokenName = "wolfpkcs11"; static byte* soPin = (byte*)"password123456"; static int soPinLen = 14; -static byte* userPin = (byte*)"someUserPin"; -static int userPinLen = 11; +static byte* userPin = (byte*)"wolfpkcs11-test"; +static int userPinLen = 15; static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; static CK_BBOOL ckTrue = CK_TRUE; diff --git a/tests/mlkem_sensitive_attr_test.c b/tests/mlkem_sensitive_attr_test.c index a8960f65..8fdf0227 100644 --- a/tests/mlkem_sensitive_attr_test.c +++ b/tests/mlkem_sensitive_attr_test.c @@ -89,8 +89,8 @@ static CK_SLOT_ID slot = 0; static const char* tokenName = "wolfpkcs11"; static byte* soPin = (byte*)"password123456"; static int soPinLen = 14; -static byte* userPin = (byte*)"someUserPin"; -static int userPinLen = 11; +static byte* userPin = (byte*)"wolfpkcs11-test"; +static int userPinLen = 15; static CK_BBOOL ckTrue = CK_TRUE; static CK_BBOOL ckFalse = CK_FALSE; diff --git a/tests/unwrapkey_ro_token_test.c b/tests/unwrapkey_ro_token_test.c index 936b3c43..4bbd5c7d 100644 --- a/tests/unwrapkey_ro_token_test.c +++ b/tests/unwrapkey_ro_token_test.c @@ -78,8 +78,8 @@ static CK_SLOT_ID slot = 0; static const char* tokenName = "wolfpkcs11"; static byte* soPin = (byte*)"password123456"; static int soPinLen = 14; -static byte* userPin = (byte*)"someUserPin"; -static int userPinLen = 11; +static byte* userPin = (byte*)"wolfpkcs11-test"; +static int userPinLen = 15; static CK_BBOOL ckTrue = CK_TRUE; static CK_BBOOL ckFalse = CK_FALSE;