/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Netscape security libraries. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1994-2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * Initialize the PCKS 11 subsystem */ #include "seccomon.h" #include "secmod.h" #include "nssilock.h" #include "secmodi.h" #include "secmodti.h" #include "pk11func.h" #include "pki3hack.h" #include "secerr.h" #include "dev.h" #include "pkcs11ni.h" /* these are for displaying error messages */ static SECMODModuleList *modules = NULL; static SECMODModuleList *modulesDB = NULL; static SECMODModuleList *modulesUnload = NULL; static SECMODModule *internalModule = NULL; static SECMODModule *defaultDBModule = NULL; static SECMODModule *pendingModule = NULL; static SECMODListLock *moduleLock = NULL; int secmod_PrivateModuleCount = 0; extern PK11DefaultArrayEntry PK11_DefaultArray[]; extern int num_pk11_default_mechanisms; void SECMOD_Init() { /* don't initialize twice */ if (moduleLock) return; moduleLock = SECMOD_NewListLock(); PK11_InitSlotLists(); } SECStatus SECMOD_Shutdown() { /* destroy the lock */ if (moduleLock) { SECMOD_DestroyListLock(moduleLock); moduleLock = NULL; } /* free the internal module */ if (internalModule) { SECMOD_DestroyModule(internalModule); internalModule = NULL; } /* free the default database module */ if (defaultDBModule) { SECMOD_DestroyModule(defaultDBModule); defaultDBModule = NULL; } /* destroy the list */ if (modules) { SECMOD_DestroyModuleList(modules); modules = NULL; } if (modulesDB) { SECMOD_DestroyModuleList(modulesDB); modulesDB = NULL; } if (modulesUnload) { SECMOD_DestroyModuleList(modulesUnload); modulesUnload = NULL; } /* make all the slots and the lists go away */ PK11_DestroySlotLists(); nss_DumpModuleLog(); #ifdef DEBUG if (PR_GetEnv("NSS_STRICT_SHUTDOWN")) { PORT_Assert(secmod_PrivateModuleCount == 0); } #endif if (secmod_PrivateModuleCount) { PORT_SetError(SEC_ERROR_BUSY); return SECFailure; } return SECSuccess; } /* * retrieve the internal module */ SECMODModule * SECMOD_GetInternalModule(void) { return internalModule; } SECStatus secmod_AddModuleToList(SECMODModuleList **moduleList,SECMODModule *newModule) { SECMODModuleList *mlp, *newListElement, *last = NULL; newListElement = SECMOD_NewModuleListElement(); if (newListElement == NULL) { return SECFailure; } newListElement->module = SECMOD_ReferenceModule(newModule); SECMOD_GetWriteLock(moduleLock); /* Added it to the end (This is very inefficient, but Adding a module * on the fly should happen maybe 2-3 times through the life this program * on a given computer, and this list should be *SHORT*. */ for(mlp = *moduleList; mlp != NULL; mlp = mlp->next) { last = mlp; } if (last == NULL) { *moduleList = newListElement; } else { SECMOD_AddList(last,newListElement,NULL); } SECMOD_ReleaseWriteLock(moduleLock); return SECSuccess; } SECStatus SECMOD_AddModuleToList(SECMODModule *newModule) { if (newModule->internal && !internalModule) { internalModule = SECMOD_ReferenceModule(newModule); } return secmod_AddModuleToList(&modules,newModule); } SECStatus SECMOD_AddModuleToDBOnlyList(SECMODModule *newModule) { if (defaultDBModule == NULL) { defaultDBModule = SECMOD_ReferenceModule(newModule); } return secmod_AddModuleToList(&modulesDB,newModule); } SECStatus SECMOD_AddModuleToUnloadList(SECMODModule *newModule) { return secmod_AddModuleToList(&modulesUnload,newModule); } /* * get the list of PKCS11 modules that are available. */ SECMODModuleList * SECMOD_GetDefaultModuleList() { return modules; } SECMODModuleList *SECMOD_GetDeadModuleList() { return modulesUnload; } SECMODModuleList *SECMOD_GetDBModuleList() { return modulesDB; } /* * This lock protects the global module lists. * it also protects changes to the slot array (module->slots[]) and slot count * (module->slotCount) in each module. It is a read/write lock with multiple * readers or one writer. Writes are uncommon. * Because of legacy considerations protection of the slot array and count is * only necessary in applications if the application calls * SECMOD_UpdateSlotList() or SECMOD_WaitForAnyTokenEvent(), though all new * applications are encouraged to acquire this lock when reading the * slot array information directly. */ SECMODListLock *SECMOD_GetDefaultModuleListLock() { return moduleLock; } /* * find a module by name, and add a reference to it. * return that module. */ SECMODModule * SECMOD_FindModule(const char *name) { SECMODModuleList *mlp; SECMODModule *module = NULL; if (!moduleLock) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return module; } SECMOD_GetReadLock(moduleLock); for(mlp = modules; mlp != NULL; mlp = mlp->next) { if (PORT_Strcmp(name,mlp->module->commonName) == 0) { module = mlp->module; SECMOD_ReferenceModule(module); break; } } if (module) { goto found; } for(mlp = modulesUnload; mlp != NULL; mlp = mlp->next) { if (PORT_Strcmp(name,mlp->module->commonName) == 0) { module = mlp->module; SECMOD_ReferenceModule(module); break; } } found: SECMOD_ReleaseReadLock(moduleLock); return module; } /* * find a module by ID, and add a reference to it. * return that module. */ SECMODModule * SECMOD_FindModuleByID(SECMODModuleID id) { SECMODModuleList *mlp; SECMODModule *module = NULL; if (!moduleLock) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return module; } SECMOD_GetReadLock(moduleLock); for(mlp = modules; mlp != NULL; mlp = mlp->next) { if (id == mlp->module->moduleID) { module = mlp->module; SECMOD_ReferenceModule(module); break; } } SECMOD_ReleaseReadLock(moduleLock); if (module == NULL) { PORT_SetError(SEC_ERROR_NO_MODULE); } return module; } /* * Find the Slot based on ID and the module. */ PK11SlotInfo * SECMOD_FindSlotByID(SECMODModule *module, CK_SLOT_ID slotID) { int i; PK11SlotInfo *slot = NULL; if (!moduleLock) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return slot; } SECMOD_GetReadLock(moduleLock); for (i=0; i < module->slotCount; i++) { PK11SlotInfo *cSlot = module->slots[i]; if (cSlot->slotID == slotID) { slot = PK11_ReferenceSlot(cSlot); break; } } SECMOD_ReleaseReadLock(moduleLock); if (slot == NULL) { PORT_SetError(SEC_ERROR_NO_SLOT_SELECTED); } return slot; } /* * lookup the Slot module based on it's module ID and slot ID. */ PK11SlotInfo * SECMOD_LookupSlot(SECMODModuleID moduleID,CK_SLOT_ID slotID) { SECMODModule *module; PK11SlotInfo *slot; module = SECMOD_FindModuleByID(moduleID); if (module == NULL) return NULL; slot = SECMOD_FindSlotByID(module, slotID); SECMOD_DestroyModule(module); return slot; } /* * find a module by name or module pointer and delete it off the module list. * optionally remove it from secmod.db. */ SECStatus SECMOD_DeleteModuleEx(const char *name, SECMODModule *mod, int *type, PRBool permdb) { SECMODModuleList *mlp; SECMODModuleList **mlpp; SECStatus rv = SECFailure; if (!moduleLock) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return rv; } *type = SECMOD_EXTERNAL; SECMOD_GetWriteLock(moduleLock); for (mlpp = &modules,mlp = modules; mlp != NULL; mlpp = &mlp->next, mlp = *mlpp) { if ((name && (PORT_Strcmp(name,mlp->module->commonName) == 0)) || mod == mlp->module) { /* don't delete the internal module */ if (!mlp->module->internal) { SECMOD_RemoveList(mlpp,mlp); /* delete it after we release the lock */ rv = STAN_RemoveModuleFromDefaultTrustDomain(mlp->module); } else if (mlp->module->isFIPS) { *type = SECMOD_FIPS; } else { *type = SECMOD_INTERNAL; } break; } } if (mlp) { goto found; } /* not on the internal list, check the unload list */ for (mlpp = &modulesUnload,mlp = modulesUnload; mlp != NULL; mlpp = &mlp->next, mlp = *mlpp) { if ((name && (PORT_Strcmp(name,mlp->module->commonName) == 0)) || mod == mlp->module) { /* don't delete the internal module */ if (!mlp->module->internal) { SECMOD_RemoveList(mlpp,mlp); rv = SECSuccess; } else if (mlp->module->isFIPS) { *type = SECMOD_FIPS; } else { *type = SECMOD_INTERNAL; } break; } } found: SECMOD_ReleaseWriteLock(moduleLock); if (rv == SECSuccess) { if (permdb) { SECMOD_DeletePermDB(mlp->module); } SECMOD_DestroyModuleListElement(mlp); } return rv; } /* * find a module by name and delete it off the module list */ SECStatus SECMOD_DeleteModule(const char *name, int *type) { return SECMOD_DeleteModuleEx(name, NULL, type, PR_TRUE); } /* * find a module by name and delete it off the module list */ SECStatus SECMOD_DeleteInternalModule(const char *name) { SECMODModuleList *mlp; SECMODModuleList **mlpp; SECStatus rv = SECFailure; if (pendingModule) { PORT_SetError(SEC_ERROR_MODULE_STUCK); return rv; } if (!moduleLock) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return rv; } SECMOD_GetWriteLock(moduleLock); for(mlpp = &modules,mlp = modules; mlp != NULL; mlpp = &mlp->next, mlp = *mlpp) { if (PORT_Strcmp(name,mlp->module->commonName) == 0) { /* don't delete the internal module */ if (mlp->module->internal) { SECMOD_RemoveList(mlpp,mlp); rv = STAN_RemoveModuleFromDefaultTrustDomain(mlp->module); } break; } } SECMOD_ReleaseWriteLock(moduleLock); if (rv == SECSuccess) { SECMODModule *newModule,*oldModule; if (mlp->module->isFIPS) { newModule = SECMOD_CreateModule(NULL, SECMOD_INT_NAME, NULL, SECMOD_INT_FLAGS); } else { newModule = SECMOD_CreateModule(NULL, SECMOD_FIPS_NAME, NULL, SECMOD_FIPS_FLAGS); } if (newModule) { newModule->libraryParams = PORT_ArenaStrdup(newModule->arena,mlp->module->libraryParams); rv = SECMOD_AddModule(newModule); if (rv != SECSuccess) { SECMOD_DestroyModule(newModule); newModule = NULL; } } if (newModule == NULL) { SECMODModuleList *last = NULL,*mlp2; /* we're in pretty deep trouble if this happens...Security * not going to work well... try to put the old module back on * the list */ SECMOD_GetWriteLock(moduleLock); for(mlp2 = modules; mlp2 != NULL; mlp2 = mlp->next) { last = mlp2; } if (last == NULL) { modules = mlp; } else { SECMOD_AddList(last,mlp,NULL); } SECMOD_ReleaseWriteLock(moduleLock); return SECFailure; } pendingModule = oldModule = internalModule; internalModule = NULL; SECMOD_DestroyModule(oldModule); SECMOD_DeletePermDB(mlp->module); SECMOD_DestroyModuleListElement(mlp); internalModule = newModule; /* adopt the module */ } return rv; } SECStatus SECMOD_AddModule(SECMODModule *newModule) { SECStatus rv; SECMODModule *oldModule; /* Test if a module w/ the same name already exists */ /* and return SECWouldBlock if so. */ /* We should probably add a new return value such as */ /* SECDublicateModule, but to minimize ripples, I'll */ /* give SECWouldBlock a new meaning */ if ((oldModule = SECMOD_FindModule(newModule->commonName)) != NULL) { SECMOD_DestroyModule(oldModule); return SECWouldBlock; /* module already exists. */ } rv = SECMOD_LoadPKCS11Module(newModule); if (rv != SECSuccess) { return rv; } if (newModule->parent == NULL) { newModule->parent = SECMOD_ReferenceModule(defaultDBModule); } SECMOD_AddPermDB(newModule); SECMOD_AddModuleToList(newModule); rv = STAN_AddModuleToDefaultTrustDomain(newModule); return rv; } PK11SlotInfo * SECMOD_FindSlot(SECMODModule *module,const char *name) { int i; char *string; PK11SlotInfo *retSlot = NULL; if (!moduleLock) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return retSlot; } SECMOD_GetReadLock(moduleLock); for (i=0; i < module->slotCount; i++) { PK11SlotInfo *slot = module->slots[i]; if (PK11_IsPresent(slot)) { string = PK11_GetTokenName(slot); } else { string = PK11_GetSlotName(slot); } if (PORT_Strcmp(name,string) == 0) { retSlot = PK11_ReferenceSlot(slot); break; } } SECMOD_ReleaseReadLock(moduleLock); if (retSlot == NULL) { PORT_SetError(SEC_ERROR_NO_SLOT_SELECTED); } return retSlot; } SECStatus PK11_GetModInfo(SECMODModule *mod,CK_INFO *info) { CK_RV crv; if (mod->functionList == NULL) return SECFailure; crv = PK11_GETTAB(mod)->C_GetInfo(info); if (crv != CKR_OK) { PORT_SetError(PK11_MapError(crv)); } return (crv == CKR_OK) ? SECSuccess : SECFailure; } /* Determine if we have the FIP's module loaded as the default * module to trigger other bogus FIPS requirements in PKCS #12 and * SSL */ PRBool PK11_IsFIPS(void) { SECMODModule *mod = SECMOD_GetInternalModule(); if (mod && mod->internal) { return mod->isFIPS; } return PR_FALSE; } /* combines NewModule() & AddModule */ /* give a string for the module name & the full-path for the dll, */ /* installs the PKCS11 module & update registry */ SECStatus SECMOD_AddNewModuleEx(const char* moduleName, const char* dllPath, unsigned long defaultMechanismFlags, unsigned long cipherEnableFlags, char* modparms, char* nssparms) { SECMODModule *module; SECStatus result = SECFailure; int s,i; PK11SlotInfo* slot; PR_SetErrorText(0, NULL); if (!moduleLock) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return result; } module = SECMOD_CreateModule(dllPath, moduleName, modparms, nssparms); if (module == NULL) { return result; } if (module->dllName != NULL) { if (module->dllName[0] != 0) { result = SECMOD_AddModule(module); if (result == SECSuccess) { /* turn on SSL cipher enable flags */ module->ssl[0] = cipherEnableFlags; SECMOD_GetReadLock(moduleLock); /* check each slot to turn on appropriate mechanisms */ for (s = 0; s < module->slotCount; s++) { slot = (module->slots)[s]; /* for each possible mechanism */ for (i=0; i < num_pk11_default_mechanisms; i++) { /* we are told to turn it on by default ? */ PRBool add = (PK11_DefaultArray[i].flag & defaultMechanismFlags) ? PR_TRUE: PR_FALSE; result = PK11_UpdateSlotAttribute(slot, &(PK11_DefaultArray[i]), add); } /* for each mechanism */ /* disable each slot if the defaultFlags say so */ if (defaultMechanismFlags & PK11_DISABLE_FLAG) { PK11_UserDisableSlot(slot); } } /* for each slot of this module */ SECMOD_ReleaseReadLock(moduleLock); /* delete and re-add module in order to save changes * to the module */ result = SECMOD_UpdateModule(module); } } } SECMOD_DestroyModule(module); return result; } SECStatus SECMOD_AddNewModule(const char* moduleName, const char* dllPath, unsigned long defaultMechanismFlags, unsigned long cipherEnableFlags) { return SECMOD_AddNewModuleEx(moduleName, dllPath, defaultMechanismFlags, cipherEnableFlags, NULL, NULL); /* don't pass module or nss params */ } SECStatus SECMOD_UpdateModule(SECMODModule *module) { SECStatus result; result = SECMOD_DeletePermDB(module); if (result == SECSuccess) { result = SECMOD_AddPermDB(module); } return result; } /* Public & Internal(Security Library) representation of * encryption mechanism flags conversion */ /* Currently, the only difference is that internal representation * puts RANDOM_FLAG at bit 31 (Most-significant bit), but * public representation puts this bit at bit 28 */ unsigned long SECMOD_PubMechFlagstoInternal(unsigned long publicFlags) { unsigned long internalFlags = publicFlags; if (publicFlags & PUBLIC_MECH_RANDOM_FLAG) { internalFlags &= ~PUBLIC_MECH_RANDOM_FLAG; internalFlags |= SECMOD_RANDOM_FLAG; } return internalFlags; } unsigned long SECMOD_InternaltoPubMechFlags(unsigned long internalFlags) { unsigned long publicFlags = internalFlags; if (internalFlags & SECMOD_RANDOM_FLAG) { publicFlags &= ~SECMOD_RANDOM_FLAG; publicFlags |= PUBLIC_MECH_RANDOM_FLAG; } return publicFlags; } /* Public & Internal(Security Library) representation of */ /* cipher flags conversion */ /* Note: currently they are just stubs */ unsigned long SECMOD_PubCipherFlagstoInternal(unsigned long publicFlags) { return publicFlags; } unsigned long SECMOD_InternaltoPubCipherFlags(unsigned long internalFlags) { return internalFlags; } /* Funtion reports true if module of modType is installed/configured */ PRBool SECMOD_IsModulePresent( unsigned long int pubCipherEnableFlags ) { PRBool result = PR_FALSE; SECMODModuleList *mods; if (!moduleLock) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return result; } SECMOD_GetReadLock(moduleLock); mods = SECMOD_GetDefaultModuleList(); for ( ; mods != NULL; mods = mods->next) { if (mods->module->ssl[0] & SECMOD_PubCipherFlagstoInternal(pubCipherEnableFlags)) { result = PR_TRUE; } } SECMOD_ReleaseReadLock(moduleLock); return result; } /* create a new ModuleListElement */ SECMODModuleList *SECMOD_NewModuleListElement(void) { SECMODModuleList *newModList; newModList= (SECMODModuleList *) PORT_Alloc(sizeof(SECMODModuleList)); if (newModList) { newModList->next = NULL; newModList->module = NULL; } return newModList; } /* * make a new reference to a module so It doesn't go away on us */ SECMODModule * SECMOD_ReferenceModule(SECMODModule *module) { PZ_Lock(module->refLock); PORT_Assert(module->refCount > 0); module->refCount++; PZ_Unlock(module->refLock); return module; } /* destroy an existing module */ void SECMOD_DestroyModule(SECMODModule *module) { PRBool willfree = PR_FALSE; int slotCount; int i; PZ_Lock(module->refLock); if (module->refCount-- == 1) { willfree = PR_TRUE; } PORT_Assert(willfree || (module->refCount > 0)); PZ_Unlock(module->refLock); if (!willfree) { return; } if (module->parent != NULL) { SECMODModule *parent = module->parent; /* paranoia, don't loop forever if the modules are looped */ module->parent = NULL; SECMOD_DestroyModule(parent); } /* slots can't really disappear until our module starts freeing them, * so this check is safe */ slotCount = module->slotCount; if (slotCount == 0) { SECMOD_SlotDestroyModule(module,PR_FALSE); return; } /* now free all out slots, when they are done, they will cause the * module to disappear altogether */ for (i=0 ; i < slotCount; i++) { if (!module->slots[i]->disabled) { PK11_ClearSlotList(module->slots[i]); } PK11_FreeSlot(module->slots[i]); } /* WARNING: once the last slot has been freed is it possible (even likely) * that module is no more... touching it now is a good way to go south */ } /* we can only get here if we've destroyed the module, or some one has * erroneously freed a slot that wasn't referenced. */ void SECMOD_SlotDestroyModule(SECMODModule *module, PRBool fromSlot) { PRBool willfree = PR_FALSE; if (fromSlot) { PORT_Assert(module->refCount == 0); PZ_Lock(module->refLock); if (module->slotCount-- == 1) { willfree = PR_TRUE; } PORT_Assert(willfree || (module->slotCount > 0)); PZ_Unlock(module->refLock); if (!willfree) return; } if (module == pendingModule) { pendingModule = NULL; } if (module->loaded) { SECMOD_UnloadModule(module); } PZ_DestroyLock(module->refLock); PORT_FreeArena(module->arena,PR_FALSE); secmod_PrivateModuleCount--; } /* destroy a list element * this destroys a single element, and returns the next element * on the chain. It makes it easy to implement for loops to delete * the chain. It also make deleting a single element easy */ SECMODModuleList * SECMOD_DestroyModuleListElement(SECMODModuleList *element) { SECMODModuleList *next = element->next; if (element->module) { SECMOD_DestroyModule(element->module); element->module = NULL; } PORT_Free(element); return next; } /* * Destroy an entire module list */ void SECMOD_DestroyModuleList(SECMODModuleList *list) { SECMODModuleList *lp; for ( lp = list; lp != NULL; lp = SECMOD_DestroyModuleListElement(lp)) ; } PRBool SECMOD_CanDeleteInternalModule(void) { return (PRBool) (pendingModule == NULL); } /* * check to see if the module has added new slots. PKCS 11 v2.20 allows for * modules to add new slots, but never remove them. Slots cannot be added * between a call to C_GetSlotLlist(Flag, NULL, &count) and the subsequent * C_GetSlotList(flag, &data, &count) so that the array doesn't accidently * grow on the caller. It is permissible for the slots to increase between * successive calls with NULL to get the size. */ SECStatus SECMOD_UpdateSlotList(SECMODModule *mod) { CK_RV crv; CK_ULONG count; CK_ULONG i, oldCount; PRBool freeRef = PR_FALSE; void *mark = NULL; CK_ULONG *slotIDs = NULL; PK11SlotInfo **newSlots = NULL; PK11SlotInfo **oldSlots = NULL; if (!moduleLock) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return SECFailure; } /* C_GetSlotList is not a session function, make sure * calls are serialized */ PZ_Lock(mod->refLock); freeRef = PR_TRUE; /* see if the number of slots have changed */ crv = PK11_GETTAB(mod)->C_GetSlotList(PR_FALSE, NULL, &count); if (crv != CKR_OK) { PORT_SetError(PK11_MapError(crv)); goto loser; } /* nothing new, blow out early, we want this function to be quick * and cheap in the normal case */ if (count == mod->slotCount) { PZ_Unlock(mod->refLock); return SECSuccess; } if (count < (CK_ULONG)mod->slotCount) { /* shouldn't happen with a properly functioning PKCS #11 module */ PORT_SetError( SEC_ERROR_INCOMPATIBLE_PKCS11 ); goto loser; } /* get the new slot list */ slotIDs = PORT_NewArray(CK_SLOT_ID, count); if (slotIDs == NULL) { goto loser; } crv = PK11_GETTAB(mod)->C_GetSlotList(PR_FALSE, slotIDs, &count); if (crv != CKR_OK) { PORT_SetError(PK11_MapError(crv)); goto loser; } freeRef = PR_FALSE; PZ_Unlock(mod->refLock); mark = PORT_ArenaMark(mod->arena); if (mark == NULL) { goto loser; } newSlots = PORT_ArenaZNewArray(mod->arena,PK11SlotInfo *,count); /* walk down the new slot ID list returned from the module. We keep * the old slots which match a returned ID, and we initialize the new * slots. */ for (i=0; i < count; i++) { PK11SlotInfo *slot = SECMOD_FindSlotByID(mod,slotIDs[i]); if (!slot) { /* we have a new slot create a new slot data structure */ slot = PK11_NewSlotInfo(mod); if (!slot) { goto loser; } PK11_InitSlot(mod, slotIDs[i], slot); STAN_InitTokenForSlotInfo(NULL, slot); } newSlots[i] = slot; } STAN_ResetTokenInterator(NULL); PORT_Free(slotIDs); slotIDs = NULL; PORT_ArenaUnmark(mod->arena, mark); /* until this point we're still using the old slot list. Now we update * module slot list. We update the slots (array) first then the count, * since we've already guarrenteed that count has increased (just in case * someone is looking at the slots field of module without holding the * moduleLock */ SECMOD_GetWriteLock(moduleLock); oldCount =mod->slotCount; oldSlots = mod->slots; mod->slots = newSlots; /* typical arena 'leak'... old mod->slots is * allocated out of the module arena and won't * be freed until the module is freed */ mod->slotCount = count; SECMOD_ReleaseWriteLock(moduleLock); /* free our old references before forgetting about oldSlot*/ for (i=0; i < oldCount; i++) { PK11_FreeSlot(oldSlots[i]); } return SECSuccess; loser: if (freeRef) { PZ_Unlock(mod->refLock); } if (slotIDs) { PORT_Free(slotIDs); } /* free all the slots we allocated. newSlots are part of the * mod arena. NOTE: the newSlots array contain both new and old * slots, but we kept a reference to the old slots when we built the new * array, so we need to free all the slots in newSlots array. */ if (newSlots) { for (i=0; i < count; i++) { if (newSlots[i] == NULL) { break; /* hit the last one */ } PK11_FreeSlot(newSlots[i]); } } /* must come after freeing newSlots */ if (mark) { PORT_ArenaRelease(mod->arena, mark); } return SECFailure; } /* * this handles modules that do not support C_WaitForSlotEvent(). * The internal flags are stored. Note that C_WaitForSlotEvent() does not * have a timeout, so we don't have one for handleWaitForSlotEvent() either. */ PK11SlotInfo * secmod_HandleWaitForSlotEvent(SECMODModule *mod, unsigned long flags, PRIntervalTime latency) { PRBool removableSlotsFound = PR_FALSE; int i; int error = SEC_ERROR_NO_EVENT; if (!moduleLock) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return NULL; } PZ_Lock(mod->refLock); if (mod->evControlMask & SECMOD_END_WAIT) { mod->evControlMask &= ~SECMOD_END_WAIT; PZ_Unlock(mod->refLock); PORT_SetError(SEC_ERROR_NO_EVENT); return NULL; } mod->evControlMask |= SECMOD_WAIT_SIMULATED_EVENT; while (mod->evControlMask & SECMOD_WAIT_SIMULATED_EVENT) { PZ_Unlock(mod->refLock); /* now is a good time to see if new slots have been added */ SECMOD_UpdateSlotList(mod); /* loop through all the slots on a module */ SECMOD_GetReadLock(moduleLock); for (i=0; i < mod->slotCount; i++) { PK11SlotInfo *slot = mod->slots[i]; uint16 series; PRBool present; /* perm modules do not change */ if (slot->isPerm) { continue; } removableSlotsFound = PR_TRUE; /* simulate the PKCS #11 module flags. are the flags different * from the last time we called? */ series = slot->series; present = PK11_IsPresent(slot); if ((slot->flagSeries != series) || (slot->flagState != present)) { slot->flagState = present; slot->flagSeries = series; SECMOD_ReleaseReadLock(moduleLock); PZ_Lock(mod->refLock); mod->evControlMask &= ~SECMOD_END_WAIT; PZ_Unlock(mod->refLock); return PK11_ReferenceSlot(slot); } } SECMOD_ReleaseReadLock(moduleLock); /* if everything was perm modules, don't lock up forever */ if (!removableSlotsFound) { error =SEC_ERROR_NO_SLOT_SELECTED; PZ_Lock(mod->refLock); break; } if (flags & CKF_DONT_BLOCK) { PZ_Lock(mod->refLock); break; } PR_Sleep(latency); PZ_Lock(mod->refLock); } mod->evControlMask &= ~SECMOD_END_WAIT; PZ_Unlock(mod->refLock); PORT_SetError(error); return NULL; } /* * this function waits for a token event on any slot of a given module * This function should not be called from more than one thread of the * same process (though other threads can make other library calls * on this module while this call is blocked). */ PK11SlotInfo * SECMOD_WaitForAnyTokenEvent(SECMODModule *mod, unsigned long flags, PRIntervalTime latency) { CK_SLOT_ID id; CK_RV crv; PK11SlotInfo *slot; if (!pk11_getFinalizeModulesOption() || ((mod->cryptokiVersion.major == 2) && (mod->cryptokiVersion.minor < 1))) { /* if we are sharing the module with other software in our * address space, we can't reliably use C_WaitForSlotEvent(), * and if the module is version 2.0, C_WaitForSlotEvent() doesn't * exist */ return secmod_HandleWaitForSlotEvent(mod, flags, latency); } /* first the the PKCS #11 call */ PZ_Lock(mod->refLock); if (mod->evControlMask & SECMOD_END_WAIT) { goto end_wait; } mod->evControlMask |= SECMOD_WAIT_PKCS11_EVENT; PZ_Unlock(mod->refLock); crv = PK11_GETTAB(mod)->C_WaitForSlotEvent(flags, &id, NULL); PZ_Lock(mod->refLock); mod->evControlMask &= ~SECMOD_WAIT_PKCS11_EVENT; /* if we are in end wait, short circuit now, don't even risk * going into secmod_HandleWaitForSlotEvent */ if (mod->evControlMask & SECMOD_END_WAIT) { goto end_wait; } PZ_Unlock(mod->refLock); if (crv == CKR_FUNCTION_NOT_SUPPORTED) { /* module doesn't support that call, simulate it */ return secmod_HandleWaitForSlotEvent(mod, flags, latency); } if (crv != CKR_OK) { /* we can get this error if finalize was called while we were * still running. This is the only way to force a C_WaitForSlotEvent() * to return in PKCS #11. In this case, just return that there * was no event. */ if (crv == CKR_CRYPTOKI_NOT_INITIALIZED) { PORT_SetError(SEC_ERROR_NO_EVENT); } else { PORT_SetError(PK11_MapError(crv)); } return NULL; } slot = SECMOD_FindSlotByID(mod, id); if (slot == NULL) { /* possibly a new slot that was added? */ SECMOD_UpdateSlotList(mod); slot = SECMOD_FindSlotByID(mod, id); } /* if we are in the delay period for the "isPresent" call, reset * the delay since we know things have probably changed... */ if (slot && slot->nssToken && slot->nssToken->slot) { nssSlot_ResetDelay(slot->nssToken->slot); } return slot; /* must be called with the lock on. */ end_wait: mod->evControlMask &= ~SECMOD_END_WAIT; PZ_Unlock(mod->refLock); PORT_SetError(SEC_ERROR_NO_EVENT); return NULL; } /* * This function "wakes up" WaitForAnyTokenEvent. It's a pretty drastic * function, possibly bringing down the pkcs #11 module in question. This * should be OK because 1) it does reinitialize, and 2) it should only be * called when we are on our way to tear the whole system down anyway. */ SECStatus SECMOD_CancelWait(SECMODModule *mod) { unsigned long controlMask = mod->evControlMask; SECStatus rv = SECSuccess; CK_RV crv; PZ_Lock(mod->refLock); mod->evControlMask |= SECMOD_END_WAIT; controlMask = mod->evControlMask; if (controlMask & SECMOD_WAIT_PKCS11_EVENT) { if (!pk11_getFinalizeModulesOption()) { /* can't get here unless pk11_getFinalizeModulesOption is set */ PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); rv = SECFailure; goto loser; } /* NOTE: this call will drop all transient keys, in progress * operations, and any authentication. This is the only documented * way to get WaitForSlotEvent to return. Also note: for non-thread * safe tokens, we need to hold the module lock, this is not yet at * system shutdown/startup time, so we need to protect these calls */ crv = PK11_GETTAB(mod)->C_Finalize(NULL); /* ok, we slammed the module down, now we need to reinit it in case * we intend to use it again */ if (CKR_OK == crv) { PRBool alreadyLoaded; secmod_ModuleInit(mod, &alreadyLoaded); } else { /* Finalized failed for some reason, notify the application * so maybe it has a prayer of recovering... */ PORT_SetError(PK11_MapError(crv)); rv = SECFailure; } } else if (controlMask & SECMOD_WAIT_SIMULATED_EVENT) { mod->evControlMask &= ~SECMOD_WAIT_SIMULATED_EVENT; /* Simulated events will eventually timeout * and wake up in the loop */ } loser: PZ_Unlock(mod->refLock); return rv; } /* * check to see if the module has removable slots that we may need to * watch for. */ PRBool SECMOD_HasRemovableSlots(SECMODModule *mod) { int i; PRBool ret = PR_FALSE; if (!moduleLock) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return ret; } SECMOD_GetReadLock(moduleLock); for (i=0; i < mod->slotCount; i++) { PK11SlotInfo *slot = mod->slots[i]; /* perm modules are not inserted or removed */ if (slot->isPerm) { continue; } ret = PR_TRUE; break; } SECMOD_ReleaseReadLock(moduleLock); return ret; } /* * helper function to actually create and destroy user defined slots */ static SECStatus secmod_UserDBOp(PK11SlotInfo *slot, CK_OBJECT_CLASS objClass, const char *sendSpec) { CK_OBJECT_HANDLE dummy; CK_ATTRIBUTE template[2] ; CK_ATTRIBUTE *attrs = template; CK_RV crv; PK11_SETATTRS(attrs, CKA_CLASS, &objClass, sizeof(objClass)); attrs++; PK11_SETATTRS(attrs, CKA_NETSCAPE_MODULE_SPEC , (unsigned char *)sendSpec, strlen(sendSpec)+1); attrs++; PORT_Assert(attrs-template <= 2); PK11_EnterSlotMonitor(slot); crv = PK11_CreateNewObject(slot, slot->session, template, attrs-template, PR_FALSE, &dummy); PK11_ExitSlotMonitor(slot); if (crv != CKR_OK) { PORT_SetError(PK11_MapError(crv)); return SECFailure; } return SECMOD_UpdateSlotList(slot->module); } /* * add escapes to protect quote characters... */ static char * nss_addEscape(const char *string, char quote) { char *newString = 0; int escapes = 0, size = 0; const char *src; char *dest; for (src=string; *src ; src++) { if ((*src == quote) || (*src == '\\')) escapes++; size++; } newString = PORT_ZAlloc(escapes+size+1); if (newString == NULL) { return NULL; } for (src=string, dest=newString; *src; src++,dest++) { if ((*src == '\\') || (*src == quote)) { *dest++ = '\\'; } *dest = *src; } return newString; } static char * nss_doubleEscape(const char *string) { char *round1 = NULL; char *retValue = NULL; if (string == NULL) { goto done; } round1 = nss_addEscape(string,'>'); if (round1) { retValue = nss_addEscape(round1,']'); PORT_Free(round1); } done: if (retValue == NULL) { retValue = PORT_Strdup(""); } return retValue; } /* * return true if the selected slot ID is not present or doesn't exist */ static PRBool secmod_SlotIsEmpty(SECMODModule *mod, CK_SLOT_ID slotID) { PK11SlotInfo *slot = SECMOD_LookupSlot(mod->moduleID, slotID); if (slot) { PRBool present = PK11_IsPresent(slot); PK11_FreeSlot(slot); if (present) { return PR_FALSE; } } /* it doesn't exist or isn't present, it's available */ return PR_TRUE; } /* * Find an unused slot id in module. */ static CK_SLOT_ID secmod_FindFreeSlot(SECMODModule *mod) { CK_SLOT_ID i, minSlotID, maxSlotID; /* look for a free slot id on the internal module */ if (mod->internal && mod->isFIPS) { minSlotID = SFTK_MIN_FIPS_USER_SLOT_ID; maxSlotID = SFTK_MAX_FIPS_USER_SLOT_ID; } else { minSlotID = SFTK_MIN_USER_SLOT_ID; maxSlotID = SFTK_MAX_USER_SLOT_ID; } for (i=minSlotID; i < maxSlotID; i++) { if (secmod_SlotIsEmpty(mod,i)) { return i; } } PORT_SetError(SEC_ERROR_NO_SLOT_SELECTED); return (CK_SLOT_ID) -1; } /* * Attempt to open a new slot. * * This works the same os OpenUserDB except it can be called against * any module that understands the softoken protocol for opening new * slots, not just the softoken itself. If the selected module does not * understand the protocol, C_CreateObject will fail with * CKR_INVALID_ATTRIBUTE, and SECMOD_OpenNewSlot will return NULL and set * SEC_ERROR_BAD_DATA. * * NewSlots can be closed with SECMOD_CloseUserDB(); * * Modulespec is module dependent. */ PK11SlotInfo * SECMOD_OpenNewSlot(SECMODModule *mod, const char *moduleSpec) { CK_SLOT_ID slotID = 0; PK11SlotInfo *slot; char *escSpec; char *sendSpec; SECStatus rv; slotID = secmod_FindFreeSlot(mod); if (slotID == (CK_SLOT_ID) -1) { return NULL; } if (mod->slotCount == 0) { return NULL; } /* just grab the first slot in the module, any present slot should work */ slot = PK11_ReferenceSlot(mod->slots[0]); if (slot == NULL) { return NULL; } /* we've found the slot, now build the moduleSpec */ escSpec = nss_doubleEscape(moduleSpec); if (escSpec == NULL) { PK11_FreeSlot(slot); return NULL; } sendSpec = PR_smprintf("tokens=[0x%x=<%s>]", slotID, escSpec); PORT_Free(escSpec); if (sendSpec == NULL) { /* PR_smprintf does not set SEC_ERROR_NO_MEMORY on failure. */ PK11_FreeSlot(slot); PORT_SetError(SEC_ERROR_NO_MEMORY); return NULL; } rv = secmod_UserDBOp(slot, CKO_NETSCAPE_NEWSLOT, sendSpec); PR_smprintf_free(sendSpec); PK11_FreeSlot(slot); if (rv != SECSuccess) { return NULL; } return SECMOD_FindSlotByID(mod, slotID); } /* * Open a new database using the softoken. The caller is responsible for making * sure the module spec is correct and usable. The caller should ask for one * new database per call if the caller wants to get meaningful information * about the new database. * * moduleSpec is the same data that you would pass to softoken at * initialization time under the 'tokens' options. For example, if you were * to specify tokens=<0x4=[configdir='./mybackup' tokenDescription='Backup']> * You would specify "configdir='./mybackup' tokenDescription='Backup'" as your * module spec here. The slot ID will be calculated for you by * SECMOD_OpenUserDB(). * * Typical parameters here are configdir, tokenDescription and flags. * * a Full list is below: * * * configDir - The location of the databases for this token. If configDir is * not specified, and noCertDB and noKeyDB is not specified, the load * will fail. * certPrefix - Cert prefix for this token. * keyPrefix - Prefix for the key database for this token. (if not specified, * certPrefix will be used). * tokenDescription - The label value for this token returned in the * CK_TOKEN_INFO structure with an internationalize string (UTF8). * This value will be truncated at 32 bytes (no NULL, partial UTF8 * characters dropped). You should specify a user friendly name here * as this is the value the token will be refered to in most * application UI's. You should make sure tokenDescription is unique. * slotDescription - The slotDescription value for this token returned * in the CK_SLOT_INFO structure with an internationalize string * (UTF8). This value will be truncated at 64 bytes (no NULL, partial * UTF8 characters dropped). This name will not change after the * database is closed. It should have some number to make this unique. * minPWLen - minimum password length for this token. * flags - comma separated list of flag values, parsed case-insensitive. * Valid flags are: * readOnly - Databases should be opened read only. * noCertDB - Don't try to open a certificate database. * noKeyDB - Don't try to open a key database. * forceOpen - Don't fail to initialize the token if the * databases could not be opened. * passwordRequired - zero length passwords are not acceptable * (valid only if there is a keyDB). * optimizeSpace - allocate smaller hash tables and lock tables. * When this flag is not specified, Softoken will allocate * large tables to prevent lock contention. */ PK11SlotInfo * SECMOD_OpenUserDB(const char *moduleSpec) { SECMODModule *mod; if (moduleSpec == NULL) { return NULL; } /* NOTE: unlike most PK11 function, this does not return a reference * to the module */ mod = SECMOD_GetInternalModule(); if (!mod) { /* shouldn't happen */ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return NULL; } return SECMOD_OpenNewSlot(mod, moduleSpec); } /* * close an already opened user database. NOTE: the database must be * in the internal token, and must be one created with SECMOD_OpenUserDB(). * Once the database is closed, the slot will remain as an empty slot * until it's used again with SECMOD_OpenUserDB() or SECMOD_OpenNewSlot(). */ SECStatus SECMOD_CloseUserDB(PK11SlotInfo *slot) { SECStatus rv; char *sendSpec; sendSpec = PR_smprintf("tokens=[0x%x=<>]", slot->slotID); if (sendSpec == NULL) { /* PR_smprintf does not set no memory error */ PORT_SetError(SEC_ERROR_NO_MEMORY); return SECFailure; } rv = secmod_UserDBOp(slot, CKO_NETSCAPE_DELSLOT, sendSpec); PR_smprintf_free(sendSpec); return rv; }