diff options
Diffstat (limited to '')
-rw-r--r-- | security/nss/lib/pk11wrap/pk11load.c | 715 |
1 files changed, 715 insertions, 0 deletions
diff --git a/security/nss/lib/pk11wrap/pk11load.c b/security/nss/lib/pk11wrap/pk11load.c new file mode 100644 index 0000000000..119c8c5120 --- /dev/null +++ b/security/nss/lib/pk11wrap/pk11load.c @@ -0,0 +1,715 @@ +/* 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/. */ +/* + * The following handles the loading, unloading and management of + * various PCKS #11 modules + */ +#define FORCE_PR_LOG 1 +#include "base.h" +#include "seccomon.h" +#include "pkcs11.h" +#include "secmod.h" +#include "prlink.h" +#include "pk11func.h" +#include "secmodi.h" +#include "secmodti.h" +#include "nssilock.h" +#include "secerr.h" +#include "prenv.h" +#include "utilpars.h" +#include "prio.h" +#include "prprf.h" +#include <stdio.h> +#include "prsystem.h" + +#define DEBUG_MODULE 1 + +#ifdef DEBUG_MODULE +static char *modToDBG = NULL; + +#include "debug_module.c" +#endif + +/* build the PKCS #11 2.01 lock files */ +CK_RV PR_CALLBACK +secmodCreateMutext(CK_VOID_PTR_PTR pmutex) +{ + *pmutex = (CK_VOID_PTR)PZ_NewLock(nssILockOther); + if (*pmutex) + return CKR_OK; + return CKR_HOST_MEMORY; +} + +CK_RV PR_CALLBACK +secmodDestroyMutext(CK_VOID_PTR mutext) +{ + PZ_DestroyLock((PZLock *)mutext); + return CKR_OK; +} + +CK_RV PR_CALLBACK +secmodLockMutext(CK_VOID_PTR mutext) +{ + PZ_Lock((PZLock *)mutext); + return CKR_OK; +} + +CK_RV PR_CALLBACK +secmodUnlockMutext(CK_VOID_PTR mutext) +{ + PZ_Unlock((PZLock *)mutext); + return CKR_OK; +} + +static SECMODModuleID nextModuleID = 1; +static const CK_C_INITIALIZE_ARGS secmodLockFunctions = { + secmodCreateMutext, secmodDestroyMutext, secmodLockMutext, + secmodUnlockMutext, CKF_LIBRARY_CANT_CREATE_OS_THREADS | CKF_OS_LOCKING_OK, + NULL +}; +static const CK_C_INITIALIZE_ARGS secmodNoLockArgs = { + NULL, NULL, NULL, NULL, + CKF_LIBRARY_CANT_CREATE_OS_THREADS, NULL +}; + +static PRBool loadSingleThreadedModules = PR_TRUE; +static PRBool enforceAlreadyInitializedError = PR_TRUE; +static PRBool finalizeModules = PR_TRUE; + +/* set global options for NSS PKCS#11 module loader */ +SECStatus +pk11_setGlobalOptions(PRBool noSingleThreadedModules, + PRBool allowAlreadyInitializedModules, + PRBool dontFinalizeModules) +{ + if (noSingleThreadedModules) { + loadSingleThreadedModules = PR_FALSE; + } else { + loadSingleThreadedModules = PR_TRUE; + } + if (allowAlreadyInitializedModules) { + enforceAlreadyInitializedError = PR_FALSE; + } else { + enforceAlreadyInitializedError = PR_TRUE; + } + if (dontFinalizeModules) { + finalizeModules = PR_FALSE; + } else { + finalizeModules = PR_TRUE; + } + return SECSuccess; +} + +PRBool +pk11_getFinalizeModulesOption(void) +{ + return finalizeModules; +} + +/* + * Allow specification loading the same module more than once at init time. + * This enables 2 things. + * + * 1) we can load additional databases by manipulating secmod.db/pkcs11.txt. + * 2) we can handle the case where some library has already initialized NSS + * before the main application. + * + * oldModule is the module we have already initialized. + * char *modulespec is the full module spec for the library we want to + * initialize. + */ +static SECStatus +secmod_handleReload(SECMODModule *oldModule, SECMODModule *newModule) +{ + PK11SlotInfo *slot; + char *modulespec; + char *newModuleSpec; + char **children; + CK_SLOT_ID *ids; + SECMODConfigList *conflist = NULL; + SECStatus rv = SECFailure; + int count = 0; + + /* first look for tokens= key words from the module spec */ + modulespec = newModule->libraryParams; + newModuleSpec = secmod_ParseModuleSpecForTokens(PR_TRUE, + newModule->isFIPS, modulespec, &children, &ids); + if (!newModuleSpec) { + return SECFailure; + } + + /* + * We are now trying to open a new slot on an already loaded module. + * If that slot represents a cert/key database, we don't want to open + * multiple copies of that same database. Unfortunately we understand + * the softoken flags well enough to be able to do this, so we can only get + * the list of already loaded databases if we are trying to open another + * internal module. + */ + if (oldModule->internal) { + conflist = secmod_GetConfigList(oldModule->isFIPS, + oldModule->libraryParams, &count); + } + + /* don't open multiple of the same db */ + if (conflist && secmod_MatchConfigList(newModuleSpec, conflist, count)) { + rv = SECSuccess; + goto loser; + } + slot = SECMOD_OpenNewSlot(oldModule, newModuleSpec); + if (slot) { + int newID; + char **thisChild; + CK_SLOT_ID *thisID; + char *oldModuleSpec; + + if (secmod_IsInternalKeySlot(newModule)) { + pk11_SetInternalKeySlotIfFirst(slot); + } + newID = slot->slotID; + PK11_FreeSlot(slot); + for (thisChild = children, thisID = ids; thisChild && *thisChild; + thisChild++, thisID++) { + if (conflist && + secmod_MatchConfigList(*thisChild, conflist, count)) { + *thisID = (CK_SLOT_ID)-1; + continue; + } + slot = SECMOD_OpenNewSlot(oldModule, *thisChild); + if (slot) { + *thisID = slot->slotID; + PK11_FreeSlot(slot); + } else { + *thisID = (CK_SLOT_ID)-1; + } + } + + /* update the old module initialization string in case we need to + * shutdown and reinit the whole mess (this is rare, but can happen + * when trying to stop smart card insertion/removal threads)... */ + oldModuleSpec = secmod_MkAppendTokensList(oldModule->arena, + oldModule->libraryParams, newModuleSpec, newID, + children, ids); + if (oldModuleSpec) { + oldModule->libraryParams = oldModuleSpec; + } + + rv = SECSuccess; + } + +loser: + secmod_FreeChildren(children, ids); + PORT_Free(newModuleSpec); + if (conflist) { + secmod_FreeConfigList(conflist, count); + } + return rv; +} + +/* + * collect the steps we need to initialize a module in a single function + */ +SECStatus +secmod_ModuleInit(SECMODModule *mod, SECMODModule **reload, + PRBool *alreadyLoaded) +{ + CK_C_INITIALIZE_ARGS moduleArgs; + CK_VOID_PTR pInitArgs; + CK_RV crv; + + if (reload) { + *reload = NULL; + } + + if (!mod || !alreadyLoaded) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (mod->libraryParams == NULL) { + if (mod->isThreadSafe) { + pInitArgs = (void *)&secmodLockFunctions; + } else { + pInitArgs = NULL; + } + } else { + if (mod->isThreadSafe) { + moduleArgs = secmodLockFunctions; + } else { + moduleArgs = secmodNoLockArgs; + } + moduleArgs.LibraryParameters = (void *)mod->libraryParams; + pInitArgs = &moduleArgs; + } + crv = PK11_GETTAB(mod)->C_Initialize(pInitArgs); + if (CKR_CRYPTOKI_ALREADY_INITIALIZED == crv) { + SECMODModule *oldModule = NULL; + + /* Library has already been loaded once, if caller expects it, and it + * has additional configuration, try reloading it as well. */ + if (reload != NULL && mod->libraryParams) { + oldModule = secmod_FindModuleByFuncPtr(mod->functionList); + } + /* Library has been loaded by NSS. It means it may be capable of + * reloading */ + if (oldModule) { + SECStatus rv; + rv = secmod_handleReload(oldModule, mod); + if (rv == SECSuccess) { + /* This module should go away soon, since we've + * simply expanded the slots on the old module. + * When it goes away, it should not Finalize since + * that will close our old module as well. Setting + * the function list to NULL will prevent that close */ + mod->functionList = NULL; + *reload = oldModule; + return SECSuccess; + } + SECMOD_DestroyModule(oldModule); + } + /* reload not possible, fall back to old semantics */ + if (!enforceAlreadyInitializedError) { + *alreadyLoaded = PR_TRUE; + return SECSuccess; + } + } + if (crv != CKR_OK) { + if (!mod->isThreadSafe || + crv == CKR_NSS_CERTDB_FAILED || + crv == CKR_NSS_KEYDB_FAILED) { + PORT_SetError(PK11_MapError(crv)); + return SECFailure; + } + /* If we had attempted to init a single threaded module "with" + * parameters and it failed, should we retry "without" parameters? + * (currently we don't retry in this scenario) */ + + if (!loadSingleThreadedModules) { + PORT_SetError(SEC_ERROR_INCOMPATIBLE_PKCS11); + return SECFailure; + } + /* If we arrive here, the module failed a ThreadSafe init. */ + mod->isThreadSafe = PR_FALSE; + if (!mod->libraryParams) { + pInitArgs = NULL; + } else { + moduleArgs = secmodNoLockArgs; + moduleArgs.LibraryParameters = (void *)mod->libraryParams; + pInitArgs = &moduleArgs; + } + crv = PK11_GETTAB(mod)->C_Initialize(pInitArgs); + if ((CKR_CRYPTOKI_ALREADY_INITIALIZED == crv) && + (!enforceAlreadyInitializedError)) { + *alreadyLoaded = PR_TRUE; + return SECSuccess; + } + if (crv != CKR_OK) { + PORT_SetError(PK11_MapError(crv)); + return SECFailure; + } + } + return SECSuccess; +} + +/* + * set the hasRootCerts flags in the module so it can be stored back + * into the database. + */ +void +SECMOD_SetRootCerts(PK11SlotInfo *slot, SECMODModule *mod) +{ + PK11PreSlotInfo *psi = NULL; + int i; + + if (slot->hasRootCerts) { + for (i = 0; i < mod->slotInfoCount; i++) { + if (slot->slotID == mod->slotInfo[i].slotID) { + psi = &mod->slotInfo[i]; + break; + } + } + if (psi == NULL) { + /* allocate more slots */ + PK11PreSlotInfo *psi_list = (PK11PreSlotInfo *) + PORT_ArenaAlloc(mod->arena, + (mod->slotInfoCount + 1) * sizeof(PK11PreSlotInfo)); + /* copy the old ones */ + if (mod->slotInfoCount > 0) { + PORT_Memcpy(psi_list, mod->slotInfo, + (mod->slotInfoCount) * sizeof(PK11PreSlotInfo)); + } + /* assign psi to the last new slot */ + psi = &psi_list[mod->slotInfoCount]; + psi->slotID = slot->slotID; + psi->askpw = 0; + psi->timeout = 0; + psi->defaultFlags = 0; + + /* increment module count & store new list */ + mod->slotInfo = psi_list; + mod->slotInfoCount++; + } + psi->hasRootCerts = 1; + } +} + +#ifndef NSS_STATIC_SOFTOKEN +static const char *my_shlib_name = + SHLIB_PREFIX "nss" NSS_SHLIB_VERSION "." SHLIB_SUFFIX; +static const char *softoken_shlib_name = + SHLIB_PREFIX "softokn" SOFTOKEN_SHLIB_VERSION "." SHLIB_SUFFIX; +static const PRCallOnceType pristineCallOnce; +static PRCallOnceType loadSoftokenOnce; +static PRLibrary *softokenLib; +static PRInt32 softokenLoadCount; + +/* This function must be run only once. */ +/* determine if hybrid platform, then actually load the DSO. */ +static PRStatus +softoken_LoadDSO(void) +{ + PRLibrary *handle; + + handle = PORT_LoadLibraryFromOrigin(my_shlib_name, + (PRFuncPtr)&softoken_LoadDSO, + softoken_shlib_name); + if (handle) { + softokenLib = handle; + return PR_SUCCESS; + } + return PR_FAILURE; +} +#else +CK_RV NSC_GetInterface(CK_UTF8CHAR_PTR pInterfaceName, + CK_VERSION_PTR pVersion, + CK_INTERFACE_PTR_PTR *ppInterface, CK_FLAGS flags); +char **NSC_ModuleDBFunc(unsigned long function, char *parameters, void *args); +#endif + +/* + * load a new module into our address space and initialize it. + */ +SECStatus +secmod_LoadPKCS11Module(SECMODModule *mod, SECMODModule **oldModule) +{ + PRLibrary *library = NULL; + CK_C_GetInterface ientry = NULL; + CK_C_GetFunctionList fentry = NULL; + CK_INFO info; + CK_ULONG slotCount = 0; + SECStatus rv; + PRBool alreadyLoaded = PR_FALSE; + char *disableUnload = NULL; +#ifndef NSS_STATIC_SOFTOKEN + const char *nss_interface; + const char *nss_function; +#endif + CK_INTERFACE_PTR interface; + + if (mod->loaded) + return SECSuccess; + + mod->fipsIndicator = NULL; + + /* internal modules get loaded from their internal list */ + if (mod->internal && (mod->dllName == NULL)) { +#ifdef NSS_STATIC_SOFTOKEN + ientry = (CK_C_GetInterface)NSC_GetInterface; +#else + /* + * Loads softoken as a dynamic library, + * even though the rest of NSS assumes this as the "internal" module. + */ + if (!softokenLib && + PR_SUCCESS != PR_CallOnce(&loadSoftokenOnce, &softoken_LoadDSO)) + return SECFailure; + + PR_ATOMIC_INCREMENT(&softokenLoadCount); + + if (mod->isFIPS) { + nss_interface = "FC_GetInterface"; + nss_function = "FC_GetFunctionList"; + } else { + nss_interface = "NSC_GetInterface"; + nss_function = "NSC_GetFunctionList"; + } + + ientry = (CK_C_GetInterface) + PR_FindSymbol(softokenLib, nss_interface); + if (!ientry) { + fentry = (CK_C_GetFunctionList) + PR_FindSymbol(softokenLib, nss_function); + if (!fentry) { + return SECFailure; + } + } +#endif + + if (mod->isModuleDB) { + mod->moduleDBFunc = (CK_C_GetFunctionList) +#ifdef NSS_STATIC_SOFTOKEN + NSC_ModuleDBFunc; +#else + PR_FindSymbol(softokenLib, "NSC_ModuleDBFunc"); +#endif + } + + if (mod->moduleDBOnly) { + mod->loaded = PR_TRUE; + return SECSuccess; + } + } else { + /* Not internal, load the DLL and look up C_GetFunctionList */ + if (mod->dllName == NULL) { + return SECFailure; + } + +/* load the library. If this succeeds, then we have to remember to + * unload the library if anything goes wrong from here on out... + */ +#if defined(_WIN32) + if (nssUTF8_Length(mod->dllName, NULL)) { + wchar_t *dllNameWide = _NSSUTIL_UTF8ToWide(mod->dllName); + if (dllNameWide) { + PRLibSpec libSpec; + libSpec.type = PR_LibSpec_PathnameU; + libSpec.value.pathname_u = dllNameWide; + library = PR_LoadLibraryWithFlags(libSpec, 0); + PORT_Free(dllNameWide); + } + } + if (library == NULL) { + // fallback to system code page + library = PR_LoadLibrary(mod->dllName); + } +#else + library = PR_LoadLibrary(mod->dllName); +#endif // defined(_WIN32) + mod->library = (void *)library; + + if (library == NULL) { + return SECFailure; + } + + /* + * now we need to get the entry point to find the function pointers + */ + if (!mod->moduleDBOnly) { + ientry = (CK_C_GetInterface) + PR_FindSymbol(library, "C_GetInterface"); + if (!ientry) { + fentry = (CK_C_GetFunctionList) + PR_FindSymbol(library, "C_GetFunctionList"); + } + } + if (mod->isModuleDB) { + mod->moduleDBFunc = (void *) + PR_FindSymbol(library, "NSS_ReturnModuleSpecData"); + } + if (mod->moduleDBFunc == NULL) + mod->isModuleDB = PR_FALSE; + if ((ientry == NULL) && (fentry == NULL)) { + if (mod->isModuleDB) { + mod->loaded = PR_TRUE; + mod->moduleDBOnly = PR_TRUE; + return SECSuccess; + } + PR_UnloadLibrary(library); + return SECFailure; + } + } + + /* + * We need to get the function list + */ + if (ientry) { + /* we first try to get a FORK_SAFE interface */ + if ((*ientry)((CK_UTF8CHAR_PTR) "PKCS 11", NULL, &interface, + CKF_INTERFACE_FORK_SAFE) != CKR_OK) { + /* one is not appearantly available, get a non-fork safe version */ + if ((*ientry)((CK_UTF8CHAR_PTR) "PKCS 11", NULL, &interface, 0) != CKR_OK) { + goto fail; + } + } + mod->functionList = interface->pFunctionList; + mod->flags = interface->flags; + /* if we have a fips indicator, grab it */ + if ((*ientry)((CK_UTF8CHAR_PTR) "Vendor NSS FIPS Interface", NULL, + &interface, 0) == CKR_OK) { + mod->fipsIndicator = ((CK_NSS_FIPS_FUNCTIONS *)(interface->pFunctionList))->NSC_NSSGetFIPSStatus; + } + } else { + if ((*fentry)((CK_FUNCTION_LIST_PTR *)&mod->functionList) != CKR_OK) + goto fail; + mod->flags = 0; + } + +#ifdef DEBUG_MODULE + modToDBG = PR_GetEnvSecure("NSS_DEBUG_PKCS11_MODULE"); + if (modToDBG && strcmp(mod->commonName, modToDBG) == 0) { + mod->functionList = (void *)nss_InsertDeviceLog( + (CK_FUNCTION_LIST_3_0_PTR)mod->functionList); + } +#endif + + /* This test operation makes sure our locking system is + * consistent even if we are using non-thread safe tokens by + * simulating unsafe tokens with safe ones. */ + mod->isThreadSafe = !PR_GetEnvSecure("NSS_FORCE_TOKEN_LOCK"); + + /* Now we initialize the module */ + rv = secmod_ModuleInit(mod, oldModule, &alreadyLoaded); + if (rv != SECSuccess) { + goto fail; + } + + /* module has been reloaded, this module itself is done, + * return to the caller */ + if (mod->functionList == NULL) { + mod->loaded = PR_TRUE; /* technically the module is loaded.. */ + return SECSuccess; + } + + /* check the version number */ + if (PK11_GETTAB(mod)->C_GetInfo(&info) != CKR_OK) + goto fail2; + if (info.cryptokiVersion.major < 2) + goto fail2; + /* all 2.0 are a priori *not* thread safe */ + if ((info.cryptokiVersion.major == 2) && (info.cryptokiVersion.minor < 1)) { + if (!loadSingleThreadedModules) { + PORT_SetError(SEC_ERROR_INCOMPATIBLE_PKCS11); + goto fail2; + } else { + mod->isThreadSafe = PR_FALSE; + } + } + mod->cryptokiVersion = info.cryptokiVersion; + + /* If we don't have a common name, get it from the PKCS 11 module */ + if ((mod->commonName == NULL) || (mod->commonName[0] == 0)) { + mod->commonName = PK11_MakeString(mod->arena, NULL, + (char *)info.libraryDescription, sizeof(info.libraryDescription)); + if (mod->commonName == NULL) + goto fail2; + } + + /* initialize the Slots */ + if (PK11_GETTAB(mod)->C_GetSlotList(CK_FALSE, NULL, &slotCount) == CKR_OK) { + CK_SLOT_ID *slotIDs; + int i; + CK_RV crv; + + mod->slots = (PK11SlotInfo **)PORT_ArenaAlloc(mod->arena, + sizeof(PK11SlotInfo *) * slotCount); + if (mod->slots == NULL) + goto fail2; + + slotIDs = (CK_SLOT_ID *)PORT_Alloc(sizeof(CK_SLOT_ID) * slotCount); + if (slotIDs == NULL) { + goto fail2; + } + crv = PK11_GETTAB(mod)->C_GetSlotList(CK_FALSE, slotIDs, &slotCount); + if (crv != CKR_OK) { + PORT_Free(slotIDs); + goto fail2; + } + + /* Initialize each slot */ + for (i = 0; i < (int)slotCount; i++) { + mod->slots[i] = PK11_NewSlotInfo(mod); + PK11_InitSlot(mod, slotIDs[i], mod->slots[i]); + /* look down the slot info table */ + PK11_LoadSlotList(mod->slots[i], mod->slotInfo, mod->slotInfoCount); + SECMOD_SetRootCerts(mod->slots[i], mod); + /* explicitly mark the internal slot as such if IsInternalKeySlot() + * is set */ + if (secmod_IsInternalKeySlot(mod) && (i == (mod->isFIPS ? 0 : 1))) { + pk11_SetInternalKeySlotIfFirst(mod->slots[i]); + } + } + mod->slotCount = slotCount; + mod->slotInfoCount = 0; + PORT_Free(slotIDs); + } + + mod->loaded = PR_TRUE; + mod->moduleID = nextModuleID++; + return SECSuccess; +fail2: + if (enforceAlreadyInitializedError || (!alreadyLoaded)) { + PK11_GETTAB(mod)->C_Finalize(NULL); + } +fail: + mod->functionList = NULL; + disableUnload = PR_GetEnvSecure("NSS_DISABLE_UNLOAD"); + if (library && !disableUnload) { + PR_UnloadLibrary(library); + } + return SECFailure; +} + +SECStatus +SECMOD_UnloadModule(SECMODModule *mod) +{ + PRLibrary *library; + char *disableUnload = NULL; + + if (!mod->loaded) { + return SECFailure; + } + if (finalizeModules) { + if (mod->functionList && !mod->moduleDBOnly) { + PK11_GETTAB(mod)->C_Finalize(NULL); + } + } + mod->moduleID = 0; + mod->loaded = PR_FALSE; + + /* do we want the semantics to allow unloading the internal library? + * if not, we should change this to SECFailure and move it above the + * mod->loaded = PR_FALSE; */ + if (mod->internal && (mod->dllName == NULL)) { +#ifndef NSS_STATIC_SOFTOKEN + if (0 == PR_ATOMIC_DECREMENT(&softokenLoadCount)) { + if (softokenLib) { + disableUnload = PR_GetEnvSecure("NSS_DISABLE_UNLOAD"); + if (!disableUnload) { +#ifdef DEBUG + PRStatus status = PR_UnloadLibrary(softokenLib); + PORT_Assert(PR_SUCCESS == status); +#else + PR_UnloadLibrary(softokenLib); +#endif + } + softokenLib = NULL; + } + loadSoftokenOnce = pristineCallOnce; + } +#endif + return SECSuccess; + } + + library = (PRLibrary *)mod->library; + /* paranoia */ + if (library == NULL) { + return SECFailure; + } + + disableUnload = PR_GetEnvSecure("NSS_DISABLE_UNLOAD"); + if (!disableUnload) { + PR_UnloadLibrary(library); + } + return SECSuccess; +} + +void +nss_DumpModuleLog(void) +{ +#ifdef DEBUG_MODULE + if (modToDBG) { + print_final_statistics(); + } +#endif +} |