diff options
Diffstat (limited to 'lib/isc/pk11.c')
-rw-r--r-- | lib/isc/pk11.c | 1399 |
1 files changed, 1399 insertions, 0 deletions
diff --git a/lib/isc/pk11.c b/lib/isc/pk11.c new file mode 100644 index 0000000..c5d2310 --- /dev/null +++ b/lib/isc/pk11.c @@ -0,0 +1,1399 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include <config.h> + +#include <stdio.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include <isc/log.h> +#include <isc/mem.h> +#include <isc/once.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/stdio.h> +#include <isc/string.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include <dst/result.h> + +#include <pk11/pk11.h> +#include <pk11/internal.h> +#include <pk11/result.h> +#include <pk11/site.h> + +#include <pkcs11/cryptoki.h> +#include <pkcs11/pkcs11.h> +#include <pkcs11/eddsa.h> + +/* was 32 octets, Petr Spacek suggested 1024, SoftHSMv2 uses 256... */ +#ifndef PINLEN +#define PINLEN 256 +#endif + +#ifndef PK11_NO_LOGERR +#define PK11_NO_LOGERR 1 +#endif + +LIBISC_EXTERNAL_DATA bool pk11_verbose_init = false; + +static isc_once_t once = ISC_ONCE_INIT; +static isc_mem_t *pk11_mctx = NULL; +static int32_t allocsize = 0; +static bool initialized = false; + +typedef struct pk11_session pk11_session_t; +typedef struct pk11_token pk11_token_t; +typedef ISC_LIST(pk11_session_t) pk11_sessionlist_t; + +struct pk11_session { + unsigned int magic; + CK_SESSION_HANDLE session; + ISC_LINK(pk11_session_t) link; + pk11_token_t *token; +}; + +struct pk11_token { + unsigned int magic; + unsigned int operations; + ISC_LINK(pk11_token_t) link; + CK_SLOT_ID slotid; + pk11_sessionlist_t sessions; + bool logged; + char name[32]; + char manuf[32]; + char model[16]; + char serial[16]; + char pin[PINLEN + 1]; +}; +static ISC_LIST(pk11_token_t) tokens; + +static pk11_token_t *rand_token; +static pk11_token_t *best_rsa_token; +static pk11_token_t *best_dsa_token; +static pk11_token_t *best_dh_token; +static pk11_token_t *digest_token; +static pk11_token_t *best_ec_token; +static pk11_token_t *best_gost_token; +static pk11_token_t *aes_token; + +static isc_result_t free_all_sessions(void); +static isc_result_t free_session_list(pk11_sessionlist_t *slist); +static isc_result_t setup_session(pk11_session_t *sp, + pk11_token_t *token, + bool rw); +static void scan_slots(void); +static isc_result_t token_login(pk11_session_t *sp); +static char *percent_decode(char *x, size_t *len); +static bool pk11strcmp(const char *x, size_t lenx, + const char *y, size_t leny); +static CK_ATTRIBUTE *push_attribute(pk11_object_t *obj, + isc_mem_t *mctx, + size_t len); + +static isc_mutex_t alloclock; +static isc_mutex_t sessionlock; + +static pk11_sessionlist_t actives; + +static CK_C_INITIALIZE_ARGS pk11_init_args = { + NULL_PTR, /* CreateMutex */ + NULL_PTR, /* DestroyMutex */ + NULL_PTR, /* LockMutex */ + NULL_PTR, /* UnlockMutex */ + CKF_OS_LOCKING_OK, /* flags */ + NULL_PTR, /* pReserved */ +}; + +#ifndef PK11_LIB_LOCATION +#define PK11_LIB_LOCATION "unknown_provider" +#endif + +#ifndef WIN32 +static const char *lib_name = PK11_LIB_LOCATION; +#else +static const char *lib_name = PK11_LIB_LOCATION ".dll"; +#endif + +void +pk11_set_lib_name(const char *name) { + lib_name = name; +} + +const char * +pk11_get_lib_name(void) { + return (lib_name); +} + +static void +initialize(void) { + char *pk11_provider; + + RUNTIME_CHECK(isc_mutex_init(&alloclock) == ISC_R_SUCCESS); + RUNTIME_CHECK(isc_mutex_init(&sessionlock) == ISC_R_SUCCESS); + + pk11_provider = getenv("PKCS11_PROVIDER"); + if (pk11_provider != NULL) + lib_name = pk11_provider; +} + +void * +pk11_mem_get(size_t size) { + void *ptr; + + LOCK(&alloclock); + if (pk11_mctx != NULL) + ptr = isc_mem_get(pk11_mctx, size); + else { + ptr = malloc(size); + if (ptr != NULL) + allocsize += (int)size; + } + UNLOCK(&alloclock); + + if (ptr != NULL) + memset(ptr, 0, size); + return (ptr); +} + +void +pk11_mem_put(void *ptr, size_t size) { + if (ptr != NULL) + memset(ptr, 0, size); + LOCK(&alloclock); + if (pk11_mctx != NULL) + isc_mem_put(pk11_mctx, ptr, size); + else { + if (ptr != NULL) + allocsize -= (int)size; + free(ptr); + } + UNLOCK(&alloclock); +} + +isc_result_t +pk11_initialize(isc_mem_t *mctx, const char *engine) { + isc_result_t result = ISC_R_SUCCESS; + CK_RV rv; + + RUNTIME_CHECK(isc_once_do(&once, initialize) == ISC_R_SUCCESS); + + LOCK(&sessionlock); + LOCK(&alloclock); + if ((mctx != NULL) && (pk11_mctx == NULL) && (allocsize == 0)) + isc_mem_attach(mctx, &pk11_mctx); + UNLOCK(&alloclock); + if (initialized) { + goto unlock; + } else { + initialized = true; + } + + ISC_LIST_INIT(tokens); + ISC_LIST_INIT(actives); + + if (engine != NULL) + lib_name = engine; + + /* Initialize the CRYPTOKI library */ + rv = pkcs_C_Initialize((CK_VOID_PTR) &pk11_init_args); + + if (rv == 0xfe) { + result = PK11_R_NOPROVIDER; + fprintf(stderr, "Can't load PKCS#11 provider: %s\n", + pk11_get_load_error_message()); + goto unlock; + } + if (rv != CKR_OK) { + result = PK11_R_INITFAILED; + goto unlock; + } + + scan_slots(); +#ifdef PKCS11CRYPTO + if (rand_token == NULL) { + result = PK11_R_NORANDOMSERVICE; + goto unlock; + } + if (digest_token == NULL) { + result = PK11_R_NODIGESTSERVICE; + goto unlock; + } +#if defined(AES_CC) + if (aes_token == NULL) { + result = PK11_R_NOAESSERVICE; + goto unlock; + } +#endif +#endif /* PKCS11CRYPTO */ + unlock: + UNLOCK(&sessionlock); + return (result); +} + +isc_result_t +pk11_finalize(void) { + pk11_token_t *token, *next; + isc_result_t ret; + + ret = free_all_sessions(); + (void) pkcs_C_Finalize(NULL_PTR); + token = ISC_LIST_HEAD(tokens); + while (token != NULL) { + next = ISC_LIST_NEXT(token, link); + ISC_LIST_UNLINK(tokens, token, link); + if (token == rand_token) + rand_token = NULL; + if (token == best_rsa_token) + best_rsa_token = NULL; + if (token == best_dsa_token) + best_dsa_token = NULL; + if (token == best_dh_token) + best_dh_token = NULL; + if (token == digest_token) + digest_token = NULL; + if (token == best_ec_token) + best_ec_token = NULL; + if (token == best_gost_token) + best_gost_token = NULL; + if (token == aes_token) + aes_token = NULL; + pk11_mem_put(token, sizeof(*token)); + token = next; + } + if (pk11_mctx != NULL) + isc_mem_detach(&pk11_mctx); + initialized = false; + return (ret); +} + +isc_result_t +pk11_rand_bytes(unsigned char *buf, int num) { + isc_result_t ret; + CK_RV rv; + pk11_context_t ctx; + + ret = pk11_get_session(&ctx, OP_RAND, false, false, + false, NULL, 0); + if ((ret != ISC_R_SUCCESS) && + (ret != PK11_R_NODIGESTSERVICE) && + (ret != PK11_R_NOAESSERVICE)) + return (ret); + RUNTIME_CHECK(ctx.session != CK_INVALID_HANDLE); + rv = pkcs_C_GenerateRandom(ctx.session, + (CK_BYTE_PTR) buf, (CK_ULONG) num); + pk11_return_session(&ctx); + if (rv == CKR_OK) + return (ISC_R_SUCCESS); + else + return (DST_R_CRYPTOFAILURE); +} + +#define SEEDSIZE 1024 + +static CK_BYTE seed[SEEDSIZE]; + +void +pk11_rand_seed_fromfile(const char *randomfile) { + pk11_context_t ctx; + FILE *stream = NULL; + size_t cc = 0; + isc_result_t ret; + + ret = pk11_get_session(&ctx, OP_RAND, false, false, + false, NULL, 0); + if ((ret != ISC_R_SUCCESS) && + (ret != PK11_R_NODIGESTSERVICE) && + (ret != PK11_R_NOAESSERVICE)) + return; + RUNTIME_CHECK(ctx.session != CK_INVALID_HANDLE); + ret = isc_stdio_open(randomfile, "r", &stream); + if (ret != ISC_R_SUCCESS) + goto cleanup; + ret = isc_stdio_read(seed, 1, SEEDSIZE, stream, &cc); + if (ret!= ISC_R_SUCCESS) + goto cleanup; + ret = isc_stdio_close(stream); + stream = NULL; + if (ret!= ISC_R_SUCCESS) + goto cleanup; + (void) pkcs_C_SeedRandom(ctx.session, seed, (CK_ULONG) cc); + + cleanup: + if (stream != NULL) + (void) isc_stdio_close(stream); + pk11_return_session(&ctx); +} + +isc_result_t +pk11_get_session(pk11_context_t *ctx, pk11_optype_t optype, + bool need_services, bool rw, + bool logon, const char *pin, CK_SLOT_ID slot) +{ + pk11_token_t *token = NULL; + pk11_sessionlist_t *freelist; + pk11_session_t *sp; + isc_result_t ret; +#ifdef PKCS11CRYPTO + isc_result_t service_ret = ISC_R_SUCCESS; +#else + UNUSED(need_services); +#endif + + memset(ctx, 0, sizeof(pk11_context_t)); + ctx->handle = NULL; + ctx->session = CK_INVALID_HANDLE; + + ret = pk11_initialize(NULL, NULL); +#ifdef PKCS11CRYPTO + if (ret == PK11_R_NORANDOMSERVICE || + ret == PK11_R_NODIGESTSERVICE || + ret == PK11_R_NOAESSERVICE) { + if (need_services) + return (ret); + service_ret = ret; + } + else +#endif /* PKCS11CRYPTO */ + if (ret != ISC_R_SUCCESS) + return (ret); + + LOCK(&sessionlock); + /* wait for initialization to finish */ + UNLOCK(&sessionlock); + + switch(optype) { +#ifdef PKCS11CRYPTO + case OP_RAND: + token = rand_token; + break; + case OP_DIGEST: + token = digest_token; + break; + case OP_AES: + token = aes_token; + break; + case OP_ANY: + for (token = ISC_LIST_HEAD(tokens); + token != NULL; + token = ISC_LIST_NEXT(token, link)) + if (token->slotid == slot) + break; + break; +#endif + default: + for (token = ISC_LIST_HEAD(tokens); + token != NULL; + token = ISC_LIST_NEXT(token, link)) + if (token->slotid == slot) + break; +#ifdef PKCS11CRYPTO + if ((token == NULL) || + ((token->operations & (1 << optype)) == 0)) + return (ISC_R_NOTFOUND); +#endif + break; + } + if (token == NULL) + return (ISC_R_NOTFOUND); + + /* Override the token's PIN */ + if (logon && pin != NULL && *pin != '\0') { + if (strlen(pin) > PINLEN) + return (ISC_R_RANGE); + /* + * We want to zero out the old pin before + * overwriting with a new one. + */ + memset(token->pin, 0, sizeof(token->pin)); + strlcpy(token->pin, pin, sizeof(token->pin)); + } + + freelist = &token->sessions; + + LOCK(&sessionlock); + sp = ISC_LIST_HEAD(*freelist); + if (sp != NULL) { + ISC_LIST_UNLINK(*freelist, sp, link); + ISC_LIST_APPEND(actives, sp, link); + UNLOCK(&sessionlock); + if (logon) + ret = token_login(sp); + ctx->handle = sp; + ctx->session = sp->session; + return (ret); + } + UNLOCK(&sessionlock); + + sp = pk11_mem_get(sizeof(*sp)); + if (sp == NULL) + return (ISC_R_NOMEMORY); + sp->magic = SES_MAGIC; + sp->token = token; + sp->session = CK_INVALID_HANDLE; + ISC_LINK_INIT(sp, link); + ret = setup_session(sp, token, rw); + if ((ret == ISC_R_SUCCESS) && logon) + ret = token_login(sp); + LOCK(&sessionlock); + ISC_LIST_APPEND(actives, sp, link); + UNLOCK(&sessionlock); + ctx->handle = sp; + ctx->session = sp->session; +#ifdef PKCS11CRYPTO + if (ret == ISC_R_SUCCESS) + ret = service_ret; +#endif + return (ret); +} + +void +pk11_return_session(pk11_context_t *ctx) { + pk11_session_t *sp = (pk11_session_t *) ctx->handle; + + if (sp == NULL) + return; + ctx->handle = NULL; + ctx->session = CK_INVALID_HANDLE; + + LOCK(&sessionlock); + ISC_LIST_UNLINK(actives, sp, link); + UNLOCK(&sessionlock); + if (sp->session == CK_INVALID_HANDLE) { + pk11_mem_put(sp, sizeof(*sp)); + return; + } + + LOCK(&sessionlock); + ISC_LIST_APPEND(sp->token->sessions, sp, link); + UNLOCK(&sessionlock); +} + +static isc_result_t +free_all_sessions(void) { + pk11_token_t *token; + isc_result_t ret = ISC_R_SUCCESS; + isc_result_t oret; + + for (token = ISC_LIST_HEAD(tokens); + token != NULL; + token = ISC_LIST_NEXT(token, link)) { + oret = free_session_list(&token->sessions); + if (oret != ISC_R_SUCCESS) + ret = oret; + } + if (!ISC_LIST_EMPTY(actives)) { + ret = ISC_R_ADDRINUSE; + oret = free_session_list(&actives); + if (oret != ISC_R_SUCCESS) + ret = oret; + } + return (ret); +} + +static isc_result_t +free_session_list(pk11_sessionlist_t *slist) { + pk11_session_t *sp; + CK_RV rv; + isc_result_t ret; + + ret = ISC_R_SUCCESS; + LOCK(&sessionlock); + while (!ISC_LIST_EMPTY(*slist)) { + sp = ISC_LIST_HEAD(*slist); + ISC_LIST_UNLINK(*slist, sp, link); + UNLOCK(&sessionlock); + if (sp->session != CK_INVALID_HANDLE) { + rv = pkcs_C_CloseSession(sp->session); + if (rv != CKR_OK) + ret = DST_R_CRYPTOFAILURE; + } + LOCK(&sessionlock); + pk11_mem_put(sp, sizeof(*sp)); + } + UNLOCK(&sessionlock); + + return (ret); +} + +static isc_result_t +setup_session(pk11_session_t *sp, pk11_token_t *token, + bool rw) +{ + CK_RV rv; + CK_FLAGS flags = CKF_SERIAL_SESSION; + + if (rw) + flags += CKF_RW_SESSION; + + rv = pkcs_C_OpenSession(token->slotid, flags, NULL_PTR, + NULL_PTR, &sp->session); + if (rv != CKR_OK) + return (DST_R_CRYPTOFAILURE); + return (ISC_R_SUCCESS); +} + +static isc_result_t +token_login(pk11_session_t *sp) { + CK_RV rv; + pk11_token_t *token = sp->token; + isc_result_t ret = ISC_R_SUCCESS; + + LOCK(&sessionlock); + if (!token->logged) { + rv = pkcs_C_Login(sp->session, CKU_USER, + (CK_UTF8CHAR_PTR) token->pin, + (CK_ULONG) strlen(token->pin)); + if (rv != CKR_OK) { + ret = ISC_R_NOPERM; +#if PK11_NO_LOGERR + pk11_error_fatalcheck(__FILE__, __LINE__, + "pkcs_C_Login", rv); +#endif + } else + token->logged = true; + } + UNLOCK(&sessionlock); + return (ret); +} + +#define PK11_TRACE(fmt) \ + if (pk11_verbose_init) fprintf(stderr, fmt) +#define PK11_TRACE1(fmt, arg) \ + if (pk11_verbose_init) fprintf(stderr, fmt, arg) +#define PK11_TRACE2(fmt, arg1, arg2) \ + if (pk11_verbose_init) fprintf(stderr, fmt, arg1, arg2) +#define PK11_TRACEM(mech) \ + if (pk11_verbose_init) fprintf(stderr, #mech ": 0x%lx\n", rv) + +static void +scan_slots(void) { + CK_MECHANISM_INFO mechInfo; + CK_TOKEN_INFO tokenInfo; + CK_RV rv; + CK_SLOT_ID slot; + CK_SLOT_ID_PTR slotList; + CK_ULONG slotCount; + pk11_token_t *token; + unsigned int i; + bool bad; + + slotCount = 0; + PK11_FATALCHECK(pkcs_C_GetSlotList, (CK_FALSE, NULL_PTR, &slotCount)); + PK11_TRACE1("slotCount=%lu\n", slotCount); + /* it's not an error if we didn't find any providers */ + if (slotCount == 0) + return; + slotList = pk11_mem_get(sizeof(CK_SLOT_ID) * slotCount); + RUNTIME_CHECK(slotList != NULL); + PK11_FATALCHECK(pkcs_C_GetSlotList, (CK_FALSE, slotList, &slotCount)); + + for (i = 0; i < slotCount; i++) { + slot = slotList[i]; + PK11_TRACE2("slot#%u=0x%lx\n", i, slot); + + rv = pkcs_C_GetTokenInfo(slot, &tokenInfo); + if (rv != CKR_OK) + continue; + token = pk11_mem_get(sizeof(*token)); + RUNTIME_CHECK(token != NULL); + token->magic = TOK_MAGIC; + token->slotid = slot; + ISC_LINK_INIT(token, link); + ISC_LIST_INIT(token->sessions); + memmove(token->name, tokenInfo.label, 32); + memmove(token->manuf, tokenInfo.manufacturerID, 32); + memmove(token->model, tokenInfo.model, 16); + memmove(token->serial, tokenInfo.serialNumber, 16); + ISC_LIST_APPEND(tokens, token, link); + if ((tokenInfo.flags & CKF_RNG) == 0) { + PK11_TRACE("no CKF_RNG\n"); + goto try_rsa; + } + token->operations |= 1 << OP_RAND; + if (rand_token == NULL) + rand_token = token; + + try_rsa: + bad = false; + rv = pkcs_C_GetMechanismInfo(slot, CKM_RSA_PKCS_KEY_PAIR_GEN, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_GENERATE_KEY_PAIR) == 0)) { + bad = true; + PK11_TRACEM(CKM_RSA_PKCS_KEY_PAIR_GEN); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_MD5_RSA_PKCS, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) { +#if !defined(PK11_MD5_DISABLE) && !defined(PK11_RSA_PKCS_REPLACE) + bad = true; +#endif + PK11_TRACEM(CKM_MD5_RSA_PKCS); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA1_RSA_PKCS, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) { +#ifndef PK11_RSA_PKCS_REPLACE + bad = true; +#endif + PK11_TRACEM(CKM_SHA1_RSA_PKCS); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA256_RSA_PKCS, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) { +#ifndef PK11_RSA_PKCS_REPLACE + bad = true; +#endif + PK11_TRACEM(CKM_SHA256_RSA_PKCS); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA512_RSA_PKCS, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) { +#ifndef PK11_RSA_PKCS_REPLACE + bad = true; +#endif + PK11_TRACEM(CKM_SHA512_RSA_PKCS); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_RSA_PKCS, &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) { +#ifdef PK11_RSA_PKCS_REPLACE + bad = true; +#endif + PK11_TRACEM(CKM_RSA_PKCS); + } + if (bad) + goto try_dsa; + token->operations |= 1 << OP_RSA; + if (best_rsa_token == NULL) + best_rsa_token = token; + + try_dsa: + bad = false; + rv = pkcs_C_GetMechanismInfo(slot, CKM_DSA_PARAMETER_GEN, + &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_GENERATE) == 0)) { +#ifndef PK11_DSA_PARAMETER_GEN_SKIP + bad = true; +#endif + PK11_TRACEM(CKM_DSA_PARAMETER_GEN); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_DSA_KEY_PAIR_GEN, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_GENERATE_KEY_PAIR) == 0)) { + bad = true; + PK11_TRACEM(CKM_DSA_PARAMETER_GEN); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_DSA_SHA1, &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) { + bad = true; + PK11_TRACEM(CKM_DSA_SHA1); + } + if (bad) + goto try_dh; +#ifndef PK11_DSA_DISABLE + token->operations |= 1 << OP_DSA; + if (best_dsa_token == NULL) + best_dsa_token = token; +#endif + + try_dh: + bad = false; + rv = pkcs_C_GetMechanismInfo(slot, CKM_DH_PKCS_PARAMETER_GEN, + &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_GENERATE) == 0)) { + PK11_TRACEM(CKM_DH_PKCS_PARAMETER_GEN); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_DH_PKCS_KEY_PAIR_GEN, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_GENERATE_KEY_PAIR) == 0)) { +#ifndef PK11_DH_PKCS_PARAMETER_GEN_SKIP + bad = true; +#endif + PK11_TRACEM(CKM_DH_PKCS_KEY_PAIR_GEN); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_DH_PKCS_DERIVE, + &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_DERIVE) == 0)) { + bad = true; + PK11_TRACEM(CKM_DH_PKCS_DERIVE); + } + if (bad) + goto try_digest; +#ifndef PK11_DH_DISABLE + token->operations |= 1 << OP_DH; + if (best_dh_token == NULL) + best_dh_token = token; +#endif + + try_digest: + bad = false; + rv = pkcs_C_GetMechanismInfo(slot, CKM_MD5, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_DIGEST) == 0)) { +#ifndef PK11_MD5_DISABLE + bad = true; +#endif + PK11_TRACEM(CKM_MD5); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA_1, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_DIGEST) == 0)) { + bad = true; + PK11_TRACEM(CKM_SHA_1); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA224, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_DIGEST) == 0)) { + bad = true; + PK11_TRACEM(CKM_SHA224); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA256, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_DIGEST) == 0)) { + bad = true; + PK11_TRACEM(CKM_SHA256); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA384, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_DIGEST) == 0)) { + bad = true; + PK11_TRACEM(CKM_SHA384); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA512, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_DIGEST) == 0)) { + bad = true; + PK11_TRACEM(CKM_SHA512); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_MD5_HMAC, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0)) { +#if !defined(PK11_MD5_DISABLE) && !defined(PK11_MD5_HMAC_REPLACE) + bad = true; +#endif + PK11_TRACEM(CKM_MD5_HMAC); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA_1_HMAC, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0)) { +#ifndef PK11_SHA_1_HMAC_REPLACE + bad = true; +#endif + PK11_TRACEM(CKM_SHA_1_HMAC); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA224_HMAC, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0)) { +#ifndef PK11_SHA224_HMAC_REPLACE + bad = true; +#endif + PK11_TRACEM(CKM_SHA224_HMAC); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA256_HMAC, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0)) { +#ifndef PK11_SHA256_HMAC_REPLACE + bad = true; +#endif + PK11_TRACEM(CKM_SHA256_HMAC); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA384_HMAC, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0)) { +#ifndef PK11_SHA384_HMAC_REPLACE + bad = true; +#endif + PK11_TRACEM(CKM_SHA384_HMAC); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA512_HMAC, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0)) { +#ifndef PK11_SHA512_HMAC_REPLACE + bad = true; +#endif + PK11_TRACEM(CKM_SHA512_HMAC); + } + if (!bad) { + token->operations |= 1 << OP_DIGEST; + if (digest_token == NULL) + digest_token = token; + } + + /* ECDSA requires digest */ + rv = pkcs_C_GetMechanismInfo(slot, CKM_EC_KEY_PAIR_GEN, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_GENERATE_KEY_PAIR) == 0)) { + bad = true; + PK11_TRACEM(CKM_EC_KEY_PAIR_GEN); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_ECDSA, &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) { + bad = true; + PK11_TRACEM(CKM_ECDSA); + } + if (bad) + goto try_gost; + token->operations |= 1 << OP_EC; + if (best_ec_token == NULL) + best_ec_token = token; + + try_gost: + bad = false; + /* does GOST require digest too? */ + rv = pkcs_C_GetMechanismInfo(slot, CKM_GOSTR3411, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_DIGEST) == 0)) { + bad = true; + PK11_TRACEM(CKM_GOSTR3411); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_GOSTR3410_KEY_PAIR_GEN, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_GENERATE_KEY_PAIR) == 0)) { + bad = true; + PK11_TRACEM(CKM_GOSTR3410_KEY_PAIR_GEN); + } + rv = pkcs_C_GetMechanismInfo(slot, + CKM_GOSTR3410_WITH_GOSTR3411, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) { + bad = true; + PK11_TRACEM(CKM_GOSTR3410_WITH_GOSTR3411); + } + if (bad) + goto try_eddsa; + token->operations |= 1 << OP_GOST; + if (best_gost_token == NULL) + best_gost_token = token; + + try_eddsa: +#if defined(CKM_EDDSA_KEY_PAIR_GEN) && defined(CKM_EDDSA) && defined(CKK_EDDSA) + bad = false; + rv = pkcs_C_GetMechanismInfo(slot, CKM_EDDSA_KEY_PAIR_GEN, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_GENERATE_KEY_PAIR) == 0)) { + bad = true; + PK11_TRACEM(CKM_EDDSA_KEY_PAIR_GEN); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_EDDSA, &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) { + bad = true; + PK11_TRACEM(CKM_EDDSA); + } + if (bad) + goto try_aes; + + try_aes: +#endif + bad = false; + rv = pkcs_C_GetMechanismInfo(slot, CKM_AES_ECB, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_ENCRYPT) == 0)) { + bad = true; + PK11_TRACEM(CKM_AES_ECB); + } + if (bad) + continue; + token->operations |= 1 << OP_AES; + if (aes_token == NULL) + aes_token = token; + } + + if (slotList != NULL) { + pk11_mem_put(slotList, sizeof(CK_SLOT_ID) * slotCount); + } +} + +CK_SLOT_ID +pk11_get_best_token(pk11_optype_t optype) { + pk11_token_t *token = NULL; + + switch (optype) { + case OP_RAND: + token = rand_token; + break; + case OP_RSA: + token = best_rsa_token; + break; + case OP_DSA: + token = best_dsa_token; + break; + case OP_DH: + token = best_dh_token; + break; + case OP_DIGEST: + token = digest_token; + break; + case OP_EC: + token = best_ec_token; + break; + case OP_GOST: + token = best_gost_token; + break; + case OP_AES: + token = aes_token; + break; + default: + break; + } + if (token == NULL) + return (0); + return (token->slotid); +} + +unsigned int +pk11_numbits(CK_BYTE_PTR data, unsigned int bytecnt) { + unsigned int bitcnt, i; + CK_BYTE top; + + if (bytecnt == 0) + return (0); + bitcnt = bytecnt * 8; + for (i = 0; i < bytecnt; i++) { + top = data[i]; + if (top == 0) { + bitcnt -= 8; + continue; + } + if (top & 0x80) + return (bitcnt); + if (top & 0x40) + return (bitcnt - 1); + if (top & 0x20) + return (bitcnt - 2); + if (top & 0x10) + return (bitcnt - 3); + if (top & 0x08) + return (bitcnt - 4); + if (top & 0x04) + return (bitcnt - 5); + if (top & 0x02) + return (bitcnt - 6); + if (top & 0x01) + return (bitcnt - 7); + break; + } + INSIST(0); +} + +CK_ATTRIBUTE * +pk11_attribute_first(const pk11_object_t *obj) { + return (obj->repr); +} + +CK_ATTRIBUTE * +pk11_attribute_next(const pk11_object_t *obj, CK_ATTRIBUTE *attr) { + CK_ATTRIBUTE *next; + + next = attr + 1; + if ((next - obj->repr) >= obj->attrcnt) + return (NULL); + return (next); +} + +CK_ATTRIBUTE * +pk11_attribute_bytype(const pk11_object_t *obj, CK_ATTRIBUTE_TYPE type) { + CK_ATTRIBUTE *attr; + + for(attr = pk11_attribute_first(obj); + attr != NULL; + attr = pk11_attribute_next(obj, attr)) + if (attr->type == type) + return (attr); + return (NULL); +} + +static char * +percent_decode(char *x, size_t *len) { + char *p, *c; + unsigned char v; + + INSIST(len != NULL); + + for (p = c = x; p[0] != '\0'; p++, c++) { + switch (p[0]) { + case '%': + v = 0; + switch (p[1]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + v = (p[1] - '0') << 4; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + v = (p[1] - 'A' + 10) << 4; + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + v = (p[1] - 'a' + 10) << 4; + break; + default: + return (NULL); + } + switch (p[2]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + v |= (p[2] - '0') & 0x0f; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + v = (p[2] - 'A' + 10) & 0x0f; + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + v = (p[2] - 'a' + 10) & 0x0f; + break; + default: + return (NULL); + } + p += 2; + *c = (char) v; + (*len)++; + break; + default: + *c = *p; + (*len)++; + } + } + return (x); +} + +static bool +pk11strcmp(const char *x, size_t lenx, const char *y, size_t leny) { + char buf[32]; + + INSIST((leny == 32) || (leny == 16)); + + memset(buf, ' ', 32); + if (lenx > leny) + lenx = leny; + memmove(buf, x, lenx); + return (memcmp(buf, y, leny) == 0); +} + +static CK_ATTRIBUTE * +push_attribute(pk11_object_t *obj, isc_mem_t *mctx, size_t len) { + CK_ATTRIBUTE *old = obj->repr; + CK_ATTRIBUTE *attr; + CK_BYTE cnt = obj->attrcnt; + + obj->repr = isc_mem_get(mctx, (cnt + 1) * sizeof(*attr)); + if (obj->repr == NULL) { + obj->repr = old; + return (NULL); + } + memset(obj->repr, 0, (cnt + 1) * sizeof(*attr)); + memmove(obj->repr, old, cnt * sizeof(*attr)); + attr = obj->repr + cnt; + attr->ulValueLen = (CK_ULONG) len; + attr->pValue = isc_mem_get(mctx, len); + if (attr->pValue == NULL) { + memset(obj->repr, 0, (cnt + 1) * sizeof(*attr)); + isc_mem_put(mctx, obj->repr, (cnt + 1) * sizeof(*attr)); + obj->repr = old; + return (NULL); + } + memset(attr->pValue, 0, len); + if (old != NULL) { + memset(old, 0, cnt * sizeof(*attr)); + isc_mem_put(mctx, old, cnt * sizeof(*attr)); + } + obj->attrcnt++; + return (attr); +} + +#define DST_RET(a) { ret = a; goto err; } + +isc_result_t +pk11_parse_uri(pk11_object_t *obj, const char *label, + isc_mem_t *mctx, pk11_optype_t optype) +{ + CK_ATTRIBUTE *attr; + pk11_token_t *token = NULL; + char *uri, *p, *a, *na, *v; + size_t len, l; + FILE *stream = NULL; + char pin[PINLEN + 1]; + bool gotpin = false; + isc_result_t ret; + + /* get values to work on */ + len = strlen(label) + 1; + uri = isc_mem_get(mctx, len); + if (uri == NULL) + return (ISC_R_NOMEMORY); + memmove(uri, label, len); + + /* get the URI scheme */ + p = strchr(uri, ':'); + if (p == NULL) + DST_RET(PK11_R_NOPROVIDER); + *p++ = '\0'; + if (strcmp(uri, "pkcs11") != 0) + DST_RET(PK11_R_NOPROVIDER); + + /* get attributes */ + for (na = p; na != NULL;) { + a = na; + p = strchr(a, ';'); + if (p == NULL) { + /* last attribute */ + na = NULL; + } else { + *p++ = '\0'; + na = p; + } + p = strchr(a, '='); + if (p != NULL) { + *p++ = '\0'; + v = p; + } else + v = a; + l = 0; + v = percent_decode(v, &l); + if (v == NULL) + DST_RET(PK11_R_NOPROVIDER); + if ((a == v) || (strcmp(a, "object") == 0)) { + /* object: CKA_LABEL */ + attr = pk11_attribute_bytype(obj, CKA_LABEL); + if (attr != NULL) + DST_RET(PK11_R_NOPROVIDER); + attr = push_attribute(obj, mctx, l); + if (attr == NULL) + DST_RET(ISC_R_NOMEMORY); + attr->type = CKA_LABEL; + memmove(attr->pValue, v, l); + } else if (strcmp(a, "token") == 0) { + /* token: CK_TOKEN_INFO label */ + if (token == NULL) + for (token = ISC_LIST_HEAD(tokens); + token != NULL; + token = ISC_LIST_NEXT(token, link)) + if (pk11strcmp(v, l, token->name, 32)) + break; + } else if (strcmp(a, "manufacturer") == 0) { + /* manufacturer: CK_TOKEN_INFO manufacturerID */ + if (token == NULL) + for (token = ISC_LIST_HEAD(tokens); + token != NULL; + token = ISC_LIST_NEXT(token, link)) + if (pk11strcmp(v, l, token->manuf, 32)) + break; + } else if (strcmp(a, "serial") == 0) { + /* serial: CK_TOKEN_INFO serialNumber */ + if (token == NULL) + for (token = ISC_LIST_HEAD(tokens); + token != NULL; + token = ISC_LIST_NEXT(token, link)) + if (pk11strcmp(v, l, token->serial, 16)) + break; + } else if (strcmp(a, "model") == 0) { + /* model: CK_TOKEN_INFO model */ + if (token == NULL) + for (token = ISC_LIST_HEAD(tokens); + token != NULL; + token = ISC_LIST_NEXT(token, link)) + if (pk11strcmp(v, l, token->model, 16)) + break; + } else if (strcmp(a, "library-manufacturer") == 0) { + /* ignored */ + } else if (strcmp(a, "library-description") == 0) { + /* ignored */ + } else if (strcmp(a, "library-version") == 0) { + /* ignored */ + } else if (strcmp(a, "object-type") == 0) { + /* object-type: CKA_CLASS */ + /* only private makes sense */ + if (strcmp(v, "private") != 0) + DST_RET(PK11_R_NOPROVIDER); + } else if (strcmp(a, "id") == 0) { + /* id: CKA_ID */ + attr = pk11_attribute_bytype(obj, CKA_ID); + if (attr != NULL) + DST_RET(PK11_R_NOPROVIDER); + attr = push_attribute(obj, mctx, l); + if (attr == NULL) + DST_RET(ISC_R_NOMEMORY); + attr->type = CKA_ID; + memmove(attr->pValue, v, l); + } else if (strcmp(a, "pin-source") == 0) { + /* pin-source: PIN */ + ret = isc_stdio_open(v, "r", &stream); + if (ret != ISC_R_SUCCESS) + goto err; + memset(pin, 0, PINLEN + 1); + ret = isc_stdio_read(pin, 1, PINLEN + 1, stream, &l); + if ((ret != ISC_R_SUCCESS) && (ret != ISC_R_EOF)) + goto err; + if (l > PINLEN) + DST_RET(ISC_R_RANGE); + ret = isc_stdio_close(stream); + stream = NULL; + if (ret != ISC_R_SUCCESS) + goto err; + gotpin = true; + } else + DST_RET(PK11_R_NOPROVIDER); + } + + if ((pk11_attribute_bytype(obj, CKA_LABEL) == NULL) && + (pk11_attribute_bytype(obj, CKA_ID) == NULL)) + DST_RET(ISC_R_NOTFOUND); + + if (token == NULL) { + if (optype == OP_RSA) + token = best_rsa_token; + else if (optype == OP_DSA) + token = best_dsa_token; + else if (optype == OP_DH) + token = best_dh_token; + else if (optype == OP_EC) + token = best_ec_token; + } + if (token == NULL) + DST_RET(ISC_R_NOTFOUND); + obj->slot = token->slotid; + if (gotpin) { + memmove(token->pin, pin, PINLEN + 1); + obj->reqlogon = true; + } + + ret = ISC_R_SUCCESS; + + err: + if (stream != NULL) + (void) isc_stdio_close(stream); + isc_mem_put(mctx, uri, len); + return (ret); +} + +void +pk11_error_fatalcheck(const char *file, int line, + const char *funcname, CK_RV rv) +{ + isc_error_fatal(file, line, "%s: Error = 0x%.8lX\n", funcname, rv); +} + +void +pk11_dump_tokens(void) { + pk11_token_t *token; + bool first; + + printf("DEFAULTS\n"); + printf("\trand_token=%p\n", rand_token); + printf("\tbest_rsa_token=%p\n", best_rsa_token); + printf("\tbest_dsa_token=%p\n", best_dsa_token); + printf("\tbest_dh_token=%p\n", best_dh_token); + printf("\tdigest_token=%p\n", digest_token); + printf("\tbest_ec_token=%p\n", best_ec_token); + printf("\tbest_gost_token=%p\n", best_gost_token); + printf("\taes_token=%p\n", aes_token); + + for (token = ISC_LIST_HEAD(tokens); + token != NULL; + token = ISC_LIST_NEXT(token, link)) { + printf("\nTOKEN\n"); + printf("\taddress=%p\n", token); + printf("\tslotID=%lu\n", token->slotid); + printf("\tlabel=%.32s\n", token->name); + printf("\tmanufacturerID=%.32s\n", token->manuf); + printf("\tmodel=%.16s\n", token->model); + printf("\tserialNumber=%.16s\n", token->serial); + printf("\tsupported operations=0x%x (", token->operations); + first = true; + if (token->operations & (1 << OP_RAND)) { + if (!first) + printf(","); + first = false; + printf("RAND"); + } + if (token->operations & (1 << OP_RSA)) { + first = false; + printf("RSA"); + } + if (token->operations & (1 << OP_DSA)) { + if (!first) + printf(","); + first = false; + printf("DSA"); + } + if (token->operations & (1 << OP_DH)) { + if (!first) + printf(","); + first = false; + printf("DH"); + } + if (token->operations & (1 << OP_DIGEST)) { + if (!first) + printf(","); + first = false; + printf("DIGEST"); + } + if (token->operations & (1 << OP_EC)) { + if (!first) + printf(","); + first = false; + printf("EC"); + } + if (token->operations & (1 << OP_AES)) { + if (!first) + printf(","); + first = false; + printf("AES"); + } + printf(")\n"); + } +} |