Bug 272484 Certificate manager crashes [@ _PR_MD_ATOMIC_DECREMENT - PK11_FreeSymKey]

The problem only happens if we try to import a key into a token which then fails
to import. The basic issue was a hack in the pkcs 7 code to support PKCS 12, A
special structure was used to replace the SymKey structure, and the code 'knew'
the special structure existed before it dealt with the symkey. The fix addes a
new capability to symkeys, where applications can attach application specific
data to the key structure. PKCS 12 uses this to attache the PBE information
for CMS. (part 1 of 3)

This patch also improves the key's reuse of sessions, so sessions are not thrashed
when SSL is used with them.

r=wtc
This commit is contained in:
relyea%netscape.com 2005-10-03 21:55:29 +00:00
Родитель d495de8006
Коммит 53f4189369
7 изменённых файлов: 307 добавлений и 110 удалений

Просмотреть файл

@ -2392,3 +2392,41 @@ PK11_ListCertsInSlot(PK11SlotInfo *slot)
return certs;
}
PK11SlotList *
PK11_GetAllSlotsForCert(NSSCertificate *c, void *arg)
{
/* add multiple instances to the cert list */
nssCryptokiObject **ip;
nssCryptokiObject **instances = nssPKIObject_GetInstances(&c->object);
PK11SlotList *slotList;
PRBool found = PR_FALSE;
if (!instances) {
PORT_SetError(SEC_ERROR_NO_TOKEN);
return NULL;
}
slotList = PK11_NewSlotList();
if (!slotList) {
nssCryptokiObjectArray_Destroy(instances);
return NULL;
}
for (ip = instances; *ip; ip++) {
nssCryptokiObject *instance = *ip;
PK11SlotInfo *slot = instance->token->pk11slot;
if (slot) {
PK11_AddSlotToList(slotList, slot);
found = PR_TRUE;
}
}
if (!found) {
PK11_FreeSlotList(slotList);
PORT_SetError(SEC_ERROR_NO_TOKEN);
slotList = NULL;
}
nssCryptokiObjectArray_Destroy(instances);
return slotList;
}

Просмотреть файл

