diff --git a/README.md b/README.md index 849cdba2..84f42ba1 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,16 @@ Enables debugging printf's for store. Removes default implementation of storage functions. See wolfpkcs11/store.h for prototypes of functions to implement. +#### Define WOLFPKCS11_CUSTOM_STORE_CLOSE_COMMIT + +With `WOLFPKCS11_CUSTOM_STORE`, records that were written are closed with +`wolfPKCS11_Store_CloseCommit()` instead of `wolfPKCS11_Store_Close()`, so the +custom store must implement it too (`wolfPKCS11_Store_Close()` still closes +records that were read). It returns non-zero when the record did not commit, +for example when a flash write failed. wolfPKCS11 then fails the operation and +writes all records of the token on its next store. Without this define, the +custom store cannot report a failed commit. + #### Define WOLFPKCS11_KEYPAIR_GEN_COMMON_LABEL Sets the private key's label against the public key when generating key pairs. diff --git a/configure.ac b/configure.ac index 569d38d7..97caf8bc 100644 --- a/configure.ac +++ b/configure.ac @@ -664,6 +664,18 @@ AX_HARDEN_CC_COMPILER_FLAGS OPTION_FLAGS="$CFLAGS $CPPFLAGS $AM_CFLAGS" +# The custom store tests build the library with WOLFPKCS11_CUSTOM_STORE. NSS +# needs the file store, and a custom store cannot be built with the TPM store. +# Either macro may be defined with a value. +AS_CASE([" $OPTION_FLAGS "], + [*" -DWOLFPKCS11_NSS "*|*" -DWOLFPKCS11_NSS="*], + [ENABLED_CUSTOM_STORE_TEST=no], + [*" -DWOLFPKCS11_TPM_STORE "*|*" -DWOLFPKCS11_TPM_STORE="*], + [ENABLED_CUSTOM_STORE_TEST=no], + [ENABLED_CUSTOM_STORE_TEST=yes]) +AM_CONDITIONAL([BUILD_CUSTOM_STORE_TEST], + [test "x$ENABLED_CUSTOM_STORE_TEST" = "xyes"]) + AS_IF([test "x$WOLFPKCS11_STATIC_WOLFSSL" = "xyes"], [WOLFPKCS11_CONFIG_LIBS="$LIBS -lm" LIBS="$STATIC_WOLFSSL $LIBS -lm"], diff --git a/src/internal.c b/src/internal.c index 8efcee80..a1d8eff7 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2999,7 +2999,7 @@ static int wolfPKCS11_Store_TpmCommit(WP11_TpmStore* tpmStore) * @return 0 on success. * @return Other value when written data could not be committed. */ -static int wolfPKCS11_Store_CloseCommit(void* store, int commit) +int wolfPKCS11_Store_CloseCommit(void* store, int commit) { int ret = 0; #ifdef WOLFPKCS11_TPM_STORE @@ -3220,6 +3220,26 @@ int wolfPKCS11_Store_Write(void* store, unsigned char* buffer, int len) } #endif +#ifdef WOLFPKCS11_CUSTOM_STORE +/* + * Maps a custom store's failure code to BUFFER_E. + * The store's own code could otherwise read as another status, such as + * PIN_INVALID_E. + * + * @param [in] ret Result of the store call. + * @param [in] keepNotAv 1 to keep NOT_AVAILABLE_E, which means the data is + * not stored. + * @return 0, or NOT_AVAILABLE_E when kept, as passed in. + * @return BUFFER_E for any other failure. + */ +static int wp11_storage_custom_ret(int ret, int keepNotAv) +{ + if (ret != 0 && (ret != NOT_AVAILABLE_E || !keepNotAv)) + ret = BUFFER_E; + return ret; +} +#endif + /* * Opens access to location to read token data. * @@ -3236,7 +3256,12 @@ int wolfPKCS11_Store_Write(void* store, unsigned char* buffer, int len) static int wp11_storage_open_readonly(int type, CK_ULONG id1, CK_ULONG id2, void** storage) { - return wolfPKCS11_Store_Open(type, id1, id2, 1, storage); + int ret = wolfPKCS11_Store_Open(type, id1, id2, 1, storage); + +#ifdef WOLFPKCS11_CUSTOM_STORE + ret = wp11_storage_custom_ret(ret, 1); +#endif + return ret; } /* @@ -3257,14 +3282,28 @@ static int wp11_storage_open(int type, CK_ULONG id1, CK_ULONG id2, #ifdef WOLFPKCS11_TPM_STORE return wolfPKCS11_Store_OpenSz(type, id1, id2, 0, variableSz, storage); #else + int ret; + (void)variableSz; /* not used */ - return wolfPKCS11_Store_Open(type, id1, id2, 0, storage); + ret = wolfPKCS11_Store_Open(type, id1, id2, 0, storage); +#ifdef WOLFPKCS11_CUSTOM_STORE + /* NOT_AVAILABLE_E fails too: it would read as storage turned off, and the + * data would not be stored. */ + ret = wp11_storage_custom_ret(ret, 0); +#endif + return ret; #endif } static int wp11_storage_remove(int type, CK_ULONG id1, CK_ULONG id2) { - return wolfPKCS11_Store_Remove(type, id1, id2); + int ret = wolfPKCS11_Store_Remove(type, id1, id2); + +#ifdef WOLFPKCS11_CUSTOM_STORE + /* NOT_AVAILABLE_E is kept: there was nothing to remove. */ + ret = wp11_storage_custom_ret(ret, 1); +#endif + return ret; } /* @@ -3289,11 +3328,19 @@ static void wp11_storage_close(void* storage) */ static int wp11_storage_close_write(void* storage, int ret) { -#ifdef WOLFPKCS11_CUSTOM_STORE +#if defined(WOLFPKCS11_CUSTOM_STORE) && \ + !defined(WOLFPKCS11_CUSTOM_STORE_CLOSE_COMMIT) + /* The store cannot report a failed commit. */ wolfPKCS11_Store_Close(storage); #else int closeRet = wolfPKCS11_Store_CloseCommit(storage, ret == 0); +#ifdef WOLFPKCS11_CUSTOM_STORE + /* Any failure means the data did not commit. A custom store's code could + * otherwise read as NOT_AVAILABLE_E, which means storage is turned off. */ + if (closeRet != 0) + closeRet = BUFFER_E; +#endif if (ret == 0) ret = closeRet; #endif @@ -3351,6 +3398,14 @@ static int wp11_storage_read(void* storage, unsigned char* buffer, int len) /* Failed to read all data. */ ret = BUFFER_E; } +#ifdef WOLFPKCS11_CUSTOM_STORE + else { + /* A custom store's failure code could otherwise read as another + * status, such as PIN_INVALID_E. Not BUFFER_E: loaders take that as + * the end of a record in an older format. */ + ret = WC_HW_E; + } +#endif return ret; } @@ -3425,8 +3480,9 @@ static int wp11_storage_write(void* storage, unsigned char* buffer, int len) /* All written successfully. */ ret = 0; } - else if (ret == 0) { - /* Failed to write all data. */ + else { + /* Failed to write all data. A custom store's failure code could + * otherwise read as another status, such as PIN_INVALID_E. */ ret = BUFFER_E; } @@ -4987,27 +5043,27 @@ static int wp11_Object_Load_Data(WP11_Object* object, int tokenId, int objId) &tempLen); if (ret == 0) object->data.genericData.dataLen = (word32)tempLen; - } - /* Read application length and application. */ - if (ret == 0) { - ret = wp11_storage_read_alloc_array(storage, - &object->data.genericData.application, - &tempLen); - if (ret == 0) - object->data.genericData.applicationLen = (word32)tempLen; - } + /* Read application length and application. */ + if (ret == 0) { + ret = wp11_storage_read_alloc_array(storage, + &object->data.genericData.application, + &tempLen); + if (ret == 0) + object->data.genericData.applicationLen = (word32)tempLen; + } - /* Read object ID length and object ID. */ - if (ret == 0) { - ret = wp11_storage_read_alloc_array(storage, - &object->data.genericData.objectId, - &tempLen); - if (ret == 0) - object->data.genericData.objectIdLen = (word32)tempLen; - } + /* Read object ID length and object ID. */ + if (ret == 0) { + ret = wp11_storage_read_alloc_array(storage, + &object->data.genericData.objectId, + &tempLen); + if (ret == 0) + object->data.genericData.objectIdLen = (word32)tempLen; + } - wp11_storage_close(storage); + wp11_storage_close(storage); + } return ret; } @@ -5556,28 +5612,28 @@ static int wp11_Object_Store_Data(WP11_Object* object, int tokenId, int objId) /* Open access to data. */ ret = wp11_storage_open(WOLFPKCS11_STORE_DATA, tokenId, objId, variableSz, &storage); - /* Write data length and data. */ if (ret == 0) { + /* Write data length and data. */ ret = wp11_storage_write_array(storage, object->data.genericData.data, object->data.genericData.dataLen); - } - /* Write application length and application. */ - if (ret == 0) { - ret = wp11_storage_write_array(storage, - object->data.genericData.application, - object->data.genericData.applicationLen); - } + /* Write application length and application. */ + if (ret == 0) { + ret = wp11_storage_write_array(storage, + object->data.genericData.application, + object->data.genericData.applicationLen); + } /* Write object ID length and object ID. */ - if (ret == 0) { - ret = wp11_storage_write_array(storage, - object->data.genericData.objectId, - object->data.genericData.objectIdLen); - } + if (ret == 0) { + ret = wp11_storage_write_array(storage, + object->data.genericData.objectId, + object->data.genericData.objectIdLen); + } - ret = wp11_storage_close_write(storage, ret); + ret = wp11_storage_close_write(storage, ret); + } return ret; } diff --git a/tests/README.md b/tests/README.md index 32fbe0be..d5af5493 100644 --- a/tests/README.md +++ b/tests/README.md @@ -5,3 +5,6 @@ The test files have the following purposes: * `pkcs11mtt.c`: is the multithreaded tests * `pkcs11str.c`: is a storage test * `debug_test.c`: is a debug functionality demonstration test +* `custom_store_test.c`: is a custom store (`WOLFPKCS11_CUSTOM_STORE`) test. It + builds the library sources in, with and without + `WOLFPKCS11_CUSTOM_STORE_CLOSE_COMMIT` diff --git a/tests/custom_store_test.c b/tests/custom_store_test.c new file mode 100644 index 00000000..912eb12e --- /dev/null +++ b/tests/custom_store_test.c @@ -0,0 +1,717 @@ +/* custom_store_test.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * The library sources are built into this test with WOLFPKCS11_CUSTOM_STORE, + * and the test implements the store in memory. It is built twice: + * - custom_store_test, with WOLFPKCS11_CUSTOM_STORE_CLOSE_COMMIT, checks that + * a record whose commit fails, with any code, or whose write fails, fails + * the create, that a failed write closes the record with commit set to 0, + * that a failed commit fails a destroy, and that the next store writes the + * records of every object again, + * - custom_store_legacy_test, without it, checks that a store implementing + * only wolfPKCS11_Store_Close() still builds and keeps token objects. + * Both check that a record that fails to open for writing fails the create + * with CKR_FUNCTION_FAILED, whatever code the store returns, that a failed + * remove fails the destroy, that any failed read or open for reading fails + * loading the token, and that a record that fails to open, for writing or + * reading, is not closed. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include +#include + +#include +#include +#include + +/* The library turns storage off without AES (wolfpkcs11/internal.h). */ +#if !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_TPM_STORE) && \ + !defined(NO_AES) +#define RUN_TEST +#endif + +/* Store status codes, as in wolfpkcs11/internal.h. */ +#define NOT_AVAILABLE_E -4 +/* Failure code of the store. The same value as PIN_INVALID_E, so a failed + * create only reports CKR_FUNCTION_FAILED when the code is not passed on. */ +#define STORE_FAIL_E -1 + +/* ---- Store kept in memory ---- */ + +#define STORE_RECORDS 64 + +typedef struct StoreRecord { + int used; + int type; + CK_ULONG id1; + CK_ULONG id2; + unsigned char* data; + int len; + int commits; /* Times the record was committed */ +} StoreRecord; + +typedef struct StoreHandle { + int read; + int type; + CK_ULONG id1; + CK_ULONG id2; + unsigned char* data; /* Record read, or data written */ + int len; + int pos; /* Read position */ +} StoreHandle; + +static StoreRecord records[STORE_RECORDS]; +static int commitCnt; /* Commits tried */ +static int openCnt; /* Opens for writing tried */ +static int writeCnt; /* Writes tried */ +static int readOpenCnt; /* Opens for reading tried */ +static int readCnt; /* Reads tried */ +static int removeCnt; /* Removes tried */ +static int discardCnt; /* Written records closed without commit */ +static int nullCloseCnt; /* Closes of a store that was not opened */ +static int injectCnt; /* Failures injected */ +static int failCommitAt = -1; /* Commit to fail, counted by commitCnt */ +static int failOpenAt = -1; /* Open to fail, counted by openCnt */ +static int failWriteAt = -1; /* Write to fail, counted by writeCnt */ +static int failReadOpenAt = -1; /* Open to fail, counted by readOpenCnt */ +static int failReadAt = -1; /* Read to fail, counted by readCnt */ +static int failRemoveAt = -1; /* Remove to fail, counted by removeCnt */ +static int failCommitCode = STORE_FAIL_E; /* Code of a failed commit */ +static int failOpenCode = STORE_FAIL_E; /* Code of a failed write open */ + +static StoreRecord* store_find(int type, CK_ULONG id1, CK_ULONG id2) +{ + int i; + + for (i = 0; i < STORE_RECORDS; i++) { + if (records[i].used && records[i].type == type && + records[i].id1 == id1 && records[i].id2 == id2) { + return &records[i]; + } + } + return NULL; +} + +static int store_commit(StoreHandle* handle) +{ + StoreRecord* rec = store_find(handle->type, handle->id1, handle->id2); + int i; + + for (i = 0; rec == NULL && i < STORE_RECORDS; i++) { + if (!records[i].used) { + rec = &records[i]; + XMEMSET(rec, 0, sizeof(*rec)); + rec->used = 1; + rec->type = handle->type; + rec->id1 = handle->id1; + rec->id2 = handle->id2; + } + } + if (rec == NULL) + return STORE_FAIL_E; + + free(rec->data); + rec->data = handle->data; + rec->len = handle->len; + rec->commits++; + handle->data = NULL; + return 0; +} + +static int store_close(void* store, int commit) +{ + StoreHandle* handle = (StoreHandle*)store; + int ret = 0; + + if (handle == NULL) { + nullCloseCnt++; + return 0; + } + if (!handle->read) { + if (!commit) { + discardCnt++; + } + else if (commitCnt++ == failCommitAt) { + /* As a failed flash commit: the record keeps its old data. */ + injectCnt++; + ret = failCommitCode; + } + else { + ret = store_commit(handle); + } + free(handle->data); + } + free(handle); + return ret; +} + +int wolfPKCS11_Store_Open(int type, CK_ULONG id1, CK_ULONG id2, int read, + void** store) +{ + StoreRecord* rec = store_find(type, id1, id2); + StoreHandle* handle; + + *store = NULL; + if (read && readOpenCnt++ == failReadOpenAt) { + injectCnt++; + return STORE_FAIL_E; + } + if (read && rec == NULL) + return NOT_AVAILABLE_E; + if (!read && openCnt++ == failOpenAt) { + injectCnt++; + return failOpenCode; + } + handle = (StoreHandle*)calloc(1, sizeof(*handle)); + if (handle == NULL) + return STORE_FAIL_E; + handle->read = read; + handle->type = type; + handle->id1 = id1; + handle->id2 = id2; + if (read) { + /* Reads use the record's data, which no write changes while open. */ + handle->data = rec->data; + handle->len = rec->len; + } + *store = handle; + return 0; +} + +int wolfPKCS11_Store_Remove(int type, CK_ULONG id1, CK_ULONG id2) +{ + StoreRecord* rec = store_find(type, id1, id2); + + if (removeCnt++ == failRemoveAt) { + injectCnt++; + return STORE_FAIL_E; + } + if (rec == NULL) + return NOT_AVAILABLE_E; + free(rec->data); + XMEMSET(rec, 0, sizeof(*rec)); + return 0; +} + +void wolfPKCS11_Store_Close(void* store) +{ + (void)store_close(store, 1); +} + +#ifdef WOLFPKCS11_CUSTOM_STORE_CLOSE_COMMIT +int wolfPKCS11_Store_CloseCommit(void* store, int commit) +{ + return store_close(store, commit); +} +#endif + +int wolfPKCS11_Store_Read(void* store, unsigned char* buffer, int len) +{ + StoreHandle* handle = (StoreHandle*)store; + + if (handle == NULL || !handle->read || len < 0) + return STORE_FAIL_E; + if (readCnt++ == failReadAt) { + injectCnt++; + return STORE_FAIL_E; + } + if (len > handle->len - handle->pos) + len = handle->len - handle->pos; + if (len > 0) + XMEMCPY(buffer, handle->data + handle->pos, len); + handle->pos += len; + return len; +} + +int wolfPKCS11_Store_Write(void* store, unsigned char* buffer, int len) +{ + StoreHandle* handle = (StoreHandle*)store; + unsigned char* data; + + if (handle == NULL || handle->read || len < 0) + return STORE_FAIL_E; + if (writeCnt++ == failWriteAt) { + injectCnt++; + return STORE_FAIL_E; + } + data = (unsigned char*)realloc(handle->data, handle->len + len + 1); + if (data == NULL) + return STORE_FAIL_E; + if (len > 0) + XMEMCPY(data + handle->len, buffer, len); + handle->data = data; + handle->len += len; + return len; +} + +#ifdef RUN_TEST + +#define CHECK(cond, msg) \ + do { \ + if (!(cond)) { \ + fprintf(stderr, "%s:%d - %s - FAIL\n", __FILE__, __LINE__, \ + msg); \ + return -1; \ + } \ + } \ + while (0) +#define CHECK_RV(rv, msg) \ + do { \ + if ((rv) != CKR_OK) { \ + fprintf(stderr, "%s:%d - %s: 0x%lx - FAIL\n", __FILE__, \ + __LINE__, msg, (unsigned long)(rv)); \ + return -1; \ + } \ + } \ + while (0) + +static CK_FUNCTION_LIST* funcList; +static CK_SLOT_ID slot; +static unsigned char tokenName[32] = "wolfpkcs11"; +static byte* soPin = (byte*)"password123456"; +static int soPinLen = 14; +static byte* userPin = (byte*)"wolfpkcs11-test"; +static int userPinLen = 15; + +static CK_BBOOL ckTrue = CK_TRUE; +static CK_BBOOL ckFalse = CK_FALSE; + +static void store_free(void) +{ + int i; + + for (i = 0; i < STORE_RECORDS; i++) + free(records[i].data); + XMEMSET(records, 0, sizeof(records)); +} + +/* Remove the record of the given type for the object with the given store id. + * Returns 1 when removed. */ +static int store_drop(int type, CK_ULONG id) +{ + int i; + + for (i = 0; i < STORE_RECORDS; i++) { + if (records[i].used && records[i].type == type && + records[i].id2 == id) { + free(records[i].data); + XMEMSET(&records[i], 0, sizeof(records[i])); + return 1; + } + } + return 0; +} + +/* Commits of the records of the object with the given store id. */ +static int object_commits(CK_ULONG id) +{ + int i; + int cnt = 0; + + for (i = 0; i < STORE_RECORDS; i++) { + if (records[i].used && records[i].type != WOLFPKCS11_STORE_TOKEN && + records[i].id2 == id) { + cnt += records[i].commits; + } + } + return cnt; +} + +static CK_RV pkcs11_init(void) +{ + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + + rv = C_GetFunctionList(&funcList); + if (rv != CKR_OK) + return rv; + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + if (rv == CKR_OK) + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv == CKR_OK && slotCount == 0) + rv = CKR_GENERAL_ERROR; + if (rv == CKR_OK) + slot = slotList[0]; + return rv; +} + +static CK_RV provision(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session; + + rv = funcList->C_InitToken(slot, soPin, soPinLen, tokenName); + if (rv == CKR_OK) { + rv = funcList->C_OpenSession(slot, + CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &session); + } + if (rv == CKR_OK) { + rv = funcList->C_Login(session, CKU_SO, soPin, soPinLen); + if (rv == CKR_OK) + rv = funcList->C_InitPIN(session, userPin, userPinLen); + (void)funcList->C_Logout(session); + (void)funcList->C_CloseSession(session); + } + return rv; +} + +static CK_RV open_user_session(CK_SESSION_HANDLE* session) +{ + CK_RV rv; + + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, session); + if (rv == CKR_OK) + rv = funcList->C_Login(*session, CKU_USER, userPin, userPinLen); + return rv; +} + +/* Create a token data object whose label and value are the name. */ +static CK_RV create_obj(CK_SESSION_HANDLE session, const char* name) +{ + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_OBJECT_HANDLE obj; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_LABEL, (void*)name, strlen(name) }, + { CKA_VALUE, (void*)name, strlen(name) }, + }; + + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(tmpl[0]), &obj); +} + +/* Destroy the token object with the name as label. */ +static CK_RV destroy_obj(CK_SESSION_HANDLE session, const char* name) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_LABEL, (void*)name, strlen(name) }, + }; + CK_OBJECT_HANDLE obj; + CK_ULONG cnt = 0; + CK_RV rv; + + rv = funcList->C_FindObjectsInit(session, tmpl, 2); + if (rv == CKR_OK) { + rv = funcList->C_FindObjects(session, &obj, 1, &cnt); + (void)funcList->C_FindObjectsFinal(session); + } + if (rv == CKR_OK && cnt != 1) + rv = CKR_OBJECT_HANDLE_INVALID; + if (rv == CKR_OK) + rv = funcList->C_DestroyObject(session, obj); + return rv; +} + +/* Find the token object with the name as label, and check its value. + * Returns the number found, or -1 on a wrong value. */ +static int find_obj(CK_SESSION_HANDLE session, const char* name) +{ + CK_ATTRIBUTE tmpl[] = { + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_LABEL, (void*)name, strlen(name) }, + }; + CK_OBJECT_HANDLE found[4]; + CK_ULONG cnt = 0; + byte value[32]; + CK_ATTRIBUTE attr = { CKA_VALUE, value, sizeof(value) }; + + if (funcList->C_FindObjectsInit(session, tmpl, 2) != CKR_OK) + return -1; + if (funcList->C_FindObjects(session, found, 4, &cnt) != CKR_OK) + cnt = 0; + (void)funcList->C_FindObjectsFinal(session); + if (cnt > 0) { + if (funcList->C_GetAttributeValue(session, found[0], &attr, 1) != + CKR_OK) { + return -1; + } + if (attr.ulValueLen != strlen(name) || + XMEMCMP(value, name, attr.ulValueLen) != 0) { + return -1; + } + } + return (int)cnt; +} + +static int count_token_objects(CK_SESSION_HANDLE session) +{ + CK_ATTRIBUTE tmpl[] = { { CKA_TOKEN, &ckTrue, sizeof(ckTrue) } }; + CK_OBJECT_HANDLE found[16]; + CK_ULONG cnt = 0; + + if (funcList->C_FindObjectsInit(session, tmpl, 1) != CKR_OK) + return -1; + if (funcList->C_FindObjects(session, found, 16, &cnt) != CKR_OK) + cnt = 0; + (void)funcList->C_FindObjectsFinal(session); + return (int)cnt; +} + +/* Load the token and check that exactly the kept objects load back. Returns + * the number of reads and of opens for reading that loading took. */ +static int load_kept(const char** kept, int keptCnt, int* reads, int* opens) +{ + CK_RV rv; + CK_SESSION_HANDLE session; + int startReads = readCnt; + int startOpens = readOpenCnt; + int i; + + rv = pkcs11_init(); + CHECK_RV(rv, "Initialize again"); + *reads = readCnt - startReads; + *opens = readOpenCnt - startOpens; + rv = open_user_session(&session); + CHECK_RV(rv, "Open user session again"); + CHECK(count_token_objects(session) == keptCnt, "Object count"); + for (i = 0; i < keptCnt; i++) + CHECK(find_obj(session, kept[i]) == 1, "Object loaded"); + (void)funcList->C_Logout(session); + (void)funcList->C_CloseSession(session); + (void)funcList->C_Finalize(NULL); + return 0; +} + +/* Fail each of the store calls that loading the token makes, one per load, + * and check that every load fails. */ +static int load_fails(int* failAt, const int* callCnt, int calls, + const char* msg) +{ + CK_RV rv; + int i; + + for (i = 0; i < calls; i++) { + injectCnt = 0; + *failAt = *callCnt + i; + rv = pkcs11_init(); + *failAt = -1; + if (rv == CKR_OK) + (void)funcList->C_Finalize(NULL); + CHECK(injectCnt == 1, "Failure injected while loading"); + if (rv == CKR_OK) + fprintf(stderr, "Store call %d of %d while loading\n", i, calls); + CHECK(rv != CKR_OK, msg); + } + return 0; +} + +static int custom_store_test(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session; + /* Objects that exist at the end. */ + const char* kept[] = { "objA", "objB", "objD", "objG" }; + int keptCnt; + int commits; + int discards = 0; + int reads; + int opens; + + rv = pkcs11_init(); + CHECK_RV(rv, "Initialize"); + rv = provision(); + CHECK_RV(rv, "Provision token"); + rv = open_user_session(&session); + CHECK_RV(rv, "Open user session"); + + /* objA takes store id 0. Creating objB writes only objB and the token. */ + rv = create_obj(session, "objA"); + CHECK_RV(rv, "Create objA"); + commits = object_commits(0); + CHECK(commits > 0, "objA stored"); + rv = create_obj(session, "objB"); + CHECK_RV(rv, "Create objB"); + CHECK(object_commits(0) == commits, "Create leaves objA's records"); + keptCnt = 2; + + /* objX's first record does not open for writing, so the create fails. */ + failOpenAt = openCnt; + rv = create_obj(session, "objX"); + failOpenAt = -1; + CHECK(rv == CKR_FUNCTION_FAILED, "Failed open fails the create"); + CHECK(find_obj(session, "objX") == 0, "Failed create not added"); + + /* objY's object record is written, then its data record does not open. */ + failOpenAt = openCnt + 1; + rv = create_obj(session, "objY"); + failOpenAt = -1; + CHECK(rv == CKR_FUNCTION_FAILED, "Failed open fails the create"); + CHECK(find_obj(session, "objY") == 0, "Failed create not added"); + CHECK(nullCloseCnt == 0, "Record that did not open not closed"); + + /* NOT_AVAILABLE_E from an open for writing fails the create too, rather + * than reading as storage turned off. */ + failOpenAt = openCnt; + failOpenCode = NOT_AVAILABLE_E; + rv = create_obj(session, "objZ"); + failOpenAt = -1; + failOpenCode = STORE_FAIL_E; + CHECK(rv == CKR_FUNCTION_FAILED, "Unavailable open fails the create"); + CHECK(find_obj(session, "objZ") == 0, "Failed create not added"); + + /* objR's records fail to be removed when it is destroyed. */ + rv = create_obj(session, "objR"); + CHECK_RV(rv, "Create objR"); + failRemoveAt = removeCnt; + rv = destroy_obj(session, "objR"); + failRemoveAt = -1; + CHECK(rv == CKR_FUNCTION_FAILED, "Failed remove fails the destroy"); + CHECK(find_obj(session, "objR") == 0, "Destroyed object removed"); + /* Creating objR wrote every object, after the failures above. */ + commits = object_commits(0); + +#ifdef WOLFPKCS11_CUSTOM_STORE_CLOSE_COMMIT + /* The first record of objC does not commit, so the create fails. */ + failCommitAt = commitCnt; + rv = create_obj(session, "objC"); + failCommitAt = -1; + CHECK(rv == CKR_FUNCTION_FAILED, "Failed commit fails the create"); + + /* The next create writes every object again, then only its own. */ + rv = create_obj(session, "objD"); + CHECK_RV(rv, "Create objD"); + CHECK(object_commits(0) > commits, "objA rewritten after failure"); + commits = object_commits(0); + rv = create_obj(session, "objE"); + CHECK_RV(rv, "Create objE"); + CHECK(object_commits(0) == commits, "objA not rewritten again"); + + /* A failed write fails the create, and the record is not committed. */ + failWriteAt = writeCnt; + rv = create_obj(session, "objF"); + failWriteAt = -1; + CHECK(rv == CKR_FUNCTION_FAILED, "Failed write fails the create"); + discards++; + CHECK(discardCnt == discards, "Record closed with commit of 0"); + + rv = create_obj(session, "objG"); + CHECK_RV(rv, "Create objG"); + CHECK(object_commits(0) > commits, "objA rewritten after failure"); + keptCnt = (int)(sizeof(kept) / sizeof(kept[0])); + + CHECK(find_obj(session, "objC") == 0, "Failed create not added"); + CHECK(find_obj(session, "objF") == 0, "Failed create not added"); + + /* A destroy whose commit fails reports it. The object is gone, and the + * token is written in full when finalized. */ + failCommitAt = commitCnt; + rv = destroy_obj(session, "objE"); + failCommitAt = -1; + CHECK(rv == CKR_FUNCTION_FAILED, "Failed commit fails the destroy"); + CHECK(find_obj(session, "objE") == 0, "Destroyed object removed"); + + /* NOT_AVAILABLE_E from a commit fails the create too, rather than + * reading as storage turned off. */ + failCommitAt = commitCnt; + failCommitCode = NOT_AVAILABLE_E; + rv = create_obj(session, "objH"); + failCommitAt = -1; + failCommitCode = STORE_FAIL_E; + CHECK(rv == CKR_FUNCTION_FAILED, "Unavailable commit fails the create"); + CHECK(find_obj(session, "objH") == 0, "Failed create not added"); +#endif + /* Records written without a failure are closed with commit of 1. */ + CHECK(discardCnt == discards, "No other record discarded"); + + (void)funcList->C_Logout(session); + (void)funcList->C_CloseSession(session); + (void)funcList->C_Finalize(NULL); + + /* The objects load back from the store. */ + if (load_kept(kept, keptCnt, &reads, &opens) != 0) + return -1; + + /* Any read, or open for reading, that fails while loading fails the + * load. A failed read must not read as the end of a record in an older + * format, which would load it without the fields after that point. */ + if (load_fails(&failReadAt, &readCnt, reads, + "Failed read fails the load") != 0) { + return -1; + } + if (load_fails(&failReadOpenAt, &readOpenCnt, opens, + "Failed open fails the load") != 0) { + return -1; + } + CHECK(nullCloseCnt == 0, "Record that did not open not closed"); + /* The failed loads left the store as it was. */ + if (load_kept(kept, keptCnt, &reads, &opens) != 0) + return -1; + + /* A data record that is missing does not open for reading. Loading the + * token fails, and the record is not closed. */ + CHECK(store_drop(WOLFPKCS11_STORE_DATA, 0), "Data record removed"); + rv = pkcs11_init(); + if (rv == CKR_OK) + (void)funcList->C_Finalize(NULL); + CHECK(rv != CKR_OK, "Missing record fails the load"); + CHECK(nullCloseCnt == 0, "Record that did not open not closed"); + + return 0; +} + +#endif /* RUN_TEST */ + +int main(int argc, char* argv[]) +{ +#ifdef RUN_TEST + int ret; + + (void)argc; + (void)argv; + +#ifdef WOLFPKCS11_CUSTOM_STORE_CLOSE_COMMIT + printf("Custom store test (close with commit status)\n"); +#else + printf("Custom store test (close only)\n"); +#endif + ret = custom_store_test(); + store_free(); + if (ret == 0) + printf("All tests passed!\n"); + return (ret == 0) ? 0 : 1; +#else + (void)argc; + (void)argv; + printf("Test not built for this configuration\n"); + return 77; +#endif +} diff --git a/tests/include.am b/tests/include.am index 21098b8f..3321f201 100644 --- a/tests/include.am +++ b/tests/include.am @@ -408,6 +408,28 @@ noinst_PROGRAMS += tests/token_partial_store_test tests_token_partial_store_test_SOURCES = tests/token_partial_store_test.c tests_token_partial_store_test_LDADD = +# The custom store tests build the library sources in, with the store in the +# test, so they are built for make check only. See BUILD_CUSTOM_STORE_TEST in +# configure.ac for when they are built. +if BUILD_CUSTOM_STORE_TEST +check_PROGRAMS += tests/custom_store_test +tests_custom_store_test_SOURCES = tests/custom_store_test.c \ + $(src_libwolfpkcs11_la_SOURCES) +tests_custom_store_test_CFLAGS = -DBUILDING_WOLFPKCS11 \ + -DWOLFPKCS11_CUSTOM_STORE \ + -DWOLFPKCS11_CUSTOM_STORE_CLOSE_COMMIT \ + $(AM_CFLAGS) +tests_custom_store_test_LDADD = + +check_PROGRAMS += tests/custom_store_legacy_test +tests_custom_store_legacy_test_SOURCES = tests/custom_store_test.c \ + $(src_libwolfpkcs11_la_SOURCES) +tests_custom_store_legacy_test_CFLAGS = -DBUILDING_WOLFPKCS11 \ + -DWOLFPKCS11_CUSTOM_STORE \ + $(AM_CFLAGS) +tests_custom_store_legacy_test_LDADD = +endif + if !BUILD_STATIC_WOLFSSL check_PROGRAMS += tests/token_store_test check_PROGRAMS += tests/logout_retire_test diff --git a/wolfpkcs11/store.h b/wolfpkcs11/store.h index 7852aacb..a48589b6 100644 --- a/wolfpkcs11/store.h +++ b/wolfpkcs11/store.h @@ -56,6 +56,9 @@ * @return 0 on success. * @return -4 when data not available. * @return Other value to indicate failure. + * + * With a custom store (WOLFPKCS11_CUSTOM_STORE), any failure to open for + * writing, -4 included, fails the operation that writes. */ WP11_LOCAL int wolfPKCS11_Store_Open(int type, CK_ULONG id1, CK_ULONG id2, int read, void** store); @@ -97,6 +100,23 @@ WP11_LOCAL int wolfPKCS11_Store_OpenSz(int type, CK_ULONG id1, CK_ULONG id2, int */ WP11_LOCAL void wolfPKCS11_Store_Close(void* store); +/* + * Closes access to location being written and reports whether the written + * data was committed. + * Any dynamic memory associated with the store is freed here. + * + * A custom store (WOLFPKCS11_CUSTOM_STORE) implements this only when + * WOLFPKCS11_CUSTOM_STORE_CLOSE_COMMIT is defined. Otherwise records are + * closed with wolfPKCS11_Store_Close() and are taken to be committed. + * + * @param [in] store Context for operation. + * @param [in] commit 1 to commit the written data. 0 when writing failed: + * the store discards the data when it can. + * @return 0 on success. + * @return Other value when the written data was not committed. + */ +WP11_LOCAL int wolfPKCS11_Store_CloseCommit(void* store, int commit); + /* * Reads a specific number of bytes into buffer. *