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 diff --git a/tests/aes_ccm_decrypt_len_test.c b/tests/aes_ccm_decrypt_len_test.c new file mode 100644 index 00000000..6919c44e --- /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*)"wolfpkcs11-test"; +static int userPinLen = 15; + +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/aes_ctr_counterbits_test.c b/tests/aes_ctr_counterbits_test.c new file mode 100644 index 00000000..df357d1d --- /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*)"wolfpkcs11-test"; +static int userPinLen = 15; + +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/aes_gcm_decrypt_len_test.c b/tests/aes_gcm_decrypt_len_test.c new file mode 100644 index 00000000..86fb90b3 --- /dev/null +++ b/tests/aes_gcm_decrypt_len_test.c @@ -0,0 +1,466 @@ +/* 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*)"wolfpkcs11-test"; +static int userPinLen = 15; + +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->ulIvBits = 0; + 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/aes_keywrap_decrypt_len_test.c b/tests/aes_keywrap_decrypt_len_test.c new file mode 100644 index 00000000..5a167689 --- /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*)"wolfpkcs11-test"; +static int userPinLen = 15; + +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 ac9b98a3..6e1c81da 100644 --- a/tests/include.am +++ b/tests/include.am @@ -76,6 +76,37 @@ 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/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/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/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/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/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 @@ -167,6 +198,12 @@ 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_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 @@ -192,6 +229,12 @@ 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_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 diff --git a/tests/mlkem_sensitive_attr_test.c b/tests/mlkem_sensitive_attr_test.c new file mode 100644 index 00000000..8fdf0227 --- /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*)"wolfpkcs11-test"; +static int userPinLen = 15; + +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 */ diff --git a/tests/unwrapkey_ro_token_test.c b/tests/unwrapkey_ro_token_test.c new file mode 100644 index 00000000..4bbd5c7d --- /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*)"wolfpkcs11-test"; +static int userPinLen = 15; + +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 */