@ -299,6 +299,39 @@ PK11SymKey * PK11_ListFixedKeysInSlot(PK11SlotInfo *slot, char *nickname,
PK11SymKey *PK11_GetNextSymKey(PK11SymKey *symKey);
CK_KEY_TYPE PK11_GetSymKeyType(PK11SymKey *key);
/*
* PK11_SetSymKeyUserData
* sets generic user data on keys (usually a pointer to a data structure)
* that can later be retrieved by PK11_GetSymKeyUserData().
* symKey - key where data will be set.
* data - data to be set.
* freefunc - function used to free the data.
* Setting user data on symKeys with existing user data already set will cause
* the existing user data to be freed before the new user data is set.
* Freeing user data is done by calling the user specified freefunc.
* If freefunc is NULL, the user data is assumed to be global or static an
* not freed. Passing NULL for user data to PK11_SetSymKeyUserData has the
* effect of freeing any existing user data, and clearing the user data
* pointer. If user data exists when the symKey is finally freed, that
* data will be freed with freefunc.
*
* Applications should only use this function on keys which the application
* has created directly, as there is only one user data value per key.
*/
void PK11_SetSymKeyUserData(PK11SymKey *symKey, void *data,
PK11FreeDataFunc freefunc);
/* PK11_GetSymKeyUserData
* retrieves generic user data which was set on a key by
* PK11_SetSymKeyUserData.
* symKey - key with data to be fetched
*
* If no data exists, or the data has been cleared, PK11_GetSymKeyUserData
* will return NULL. Returned data is still owned and managed by the SymKey,
* the caller should not free the data.
*
*/
void *PK11_GetSymKeyUserData(PK11SymKey *symKey);
SECStatus PK11_PubWrapSymKey(CK_MECHANISM_TYPE type, SECKEYPublicKey *pubKey,
PK11SymKey *symKey, SECItem *wrappedKey);
SECStatus PK11_WrapSymKey(CK_MECHANISM_TYPE type, SECItem *params,

Просмотреть файл

@ -153,46 +153,52 @@ pk11sdr_Shutdown(void)
SECStatus
PK11SDR_Encrypt(SECItem *keyid, SECItem *data, SECItem *result, void *cx)
{
SECStatus rv = SECSuccess;
PK11SlotInfo *slot = 0;
PK11SymKey *key = 0;
SECItem *params = 0;
PK11Context *ctx = 0;
CK_MECHANISM_TYPE type;
SDRResult sdrResult;
SECItem paddedData;
SECItem *pKeyID;
PLArenaPool *arena = 0;
SECStatus rv = SECSuccess;
PK11SlotInfo *slot = 0;
PK11SymKey *key = 0;
SECItem *params = 0;
PK11Context *ctx = 0;
CK_MECHANISM_TYPE type;
SDRResult sdrResult;
SECItem paddedData;
SECItem *pKeyID;
PLArenaPool *arena = 0;
/* Initialize */
paddedData.len = 0;
paddedData.data = 0;
/* Initialize */
paddedData.len = 0;
paddedData.data = 0;
arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
if (!arena) {
rv = SECFailure; goto loser; }
arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
if (!arena) { rv = SECFailure; goto loser; }
/* 1. Locate the requested keyid, or the default key (which has a keyid)
* 2. Create an encryption context
* 3. Encrypt
* 4. Encode the results (using ASN.1)
*/
/* 1. Locate the requested keyid, or the default key (which has a keyid)
* 2. Create an encryption context
* 3. Encrypt
* 4. Encode the results (using ASN.1)
*/
slot = PK11_GetInternalKeySlot();
if (!slot) {
rv = SECFailure;
goto loser;
}
slot = PK11_GetInternalKeySlot();
if (!slot) { rv = SECFailure; goto loser; }
/* Use triple-DES */
type = CKM_DES3_CBC;
/* Use triple-DES */
type = CKM_DES3_CBC;
/*
* Login to the internal token before we look for the key, otherwise we
* won't find it.
*/
rv = PK11_Authenticate(slot, PR_TRUE, cx);
if (rv != SECSuccess) {
goto loser;
}
/*
* Login to the internal token before we look for the key, otherwise we
* won't find it.
*/
rv = PK11_Authenticate(slot, PR_TRUE, cx);
if (rv != SECSuccess) goto loser;
/* Find the key to use */
pKeyID = keyid;
if (pKeyID->len == 0) {
/* Find the key to use */
pKeyID = keyid;
if (pKeyID->len == 0) {
pKeyID = &keyIDItem; /* Use default value */
/* put in a course lock to prevent a race between not finding the
@ -200,51 +206,68 @@ PK11SDR_Encrypt(SECItem *keyid, SECItem *data, SECItem *result, void *cx)
*/
if (pk11sdrLock) PR_Lock(pk11sdrLock);
/* Try to find the key */
key = PK11_FindFixedKey(slot, type, pKeyID, cx);
/* If the default key doesn't exist yet, try to create it */
if (!key) key = PK11_GenDES3TokenKey(slot, pKeyID, cx);
if (pk11sdrLock) PR_Unlock(pk11sdrLock);
} else {
if (!key) {
key = PK11_GenDES3TokenKey(slot, pKeyID, cx);
}
if (pk11sdrLock) {
PR_Unlock(pk11sdrLock);
}
} else {
key = PK11_FindFixedKey(slot, type, pKeyID, cx);
}
}
if (!key) { rv = SECFailure; goto loser; }
if (!key) {
rv = SECFailure;
goto loser;
}
params = PK11_GenerateNewParam(type, key);
if (!params) { rv = SECFailure; goto loser; }
params = PK11_GenerateNewParam(type, key);
if (!params) { rv = SECFailure; goto loser; }
ctx = PK11_CreateContextBySymKey(type, CKA_ENCRYPT, key, params);
if (!ctx) { rv = SECFailure; goto loser; }
ctx = PK11_CreateContextBySymKey(type, CKA_ENCRYPT, key, params);
if (!ctx) { rv = SECFailure; goto loser; }
rv = padBlock(data, PK11_GetBlockSize(type, 0), &paddedData);
if (rv != SECSuccess) goto loser;
rv = padBlock(data, PK11_GetBlockSize(type, 0), &paddedData);
if (rv != SECSuccess) {
goto loser;
}
sdrResult.data.len = paddedData.len;
sdrResult.data.data = (unsigned char *)PORT_ArenaAlloc(arena, sdrResult.data.len);
sdrResult.data.len = paddedData.len;
sdrResult.data.data = (unsigned char *)
PORT_ArenaAlloc(arena, sdrResult.data.len);
rv = PK11_CipherOp(ctx, sdrResult.data.data, (int*)&sdrResult.data.len, sdrResult.data.len,
paddedData.data, paddedData.len);
if (rv != SECSuccess) goto loser;
rv = PK11_CipherOp(ctx, sdrResult.data.data,
(int*)&sdrResult.data.len, sdrResult.data.len,
paddedData.data, paddedData.len);
if (rv != SECSuccess) {
goto loser;
}
PK11_Finalize(ctx);
PK11_Finalize(ctx);
sdrResult.keyid = *pKeyID;
sdrResult.keyid = *pKeyID;
rv = PK11_ParamToAlgid(SEC_OID_DES_EDE3_CBC, params, arena, &sdrResult.alg);
if (rv != SECSuccess) goto loser;
rv = PK11_ParamToAlgid(SEC_OID_DES_EDE3_CBC, params, arena, &sdrResult.alg);
if (rv != SECSuccess) {
goto loser;
}
if (!SEC_ASN1EncodeItem(0, result, &sdrResult, template)) { rv = SECFailure; goto loser; }
if (!SEC_ASN1EncodeItem(0, result, &sdrResult, template)) {
rv = SECFailure;
goto loser;
}
loser:
SECITEM_ZfreeItem(&paddedData, PR_FALSE);
if (arena) PORT_FreeArena(arena, PR_TRUE);
if (ctx) PK11_DestroyContext(ctx, PR_TRUE);
if (params) SECITEM_ZfreeItem(params, PR_TRUE);
if (key) PK11_FreeSymKey(key);
if (slot) PK11_FreeSlot(slot);
SECITEM_ZfreeItem(&paddedData, PR_FALSE);
if (arena) PORT_FreeArena(arena, PR_TRUE);
if (ctx) PK11_DestroyContext(ctx, PR_TRUE);
if (params) SECITEM_ZfreeItem(params, PR_TRUE);
if (key) PK11_FreeSymKey(key);
if (slot) PK11_FreeSlot(slot);
return rv;
}

Просмотреть файл

@ -69,39 +69,74 @@ pk11_ExitKeyMonitor(PK11SymKey *symKey) {
PK11_ExitSlotMonitor(symKey->slot);
}
/*
* pk11_getKeyFromList returns a symKey that has a session (if needSession
* was specified), or explicitly does not have a session (if needSession
* was not specified).
*/
static PK11SymKey *
pk11_getKeyFromList(PK11SlotInfo *slot) {
pk11_getKeyFromList(PK11SlotInfo *slot, PRBool needSession) {
PK11SymKey *symKey = NULL;
PZ_Lock(slot->freeListLock);
if (slot->freeSymKeysHead) {
symKey = slot->freeSymKeysHead;
slot->freeSymKeysHead = symKey->next;
slot->keyCount--;
/* own session list are symkeys with sessions that the symkey owns.
* 'most' symkeys will own their own session. */
if (needSession) {
if (slot->freeSymKeysWithSessionHead) {
symKey = slot->freeSymKeysWithSessionHead;
slot->freeSymKeysWithSessionHead = symKey->next;
slot->keyCount--;
}
}
/* if we don't need a symkey with its own session, or we couldn't find
* one on the owner list, get one from the non-owner free list. */
if (!symKey) {
if (slot->freeSymKeysHead) {
symKey = slot->freeSymKeysHead;
slot->freeSymKeysHead = symKey->next;
slot->keyCount--;
}
}
PZ_Unlock(slot->freeListLock);
if (symKey) {
symKey->next = NULL;
if ((symKey->series != slot->series) || (!symKey->sessionOwner))
symKey->session = pk11_GetNewSession(slot,&symKey->sessionOwner);
if (!needSession) {
return symKey;
}
/* if we are getting an owner key, make sure we have a valid session.
* session could be invalid if the token has been removed or because
* we got it from the non-owner free list */
if ((symKey->series != slot->series) ||
(symKey->session == CK_INVALID_SESSION)) {
symKey->session = pk11_GetNewSession(slot, &symKey->sessionOwner);
}
PORT_Assert(symKey->session != CK_INVALID_SESSION);
if (symKey->session != CK_INVALID_SESSION)
return symKey;
PK11_FreeSymKey(symKey);
PK11_FreeSymKey(symKey);
/* if we are here, we need a session, but couldn't get one, it's
* unlikely we pk11_GetNewSession will succeed if we call it a second
* time. */
return NULL;
}
symKey = PORT_New(PK11SymKey);
if (symKey == NULL) {
return NULL;
}
symKey->next = NULL;
symKey->session = pk11_GetNewSession(slot,&symKey->sessionOwner);
PORT_Assert(symKey->session != CK_INVALID_SESSION);
if (symKey->session != CK_INVALID_SESSION)
return symKey;
PK11_FreeSymKey(symKey);
return NULL;
if (needSession) {
symKey->session = pk11_GetNewSession(slot,&symKey->sessionOwner);
PORT_Assert(symKey->session != CK_INVALID_SESSION);
if (symKey->session == CK_INVALID_SESSION) {
PK11_FreeSymKey(symKey);
symKey = NULL;
}
} else {
symKey->session = CK_INVALID_SESSION;
}
return symKey;
}
/* Caller MUST hold slot->freeListLock (or ref count == 0?) !! */
@ -110,14 +145,18 @@ PK11_CleanKeyList(PK11SlotInfo *slot)
{
PK11SymKey *symKey = NULL;
while (slot->freeSymKeysWithSessionHead) {
symKey = slot->freeSymKeysWithSessionHead;
slot->freeSymKeysWithSessionHead = symKey->next;
pk11_CloseSession(slot, symKey->session, symKey->sessionOwner);
PORT_Free(symKey);
}
while (slot->freeSymKeysHead) {
symKey = slot->freeSymKeysHead;
slot->freeSymKeysHead = symKey->next;
/* XXX Perhaps this should be:
** if (symKey->sessionOwner) */
pk11_CloseSession(slot, symKey->session,symKey->sessionOwner);
pk11_CloseSession(slot, symKey->session, symKey->sessionOwner);
PORT_Free(symKey);
};
}
return;
}
@ -125,19 +164,25 @@ PK11_CleanKeyList(PK11SlotInfo *slot)
* create a symetric key:
* Slot is the slot to create the key in.
* type is the mechanism type
* owner is does this symKey structure own it's object handle (rare
* that this is false).
* needSession means the returned symKey will return with a valid session
* allocated already.
*/
static PK11SymKey *
pk11_CreateSymKey(PK11SlotInfo *slot, CK_MECHANISM_TYPE type, PRBool owner,
void *wincx)
pk11_CreateSymKey(PK11SlotInfo *slot, CK_MECHANISM_TYPE type,
PRBool owner, PRBool needSession, void *wincx)
{
PK11SymKey *symKey = pk11_getKeyFromList(slot);
PK11SymKey *symKey = pk11_getKeyFromList(slot, needSession);
if (symKey == NULL) {
return NULL;
}
if (symKey->session == CK_INVALID_SESSION) {
/* if needSession was specified, make sure we have a valid session.
* callers which specify needSession as false should do their own
* check of the session before returning the symKey */
if (needSession && symKey->session == CK_INVALID_SESSION) {
PK11_FreeSymKey(symKey);
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
return NULL;
@ -156,6 +201,8 @@ pk11_CreateSymKey(PK11SlotInfo *slot, CK_MECHANISM_TYPE type, PRBool owner,
symKey->refCount = 1;
symKey->origin = PK11_OriginNULL;
symKey->parent = NULL;
symKey->freeFunc = NULL;
symKey->userData = NULL;
PK11_ReferenceSlot(slot);
return symKey;
}
@ -183,11 +230,33 @@ PK11_FreeSymKey(PK11SymKey *symKey)
PORT_Memset(symKey->data.data, 0, symKey->data.len);
PORT_Free(symKey->data.data);
}
/* free any existing data */
if (symKey->userData && symKey->freeFunc) {
(*symKey->freeFunc)(symKey->userData);
}
slot = symKey->slot;
PZ_Lock(slot->freeListLock);
if (slot->keyCount < slot->maxKeyCount) {
symKey->next = slot->freeSymKeysHead;
slot->freeSymKeysHead = symKey;
/*
* freeSymkeysWithSessionHead contain a list of reusable
* SymKey structures with valid sessions.
* sessionOwner must be true.
* session must be valid.
* freeSymKeysHead contain a list of SymKey structures without
* valid session.
* session must be CK_INVALID_SESSION.
* though sessionOwner is false, callers should not depend on
* this fact.
*/
if (symKey->sessionOwner) {
PORT_Assert (symKey->session != CK_INVALID_SESSION);
symKey->next = slot->freeSymKeysWithSessionHead;
slot->freeSymKeysWithSessionHead = symKey;
} else {
symKey->session = CK_INVALID_SESSION;
symKey->next = slot->freeSymKeysHead;
slot->freeSymKeysHead = symKey;
}
slot->keyCount++;
symKey->slot = NULL;
freeit = PR_FALSE;
@ -254,6 +323,25 @@ PK11_SetSymKeyNickname(PK11SymKey *symKey, const char *nickname)
return PK11_SetObjectNickname(symKey->slot,symKey->objectID,nickname);
}
void *
PK11_GetSymKeyUserData(PK11SymKey *symKey)
{
return symKey->userData;
}
void
PK11_SetSymKeyUserData(PK11SymKey *symKey, void *userData,
PK11FreeDataFunc freeFunc)
{
/* free any existing data */
if (symKey->userData && symKey->freeFunc) {
(*symKey->freeFunc)(symKey->userData);
}
symKey->userData = userData;
symKey->freeFunc = freeFunc;
return;
}
/*
* turn key handle into an appropriate key object
*/
@ -262,12 +350,13 @@ PK11_SymKeyFromHandle(PK11SlotInfo *slot, PK11SymKey *parent, PK11Origin origin,
CK_MECHANISM_TYPE type, CK_OBJECT_HANDLE keyID, PRBool owner, void *wincx)
{
PK11SymKey *symKey;
PRBool needSession = !(owner && parent);
if (keyID == CK_INVALID_HANDLE) {
return NULL;
}
symKey = pk11_CreateSymKey(slot,type,owner,wincx);
symKey = pk11_CreateSymKey(slot, type, owner, needSession, wincx);
if (symKey == NULL) {
return NULL;
}
@ -279,11 +368,19 @@ PK11_SymKeyFromHandle(PK11SlotInfo *slot, PK11SymKey *parent, PK11Origin origin,
/* This is only used by SSL. What we really want here is a session
* structure with a ref count so the session goes away only after all the
* keys do. */
if (owner && parent) {
pk11_CloseSession(symKey->slot, symKey->session,symKey->sessionOwner);
if (!needSession) {
symKey->sessionOwner = PR_FALSE;
symKey->session = parent->session;
symKey->parent = PK11_ReferenceSymKey(parent);
/* This is the only case where pk11_CreateSymKey does not explicitly
* check symKey->session. We need to assert here to make sure.
* the session isn't invalid. */
PORT_Assert(parent->session != CK_INVALID_SESSION);
if (parent->session == CK_INVALID_SESSION) {
PK11_FreeSymKey(symKey);
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
return NULL;
}
}
return symKey;
@ -344,7 +441,7 @@ pk11_ImportSymKeyWithTempl(PK11SlotInfo *slot, CK_MECHANISM_TYPE type,
PK11SymKey * symKey;
SECStatus rv;
symKey = pk11_CreateSymKey(slot,type,!isToken,wincx);
symKey = pk11_CreateSymKey(slot, type, !isToken, PR_TRUE, wincx);
if (symKey == NULL) {
return NULL;
}
@ -843,11 +940,11 @@ PK11_TokenKeyGenWithFlags(PK11SlotInfo *slot, CK_MECHANISM_TYPE type,
return NULL;
}
symKey = pk11_CreateSymKey(bestSlot, type, !isToken, wincx);
symKey = pk11_CreateSymKey(bestSlot, type, !isToken, PR_TRUE, wincx);
PK11_FreeSlot(bestSlot);
} else {
symKey = pk11_CreateSymKey(slot, type, !isToken, wincx);
symKey = pk11_CreateSymKey(slot, type, !isToken, PR_TRUE, wincx);
}
if (symKey == NULL) return NULL;
@ -1327,7 +1424,7 @@ pk11_DeriveWithTemplate( PK11SymKey *baseKey, CK_MECHANISM_TYPE derive,
/* get our key Structure */
symKey = pk11_CreateSymKey(slot,target,!isPerm,baseKey->cx);
symKey = pk11_CreateSymKey(slot, target, !isPerm, PR_TRUE, baseKey->cx);
if (symKey == NULL) {
return NULL;
}
@ -1400,7 +1497,7 @@ PK11_PubDerive(SECKEYPrivateKey *privKey, SECKEYPublicKey *pubKey,
}
/* get our key Structure */
symKey = pk11_CreateSymKey(slot,target,PR_TRUE,wincx);
symKey = pk11_CreateSymKey(slot, target, PR_TRUE, PR_TRUE, wincx);
if (symKey == NULL) {
return NULL;
}
@ -1562,7 +1659,7 @@ PK11_PubDeriveWithKDF(SECKEYPrivateKey *privKey, SECKEYPublicKey *pubKey,
CK_RV crv;
/* get our key Structure */
symKey = pk11_CreateSymKey(slot,target,PR_TRUE,wincx);
symKey = pk11_CreateSymKey(slot, target, PR_TRUE, PR_TRUE, wincx);
if (symKey == NULL) {
return NULL;
}
@ -1846,7 +1943,7 @@ pk11_AnyUnwrapKey(PK11SlotInfo *slot, CK_OBJECT_HANDLE wrappingKey,
}
/* get our key Structure */
symKey = pk11_CreateSymKey(slot,target,!isPerm,wincx);
symKey = pk11_CreateSymKey(slot, target, !isPerm, PR_TRUE, wincx);
if (symKey == NULL) {
if (param_free) SECITEM_FreeItem(param_free,PR_TRUE);
return NULL;

Просмотреть файл

@ -364,6 +364,7 @@ PK11_NewSlotInfo(SECMODModule *mod)
PORT_Free(slot);
return slot;
}
slot->freeSymKeysWithSessionHead = NULL;
slot->freeSymKeysHead = NULL;
slot->keyCount = 0;
slot->maxKeyCount = 0;

Просмотреть файл

@ -66,6 +66,7 @@ typedef struct PK11RSAGenParamsStr PK11RSAGenParams;
typedef unsigned long SECMODModuleID;
typedef struct PK11DefaultArrayEntryStr PK11DefaultArrayEntry;
typedef struct PK11GenericObjectStr PK11GenericObject;
typedef void (*PK11FreeDataFunc)(void *);
struct SECMODModuleStr {
PRArenaPool *arena;

Просмотреть файл

@ -97,6 +97,7 @@ struct PK11SlotInfoStr {
* still in use */
PRInt32 refCount; /* to be in/decremented by atomic calls ONLY! */
PZLock *freeListLock;
PK11SymKey *freeSymKeysWithSessionHead;
PK11SymKey *freeSymKeysHead;
int keyCount;
int maxKeyCount;
@ -153,19 +154,22 @@ struct PK11SymKeyStr {
CK_OBJECT_HANDLE objectID; /* object id of this key in the slot */
PK11SlotInfo *slot; /* Slot this key is loaded into */
void *cx; /* window context in case we need to loggin */
PK11SymKey *next;
PRBool owner;
SECItem data; /* raw key data if available */
PK11SymKey *next;
PRBool owner;
SECItem data; /* raw key data if available */
CK_SESSION_HANDLE session;
PRBool sessionOwner;
PRInt32 refCount; /* number of references to this key */
int size; /* key size in bytes */
PK11Origin origin; /* where this key came from
(see def in secmodt.h) */
PK11SymKey *parent;
uint16 series; /* break up the slot info into various groups of
* inserted tokens so that keys and certs can be
* invalidated */
PRBool sessionOwner;
PRInt32 refCount; /* number of references to this key */
int size; /* key size in bytes */
PK11Origin origin; /* where this key came from
* (see def in secmodt.h) */
PK11SymKey *parent; /* potential owner key of the session */
uint16 series; /* break up the slot info into various groups
* of inserted tokens so that keys and certs
* can be invalidated */
void *userData; /* random data the application can attach to
* this key */
PK11FreeDataFunc freeFunc; /* function to free the user data */
};