diff --git a/.github/workflows/empty-pin-store-test.yml b/.github/workflows/empty-pin-store-test.yml index efd8afa9..b97a63cf 100644 --- a/.github/workflows/empty-pin-store-test.yml +++ b/.github/workflows/empty-pin-store-test.yml @@ -62,7 +62,7 @@ jobs: make - name: Create test store directory - run: mkdir -p store/empty_pin_test + run: mkdir -p store/empty_pin_test store/private_object_empty_pin_test - name: Run empty PIN store test run: | @@ -74,6 +74,16 @@ jobs: echo "" echo "=== Test completed ===" + - name: Run private object empty PIN test + run: | + echo "=== Running CKA_PRIVATE Empty PIN Test ===" + echo "This test verifies that CKA_PRIVATE access control is enforced" + echo "for public sessions even when the user PIN is empty (F-3835)." + echo "" + ./tests/private_object_empty_pin_test + echo "" + echo "=== Test completed ===" + - name: Show store directory contents on failure if: failure() run: | diff --git a/.gitignore b/.gitignore index 6942eb1f..c83eff34 100644 --- a/.gitignore +++ b/.gitignore @@ -46,8 +46,10 @@ examples/add_hmac_key examples/add_rsa_key examples/add_rsa_key_file examples/add_cert +examples/add_cert_file examples/init_token examples/mech_info +examples/nss_pkcs12_pbe_example examples/obj_list examples/slot_info examples/token_info diff --git a/src/crypto.c b/src/crypto.c index e03d5df5..e7a89d79 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -537,6 +537,29 @@ static CK_RV SetIfNotFound(WP11_Object* obj, CK_ATTRIBUTE_TYPE type, return ret; } +/* NSS uses wolfPKCS11 as its internal crypto module and relies on the + * historical permissive behavior: it reads key material directly via + * C_GetAttributeValue, derives from ephemeral keys without CKA_DERIVE, and + * uses multi-purpose RSA keys. Selecting NSS support therefore enables all of + * the "reverting" macros below, so the secure-default and enforcement + * hardening (F-4063, F-4064, F-4533, F-5519, F-5520) applies only to non-NSS + * builds. The hardening can still be toggled independently in non-NSS builds + * via the individual WOLFPKCS11_LEGACY_* macros. */ +#ifdef WOLFPKCS11_NSS + #ifndef WP11_NSS_PERMISSIVE_KEY_DEFAULTS + #define WP11_NSS_PERMISSIVE_KEY_DEFAULTS /* F-4063, F-4064 */ + #endif + #ifndef WOLFPKCS11_LEGACY_EXTRACTABLE_TRUE_DEFAULT + #define WOLFPKCS11_LEGACY_EXTRACTABLE_TRUE_DEFAULT /* F-5519 */ + #endif + #ifndef WOLFPKCS11_LEGACY_RSA_USAGE_DEFAULT + #define WOLFPKCS11_LEGACY_RSA_USAGE_DEFAULT /* F-5520 */ + #endif + #ifndef WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT + #define WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT /* F-4533 */ + #endif +#endif + static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) @@ -647,14 +670,19 @@ static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, ret = SetIfNotFound(obj, CKA_PRIVATE, trueVal, pTemplate, ulCount); #endif -#ifndef WOLFPKCS11_NSS + /* F-4063: default secret keys to CKA_SENSITIVE=CK_TRUE. */ +#ifndef WP11_NSS_PERMISSIVE_KEY_DEFAULTS if (ret == CKR_OK) ret = SetIfNotFound(obj, CKA_SENSITIVE, trueVal, pTemplate, ulCount); #endif + /* F-5519: default secret keys to CKA_EXTRACTABLE=CK_FALSE so they + * are not wrap-exportable unless the template opts in. */ +#ifdef WOLFPKCS11_LEGACY_EXTRACTABLE_TRUE_DEFAULT if (ret == CKR_OK) ret = SetIfNotFound(obj, CKA_EXTRACTABLE, trueVal, pTemplate, ulCount); +#endif if (ret == CKR_OK) ret = SetIfNotFound(obj, CKA_ENCRYPT, trueVal, pTemplate, ulCount); @@ -678,7 +706,9 @@ static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, ret = SetIfNotFound(obj, CKA_PRIVATE, trueVal, pTemplate, ulCount); #endif -#ifndef WOLFPKCS11_NSS + /* F-4063: default private keys to CKA_SENSITIVE=CK_TRUE and + * CKA_EXTRACTABLE=CK_FALSE. */ +#ifndef WP11_NSS_PERMISSIVE_KEY_DEFAULTS if (ret == CKR_OK) ret = SetIfNotFound(obj, CKA_SENSITIVE, trueVal, pTemplate, ulCount); @@ -692,6 +722,17 @@ static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, if (ret == CKR_OK) ret = SetIfNotFound(obj, CKA_EXTRACTABLE, trueVal, pTemplate, ulCount); +#endif + /* F-5520: RSA private-key usage is opt-in. A minimal template + * otherwise grants CKA_DECRYPT, CKA_SIGN and CKA_SIGN_RECOVER all + * at once, so a key intended for one purpose is silently accepted + * for several. Require each use to be requested explicitly. */ +#ifndef WOLFPKCS11_LEGACY_RSA_USAGE_DEFAULT + if (type == CKK_RSA) { + encrypt = CK_FALSE; /* CKA_DECRYPT */ + sign = CK_FALSE; /* CKA_SIGN */ + recover = CK_FALSE; /* CKA_SIGN_RECOVER */ + } #endif if (ret == CKR_OK) ret = SetIfNotFound(obj, CKA_DECRYPT, encrypt, pTemplate, @@ -8608,6 +8649,12 @@ CK_RV C_DeriveKey(CK_SESSION_HANDLE hSession, CK_ULONG symmKeyLen; unsigned char* secretKeyData[2] = { NULL, NULL }; CK_ULONG secretKeyLen[2] = { 0, 0 }; +#ifndef WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT + /* F-4533: protection attributes inherited from the base key. */ + CK_BBOOL baseSensitive = CK_FALSE; + CK_BBOOL baseExtractable = CK_TRUE; + CK_ULONG bLen; +#endif #endif WOLFPKCS11_ENTER("C_DeriveKey"); @@ -8662,11 +8709,11 @@ CK_RV C_DeriveKey(CK_SESSION_HANDLE hSession, if (ret != 0) return CKR_OBJECT_HANDLE_INVALID; -#ifndef WOLFPKCS11_NSS - /* Spec-compliance gate: reject keys whose CKA_DERIVE is CK_FALSE. NSS - * generates ephemeral EC keys for TLS ECDHE without explicitly setting - * CKA_DERIVE=CK_TRUE and relies on the historic permissive behavior, so - * skip this check in NSS builds. */ + /* F-4064: spec-compliance gate - reject keys whose CKA_DERIVE is CK_FALSE. + * Skipped for NSS, which generates ephemeral EC keys for TLS ECDHE without + * setting CKA_DERIVE=CK_TRUE and relies on the historic permissive + * behavior (WP11_NSS_PERMISSIVE_KEY_DEFAULTS is auto-enabled for NSS). */ +#ifndef WP11_NSS_PERMISSIVE_KEY_DEFAULTS ret = CheckOpSupported(obj, CKA_DERIVE); if (ret != CKR_OK) return ret; @@ -8955,7 +9002,43 @@ CK_RV C_DeriveKey(CK_SESSION_HANDLE hSession, #if defined(HAVE_ECC) || !defined(NO_DH) || defined(WOLFPKCS11_HKDF) || \ (!defined(NO_AES) && defined(HAVE_AES_CBC)) if ((ret == 0) && (derivedKey != NULL)) { +#if (defined(HAVE_ECC) || !defined(NO_DH) || defined(WOLFPKCS11_HKDF)) && \ + !defined(WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT) + /* F-4533: read the base key's protection bits while `obj' still + * refers to it, before CreateObject reuses `obj' for the new key. */ + bLen = sizeof(CK_BBOOL); + if (WP11_Object_GetAttr(obj, CKA_SENSITIVE, &baseSensitive, &bLen) != 0) + baseSensitive = CK_FALSE; + bLen = sizeof(CK_BBOOL); + if (WP11_Object_GetAttr(obj, CKA_EXTRACTABLE, &baseExtractable, + &bLen) != 0) + baseExtractable = CK_TRUE; +#endif rv = CreateObject(session, pTemplate, ulAttributeCount, &obj); + if (rv == CKR_OK) { + /* obj now refers to the newly created derived key. */ +#if (defined(HAVE_ECC) || !defined(NO_DH) || defined(WOLFPKCS11_HKDF)) && \ + !defined(WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT) + /* F-4533: PKCS#11 v3.0 5.5.5 - a derived key must be at least as + * protected as its base key. Force the inherited attributes after + * the caller template has been applied so a weaker template + * cannot win. */ + if (baseSensitive == CK_TRUE) { + CK_BBOOL bbTrue = CK_TRUE; + if (WP11_Object_SetAttr(obj, CKA_SENSITIVE, &bbTrue, + sizeof(bbTrue)) != 0) + rv = CKR_FUNCTION_FAILED; + } + if (rv == CKR_OK && baseExtractable == CK_FALSE) { + CK_BBOOL bbFalse = CK_FALSE; + if (WP11_Object_SetAttr(obj, CKA_EXTRACTABLE, &bbFalse, + sizeof(bbFalse)) != 0) + rv = CKR_FUNCTION_FAILED; + } + if (rv != CKR_OK) + WP11_Object_Free(obj); +#endif + } if (rv == CKR_OK) { ret = SymmKeyLen(obj, keyLen, &symmKeyLen); if (ret == 0) { diff --git a/src/internal.c b/src/internal.c index 1ae6d9a6..b55b984b 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8891,9 +8891,11 @@ static WP11_Object* wp11_Session_FindNext(WP11_Session* session, int onToken, if ((ret->opFlag & WP11_FLAG_PRIVATE) == WP11_FLAG_PRIVATE) { if (!onToken) WP11_Lock_LockRO(&session->slot->token.lock); - if (!WP11_Slot_Has_Empty_Pin(session->slot) && - (session->slot->token.loginState == WP11_APP_STATE_RW_PUBLIC || - session->slot->token.loginState == WP11_APP_STATE_RO_PUBLIC)) { + /* F-3835: a public session must not discover CKA_PRIVATE objects. + * An empty user PIN does not waive this - the caller can still + * authenticate with C_Login (empty PIN included). */ + if (session->slot->token.loginState == WP11_APP_STATE_RW_PUBLIC || + session->slot->token.loginState == WP11_APP_STATE_RO_PUBLIC) { object = ret; ret = NULL; } @@ -10090,9 +10092,10 @@ int WP11_Object_Find(WP11_Session* session, CK_OBJECT_HANDLE objHandle, int loginState; WP11_Lock_LockRO(&session->slot->lock); loginState = session->slot->token.loginState; - if (!WP11_Slot_Has_Empty_Pin(session->slot) && - (loginState == WP11_APP_STATE_RW_PUBLIC || - loginState == WP11_APP_STATE_RO_PUBLIC)) { + /* F-3835: resolving a CKA_PRIVATE object by handle from a public + * session must be denied even when the user PIN is empty. */ + if (loginState == WP11_APP_STATE_RW_PUBLIC || + loginState == WP11_APP_STATE_RO_PUBLIC) { ret = BAD_FUNC_ARG; } WP11_Lock_UnlockRO(&session->slot->lock); diff --git a/tests/derive_disabled_key_test.c b/tests/derive_disabled_key_test.c new file mode 100644 index 00000000..1e9aa860 --- /dev/null +++ b/tests/derive_disabled_key_test.c @@ -0,0 +1,331 @@ +/* derive_disabled_key_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Test for issue F-4064: C_DeriveKey must reject a base key whose + * CKA_DERIVE attribute is CK_FALSE. A usage-attribute denial is reported as + * CKR_KEY_FUNCTION_NOT_PERMITTED (CheckOpSupported, per F-6052). Historically + * WOLFPKCS11_NSS builds compiled out this CheckOpSupported gate, so a key + * explicitly flagged as non-derivable could still be used as the base key + * for derivation. + */ + +#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 builds keep the historical permissive behavior (derive gate compiled + * out). */ +#ifdef WOLFPKCS11_NSS + #define EXPECT_DERIVE_GATE_DISABLED +#endif + +#define TEST_DIR "./store/derive_disabled_key_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +static int test_passed = 0; +static int test_failed = 0; +static int test_skipped = 0; + +#ifndef HAVE_PKCS11_STATIC +static void* dlib; +#endif +static CK_FUNCTION_LIST* funcList; +static CK_SLOT_ID slot = 0; +static const char* tokenName = "wolfpkcs11"; +static byte* soPin = (byte*)"password123456"; +static int soPinLen = 14; +static byte* userPin = (byte*)"someUserPin"; +static int userPinLen = 11; + +#define CHECK_CKR(rv, op) do { \ + if (rv != CKR_OK) { \ + fprintf(stderr, "FAIL: %s: got 0x%lx\n", op, (unsigned long)rv); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while(0) + +#if defined(WOLFPKCS11_HKDF) +static int test_derive_rejects_non_derive_key(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; + CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; + CK_BBOOL ckFalse = CK_FALSE; + CK_BBOOL ckTrue = CK_TRUE; + CK_ULONG derivedLen = 32; + int result = 0; + + byte ikm[22]; + byte salt[13]; + + /* Base key explicitly NOT permitted for derivation. */ + CK_ATTRIBUTE baseTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE, ikm, sizeof(ikm) }, + { CKA_DERIVE, &ckFalse, sizeof(ckFalse) }, + }; + CK_ULONG baseCnt = sizeof(baseTmpl) / sizeof(*baseTmpl); + + CK_ATTRIBUTE derivedTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE_LEN, &derivedLen, sizeof(derivedLen) }, + }; + CK_ULONG derivedCnt = sizeof(derivedTmpl) / sizeof(*derivedTmpl); + + CK_HKDF_PARAMS params; + CK_MECHANISM mech; + + XMEMSET(ikm, 0x0b, sizeof(ikm)); + XMEMSET(salt, 0x00, sizeof(salt)); + + XMEMSET(¶ms, 0, sizeof(params)); + params.bExtract = CK_TRUE; + params.bExpand = CK_FALSE; + params.prfHashMechanism = CKM_SHA256_HMAC; + params.ulSaltType = CKF_HKDF_SALT_DATA; + params.pSalt = salt; + params.ulSaltLen = sizeof(salt); + params.hSaltKey = CK_INVALID_HANDLE; + params.pInfo = NULL; + params.ulInfoLen = 0; + + mech.mechanism = CKM_HKDF_DERIVE; + mech.pParameter = ¶ms; + mech.ulParameterLen = sizeof(params); + + ret = funcList->C_CreateObject(session, baseTmpl, baseCnt, &base); + CHECK_CKR(ret, "C_CreateObject (base CKA_DERIVE=FALSE)"); + + ret = funcList->C_DeriveKey(session, &mech, base, derivedTmpl, derivedCnt, + &derived); +#ifdef EXPECT_DERIVE_GATE_DISABLED + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: legacy NSS: expected derive to be permitted, " + "got 0x%lx\n", (unsigned long)ret); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: legacy NSS permits derive from CKA_DERIVE=FALSE key\n"); + test_passed++; +#else + if (ret != CKR_KEY_FUNCTION_NOT_PERMITTED) { + fprintf(stderr, "FAIL: derive from CKA_DERIVE=FALSE key returned " + "0x%lx, expected CKR_KEY_FUNCTION_NOT_PERMITTED (0x%lx)\n", + (unsigned long)ret, + (unsigned long)CKR_KEY_FUNCTION_NOT_PERMITTED); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: derive from CKA_DERIVE=FALSE key rejected " + "(CKR_KEY_FUNCTION_NOT_PERMITTED)\n"); + test_passed++; +#endif + +cleanup: + if (derived != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, derived); + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + return result; +} +#endif /* WOLFPKCS11_HKDF */ + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + +#ifndef HAVE_PKCS11_STATIC + CK_C_GetFunctionList func; + + dlib = dlopen(WOLFPKCS11_DLL_FILENAME, RTLD_NOW | RTLD_LOCAL); + if (dlib == NULL) { + fprintf(stderr, "dlopen error: %s\n", dlerror()); + return -1; + } + func = (CK_C_GetFunctionList)dlsym(dlib, "C_GetFunctionList"); + if (func == NULL) { + dlclose(dlib); + return -1; + } + ret = func(&funcList); + if (ret != CKR_OK) { + dlclose(dlib); + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) + return ret; +#endif + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + ret = funcList->C_Initialize(&args); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + if (slotCount == 0) + return CKR_GENERAL_ERROR; + slot = slotList[0]; + return ret; +} + +static CK_RV pkcs11_final(void) +{ + if (funcList != NULL) { + funcList->C_Finalize(NULL); + funcList = NULL; + } +#ifndef HAVE_PKCS11_STATIC + if (dlib) { + dlclose(dlib); + dlib = NULL; + } +#endif + return CKR_OK; +} + +static CK_RV pkcs11_setup_token(void) +{ + CK_RV ret; + unsigned char label[32]; + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + ret = funcList->C_InitToken(slot, soPin, soPinLen, label); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) + return ret; + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret == CKR_OK) + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + return ret; +} + +static int derive_disabled_key_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = 0; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + int result = 0; + + printf("\n=== Testing C_DeriveKey CKA_DERIVE enforcement ===\n"); + + ret = pkcs11_init(); + CHECK_CKR(ret, "C_Initialize"); + + ret = pkcs11_setup_token(); + CHECK_CKR(ret, "token setup"); + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &session); + CHECK_CKR(ret, "C_OpenSession"); + ret = funcList->C_Login(session, CKU_USER, userPin, userPinLen); + CHECK_CKR(ret, "C_Login"); + +#if defined(WOLFPKCS11_HKDF) + if (test_derive_rejects_non_derive_key(session) != 0) + result = -1; +#else + printf("HKDF not available, skipping CKA_DERIVE enforcement test\n"); + test_skipped = 1; +#endif + +cleanup: + if (session != 0) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + pkcs11_final(); + return result; +} + +static void print_results(void) +{ + printf("\n=== Test Results ===\n"); + printf("Tests passed: %d\n", test_passed); + printf("Tests failed: %d\n", test_failed); + if (test_skipped != 0) + printf("Tests skipped: %d\n", test_skipped); + if (test_failed == 0) + printf("ALL TESTS PASSED!\n"); + else + printf("SOME TESTS FAILED!\n"); +} + +int main(int argc, char* argv[]) +{ +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 Derive Disabled Key Test ===\n"); + + (void)derive_disabled_key_test(); + + print_results(); + return (test_failed == 0) ? 0 : 1; +} diff --git a/tests/derive_inherit_protection_test.c b/tests/derive_inherit_protection_test.c new file mode 100644 index 00000000..89913bda --- /dev/null +++ b/tests/derive_inherit_protection_test.c @@ -0,0 +1,425 @@ +/* derive_inherit_protection_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Test for issue F-4533: a derived key must inherit protection strength from + * its base key (PKCS#11 v3.0 5.5.5). If the base key is CKA_SENSITIVE=CK_TRUE + * the derived key must be CKA_SENSITIVE=CK_TRUE; if the base key is + * CKA_EXTRACTABLE=CK_FALSE the derived key must be CKA_EXTRACTABLE=CK_FALSE, + * even when the caller's template requests weaker protection. + */ + +#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" + +/* The non-inheriting behavior is expected under the legacy macro or in an NSS + * build (which keeps the historical permissive behavior). */ +#if defined(WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT) || defined(WOLFPKCS11_NSS) + #define EXPECT_NO_INHERIT +#endif + +#define TEST_DIR "./store/derive_inherit_protection_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +static int test_passed = 0; +static int test_failed = 0; +static int test_skipped = 0; + +#ifndef HAVE_PKCS11_STATIC +static void* dlib; +#endif +static CK_FUNCTION_LIST* funcList; +static CK_SLOT_ID slot = 0; +static const char* tokenName = "wolfpkcs11"; +static byte* soPin = (byte*)"password123456"; +static int soPinLen = 14; +static byte* userPin = (byte*)"someUserPin"; +static int userPinLen = 11; + +#define CHECK_CKR(rv, op) do { \ + if (rv != CKR_OK) { \ + fprintf(stderr, "FAIL: %s: got 0x%lx\n", op, (unsigned long)rv); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while(0) + +#if defined(WOLFPKCS11_HKDF) +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; + +static byte baseValue[32] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, +}; + +static int read_bool(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + CK_ATTRIBUTE_TYPE type, CK_BBOOL* out) +{ + CK_ATTRIBUTE tmpl[] = { { type, out, sizeof(*out) } }; + return (int)funcList->C_GetAttributeValue(session, obj, tmpl, 1); +} + +/* HKDF-extract derive from `base' producing a generic secret. The derived + * template deliberately requests the weakest protection. */ +static CK_RV hkdf_derive(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE base, + CK_OBJECT_HANDLE* derived) +{ + CK_ULONG derivedLen = 32; + byte salt[13]; + CK_HKDF_PARAMS params; + CK_MECHANISM mech; + CK_ATTRIBUTE derivedTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE_LEN, &derivedLen, sizeof(derivedLen) }, + }; + CK_ULONG derivedCnt = sizeof(derivedTmpl) / sizeof(*derivedTmpl); + + XMEMSET(salt, 0, sizeof(salt)); + XMEMSET(¶ms, 0, sizeof(params)); + params.bExtract = CK_TRUE; + params.bExpand = CK_FALSE; + params.prfHashMechanism = CKM_SHA256_HMAC; + params.ulSaltType = CKF_HKDF_SALT_DATA; + params.pSalt = salt; + params.ulSaltLen = sizeof(salt); + params.hSaltKey = CK_INVALID_HANDLE; + + mech.mechanism = CKM_HKDF_DERIVE; + mech.pParameter = ¶ms; + mech.ulParameterLen = sizeof(params); + + return funcList->C_DeriveKey(session, &mech, base, derivedTmpl, derivedCnt, + derived); +} + +static int test_sensitive_inheritance(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_BBOOL sensitive = 0xAA; + int result = 0; + + /* Sensitive, derivable base key. */ + CK_ATTRIBUTE baseTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE, baseValue, sizeof(baseValue) }, + { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + }; + CK_ULONG baseCnt = sizeof(baseTmpl) / sizeof(*baseTmpl); + + ret = funcList->C_CreateObject(session, baseTmpl, baseCnt, &base); + CHECK_CKR(ret, "create sensitive base key"); + + ret = hkdf_derive(session, base, &derived); + CHECK_CKR(ret, "derive from sensitive base"); + + ret = read_bool(session, derived, CKA_SENSITIVE, &sensitive); + CHECK_CKR(ret, "read derived CKA_SENSITIVE"); + +#ifdef EXPECT_NO_INHERIT + if (sensitive != CK_FALSE) { + fprintf(stderr, "FAIL: legacy: derived CKA_SENSITIVE expected FALSE\n"); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: legacy build does not inherit CKA_SENSITIVE\n"); + test_passed++; +#else + if (sensitive != CK_TRUE) { + fprintf(stderr, "FAIL: derived CKA_SENSITIVE expected CK_TRUE " + "(inherited), got %d\n", (int)sensitive); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: derived key inherits CKA_SENSITIVE=CK_TRUE\n"); + test_passed++; + + /* The derived value must not be readable. */ + { + byte val[64]; + CK_ATTRIBUTE valTmpl[] = { { CKA_VALUE, val, sizeof(val) } }; + ret = funcList->C_GetAttributeValue(session, derived, valTmpl, 1); + if (ret != CKR_ATTRIBUTE_SENSITIVE) { + fprintf(stderr, "FAIL: reading derived value returned 0x%lx, " + "expected CKR_ATTRIBUTE_SENSITIVE\n", (unsigned long)ret); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: derived sensitive key value is not readable\n"); + test_passed++; + } +#endif + +cleanup: + if (derived != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, derived); + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + return result; +} + +static int test_extractable_inheritance(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE derived = CK_INVALID_HANDLE; + CK_BBOOL extractable = 0xAA; + int result = 0; + + /* Non-extractable (but non-sensitive) derivable base key. */ + CK_ATTRIBUTE baseTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE, baseValue, sizeof(baseValue) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckFalse, sizeof(ckFalse) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + }; + CK_ULONG baseCnt = sizeof(baseTmpl) / sizeof(*baseTmpl); + + ret = funcList->C_CreateObject(session, baseTmpl, baseCnt, &base); + CHECK_CKR(ret, "create non-extractable base key"); + + ret = hkdf_derive(session, base, &derived); + CHECK_CKR(ret, "derive from non-extractable base"); + + ret = read_bool(session, derived, CKA_EXTRACTABLE, &extractable); + CHECK_CKR(ret, "read derived CKA_EXTRACTABLE"); + +#ifdef EXPECT_NO_INHERIT + if (extractable != CK_TRUE) { + fprintf(stderr, + "FAIL: legacy: derived CKA_EXTRACTABLE expected TRUE\n"); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: legacy build does not inherit CKA_EXTRACTABLE\n"); + test_passed++; +#else + if (extractable != CK_FALSE) { + fprintf(stderr, "FAIL: derived CKA_EXTRACTABLE expected CK_FALSE " + "(inherited), got %d\n", (int)extractable); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: derived key inherits CKA_EXTRACTABLE=CK_FALSE\n"); + test_passed++; +#endif + +cleanup: + if (derived != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, derived); + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + return result; +} +#endif /* WOLFPKCS11_HKDF */ + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + +#ifndef HAVE_PKCS11_STATIC + CK_C_GetFunctionList func; + + dlib = dlopen(WOLFPKCS11_DLL_FILENAME, RTLD_NOW | RTLD_LOCAL); + if (dlib == NULL) { + fprintf(stderr, "dlopen error: %s\n", dlerror()); + return -1; + } + func = (CK_C_GetFunctionList)dlsym(dlib, "C_GetFunctionList"); + if (func == NULL) { + dlclose(dlib); + return -1; + } + ret = func(&funcList); + if (ret != CKR_OK) { + dlclose(dlib); + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) + return ret; +#endif + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + ret = funcList->C_Initialize(&args); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + if (slotCount == 0) + return CKR_GENERAL_ERROR; + slot = slotList[0]; + return ret; +} + +static CK_RV pkcs11_final(void) +{ + if (funcList != NULL) { + funcList->C_Finalize(NULL); + funcList = NULL; + } +#ifndef HAVE_PKCS11_STATIC + if (dlib) { + dlclose(dlib); + dlib = NULL; + } +#endif + return CKR_OK; +} + +static CK_RV pkcs11_setup_token(void) +{ + CK_RV ret; + unsigned char label[32]; + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + ret = funcList->C_InitToken(slot, soPin, soPinLen, label); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) + return ret; + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret == CKR_OK) + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + return ret; +} + +static int derive_inherit_protection_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = 0; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + int result = 0; + + printf("\n=== Testing C_DeriveKey protection inheritance ===\n"); + + ret = pkcs11_init(); + CHECK_CKR(ret, "C_Initialize"); + + ret = pkcs11_setup_token(); + CHECK_CKR(ret, "token setup"); + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &session); + CHECK_CKR(ret, "C_OpenSession"); + ret = funcList->C_Login(session, CKU_USER, userPin, userPinLen); + CHECK_CKR(ret, "C_Login"); + +#if defined(WOLFPKCS11_HKDF) + if (test_sensitive_inheritance(session) != 0) + result = -1; + if (test_extractable_inheritance(session) != 0) + result = -1; +#else + printf("HKDF not available, skipping derive inheritance test\n"); + test_skipped = 1; +#endif + +cleanup: + if (session != 0) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + pkcs11_final(); + return result; +} + +static void print_results(void) +{ + printf("\n=== Test Results ===\n"); + printf("Tests passed: %d\n", test_passed); + printf("Tests failed: %d\n", test_failed); + if (test_skipped != 0) + printf("Tests skipped: %d\n", test_skipped); + if (test_failed == 0) + printf("ALL TESTS PASSED!\n"); + else + printf("SOME TESTS FAILED!\n"); +} + +int main(int argc, char* argv[]) +{ +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 Derive Inherit Protection Test ===\n"); + + (void)derive_inherit_protection_test(); + + print_results(); + return (test_failed == 0) ? 0 : 1; +} diff --git a/tests/empty_pin_store_test.c b/tests/empty_pin_store_test.c index 2899b88e..1b61365e 100644 --- a/tests/empty_pin_store_test.c +++ b/tests/empty_pin_store_test.c @@ -21,13 +21,15 @@ * Test for empty PIN scenario with token storage - verifies that encrypted * objects can be stored and loaded correctly when using an empty user PIN. * - * Benefit of empty PIN: the application never calls C_Login. The token is - * usable immediately after C_OpenSession (one fewer API call, no PIN - * handling). This saves time and simplifies headless/automated use (servers, - * daemons) where there is no user to enter a PIN. + * Benefit of empty PIN: login is trivial. The token requires no PIN material, + * so C_Login(CKU_USER, "", 0) always succeeds. This simplifies headless and + * automated use (servers, daemons) where there is no user to enter a PIN. * - * This test exercises that time-saving path: open session, use token objects - * (create/find, get attributes) without ever calling C_Login. + * Note (F-3835): CKA_PRIVATE objects are still gated on the USER login state + * even when the PIN is empty, so a token that stores private objects must + * call C_Login (with the empty PIN) before accessing them. This test + * exercises that path: open session, log in with the empty PIN, then use + * token objects (create/find, get attributes). */ #ifdef HAVE_CONFIG_H @@ -223,12 +225,12 @@ static CK_RV pkcs11_open_session(CK_SESSION_HANDLE* session) if (ret != CKR_OK) return ret; - /* With empty PIN, no login is required; skip C_Login when userPinLen is 0 */ - if (userPinLen != 0) { - ret = funcList->C_Login(*session, CKU_USER, userPin, userPinLen); - if (ret != CKR_OK) - return ret; - } + /* F-3835: accessing CKA_PRIVATE objects requires the USER login state. + * An empty PIN does not waive this, but C_Login with the empty PIN + * succeeds and grants access. */ + ret = funcList->C_Login(*session, CKU_USER, userPin, userPinLen); + if (ret != CKR_OK) + return ret; return CKR_OK; } diff --git a/tests/include.am b/tests/include.am index ac9b98a3..42c37937 100644 --- a/tests/include.am +++ b/tests/include.am @@ -151,6 +151,36 @@ noinst_PROGRAMS += tests/trust_attr_bufsize_test tests_trust_attr_bufsize_test_SOURCES = tests/trust_attr_bufsize_test.c tests_trust_attr_bufsize_test_LDADD = +check_PROGRAMS += tests/private_object_empty_pin_test +noinst_PROGRAMS += tests/private_object_empty_pin_test +tests_private_object_empty_pin_test_SOURCES = tests/private_object_empty_pin_test.c +tests_private_object_empty_pin_test_LDADD = + +check_PROGRAMS += tests/key_sensitive_default_test +noinst_PROGRAMS += tests/key_sensitive_default_test +tests_key_sensitive_default_test_SOURCES = tests/key_sensitive_default_test.c +tests_key_sensitive_default_test_LDADD = + +check_PROGRAMS += tests/derive_disabled_key_test +noinst_PROGRAMS += tests/derive_disabled_key_test +tests_derive_disabled_key_test_SOURCES = tests/derive_disabled_key_test.c +tests_derive_disabled_key_test_LDADD = + +check_PROGRAMS += tests/derive_inherit_protection_test +noinst_PROGRAMS += tests/derive_inherit_protection_test +tests_derive_inherit_protection_test_SOURCES = tests/derive_inherit_protection_test.c +tests_derive_inherit_protection_test_LDADD = + +check_PROGRAMS += tests/secret_extractable_default_test +noinst_PROGRAMS += tests/secret_extractable_default_test +tests_secret_extractable_default_test_SOURCES = tests/secret_extractable_default_test.c +tests_secret_extractable_default_test_LDADD = + +check_PROGRAMS += tests/rsa_private_usage_default_test +noinst_PROGRAMS += tests/rsa_private_usage_default_test +tests_rsa_private_usage_default_test_SOURCES = tests/rsa_private_usage_default_test.c +tests_rsa_private_usage_default_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -182,6 +212,12 @@ tests_gen_keypair_incomplete_test_LDADD += src/libwolfpkcs11.la tests_set_attr_readonly_test_LDADD += src/libwolfpkcs11.la tests_derive_key_type_test_LDADD += src/libwolfpkcs11.la tests_trust_attr_bufsize_test_LDADD += src/libwolfpkcs11.la +tests_private_object_empty_pin_test_LDADD += src/libwolfpkcs11.la +tests_key_sensitive_default_test_LDADD += src/libwolfpkcs11.la +tests_derive_disabled_key_test_LDADD += src/libwolfpkcs11.la +tests_derive_inherit_protection_test_LDADD += src/libwolfpkcs11.la +tests_secret_extractable_default_test_LDADD += src/libwolfpkcs11.la +tests_rsa_private_usage_default_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -205,6 +241,12 @@ tests_gen_keypair_incomplete_test_LDADD += src/libwolfpkcs11.la tests_set_attr_readonly_test_LDADD += src/libwolfpkcs11.la tests_derive_key_type_test_LDADD += src/libwolfpkcs11.la tests_trust_attr_bufsize_test_LDADD += src/libwolfpkcs11.la +tests_private_object_empty_pin_test_LDADD += src/libwolfpkcs11.la +tests_key_sensitive_default_test_LDADD += src/libwolfpkcs11.la +tests_derive_disabled_key_test_LDADD += src/libwolfpkcs11.la +tests_derive_inherit_protection_test_LDADD += src/libwolfpkcs11.la +tests_secret_extractable_default_test_LDADD += src/libwolfpkcs11.la +tests_rsa_private_usage_default_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ diff --git a/tests/key_sensitive_default_test.c b/tests/key_sensitive_default_test.c new file mode 100644 index 00000000..b310a327 --- /dev/null +++ b/tests/key_sensitive_default_test.c @@ -0,0 +1,358 @@ +/* key_sensitive_default_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Test for issue F-4063: secret and private keys must default to + * CKA_SENSITIVE=CK_TRUE (and private keys to CKA_EXTRACTABLE=CK_FALSE) so + * that key material is not readable via C_GetAttributeValue unless the + * creating template explicitly opts out. Historically WOLFPKCS11_NSS builds + * skipped these defaults, exposing key material by default. + */ + +#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 builds keep the historical permissive key defaults. */ +#ifdef WOLFPKCS11_NSS + #define EXPECT_PERMISSIVE_DEFAULTS +#endif + +#define TEST_DIR "./store/key_sensitive_default_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +static int test_passed = 0; +static int test_failed = 0; +static int test_skipped = 0; + +#ifndef HAVE_PKCS11_STATIC +static void* dlib; +#endif +static CK_FUNCTION_LIST* funcList; +static CK_SLOT_ID slot = 0; +static const char* tokenName = "wolfpkcs11"; +static byte* soPin = (byte*)"password123456"; +static int soPinLen = 14; +static byte* userPin = (byte*)"someUserPin"; +static int userPinLen = 11; + +#define CHECK_CKR(rv, op) do { \ + if (rv != CKR_OK) { \ + fprintf(stderr, "FAIL: %s: got 0x%lx\n", op, (unsigned long)rv); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while(0) + +/* Assert a boolean attribute on an object equals the expected value. */ +static int check_bool_attr(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + CK_ATTRIBUTE_TYPE type, CK_BBOOL expected, + const char* name) +{ + CK_RV ret; + CK_BBOOL val = 0xAA; + CK_ATTRIBUTE tmpl[] = { { type, &val, sizeof(val) } }; + + ret = funcList->C_GetAttributeValue(session, obj, tmpl, 1); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: get %s: 0x%lx\n", name, (unsigned long)ret); + test_failed++; + return -1; + } + if (val != expected) { + fprintf(stderr, "FAIL: %s: expected %d, got %d\n", name, + (int)expected, (int)val); + test_failed++; + return -1; + } + printf("PASS: %s is %s\n", name, expected ? "CK_TRUE" : "CK_FALSE"); + test_passed++; + return 0; +} + +#ifndef NO_AES +static int test_secret_key_sensitive_default(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mech; + CK_KEY_TYPE keyType = CKK_AES; + CK_ULONG keyLen = 32; + int result = 0; + + /* Minimal generate template: no CKA_SENSITIVE supplied. */ + CK_ATTRIBUTE genTmpl[] = { + { CKA_VALUE_LEN, &keyLen, sizeof(keyLen) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + }; + CK_ULONG genCnt = sizeof(genTmpl) / sizeof(*genTmpl); + + mech.mechanism = CKM_AES_KEY_GEN; + mech.ulParameterLen = 0; + mech.pParameter = NULL; + + ret = funcList->C_GenerateKey(session, &mech, genTmpl, genCnt, &key); + CHECK_CKR(ret, "C_GenerateKey (AES-256, minimal template)"); + +#ifdef EXPECT_PERMISSIVE_DEFAULTS + result = check_bool_attr(session, key, CKA_SENSITIVE, CK_FALSE, + "secret CKA_SENSITIVE (legacy NSS)"); +#else + result = check_bool_attr(session, key, CKA_SENSITIVE, CK_TRUE, + "secret CKA_SENSITIVE"); +#endif + +cleanup: + if (key != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, key); + return result; +} +#endif /* !NO_AES */ + +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) +static int test_private_key_sensitive_default(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE, priv = CK_INVALID_HANDLE; + CK_MECHANISM mech; + CK_ULONG bits = 2048; + byte pubExp[] = { 0x01, 0x00, 0x01 }; + CK_BBOOL ckFalse = CK_FALSE; + int result = 0; + + /* Minimal pair-gen templates: the private template sets no protection + * bits (no CKA_SENSITIVE / CKA_EXTRACTABLE), so the defaults apply. */ + CK_ATTRIBUTE pubTmpl[] = { + { CKA_MODULUS_BITS, &bits, sizeof(bits) }, + { CKA_PUBLIC_EXPONENT, pubExp, sizeof(pubExp) }, + }; + CK_ULONG pubCnt = sizeof(pubTmpl) / sizeof(*pubTmpl); + CK_ATTRIBUTE privTmpl[] = { + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + CK_ULONG privCnt = sizeof(privTmpl) / sizeof(*privTmpl); + + mech.mechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; + mech.ulParameterLen = 0; + mech.pParameter = NULL; + + ret = funcList->C_GenerateKeyPair(session, &mech, pubTmpl, pubCnt, + privTmpl, privCnt, &pub, &priv); + CHECK_CKR(ret, "C_GenerateKeyPair (RSA-2048, minimal template)"); + +#ifdef EXPECT_PERMISSIVE_DEFAULTS + if (check_bool_attr(session, priv, CKA_SENSITIVE, CK_FALSE, + "private CKA_SENSITIVE (legacy NSS)") != 0) + result = -1; + if (check_bool_attr(session, priv, CKA_EXTRACTABLE, CK_TRUE, + "private CKA_EXTRACTABLE (legacy NSS)") != 0) + result = -1; +#else + if (check_bool_attr(session, priv, CKA_SENSITIVE, CK_TRUE, + "private CKA_SENSITIVE") != 0) + result = -1; + if (check_bool_attr(session, priv, CKA_EXTRACTABLE, CK_FALSE, + "private CKA_EXTRACTABLE") != 0) + result = -1; +#endif + +cleanup: + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + return result; +} +#endif /* !NO_RSA */ + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + +#ifndef HAVE_PKCS11_STATIC + CK_C_GetFunctionList func; + + dlib = dlopen(WOLFPKCS11_DLL_FILENAME, RTLD_NOW | RTLD_LOCAL); + if (dlib == NULL) { + fprintf(stderr, "dlopen error: %s\n", dlerror()); + return -1; + } + func = (CK_C_GetFunctionList)dlsym(dlib, "C_GetFunctionList"); + if (func == NULL) { + fprintf(stderr, "Failed to get function list function\n"); + dlclose(dlib); + return -1; + } + ret = func(&funcList); + if (ret != CKR_OK) { + dlclose(dlib); + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) + return ret; +#endif + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + ret = funcList->C_Initialize(&args); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + if (slotCount == 0) + return CKR_GENERAL_ERROR; + slot = slotList[0]; + return ret; +} + +static CK_RV pkcs11_final(void) +{ + if (funcList != NULL) { + funcList->C_Finalize(NULL); + funcList = NULL; + } +#ifndef HAVE_PKCS11_STATIC + if (dlib) { + dlclose(dlib); + dlib = NULL; + } +#endif + return CKR_OK; +} + +static CK_RV pkcs11_setup_token(void) +{ + CK_RV ret; + unsigned char label[32]; + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + ret = funcList->C_InitToken(slot, soPin, soPinLen, label); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) + return ret; + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret == CKR_OK) + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + return ret; +} + +static int key_sensitive_default_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = 0; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + int result = 0; + + printf("\n=== Testing CKA_SENSITIVE / CKA_EXTRACTABLE key defaults ===\n"); + + ret = pkcs11_init(); + CHECK_CKR(ret, "C_Initialize"); + + ret = pkcs11_setup_token(); + CHECK_CKR(ret, "token setup"); + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &session); + CHECK_CKR(ret, "C_OpenSession"); + ret = funcList->C_Login(session, CKU_USER, userPin, userPinLen); + CHECK_CKR(ret, "C_Login"); + +#ifndef NO_AES + if (test_secret_key_sensitive_default(session) != 0) + result = -1; +#endif +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) + if (test_private_key_sensitive_default(session) != 0) + result = -1; +#endif + +cleanup: + if (session != 0) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + pkcs11_final(); + return result; +} + +static void print_results(void) +{ + printf("\n=== Test Results ===\n"); + printf("Tests passed: %d\n", test_passed); + printf("Tests failed: %d\n", test_failed); + if (test_skipped != 0) + printf("Tests skipped: %d\n", test_skipped); + if (test_failed == 0) + printf("ALL TESTS PASSED!\n"); + else + printf("SOME TESTS FAILED!\n"); +} + +int main(int argc, char* argv[]) +{ +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 Key Sensitive Default Test ===\n"); + + (void)key_sensitive_default_test(); + + print_results(); + return (test_failed == 0) ? 0 : 1; +} diff --git a/tests/pkcs11mtt.c b/tests/pkcs11mtt.c index fb8901bf..69cc03a6 100644 --- a/tests/pkcs11mtt.c +++ b/tests/pkcs11mtt.c @@ -2092,6 +2092,10 @@ static CK_RV get_rsa_priv_key(CK_SESSION_HANDLE session, unsigned char* privId, { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + /* CKA_SIGN / CKA_SIGN_RECOVER are opt-in for RSA private keys + * (F-5520); this helper backs sign and sign-recover tests. */ + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_SIGN_RECOVER, &ckTrue, sizeof(ckTrue) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, { CKA_PRIVATE_EXPONENT, rsa_2048_priv_exp, sizeof(rsa_2048_priv_exp) }, @@ -3517,6 +3521,9 @@ static CK_RV gen_ec_keys(CK_SESSION_HANDLE session, byte* params, int paramSz, CK_ATTRIBUTE privKeyTmpl[] = { { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + /* Readable base so the derived secret can be checked (F-4533). */ + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, { CKA_TOKEN, &token, sizeof(token) }, { CKA_ID, privId, (CK_ULONG)privIdLen }, }; @@ -4083,7 +4090,8 @@ static CK_RV test_ecc_fixed_keys_ecdh(void* args) CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; - ret = get_ecc_priv_key(session, CK_FALSE, &priv); + /* Extractable base so the derived ECDH secret can be read (F-4533). */ + ret = get_ecc_priv_key(session, CK_TRUE, &priv); if (ret == CKR_OK) ret = get_ecc_pub_key(session, &pub); if (ret == CKR_OK) { @@ -4320,6 +4328,9 @@ static CK_RV gen_dh_keys(CK_SESSION_HANDLE session, byte* prime, int primeSz, int pubTmplCnt = sizeof(pubKeyTmpl)/sizeof(*pubKeyTmpl); CK_ATTRIBUTE privKeyTmpl[] = { { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + /* Readable base so the derived secret can be checked (F-4533). */ + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, { CKA_TOKEN, &token, sizeof(token) }, { CKA_ID, privId, (CK_ULONG)privIdLen }, }; @@ -4551,7 +4562,8 @@ static CK_RV test_dh_fixed_keys(void* args) CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; - ret = get_dh_priv_key(session, CK_FALSE, &priv); + /* Extractable base so the derived DH secret can be read (F-4533). */ + ret = get_dh_priv_key(session, CK_TRUE, &priv); if (ret == CKR_OK) ret = get_dh_pub_key(session, &pub); if (ret == CKR_OK) { diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index ec39f2a8..7a3a4b76 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -4655,6 +4655,9 @@ static CK_RV get_aes_128_key(CK_SESSION_HANDLE session, unsigned char* id, { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + /* Readable base so derived secrets can be checked (F-4533). */ + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, /* CKA_WRAP/CKA_UNWRAP default to CK_FALSE per spec (Fenrir 2774). * This helper backs the wrapping-key role in test_aes_wrap_unwrap_*, * so set both explicitly. */ @@ -5706,6 +5709,10 @@ static CK_RV rsa_verify_recover(CK_SESSION_HANDLE session, { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + /* CKA_SIGN / CKA_SIGN_RECOVER are opt-in for RSA private keys + * (F-5520); this key is used for sign-recover. */ + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_SIGN_RECOVER, &ckTrue, sizeof(ckTrue) }, { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, { CKA_PRIVATE_EXPONENT, rsa_2048_priv_exp, sizeof(rsa_2048_priv_exp) }, { CKA_PRIME_1, rsa_2048_p, sizeof(rsa_2048_p) }, @@ -6706,6 +6713,10 @@ static CK_RV get_rsa_priv_key(CK_SESSION_HANDLE session, unsigned char* privId, { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + /* CKA_SIGN / CKA_SIGN_RECOVER are opt-in for RSA private keys + * (F-5520); this helper backs sign and sign-recover tests. */ + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + { CKA_SIGN_RECOVER, &ckTrue, sizeof(ckTrue) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, /* CKA_UNWRAP defaults to CK_FALSE post-2774; set explicitly so the * RSA wrap/unwrap path exercised by test_rsa_wrap_unwrap_key still @@ -7735,6 +7746,8 @@ static CK_RV test_sha256_rsa_pkcs15(void* args) { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, { CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType) }, { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, + /* CKA_SIGN is opt-in for RSA private keys (F-5520). */ + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, { CKA_MODULUS, rsa_2048_modulus, sizeof(rsa_2048_modulus) }, { CKA_PRIVATE_EXPONENT, rsa_2048_priv_exp, sizeof(rsa_2048_priv_exp) }, { CKA_PRIME_1, rsa_2048_p, sizeof(rsa_2048_p) }, @@ -8898,6 +8911,10 @@ static CK_RV gen_ec_keys(CK_SESSION_HANDLE session, byte* params, int paramSz, CK_ATTRIBUTE privKeyTmpl[] = { { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + /* Readable base so the derived secret can be checked (F-4533 makes + * the derived key inherit the base key's protection). */ + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, { CKA_TOKEN, &token, sizeof(token) }, { CKA_ID, privId, (CK_ULONG)privIdLen }, }; @@ -9746,7 +9763,8 @@ static CK_RV test_ecc_fixed_keys_ecdh(void* args) CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; - ret = get_ecc_priv_key(session, CK_FALSE, &priv); + /* Extractable base so the derived ECDH secret can be read (F-4533). */ + ret = get_ecc_priv_key(session, CK_TRUE, &priv); if (ret == CKR_OK) ret = get_ecc_pub_key(session, &pub); if (ret == CKR_OK) { @@ -9922,7 +9940,8 @@ static CK_RV test_ecdh_x963(void* args) CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; - ret = get_ecc_priv_key(session, CK_FALSE, &priv); + /* Extractable base so the derived ECDH secret can be read (F-4533). */ + ret = get_ecc_priv_key(session, CK_TRUE, &priv); if (ret == CKR_OK) ret = get_ecc_pub_key(session, &pub); if (ret == CKR_OK) { @@ -10003,6 +10022,9 @@ static CK_RV gen_dh_keys(CK_SESSION_HANDLE session, byte* prime, int primeSz, int pubTmplCnt = sizeof(pubKeyTmpl)/sizeof(*pubKeyTmpl); CK_ATTRIBUTE privKeyTmpl[] = { { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + /* Readable base so the derived secret can be checked (F-4533). */ + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, { CKA_TOKEN, &token, sizeof(token) }, { CKA_ID, privId, (CK_ULONG)privIdLen }, }; @@ -10231,7 +10253,8 @@ static CK_RV test_dh_fixed_keys(void* args) CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; - ret = get_dh_priv_key(session, CK_FALSE, &priv); + /* Extractable base so the derived DH secret can be read (F-4533). */ + ret = get_dh_priv_key(session, CK_TRUE, &priv); if (ret == CKR_OK) ret = get_dh_pub_key(session, &pub); if (ret == CKR_OK) { @@ -10270,6 +10293,9 @@ static CK_RV gen_aes_key(CK_SESSION_HANDLE session, int len, unsigned char* id, CK_ATTRIBUTE keyTmpl[] = { { CKA_VALUE_LEN, &keyLen, sizeof(keyLen) }, { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, + /* Readable base so the derived secret can be checked (F-4533). */ + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, { CKA_TOKEN, &token, sizeof(token) }, { CKA_ID, id, (CK_ULONG)idLen }, }; @@ -16129,7 +16155,9 @@ static CK_RV test_derive_tls12_master_key_dh(void* args) { {CKA_KEY_TYPE, &keyType, sizeof(keyType)}, {CKA_VALUE, preMasterSecret, ulPreMasterSecretLen}, {CKA_SENSITIVE, &falseValue, sizeof(falseValue)}, - {CKA_EXTRACTABLE, &falseValue, sizeof(falseValue)}, + /* Extractable base so the derived master secret can be read for + * comparison (F-4533 makes the derived key inherit base protection). */ + {CKA_EXTRACTABLE, &trueValue, sizeof(trueValue)}, {CKA_DERIVE, &trueValue, sizeof(trueValue)}, {CKA_TOKEN, &falseValue, sizeof(falseValue)} }; @@ -16255,7 +16283,9 @@ static CK_RV test_derive_tls12_master_key(void* args) { {CKA_KEY_TYPE, &keyType, sizeof(keyType)}, {CKA_VALUE, preMasterSecret, ulPreMasterSecretLen}, {CKA_SENSITIVE, &falseValue, sizeof(falseValue)}, - {CKA_EXTRACTABLE, &falseValue, sizeof(falseValue)}, + /* Extractable base so the derived master secret can be read for + * comparison (F-4533 makes the derived key inherit base protection). */ + {CKA_EXTRACTABLE, &trueValue, sizeof(trueValue)}, {CKA_DERIVE, &trueValue, sizeof(trueValue)}, {CKA_TOKEN, &falseValue, sizeof(falseValue)} }; @@ -16579,7 +16609,9 @@ static CK_RV test_nss_derive_tls12_master_key(void* args) { {CKA_KEY_TYPE, &keyType, sizeof(keyType)}, {CKA_VALUE, preMasterSecret, ulPreMasterSecretLen}, {CKA_SENSITIVE, &falseValue, sizeof(falseValue)}, - {CKA_EXTRACTABLE, &falseValue, sizeof(falseValue)}, + /* Extractable base so the derived master secret can be read for + * comparison (F-4533 makes the derived key inherit base protection). */ + {CKA_EXTRACTABLE, &trueValue, sizeof(trueValue)}, {CKA_DERIVE, &trueValue, sizeof(trueValue)}, {CKA_TOKEN, &falseValue, sizeof(falseValue)} }; diff --git a/tests/private_object_empty_pin_test.c b/tests/private_object_empty_pin_test.c new file mode 100644 index 00000000..7bf5d32d --- /dev/null +++ b/tests/private_object_empty_pin_test.c @@ -0,0 +1,424 @@ +/* private_object_empty_pin_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Test for issue F-3835: an empty user PIN must not silently disable + * CKA_PRIVATE access control. A public (un-logged-in) session must not be + * able to discover (C_FindObjects) or resolve by handle + * (C_GetAttributeValue) an object marked CKA_PRIVATE=TRUE, even when the + * token has a zero-length user PIN. Logging in - which is still possible + * with an empty PIN - grants access. + * + * The empty-PIN scenario requires a build with WP11_MIN_PIN_LEN=0 (the + * default for WOLFPKCS11_NSS builds). When the token enforces a minimum PIN + * length the empty PIN cannot be set and the test reports itself skipped. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" + +#define TEST_DIR "./store/private_object_empty_pin_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +static int test_passed = 0; +static int test_failed = 0; +static int test_skipped = 0; + +#define CHECK_CKR(rv, op, expected) do { \ + if (rv != expected) { \ + fprintf(stderr, "FAIL: %s: expected 0x%lx, got 0x%lx\n", op, \ + (unsigned long)(expected), (unsigned 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; + +/* Empty user PIN - the scenario under test. */ +static byte* userPin = (byte*)""; +static int userPinLen = 0; + +static CK_OBJECT_CLASS secretKeyClass = CKO_SECRET_KEY; +static CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; + +static unsigned char keyId[] = {0xDE, 0xAD, 0xBE, 0xEF}; + +static unsigned char testKeyData[32] = { + 0x74, 0x9A, 0xBD, 0xAA, 0x2A, 0x52, 0x07, 0x47, + 0xD6, 0xA6, 0x36, 0xB2, 0x07, 0x32, 0x8E, 0xD0, + 0xBA, 0x69, 0x7B, 0xC6, 0xC3, 0x44, 0x9E, 0xD4, + 0x81, 0x48, 0xFD, 0x2D, 0x68, 0xA2, 0x8B, 0x67, +}; + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + +#ifndef HAVE_PKCS11_STATIC + CK_C_GetFunctionList func; + + dlib = dlopen(WOLFPKCS11_DLL_FILENAME, RTLD_NOW | RTLD_LOCAL); + if (dlib == NULL) { + fprintf(stderr, "dlopen error: %s\n", dlerror()); + return -1; + } + + func = (CK_C_GetFunctionList)dlsym(dlib, "C_GetFunctionList"); + if (func == NULL) { + 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_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + + if (slotCount == 0) { + fprintf(stderr, "No slots available\n"); + return CKR_GENERAL_ERROR; + } + slot = slotList[0]; + + return ret; +} + +static CK_RV pkcs11_final(void) +{ + if (funcList != NULL) { + funcList->C_Finalize(NULL); + funcList = NULL; + } +#ifndef HAVE_PKCS11_STATIC + if (dlib) { + dlclose(dlib); + dlib = NULL; + } +#endif + return CKR_OK; +} + +static CK_RV pkcs11_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_set_empty_user_pin(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session; + 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_SO, soPin, soPinLen); + if (ret != CKR_OK) { + funcList->C_CloseSession(session); + return ret; + } + + ret = funcList->C_InitPIN(session, userPin, userPinLen); + funcList->C_Logout(session); + funcList->C_CloseSession(session); + return ret; +} + +static CK_RV create_private_token_key(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, testKeyData, sizeof(testKeyData) }, + { CKA_ID, keyId, sizeof(keyId) }, + }; + CK_ULONG tmplCnt = sizeof(tmpl) / sizeof(*tmpl); + + return funcList->C_CreateObject(session, tmpl, tmplCnt, key); +} + +/* Count objects matching the CKA_ID of the private key. */ +static CK_RV count_private_key(CK_SESSION_HANDLE session, CK_ULONG* countOut) +{ + CK_RV ret; + CK_OBJECT_HANDLE found[4]; + CK_ULONG count = 0; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_ID, keyId, sizeof(keyId) }, + }; + + ret = funcList->C_FindObjectsInit(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl)); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_FindObjects(session, found, + sizeof(found) / sizeof(*found), &count); + if (ret != CKR_OK) { + funcList->C_FindObjectsFinal(session); + return ret; + } + + ret = funcList->C_FindObjectsFinal(session); + if (ret != CKR_OK) + return ret; + + *countOut = count; + return CKR_OK; +} + +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 int private_object_empty_pin_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_ULONG count; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + int result = 0; + + printf("\n=== Testing CKA_PRIVATE enforcement with empty user PIN ===\n"); + + cleanup_test_files(TEST_DIR); + + ret = pkcs11_init(); + CHECK_CKR(ret, "C_Initialize", CKR_OK); + + ret = pkcs11_init_token(); + CHECK_CKR(ret, "C_InitToken", CKR_OK); + + { + CK_TOKEN_INFO tokenInfo; + ret = funcList->C_GetTokenInfo(slot, &tokenInfo); + CHECK_CKR(ret, "C_GetTokenInfo", CKR_OK); + if (tokenInfo.ulMinPinLen > 0) { + printf("Skipping: token requires minimum PIN length %lu " + "(empty PIN not allowed)\n", + (unsigned long)tokenInfo.ulMinPinLen); + test_skipped = 1; + pkcs11_final(); + return 0; + } + } + + ret = pkcs11_set_empty_user_pin(); + CHECK_CKR(ret, "C_InitPIN (empty)", CKR_OK); + + /* Open a public (un-logged-in) session. With an empty PIN the token + * reports CKF_LOGIN_REQUIRED off, so this is the canonical empty-PIN + * usage: never call C_Login. The object stays decoded in memory. */ + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &session); + CHECK_CKR(ret, "C_OpenSession (public)", CKR_OK); + + ret = create_private_token_key(session, &key); + CHECK_CKR(ret, "C_CreateObject (CKA_PRIVATE=TRUE)", CKR_OK); + + /* Security check 1 (discovery): C_FindObjects must not reveal the + * CKA_PRIVATE object to a public session, even with an empty PIN. */ + ret = count_private_key(session, &count); + CHECK_CKR(ret, "C_FindObjects (public session)", CKR_OK); + if (count != 0) { + fprintf(stderr, "FAIL: public session discovered %lu CKA_PRIVATE " + "object(s) under empty PIN (expected 0)\n", + (unsigned long)count); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: public session cannot discover the private key\n"); + test_passed++; + +#ifndef WOLFPKCS11_NSS + /* Security check 2 (lookup by handle): resolving the object by handle + * must also be denied for a public session. (Skipped under + * WOLFPKCS11_NSS, which resolves private objects by handle as the + * internal crypto module and does not call C_Login.) */ + { + CK_OBJECT_CLASS gotClass; + CK_ATTRIBUTE getTmpl[] = { + { CKA_CLASS, &gotClass, sizeof(gotClass) }, + }; + ret = funcList->C_GetAttributeValue(session, key, getTmpl, 1); + if (ret == CKR_OK) { + fprintf(stderr, "FAIL: public session resolved CKA_PRIVATE object " + "by handle under empty PIN (expected failure)\n"); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: public session cannot resolve the private key by handle " + "(got 0x%lx)\n", (unsigned long)ret); + test_passed++; + } +#endif + + /* Logging in - which an empty PIN still permits - grants access. */ + ret = funcList->C_Login(session, CKU_USER, userPin, userPinLen); + CHECK_CKR(ret, "C_Login (USER, empty PIN)", CKR_OK); + + ret = count_private_key(session, &count); + CHECK_CKR(ret, "C_FindObjects (logged in)", CKR_OK); + if (count != 1) { + fprintf(stderr, "FAIL: logged-in session should find 1 private key, " + "found %lu\n", (unsigned long)count); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: login with empty PIN grants access to the private key\n"); + test_passed++; + + { + unsigned char value[64]; + CK_ATTRIBUTE getTmpl[] = { + { CKA_VALUE, value, sizeof(value) }, + }; + ret = funcList->C_GetAttributeValue(session, key, getTmpl, 1); + CHECK_CKR(ret, "C_GetAttributeValue (logged in)", CKR_OK); + if (getTmpl[0].ulValueLen != sizeof(testKeyData) || + XMEMCMP(value, testKeyData, sizeof(testKeyData)) != 0) { + fprintf(stderr, "FAIL: key value mismatch after login\n"); + test_failed++; + result = -1; + goto cleanup; + } + printf("PASS: logged-in session reads the private key value\n"); + test_passed++; + } + +cleanup: + if (session != 0) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + pkcs11_final(); + return result; +} + +static void print_results(void) +{ + printf("\n=== Test Results ===\n"); + printf("Tests passed: %d\n", test_passed); + printf("Tests failed: %d\n", test_failed); + if (test_skipped != 0) + printf("Tests skipped: %d\n", test_skipped); + + if (test_failed == 0) + printf("ALL TESTS PASSED!\n"); + else + printf("SOME TESTS FAILED!\n"); +} + +int main(int argc, char* argv[]) +{ +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 CKA_PRIVATE Empty PIN Test ===\n"); + + (void)private_object_empty_pin_test(); + + print_results(); + + return (test_failed == 0) ? 0 : 1; +} diff --git a/tests/rsa_private_usage_default_test.c b/tests/rsa_private_usage_default_test.c new file mode 100644 index 00000000..93000aa2 --- /dev/null +++ b/tests/rsa_private_usage_default_test.c @@ -0,0 +1,351 @@ +/* rsa_private_usage_default_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Test for issue F-5520: RSA private keys must not silently default to + * multiple cryptographic uses. With a minimal template, CKA_DECRYPT, + * CKA_SIGN and CKA_SIGN_RECOVER default to CK_FALSE; each use must be + * requested explicitly. + */ + +#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" + +/* The permissive multi-use defaults are expected under the legacy macro or in + * an NSS build (which keeps the historical permissive key defaults). */ +#if defined(WOLFPKCS11_LEGACY_RSA_USAGE_DEFAULT) || \ + defined(WOLFPKCS11_NSS) + #define EXPECT_PERMISSIVE_USAGE +#endif + +#define TEST_DIR "./store/rsa_private_usage_default_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +static int test_passed = 0; +static int test_failed = 0; +static int test_skipped = 0; + +#ifndef HAVE_PKCS11_STATIC +static void* dlib; +#endif +static CK_FUNCTION_LIST* funcList; +static CK_SLOT_ID slot = 0; +static const char* tokenName = "wolfpkcs11"; +static byte* soPin = (byte*)"password123456"; +static int soPinLen = 14; +static byte* userPin = (byte*)"someUserPin"; +static int userPinLen = 11; + +#define CHECK_CKR(rv, op) do { \ + if (rv != CKR_OK) { \ + fprintf(stderr, "FAIL: %s: got 0x%lx\n", op, (unsigned long)rv); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while(0) + +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) +static int check_bool_attr(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + CK_ATTRIBUTE_TYPE type, CK_BBOOL expected, + const char* name) +{ + CK_RV ret; + CK_BBOOL val = 0xAA; + CK_ATTRIBUTE tmpl[] = { { type, &val, sizeof(val) } }; + + ret = funcList->C_GetAttributeValue(session, obj, tmpl, 1); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: get %s: 0x%lx\n", name, (unsigned long)ret); + test_failed++; + return -1; + } + if (val != expected) { + fprintf(stderr, "FAIL: %s: expected %d, got %d\n", name, + (int)expected, (int)val); + test_failed++; + return -1; + } + printf("PASS: %s is %s\n", name, expected ? "CK_TRUE" : "CK_FALSE"); + test_passed++; + return 0; +} + +static CK_RV gen_rsa(CK_SESSION_HANDLE session, CK_ATTRIBUTE* privTmpl, + CK_ULONG privCnt, CK_OBJECT_HANDLE* pub, + CK_OBJECT_HANDLE* priv) +{ + CK_MECHANISM mech; + CK_ULONG bits = 2048; + byte pubExp[] = { 0x01, 0x00, 0x01 }; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_MODULUS_BITS, &bits, sizeof(bits) }, + { CKA_PUBLIC_EXPONENT, pubExp, sizeof(pubExp) }, + }; + CK_ULONG pubCnt = sizeof(pubTmpl) / sizeof(*pubTmpl); + + mech.mechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; + mech.ulParameterLen = 0; + mech.pParameter = NULL; + return funcList->C_GenerateKeyPair(session, &mech, pubTmpl, pubCnt, + privTmpl, privCnt, pub, priv); +} + +static int test_rsa_private_usage_default(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE, priv = CK_INVALID_HANDLE; + CK_BBOOL ckFalse = CK_FALSE; + CK_BBOOL ckTrue = CK_TRUE; + int result = 0; + + /* Minimal private template: no usage attributes. */ + CK_ATTRIBUTE minTmpl[] = { + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + CK_ULONG minCnt = sizeof(minTmpl) / sizeof(*minTmpl); + + /* Opt-in: request signing only. */ + CK_ATTRIBUTE signTmpl[] = { + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, + }; + CK_ULONG signCnt = sizeof(signTmpl) / sizeof(*signTmpl); + + ret = gen_rsa(session, minTmpl, minCnt, &pub, &priv); + CHECK_CKR(ret, "C_GenerateKeyPair (minimal private template)"); + +#ifdef EXPECT_PERMISSIVE_USAGE + if (check_bool_attr(session, priv, CKA_SIGN, CK_TRUE, + "RSA priv CKA_SIGN (legacy)") != 0) + result = -1; + if (check_bool_attr(session, priv, CKA_DECRYPT, CK_TRUE, + "RSA priv CKA_DECRYPT (legacy)") != 0) + result = -1; +#else + if (check_bool_attr(session, priv, CKA_SIGN, CK_FALSE, + "RSA priv CKA_SIGN") != 0) + result = -1; + if (check_bool_attr(session, priv, CKA_DECRYPT, CK_FALSE, + "RSA priv CKA_DECRYPT") != 0) + result = -1; + if (check_bool_attr(session, priv, CKA_SIGN_RECOVER, CK_FALSE, + "RSA priv CKA_SIGN_RECOVER") != 0) + result = -1; +#endif + + funcList->C_DestroyObject(session, priv); + funcList->C_DestroyObject(session, pub); + priv = pub = CK_INVALID_HANDLE; + + /* Opt-in must still produce a signing key (and only that). */ + ret = gen_rsa(session, signTmpl, signCnt, &pub, &priv); + CHECK_CKR(ret, "C_GenerateKeyPair (CKA_SIGN=TRUE template)"); + + if (check_bool_attr(session, priv, CKA_SIGN, CK_TRUE, + "opt-in CKA_SIGN") != 0) + result = -1; +#ifndef EXPECT_PERMISSIVE_USAGE + if (check_bool_attr(session, priv, CKA_DECRYPT, CK_FALSE, + "opt-in leaves CKA_DECRYPT") != 0) + result = -1; +#endif + +cleanup: + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + return result; +} +#endif /* !NO_RSA */ + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + +#ifndef HAVE_PKCS11_STATIC + CK_C_GetFunctionList func; + + dlib = dlopen(WOLFPKCS11_DLL_FILENAME, RTLD_NOW | RTLD_LOCAL); + if (dlib == NULL) { + fprintf(stderr, "dlopen error: %s\n", dlerror()); + return -1; + } + func = (CK_C_GetFunctionList)dlsym(dlib, "C_GetFunctionList"); + if (func == NULL) { + dlclose(dlib); + return -1; + } + ret = func(&funcList); + if (ret != CKR_OK) { + dlclose(dlib); + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) + return ret; +#endif + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + ret = funcList->C_Initialize(&args); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + if (slotCount == 0) + return CKR_GENERAL_ERROR; + slot = slotList[0]; + return ret; +} + +static CK_RV pkcs11_final(void) +{ + if (funcList != NULL) { + funcList->C_Finalize(NULL); + funcList = NULL; + } +#ifndef HAVE_PKCS11_STATIC + if (dlib) { + dlclose(dlib); + dlib = NULL; + } +#endif + return CKR_OK; +} + +static CK_RV pkcs11_setup_token(void) +{ + CK_RV ret; + unsigned char label[32]; + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + ret = funcList->C_InitToken(slot, soPin, soPinLen, label); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) + return ret; + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret == CKR_OK) + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + return ret; +} + +static int rsa_private_usage_default_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = 0; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + int result = 0; + + printf("\n=== Testing RSA private key usage defaults ===\n"); + + ret = pkcs11_init(); + CHECK_CKR(ret, "C_Initialize"); + + ret = pkcs11_setup_token(); + CHECK_CKR(ret, "token setup"); + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &session); + CHECK_CKR(ret, "C_OpenSession"); + ret = funcList->C_Login(session, CKU_USER, userPin, userPinLen); + CHECK_CKR(ret, "C_Login"); + +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) + if (test_rsa_private_usage_default(session) != 0) + result = -1; +#else + printf("RSA key generation not available, skipping\n"); + test_skipped = 1; +#endif + +cleanup: + if (session != 0) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + pkcs11_final(); + return result; +} + +static void print_results(void) +{ + printf("\n=== Test Results ===\n"); + printf("Tests passed: %d\n", test_passed); + printf("Tests failed: %d\n", test_failed); + if (test_skipped != 0) + printf("Tests skipped: %d\n", test_skipped); + if (test_failed == 0) + printf("ALL TESTS PASSED!\n"); + else + printf("SOME TESTS FAILED!\n"); +} + +int main(int argc, char* argv[]) +{ +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 RSA Private Usage Default Test ===\n"); + + (void)rsa_private_usage_default_test(); + + print_results(); + return (test_failed == 0) ? 0 : 1; +} diff --git a/tests/secret_extractable_default_test.c b/tests/secret_extractable_default_test.c new file mode 100644 index 00000000..88d0441f --- /dev/null +++ b/tests/secret_extractable_default_test.c @@ -0,0 +1,333 @@ +/* secret_extractable_default_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Test for issue F-5519: a secret key created from a template that omits + * CKA_EXTRACTABLE must default to CKA_EXTRACTABLE=CK_FALSE (and + * CKA_NEVER_EXTRACTABLE=CK_TRUE) so it is not wrap-exportable by default. + * Extractability remains available when the template opts in. + */ + +#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" + +/* Extractable-by-default is expected under the legacy macro or in an NSS + * build (which keeps the historical permissive key defaults). */ +#if defined(WOLFPKCS11_LEGACY_EXTRACTABLE_TRUE_DEFAULT) || \ + defined(WOLFPKCS11_NSS) + #define EXPECT_EXTRACTABLE_DEFAULT +#endif + +#define TEST_DIR "./store/secret_extractable_default_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +static int test_passed = 0; +static int test_failed = 0; +static int test_skipped = 0; + +#ifndef HAVE_PKCS11_STATIC +static void* dlib; +#endif +static CK_FUNCTION_LIST* funcList; +static CK_SLOT_ID slot = 0; +static const char* tokenName = "wolfpkcs11"; +static byte* soPin = (byte*)"password123456"; +static int soPinLen = 14; +static byte* userPin = (byte*)"someUserPin"; +static int userPinLen = 11; + +#define CHECK_CKR(rv, op) do { \ + if (rv != CKR_OK) { \ + fprintf(stderr, "FAIL: %s: got 0x%lx\n", op, (unsigned long)rv); \ + test_failed++; \ + result = -1; \ + goto cleanup; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while(0) + +static int check_bool_attr(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + CK_ATTRIBUTE_TYPE type, CK_BBOOL expected, + const char* name) +{ + CK_RV ret; + CK_BBOOL val = 0xAA; + CK_ATTRIBUTE tmpl[] = { { type, &val, sizeof(val) } }; + + ret = funcList->C_GetAttributeValue(session, obj, tmpl, 1); + if (ret != CKR_OK) { + fprintf(stderr, "FAIL: get %s: 0x%lx\n", name, (unsigned long)ret); + test_failed++; + return -1; + } + if (val != expected) { + fprintf(stderr, "FAIL: %s: expected %d, got %d\n", name, + (int)expected, (int)val); + test_failed++; + return -1; + } + printf("PASS: %s is %s\n", name, expected ? "CK_TRUE" : "CK_FALSE"); + test_passed++; + return 0; +} + +#ifndef NO_AES +static CK_RV gen_aes(CK_SESSION_HANDLE session, CK_ATTRIBUTE* tmpl, + CK_ULONG cnt, CK_OBJECT_HANDLE* key) +{ + CK_MECHANISM mech; + mech.mechanism = CKM_AES_KEY_GEN; + mech.ulParameterLen = 0; + mech.pParameter = NULL; + return funcList->C_GenerateKey(session, &mech, tmpl, cnt, key); +} + +static int test_secret_extractable_default(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_KEY_TYPE keyType = CKK_AES; + CK_ULONG keyLen = 32; + CK_BBOOL ckTrue = CK_TRUE; + int result = 0; + + /* Minimal template: CKA_EXTRACTABLE omitted. */ + CK_ATTRIBUTE minTmpl[] = { + { CKA_VALUE_LEN, &keyLen, sizeof(keyLen) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + }; + CK_ULONG minCnt = sizeof(minTmpl) / sizeof(*minTmpl); + + /* Opt-in extractable template. */ + CK_ATTRIBUTE extrTmpl[] = { + { CKA_VALUE_LEN, &keyLen, sizeof(keyLen) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + }; + CK_ULONG extrCnt = sizeof(extrTmpl) / sizeof(*extrTmpl); + + ret = gen_aes(session, minTmpl, minCnt, &key); + CHECK_CKR(ret, "C_GenerateKey (minimal template)"); + +#ifdef EXPECT_EXTRACTABLE_DEFAULT + if (check_bool_attr(session, key, CKA_EXTRACTABLE, CK_TRUE, + "secret CKA_EXTRACTABLE (legacy)") != 0) + result = -1; +#else + if (check_bool_attr(session, key, CKA_EXTRACTABLE, CK_FALSE, + "secret CKA_EXTRACTABLE") != 0) + result = -1; + if (check_bool_attr(session, key, CKA_NEVER_EXTRACTABLE, CK_TRUE, + "secret CKA_NEVER_EXTRACTABLE") != 0) + result = -1; +#endif + + funcList->C_DestroyObject(session, key); + key = CK_INVALID_HANDLE; + + /* Opt-in must still produce an extractable key. */ + ret = gen_aes(session, extrTmpl, extrCnt, &key); + CHECK_CKR(ret, "C_GenerateKey (CKA_EXTRACTABLE=TRUE template)"); + + if (check_bool_attr(session, key, CKA_EXTRACTABLE, CK_TRUE, + "opt-in CKA_EXTRACTABLE") != 0) + result = -1; + if (check_bool_attr(session, key, CKA_NEVER_EXTRACTABLE, CK_FALSE, + "opt-in CKA_NEVER_EXTRACTABLE") != 0) + result = -1; + +cleanup: + if (key != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, key); + return result; +} +#endif /* !NO_AES */ + +static CK_RV pkcs11_init(void) +{ + CK_RV ret; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + +#ifndef HAVE_PKCS11_STATIC + CK_C_GetFunctionList func; + + dlib = dlopen(WOLFPKCS11_DLL_FILENAME, RTLD_NOW | RTLD_LOCAL); + if (dlib == NULL) { + fprintf(stderr, "dlopen error: %s\n", dlerror()); + return -1; + } + func = (CK_C_GetFunctionList)dlsym(dlib, "C_GetFunctionList"); + if (func == NULL) { + dlclose(dlib); + return -1; + } + ret = func(&funcList); + if (ret != CKR_OK) { + dlclose(dlib); + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) + return ret; +#endif + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + ret = funcList->C_Initialize(&args); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (ret != CKR_OK) + return ret; + if (slotCount == 0) + return CKR_GENERAL_ERROR; + slot = slotList[0]; + return ret; +} + +static CK_RV pkcs11_final(void) +{ + if (funcList != NULL) { + funcList->C_Finalize(NULL); + funcList = NULL; + } +#ifndef HAVE_PKCS11_STATIC + if (dlib) { + dlclose(dlib); + dlib = NULL; + } +#endif + return CKR_OK; +} + +static CK_RV pkcs11_setup_token(void) +{ + CK_RV ret; + unsigned char label[32]; + CK_SESSION_HANDLE soSession; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenName, XSTRLEN(tokenName)); + ret = funcList->C_InitToken(slot, soPin, soPinLen, label); + if (ret != CKR_OK) + return ret; + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &soSession); + if (ret != CKR_OK) + return ret; + ret = funcList->C_Login(soSession, CKU_SO, soPin, soPinLen); + if (ret == CKR_OK) + ret = funcList->C_InitPIN(soSession, userPin, userPinLen); + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + return ret; +} + +static int secret_extractable_default_test(void) +{ + CK_RV ret; + CK_SESSION_HANDLE session = 0; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + int result = 0; + + printf("\n=== Testing secret key CKA_EXTRACTABLE default ===\n"); + + ret = pkcs11_init(); + CHECK_CKR(ret, "C_Initialize"); + + ret = pkcs11_setup_token(); + CHECK_CKR(ret, "token setup"); + + ret = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &session); + CHECK_CKR(ret, "C_OpenSession"); + ret = funcList->C_Login(session, CKU_USER, userPin, userPinLen); + CHECK_CKR(ret, "C_Login"); + +#ifndef NO_AES + if (test_secret_extractable_default(session) != 0) + result = -1; +#else + printf("AES not available, skipping\n"); + test_skipped = 1; +#endif + +cleanup: + if (session != 0) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + pkcs11_final(); + return result; +} + +static void print_results(void) +{ + printf("\n=== Test Results ===\n"); + printf("Tests passed: %d\n", test_passed); + printf("Tests failed: %d\n", test_failed); + if (test_skipped != 0) + printf("Tests skipped: %d\n", test_skipped); + if (test_failed == 0) + printf("ALL TESTS PASSED!\n"); + else + printf("SOME TESTS FAILED!\n"); +} + +int main(int argc, char* argv[]) +{ +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 Secret Extractable Default Test ===\n"); + + (void)secret_extractable_default_test(); + + print_results(); + return (test_failed == 0) ? 0 : 1; +}