From f704e4f23e1c3b8accd1bfb11cf1dfc5dce3ffe6 Mon Sep 17 00:00:00 2001 From: Doug Engert Date: Mon, 11 Jan 2021 21:13:42 -0600 Subject: [PATCH] Pkcs11-tool changes to test a modules ability to use threads Option --use-locking has C_Initialize pass in parameters with the CKF_OS_LOCKING_OK to tell module to use threads. The default is it passes NULL which says threads are not needed. The following is not designed to be used by the general user. There are for debugging and test scripts and only compiled if the system has threads. Option --test-threads can be passed multiple times. Each one starts a thread. is a list of 2 byte commands seperated by ":". The thread will execute these. Current commands are: IN - C_Initialize(NULL) IL - C_Initialize with CKF_OS_LOCKING_OK Pn - Pause for n seconds GI - C_GetInfo SL - C_GetSlotList Tn - C_GetTokenInfo from slot_index n These are just enough calls to see if threads are working in the module. Output is written to stderr. Changes to be committed: modified: doc/tools/pkcs11-tool.1.xml modified: src/tools/Makefile.am modified: src/tools/pkcs11-tool.c --- doc/tools/pkcs11-tool.1.xml | 16 ++ src/tools/Makefile.am | 3 +- src/tools/pkcs11-tool.c | 318 +++++++++++++++++++++++++++++++++++- 3 files changed, 330 insertions(+), 7 deletions(-) diff --git a/doc/tools/pkcs11-tool.1.xml b/doc/tools/pkcs11-tool.1.xml index fee05864..a0f1ed4c 100644 --- a/doc/tools/pkcs11-tool.1.xml +++ b/doc/tools/pkcs11-tool.1.xml @@ -426,6 +426,22 @@ Specify the index of the object to use. + + + + + Tell pkcs11 module it should use OS thread locking. + + + + + + options + + Test a pkcs11 module's thread implication. (See source code). + + + label diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am index 1d4363c5..5689bb78 100644 --- a/src/tools/Makefile.am +++ b/src/tools/Makefile.am @@ -55,9 +55,10 @@ opensc_explorer_LDADD = $(OPTIONAL_READLINE_LIBS) pkcs15_tool_SOURCES = pkcs15-tool.c util.c ../pkcs11/pkcs11-display.c ../pkcs11/pkcs11-display.h pkcs15_tool_LDADD = $(OPTIONAL_OPENSSL_LIBS) pkcs11_tool_SOURCES = pkcs11-tool.c util.c +pkcs11_tool_CFLAGS = $(OPTIONAL_OPENSSL_CFLAGS) $(PTHREAD_CFLAGS) pkcs11_tool_LDADD = \ $(top_builddir)/src/common/libpkcs11.la \ - $(OPTIONAL_OPENSSL_LIBS) + $(OPTIONAL_OPENSSL_LIBS) $(PTHREAD_CFLAGS) if ENABLE_SHARED else pkcs11_tool_LDADD += \ diff --git a/src/tools/pkcs11-tool.c b/src/tools/pkcs11-tool.c index c0056744..7971b0b9 100644 --- a/src/tools/pkcs11-tool.c +++ b/src/tools/pkcs11-tool.c @@ -29,6 +29,9 @@ #include #include #include +#ifdef HAVE_PTHREAD +#include +#endif #else #include #include @@ -72,6 +75,10 @@ extern CK_FUNCTION_LIST_3_0 pkcs11_function_list_3_0; #endif +#if defined(_WIN32) || defined(HAVE_PTHREAD) +#define MAX_TEST_THREADS 10 +#endif + #define NEED_SESSION_RO 0x01 #define NEED_SESSION_RW 0x02 @@ -151,6 +158,11 @@ enum { OPT_DERIVE_PASS_DER, OPT_DECRYPT, OPT_TEST_FORK, +#if defined(_WIN32) || defined(HAVE_PTHREAD) + OPT_TEST_THREADS, + OPT_USE_LOCKING, +#endif + OPT_GENERATE_KEY, OPT_GENERATE_RANDOM, OPT_HASH_ALGORITHM, @@ -236,6 +248,10 @@ static const struct option options[] = { { "test-ec", 0, NULL, OPT_TEST_EC }, #ifndef _WIN32 { "test-fork", 0, NULL, OPT_TEST_FORK }, +#endif + { "use-locking", 0, NULL, OPT_USE_LOCKING }, +#if defined(_WIN32) || defined(HAVE_PTHREAD) + { "test-threads", 1, NULL, OPT_TEST_THREADS }, #endif { "generate-random", 1, NULL, OPT_GENERATE_RANDOM }, { "allow-sw", 0, NULL, OPT_ALLOW_SW }, @@ -314,9 +330,13 @@ static const char *option_help[] = { "Test EC (best used with the --login or --pin option)", #ifndef _WIN32 "Test forking and calling C_Initialize() in the child", +#endif + "Call C_initialize() with CKF_OS_LOCKING_OK.", +#if defined(_WIN32) || defined(HAVE_PTHREAD) + "Test threads. Multiple times to start additional threads, arg is string or 2 byte commands", #endif "Generate given amount of random data", - "Allow using software mechanisms (without CKF_HW)" + "Allow using software mechanisms (without CKF_HW)", }; static const char * app_name = "pkcs11-tool"; /* for utils.c */ @@ -380,6 +400,24 @@ static CK_FUNCTION_LIST_3_0_PTR p11 = NULL; static CK_SLOT_ID_PTR p11_slots = NULL; static CK_ULONG p11_num_slots = 0; static int suppress_warn = 0; +static CK_C_INITIALIZE_ARGS_PTR c_initialize_args_ptr = NULL; + +#if defined(_WIN32) || defined(HAVE_PTHREAD) +static CK_C_INITIALIZE_ARGS c_initialize_args_OS = {NULL, NULL, NULL, NULL, CKF_OS_LOCKING_OK, NULL}; +#ifdef _WIN32 +static HANDLE test_threads_handles[MAX_TEST_THREADS]; +#else +static pthread_t test_threads_handles[MAX_TEST_THREADS]; +#endif +struct test_threads_data { + int tnum; + char * tests; + volatile int state; + volatile CK_RV rv; +}; +static struct test_threads_data test_threads_datas[MAX_TEST_THREADS]; +static int test_threads_num = 0; +#endif /* defined(_WIN32) || defined(HAVE_PTHREAD) */ struct flag_info { CK_FLAGS value; @@ -480,6 +518,16 @@ static void test_ec(CK_SLOT_ID slot, CK_SESSION_HANDLE session); #ifndef _WIN32 static void test_fork(void); #endif +#if defined(_WIN32) || defined(HAVE_PTHREAD) +static void test_threads(); +static int test_threads_start(int tnum); +static int test_threads_cleanup(); +#ifdef _WIN32 +static DWORD WINAPI test_threads_run(_In_ LPVOID pttd); +#else +static void * test_threads_run(void * pttd); +#endif +#endif /* defined(_WIN32) || defiend(HAVE_PTHREAD) */ static void generate_random(CK_SESSION_HANDLE session); static CK_RV find_object_with_attributes(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE *out, CK_ATTRIBUTE *attrs, CK_ULONG attrsLen, CK_ULONG obj_index); @@ -572,7 +620,6 @@ VARATTR_METHOD(EC_POINT, unsigned char); /* getEC_POINT */ VARATTR_METHOD(EC_PARAMS, unsigned char); /* getEC_PARAMS */ VARATTR_METHOD(ALLOWED_MECHANISMS, CK_MECHANISM_TYPE); /* getALLOWED_MECHANISMS */ - int main(int argc, char * argv[]) { CK_SESSION_HANDLE session = CK_INVALID_HANDLE; @@ -600,6 +647,9 @@ int main(int argc, char * argv[]) int do_test_ec = 0; #ifndef _WIN32 int do_test_fork = 0; +#endif +#if defined(_WIN32) || defined(HAVE_PTHREAD) + int do_test_threads = 0; #endif int need_session = 0; int opt_login = 0; @@ -928,6 +978,23 @@ int main(int argc, char * argv[]) do_test_fork = 1; action_count++; break; +#endif + case OPT_USE_LOCKING: + c_initialize_args_ptr = &c_initialize_args_OS; + break; +#if defined(_WIN32) || defined(HAVE_PTHREAD) + case OPT_TEST_THREADS: + do_test_threads = 1; + if (test_threads_num < MAX_TEST_THREADS) { + test_threads_datas[test_threads_num].tnum = test_threads_num; + test_threads_datas[test_threads_num].tests = optarg; + test_threads_num++; + } else { + fprintf(stderr,"Too many --test_threads options limit is %d\n", MAX_TEST_THREADS); + util_print_usage_and_die(app_name, options, option_help, NULL); + } + action_count++; + break; #endif case OPT_GENERATE_RANDOM: need_session |= NEED_SESSION_RO; @@ -994,10 +1061,23 @@ int main(int argc, char * argv[]) if (do_list_interfaces) list_interfaces(); - rv = p11->C_Initialize(NULL); +#if defined(_WIN32) || defined(HAVE_PTHREAD) + if (do_test_threads) + test_threads(); +#endif + + rv = p11->C_Initialize(c_initialize_args_ptr); + +#if defined(_WIN32) || defined(HAVE_PTHREAD) + if (do_test_threads || rv != CKR_OK) + fprintf(stderr,"Main C_Initialize(%s) rv:%s\n", + (c_initialize_args_ptr) ? "CKF_OS_LOCKING_OK" : "NULL", CKR2Str(rv)); +#else if (rv == CKR_CRYPTOKI_ALREADY_INITIALIZED) fprintf(stderr, "\n*** Cryptoki library has already been initialized ***\n"); - else if (rv != CKR_OK) +#endif /* defined(_WIN32) || defined(HAVE_PTHREAD) */ + + if (rv != CKR_OK && rv != CKR_CRYPTOKI_ALREADY_INITIALIZED) p11_fatal("C_Initialize", rv); #ifndef _WIN32 @@ -1242,6 +1322,11 @@ end: p11_fatal("C_CloseSession", rv); } +#if defined(_WIN32) || defined(HAVE_PTHREAD) + if (do_test_threads) + test_threads_cleanup(); +#endif /* defined(_WIN32) || defiend(HAVE_PTHREAD) */ + if (p11) p11->C_Finalize(NULL_PTR); if (module) @@ -6298,7 +6383,7 @@ static CK_SESSION_HANDLE test_kpgen_certwrite(CK_SLOT_ID slot, CK_SESSION_HANDLE if (module == NULL) util_fatal("Failed to load pkcs11 module"); - rv = p11->C_Initialize(NULL); + rv = p11->C_Initialize(c_initialize_args_ptr); if (rv == CKR_CRYPTOKI_ALREADY_INITIALIZED) printf("\n*** Cryptoki library has already been initialized ***\n"); else if (rv != CKR_OK) @@ -6456,7 +6541,7 @@ static void test_fork(void) if (!pid) { printf("*** Calling C_Initialize in forked child process ***\n"); - rv = p11->C_Initialize(NULL); + rv = p11->C_Initialize(c_initialize_args_ptr); if (rv != CKR_OK) p11_fatal("C_Initialize in child\n", rv); exit(0); @@ -7134,3 +7219,224 @@ static const char * CKR2Str(CK_ULONG res) } return "unknown PKCS11 error"; } + +#if defined(_WIN32) || defined(HAVE_PTHREAD) +#ifdef _WIN32 +static DWORD WINAPI test_threads_run(_In_ LPVOID pttd) +#else +static void * test_threads_run(void * pttd) +#endif +{ + int r = 0; + CK_RV rv = CKR_OK; + CK_INFO info; + int l_slots = 0; + int state = 0; + CK_ULONG l_p11_num_slots = 0; + CK_SLOT_ID_PTR l_p11_slots = NULL; + char * pctest; + struct test_threads_data * ttd = (struct test_threads_data *)pttd; + + fprintf(stderr, "Test thread %d started with options:%s\n", ttd->tnum, ttd->tests); + /* call selected C_* routines with different options */ + pctest = ttd-> tests; + + /* series of two chatacter commands */ + while (pctest && *pctest && *(pctest + 1)) { + ttd->state = state++; + + /* Pn - pause where n is 0 to 9 iseconds */ + if (*pctest == 'P' && *(pctest + 1) >= '0' && *(pctest + 1) <= '9') { + fprintf(stderr, "Test thread %d pauseing for %d seconds\n", ttd->tnum, (*(pctest + 1) - '0')); +#ifdef _WIN32 + Sleep((*(pctest + 1) - '0') * 1000); +#else + sleep(*(pctest + 1) - '0'); +#endif + } + + /* IN - C_Initialize with NULL args */ + else if (*pctest == 'I') { + if (*(pctest + 1) == 'N') { + fprintf(stderr, "Test thread %d C_Initialize(NULL)\n", ttd->tnum); + rv = p11->C_Initialize(NULL); + ttd->rv = rv; + fprintf(stderr, "Test thread %d C_Initialize returned %s\n", ttd->tnum, CKR2Str(rv)); + } + /* CL C_Initialize with CKF_OS_LOCKING_OK */ + else if (*(pctest + 1) == 'L') { + fprintf(stderr, "Test thread %d C_Initialize CKF_OS_LOCKING_OK \n", ttd->tnum); + rv = p11->C_Initialize(&c_initialize_args_OS); + ttd->rv = rv; + fprintf(stderr, "Test thread %d C_Initialize returned %s\n", ttd->tnum, CKR2Str(rv)); + } + else + goto err; + } + + /* GI - C_GetInfo */ + else if (*pctest == 'G' && *(pctest + 1) == 'I') { + fprintf(stderr, "Test thread %d C_GetInfo\n", ttd->tnum); + rv = p11->C_GetInfo(&info); + ttd->rv = rv; + fprintf(stderr, "Test thread %d C_GetInfo returned %s\n", ttd->tnum, CKR2Str(rv)); + } + + /* SL - C_GetSlotList */ + else if (*pctest == 'S' && *(pctest + 1) == 'L') { + fprintf(stderr, "Test thread %d C_GetSlotList to get l_p11_num_slots\n", ttd->tnum); + rv = p11->C_GetSlotList(1, NULL, &l_p11_num_slots); + ttd->rv = rv; + fprintf(stderr, "Test thread %d C_GetSlotList returned %s\n", ttd->tnum, CKR2Str(rv)); + fprintf(stderr, "Test thread %d l_p11_num_slots:%ld\n", ttd->tnum, l_p11_num_slots); + if (rv == CKR_OK) { + free(l_p11_slots); + if (l_p11_num_slots > 0) { + l_p11_slots = calloc(l_p11_num_slots, sizeof(CK_SLOT_ID)); + fprintf(stderr, "Test thread %d C_GetSlotList\n", ttd->tnum); + rv = p11->C_GetSlotList(1, l_p11_slots, &l_p11_num_slots); + ttd->rv = rv; + fprintf(stderr, "Test thread %d C_GetSlotList returned %s\n", ttd->tnum, CKR2Str(rv)); + fprintf(stderr, "Test thread %d l_p11_num_slots:%ld\n", ttd->tnum, l_p11_num_slots); + if (rv == CKR_OK && l_p11_num_slots && l_p11_slots) + l_slots = 1; + } + } + } + + /* Tn Get token from slot_index n C_GetTokenInfo, where n is 0 to 9 */ + else if (*pctest == 'T' && *(pctest + 1) >= '0' && *(pctest + 1) <= '9') { + fprintf(stderr, "Test thread %d C_GetTokenInfo from slot_index %d using show_token\n", ttd->tnum, (*(pctest + 1) - '0')); + if (l_slots) { + show_token(l_p11_slots[(*(pctest + 1) - '0')]); + } else { + show_token(p11_slots[(*(pctest + 1) - '0')]); + } + } + + else { + err: + rv = CKR_GENERAL_ERROR; /* could be vendor error, */ + ttd->rv = rv; + fprintf(stderr, "Test thread %d Unknown test '%c%c'\n", ttd->tnum, *pctest, *(pctest + 1)); + break; + } + + pctest ++; + if (*pctest != 0x00) + pctest ++; + if (*pctest == ':') + pctest++; + + + if (rv != CKR_OK && rv != CKR_CRYPTOKI_ALREADY_INITIALIZED) + /* IN C_Initialize with NULL args */ + break; + } + + free(l_p11_slots); + fprintf(stderr, "Test thread %d returning rv = %d\n", ttd->tnum, r); + ttd->state = -1; /* done */ + ttd->rv = rv; +#ifdef _WIN32 + ExitThread(0); +#else + pthread_exit(NULL); +#endif +} + +static int test_threads_cleanup() +{ + + int i, j; + int ended = 0; + int ended_ok = 0; + + fprintf(stderr,"test_threads cleanup starting\n"); + for (j = 0; j < 4; j++) { + ended = 0; + ended_ok = 0; + + for (i = 0; i < test_threads_num; i++) { + if (test_threads_datas[i].state == -1) { + ended++; + } + if (test_threads_datas[i].rv == CKR_OK) { + ended_ok++; + } + } + + if (ended == test_threads_num) { + fprintf(stderr,"test_threads all threads have ended %s\n", + (ended_ok == test_threads_num)? "with CKR_OK": "some errors"); + break; + } else { + fprintf(stderr,"test_threads threads stills active:%d\n", (test_threads_num - ended)); + for (i = 0; i < test_threads_num; i++) { + fprintf(stderr,"test_threads thread:%d state:%d, rv:%s\n", + i, test_threads_datas[i].state, CKR2Str(test_threads_datas[i].rv)); + } + fprintf(stderr,"\ntest_threads waiting for 30 seconds ...\n"); +#ifdef _WIN32 + Sleep(30*1000); +#else + sleep(30); +#endif + } + } + + for (i = 0; i < test_threads_num; i++) { + fprintf(stderr,"test_threads thread:%d state:%d, rv:%s\n", + i, test_threads_datas[i].state, CKR2Str(test_threads_datas[i].rv)); + if (test_threads_datas[i].state == -1) { +#ifdef _WIN32 + TerminateThread(test_threads_handles[i], 0); +#else + pthread_join(test_threads_handles[i], NULL); + } else { + pthread_cancel(test_threads_handles[i]); +#endif + } + } + + fprintf(stderr,"test_threads cleanup finished\n"); + return 0; +} + +static int test_threads_start(int tnum) +{ + int r = 0; + +#ifdef _WIN32 + test_threads_handles[tnum] = CreateThread(NULL, 0, test_threads_run, (LPVOID) &test_threads_datas[tnum], + 0, NULL); + if (test_threads_handles[tnum] == NULL) { + r = GetLastError(); + } +#else + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + r = pthread_create(&test_threads_handles[tnum], &attr, test_threads_run, (void *) &test_threads_datas[tnum]); +#endif + if (r != 0) { + fprintf(stderr,"test_threads pthread_create failed %d for thread %d\n", r, tnum); + /* system error */ + } + return r; +} + +/*********************************************************************************************/ +static void test_threads() +{ + int i; + + /* call test_threads_start for each --test-thread option */ + + /* upon return, C_Initialize will be called, from main code */ + for (i = 0; i < test_threads_num && i < MAX_TEST_THREADS; i++) { + test_threads_start(i); + } +} +#endif /* defined(_WIN32) || defiend(HAVE_PTHREAD) */ +