From 4b0979a0cb9c512727e347514390b8fe3d1487ff Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Fri, 2 Oct 2026 11:15:31 -0700 Subject: [PATCH] fix: strip NSS database type from configdir NSS passes the database directory to C_Initialize with its database type prefix when the application uses one ("certutil -d sql:/path", Chromium's sql:$HOME/.pki/nssdb). The prefix became part of the token store path, so no token could be stored and C_InitPIN failed with CKR_FUNCTION_FAILED. Strip the sql:, dbm: and extern: prefixes. Add tests/nss_configdir_prefix_test.c. --- src/wolfpkcs11.c | 13 ++ tests/include.am | 7 + tests/nss_configdir_prefix_test.c | 263 ++++++++++++++++++++++++++++++ 3 files changed, 283 insertions(+) create mode 100644 tests/nss_configdir_prefix_test.c diff --git a/src/wolfpkcs11.c b/src/wolfpkcs11.c index 90a44720..bc1e64d0 100644 --- a/src/wolfpkcs11.c +++ b/src/wolfpkcs11.c @@ -415,6 +415,19 @@ static CK_RV ParseNssConfigString(char *nssArgs, if (keyLen == XSTR_SIZEOF("configdir") && XMEMCMP(keyStart, "configdir", keyLen) == 0) { + /* NSS prefixes the directory with the database type + * ("sql:/home/user/.pki/nssdb"); the store uses the directory. */ + static const char* const dbTypes[] = { "sql:", "dbm:", "extern:" }; + size_t i; + for (i = 0; i < sizeof(dbTypes) / sizeof(dbTypes[0]); i++) { + size_t typeLen = XSTRLEN(dbTypes[i]); + if (valueLen > typeLen && + XMEMCMP(valueStart, dbTypes[i], typeLen) == 0) { + valueStart += typeLen; + valueLen -= typeLen; + break; + } + } *configdir = valueStart; *configdirLen = valueLen; /* Exit since we only support configdir */ diff --git a/tests/include.am b/tests/include.am index 068f1074..7b242e51 100644 --- a/tests/include.am +++ b/tests/include.am @@ -290,6 +290,11 @@ noinst_PROGRAMS += tests/data_object_partial_update_test tests_data_object_partial_update_test_SOURCES = tests/data_object_partial_update_test.c tests_data_object_partial_update_test_LDADD = +check_PROGRAMS += tests/nss_configdir_prefix_test +noinst_PROGRAMS += tests/nss_configdir_prefix_test +tests_nss_configdir_prefix_test_SOURCES = tests/nss_configdir_prefix_test.c +tests_nss_configdir_prefix_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -349,6 +354,7 @@ tests_generate_keypair_class_test_LDADD += src/libwolfpkcs11.la tests_copy_data_object_test_LDADD += src/libwolfpkcs11.la tests_copy_data_object_token_test_LDADD += src/libwolfpkcs11.la tests_data_object_partial_update_test_LDADD += src/libwolfpkcs11.la +tests_nss_configdir_prefix_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -400,6 +406,7 @@ tests_generate_keypair_class_test_LDADD += src/libwolfpkcs11.la tests_copy_data_object_test_LDADD += src/libwolfpkcs11.la tests_copy_data_object_token_test_LDADD += src/libwolfpkcs11.la tests_data_object_partial_update_test_LDADD += src/libwolfpkcs11.la +tests_nss_configdir_prefix_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ diff --git a/tests/nss_configdir_prefix_test.c b/tests/nss_configdir_prefix_test.c new file mode 100644 index 00000000..6be6c829 --- /dev/null +++ b/tests/nss_configdir_prefix_test.c @@ -0,0 +1,263 @@ +/* nss_configdir_prefix_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 + * + * Test that the NSS configdir passed to C_Initialize is used as a directory + * when NSS prefixes it with its database type ("sql:/path", "dbm:/path", + * "extern:/path"), as NSS does for "certutil -d sql:/path" and for + * applications such as Chromium. The prefix must not become part of the + * token store path. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" + +#if defined(WOLFPKCS11_NSS) && !defined(WOLFPKCS11_NO_STORE) && \ + !defined(WOLFPKCS11_NO_ENV) + +#define TEST_DIR "./store/nss_configdir_prefix_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +static int test_passed = 0; +static int test_failed = 0; + +#ifndef HAVE_PKCS11_STATIC +static void* dlib; +#endif +static CK_FUNCTION_LIST* funcList; +static byte* soPin = (byte*)"password123456"; +static int soPinLen = 14; +static byte* userPin = (byte*)"wolfpkcs11-test"; +static int userPinLen = 15; + +static int check(int ok, const char* what, const char* prefix) +{ + if (ok) { + printf("PASS: %s (prefix \"%s\")\n", what, prefix); + test_passed++; + } + else { + fprintf(stderr, "FAIL: %s (prefix \"%s\")\n", what, prefix); + test_failed++; + } + return ok ? 0 : -1; +} + +static int file_exists(const char* path) +{ + struct stat st; + return stat(path, &st) == 0; +} + +static void remove_dir_files(const char* dir) +{ + char path[512]; + snprintf(path, sizeof(path), "%s/%s", dir, WOLFPKCS11_TOKEN_FILENAME); + (void)remove(path); + (void)rmdir(dir); +} + +static CK_RV load_library(void) +{ +#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 CKR_GENERAL_ERROR; + } + func = (CK_C_GetFunctionList)dlsym(dlib, "C_GetFunctionList"); + if (func == NULL) { + fprintf(stderr, "Failed to get function list function\n"); + dlclose(dlib); + dlib = NULL; + return CKR_GENERAL_ERROR; + } + return func(&funcList); +#else + return C_GetFunctionList(&funcList); +#endif +} + +static void unload_library(void) +{ +#ifndef HAVE_PKCS11_STATIC + if (dlib != NULL) { + dlclose(dlib); + dlib = NULL; + } +#endif + funcList = NULL; +} + +/* Initialize with an NSS-style parameter string whose configdir has the + * given database type prefix, set up a token and user PIN, finalize, and + * check where the token was stored. */ +static int test_prefix(const char* prefix) +{ + int result = 0; + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + unsigned char label[32]; + char params[512]; + char dir[256]; + char tokenFile[512]; + char prefixedDir[300]; + + snprintf(dir, sizeof(dir), "%s/%s", TEST_DIR, + prefix[0] == '\0' ? "none" : prefix); + /* The directory name ends in ':' for prefixed cases; that is fine on + * POSIX and keeps each case separate. */ + if (dir[strlen(dir) - 1] == ':') + dir[strlen(dir) - 1] = '\0'; + remove_dir_files(dir); + if (mkdir(dir, 0700) != 0) { + fprintf(stderr, "mkdir %s failed\n", dir); + return check(0, "create test directory", prefix); + } + snprintf(tokenFile, sizeof(tokenFile), "%s/%s", dir, + WOLFPKCS11_TOKEN_FILENAME); + snprintf(prefixedDir, sizeof(prefixedDir), "%s%s", prefix, dir); + + /* The parameter string NSS passes to its internal module. */ + snprintf(params, sizeof(params), + "configdir='%s%s' certPrefix='' keyPrefix='' secmod='secmod.db' " + "flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' " + "updateid='' updateTokenDescription='' ", prefix, dir); + + if (load_library() != CKR_OK) + return check(0, "load library", prefix); + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + args.LibraryParameters = (CK_CHAR_PTR*)params; + rv = funcList->C_Initialize(&args); + if (check(rv == CKR_OK, "C_Initialize with NSS parameters", prefix) != 0) { + unload_library(); + return -1; + } + + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv != CKR_OK || slotCount == 0) { + result = check(0, "C_GetSlotList", prefix); + goto cleanup; + } + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, "nss configdir", 13); + rv = funcList->C_InitToken(slotList[0], soPin, soPinLen, label); + if (check(rv == CKR_OK, "C_InitToken", prefix) != 0) { + result = -1; + goto cleanup; + } + + rv = funcList->C_OpenSession(slotList[0], + CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &session); + if (rv == CKR_OK) + rv = funcList->C_Login(session, CKU_SO, soPin, soPinLen); + if (rv == CKR_OK) + rv = funcList->C_InitPIN(session, userPin, userPinLen); + if (check(rv == CKR_OK, "C_InitPIN", prefix) != 0) + result = -1; + if (session != CK_INVALID_HANDLE) { + funcList->C_Logout(session); + funcList->C_CloseSession(session); + } + +cleanup: + funcList->C_Finalize(NULL); + unload_library(); + + if (check(file_exists(tokenFile), "token stored in the configdir", prefix) + != 0) + result = -1; + if (prefix[0] != '\0' && check(!file_exists(prefixedDir), + "no store path with the database type prefix", prefix) != 0) + result = -1; + + remove_dir_files(dir); + return result; +} + +int main(int argc, char* argv[]) +{ + static const char* prefixes[] = { "sql:", "dbm:", "extern:", "" }; + size_t i; + + (void)argc; + (void)argv; + + printf("=== wolfPKCS11 NSS configdir prefix test ===\n"); + + /* WOLFPKCS11_TOKEN_PATH takes precedence over configdir and would hide + * how the configdir is handled. */ + unsetenv("WOLFPKCS11_TOKEN_PATH"); + (void)mkdir("./store", 0700); + (void)mkdir(TEST_DIR, 0700); + + for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++) + (void)test_prefix(prefixes[i]); + + (void)rmdir(TEST_DIR); + + printf("\n=== Test Results ===\n"); + printf("Tests passed: %d\n", test_passed); + printf("Tests failed: %d\n", test_failed); + return (test_failed == 0) ? 0 : 1; +} + +#else + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + printf("NSS build with a token store not configured, skipping test\n"); + return 0; +} + +#endif