Source code

Revision control

Copy as Markdown

Other Tools

/* 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
}