2008-06-06 05:40:11 -07:00
|
|
|
/*
|
|
|
|
* Merge the source token into the target token.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "secmod.h"
|
|
|
|
#include "secmodi.h"
|
|
|
|
#include "secmodti.h"
|
|
|
|
#include "pk11pub.h"
|
|
|
|
#include "pk11priv.h"
|
|
|
|
#include "pkcs11.h"
|
|
|
|
#include "seccomon.h"
|
|
|
|
#include "secerr.h"
|
|
|
|
#include "keyhi.h"
|
|
|
|
#include "hasht.h"
|
|
|
|
#include "cert.h"
|
2009-01-20 19:43:31 -08:00
|
|
|
#include "certdb.h"
|
2008-06-06 05:40:11 -07:00
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* short utilities to aid in the merge
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* write a bunch of attributes out to an existing object.
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_setAttributes(PK11SlotInfo *slot, CK_OBJECT_HANDLE id,
|
|
|
|
CK_ATTRIBUTE *setTemplate, CK_ULONG setTemplCount)
|
|
|
|
{
|
|
|
|
CK_RV crv;
|
|
|
|
CK_SESSION_HANDLE rwsession;
|
|
|
|
|
|
|
|
rwsession = PK11_GetRWSession(slot);
|
|
|
|
if (rwsession == CK_INVALID_SESSION) {
|
|
|
|
PORT_SetError(SEC_ERROR_BAD_DATA);
|
|
|
|
return SECFailure;
|
|
|
|
}
|
|
|
|
crv = PK11_GETTAB(slot)->C_SetAttributeValue(rwsession, id,
|
|
|
|
setTemplate, setTemplCount);
|
|
|
|
PK11_RestoreROSession(slot, rwsession);
|
|
|
|
if (crv != CKR_OK) {
|
|
|
|
PORT_SetError(PK11_MapError(crv));
|
|
|
|
return SECFailure;
|
|
|
|
}
|
|
|
|
return SECSuccess;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* copy a template of attributes from a source object to a target object.
|
|
|
|
* if target object is not given, create it.
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_copyAttributes(PRArenaPool *arena,
|
|
|
|
PK11SlotInfo *targetSlot, CK_OBJECT_HANDLE targetID,
|
|
|
|
PK11SlotInfo *sourceSlot, CK_OBJECT_HANDLE sourceID,
|
|
|
|
CK_ATTRIBUTE *copyTemplate, CK_ULONG copyTemplateCount)
|
|
|
|
{
|
|
|
|
SECStatus rv = PK11_GetAttributes(arena, sourceSlot, sourceID,
|
|
|
|
copyTemplate, copyTemplateCount);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
if (targetID == CK_INVALID_HANDLE) {
|
|
|
|
/* we need to create the object */
|
|
|
|
rv = PK11_CreateNewObject(targetSlot, CK_INVALID_SESSION,
|
|
|
|
copyTemplate, copyTemplateCount, PR_TRUE, &targetID);
|
|
|
|
} else {
|
|
|
|
/* update the existing object with the new attributes */
|
|
|
|
rv = pk11_setAttributes(targetSlot, targetID,
|
|
|
|
copyTemplate, copyTemplateCount);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* look for a matching object across tokens.
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_matchAcrossTokens(PRArenaPool *arena, PK11SlotInfo *targetSlot,
|
|
|
|
PK11SlotInfo *sourceSlot,
|
|
|
|
CK_ATTRIBUTE *template, CK_ULONG tsize,
|
|
|
|
CK_OBJECT_HANDLE id, CK_OBJECT_HANDLE *peer)
|
|
|
|
{
|
|
|
|
|
|
|
|
CK_RV crv;
|
|
|
|
*peer = CK_INVALID_HANDLE;
|
|
|
|
|
|
|
|
crv = PK11_GetAttributes(arena, sourceSlot, id, template, tsize);
|
|
|
|
if (crv != CKR_OK) {
|
|
|
|
PORT_SetError( PK11_MapError(crv) );
|
|
|
|
goto loser;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (template[0].ulValueLen == -1) {
|
|
|
|
crv = CKR_ATTRIBUTE_TYPE_INVALID;
|
|
|
|
PORT_SetError( PK11_MapError(crv) );
|
|
|
|
goto loser;
|
|
|
|
}
|
|
|
|
|
|
|
|
*peer = pk11_FindObjectByTemplate(targetSlot, template, tsize);
|
|
|
|
return SECSuccess;
|
|
|
|
|
|
|
|
loser:
|
|
|
|
return SECFailure;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Encrypt using key and parameters
|
|
|
|
*/
|
|
|
|
SECStatus
|
|
|
|
pk11_encrypt(PK11SymKey *symKey, CK_MECHANISM_TYPE mechType, SECItem *param,
|
|
|
|
SECItem *input, SECItem **output)
|
|
|
|
{
|
|
|
|
PK11Context *ctxt = NULL;
|
|
|
|
SECStatus rv = SECSuccess;
|
|
|
|
|
|
|
|
if (*output) {
|
|
|
|
SECITEM_FreeItem(*output,PR_TRUE);
|
|
|
|
}
|
|
|
|
*output = SECITEM_AllocItem(NULL, NULL, input->len+20 /*slop*/);
|
|
|
|
if (!*output) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctxt = PK11_CreateContextBySymKey(mechType, CKA_ENCRYPT, symKey, param);
|
|
|
|
if (ctxt == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
rv = PK11_CipherOp(ctxt, (*output)->data,
|
|
|
|
(int *)&((*output)->len),
|
|
|
|
(*output)->len, input->data, input->len);
|
|
|
|
|
|
|
|
done:
|
|
|
|
if (ctxt) {
|
|
|
|
PK11_Finalize(ctxt);
|
|
|
|
PK11_DestroyContext(ctxt,PR_TRUE);
|
|
|
|
}
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
if (*output) {
|
|
|
|
SECITEM_FreeItem(*output, PR_TRUE);
|
|
|
|
*output = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* Private Keys
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Fetch the key usage based on the pkcs #11 flags
|
|
|
|
*/
|
|
|
|
unsigned int
|
|
|
|
pk11_getPrivateKeyUsage(PK11SlotInfo *slot, CK_OBJECT_HANDLE id)
|
|
|
|
{
|
|
|
|
unsigned int usage = 0;
|
|
|
|
|
|
|
|
if ((PK11_HasAttributeSet(slot, id, CKA_UNWRAP) ||
|
|
|
|
PK11_HasAttributeSet(slot,id, CKA_DECRYPT))) {
|
|
|
|
usage |= KU_KEY_ENCIPHERMENT;
|
|
|
|
}
|
|
|
|
if (PK11_HasAttributeSet(slot, id, CKA_DERIVE)) {
|
|
|
|
usage |= KU_KEY_AGREEMENT;
|
|
|
|
}
|
|
|
|
if ((PK11_HasAttributeSet(slot, id, CKA_SIGN_RECOVER) ||
|
|
|
|
PK11_HasAttributeSet(slot, id, CKA_SIGN))) {
|
|
|
|
usage |= KU_DIGITAL_SIGNATURE;
|
|
|
|
}
|
|
|
|
return usage;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* merge a private key,
|
|
|
|
*
|
|
|
|
* Private keys are merged using PBE wrapped keys with a random
|
|
|
|
* value as the 'password'. Once the base key is moved, The remaining
|
|
|
|
* attributes (SUBJECT) is copied.
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_mergePrivateKey(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
|
|
|
|
CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
|
|
|
|
{
|
|
|
|
SECKEYPrivateKey *sourceKey = NULL;
|
|
|
|
CK_OBJECT_HANDLE targetKeyID;
|
|
|
|
SECKEYEncryptedPrivateKeyInfo *epki = NULL;
|
|
|
|
char *nickname = NULL;
|
|
|
|
SECItem nickItem;
|
|
|
|
SECItem pwitem;
|
|
|
|
SECItem publicValue;
|
|
|
|
PRArenaPool *arena = NULL;
|
|
|
|
SECStatus rv = SECSuccess;
|
|
|
|
unsigned int keyUsage;
|
|
|
|
unsigned char randomData[SHA1_LENGTH];
|
|
|
|
SECOidTag algTag = SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC;
|
|
|
|
CK_ATTRIBUTE privTemplate[] = {
|
|
|
|
{ CKA_ID, NULL, 0 },
|
|
|
|
{ CKA_CLASS, NULL, 0 }
|
|
|
|
};
|
|
|
|
CK_ULONG privTemplateCount = sizeof(privTemplate)/sizeof(privTemplate[0]);
|
|
|
|
CK_ATTRIBUTE privCopyTemplate[] = {
|
|
|
|
{ CKA_SUBJECT, NULL, 0 }
|
|
|
|
};
|
|
|
|
CK_ULONG privCopyTemplateCount =
|
|
|
|
sizeof(privCopyTemplate)/sizeof(privCopyTemplate[0]);
|
|
|
|
|
|
|
|
arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if (arena == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check to see if the key is already in the target slot */
|
|
|
|
rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot, privTemplate,
|
|
|
|
privTemplateCount, id, &targetKeyID);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (targetKeyID != CK_INVALID_HANDLE) {
|
|
|
|
/* match found, not an error ... */
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get an NSS representation of our source key */
|
|
|
|
sourceKey = PK11_MakePrivKey(sourceSlot, nullKey, PR_FALSE,
|
|
|
|
id, sourcePwArg);
|
|
|
|
if (sourceKey == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Load the private key */
|
|
|
|
/* generate a random pwitem */
|
|
|
|
rv = PK11_GenerateRandom(randomData, sizeof(randomData));
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
pwitem.data = randomData;
|
|
|
|
pwitem.len = sizeof(randomData);
|
|
|
|
/* fetch the private key encrypted */
|
|
|
|
epki = PK11_ExportEncryptedPrivKeyInfo(sourceSlot, algTag, &pwitem,
|
|
|
|
sourceKey, 1, sourcePwArg);
|
|
|
|
if (epki == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
nickname = PK11_GetObjectNickname(sourceSlot, id);
|
|
|
|
/* NULL nickanme is fine (in fact is often normal) */
|
|
|
|
if (nickname) {
|
|
|
|
nickItem.data = (unsigned char *)nickname;
|
|
|
|
nickItem.len = PORT_Strlen(nickname);
|
|
|
|
}
|
|
|
|
keyUsage = pk11_getPrivateKeyUsage(sourceSlot, id);
|
|
|
|
/* pass in the CKA_ID */
|
|
|
|
publicValue.data = privTemplate[0].pValue;
|
|
|
|
publicValue.len = privTemplate[0].ulValueLen;
|
|
|
|
rv = PK11_ImportEncryptedPrivateKeyInfo(targetSlot, epki, &pwitem,
|
|
|
|
nickname? &nickItem : NULL , &publicValue,
|
|
|
|
PR_TRUE, PR_TRUE, sourceKey->keyType, keyUsage,
|
|
|
|
targetPwArg);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* make sure it made it */
|
|
|
|
rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot, privTemplate,
|
|
|
|
privTemplateCount, id, &targetKeyID);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (targetKeyID == CK_INVALID_HANDLE) {
|
|
|
|
/* this time the key should exist */
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* fill in remaining attributes */
|
|
|
|
rv = pk11_copyAttributes(arena, targetSlot, targetKeyID, sourceSlot, id,
|
|
|
|
privCopyTemplate, privCopyTemplateCount);
|
|
|
|
done:
|
|
|
|
/* make sure the 'key' is cleared */
|
|
|
|
PORT_Memset(randomData, 0, sizeof(randomData));
|
|
|
|
if (nickname) {
|
|
|
|
PORT_Free(nickname);
|
|
|
|
}
|
|
|
|
if (sourceKey) {
|
|
|
|
SECKEY_DestroyPrivateKey(sourceKey);
|
|
|
|
}
|
|
|
|
if (epki) {
|
|
|
|
SECKEY_DestroyEncryptedPrivateKeyInfo(epki, PR_TRUE);
|
|
|
|
}
|
|
|
|
if (arena) {
|
|
|
|
PORT_FreeArena(arena,PR_FALSE);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* Secret Keys
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* we need to find a unique CKA_ID.
|
|
|
|
* The basic idea is to just increment the lowest byte.
|
|
|
|
* This code also handles the following corner cases:
|
|
|
|
* 1) the single byte overflows. On overflow we increment the next byte up
|
|
|
|
* and so forth until we have overflowed the entire CKA_ID.
|
|
|
|
* 2) If we overflow the entire CKA_ID we expand it by one byte.
|
|
|
|
* 3) the CKA_ID is non-existant, we create a new one with one byte.
|
|
|
|
* This means no matter what CKA_ID is passed, the result of this function
|
|
|
|
* is always a new CKA_ID, and this function will never return the same
|
|
|
|
* CKA_ID the it has returned in the passed.
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_incrementID(PRArenaPool *arena, CK_ATTRIBUTE *ptemplate)
|
|
|
|
{
|
|
|
|
unsigned char *buf = ptemplate->pValue;
|
|
|
|
CK_ULONG len = ptemplate->ulValueLen;
|
|
|
|
|
|
|
|
if (buf == NULL || len == (CK_ULONG)-1) {
|
|
|
|
/* we have no valid CKAID, we'll create a basic one byte CKA_ID below */
|
|
|
|
len = 0;
|
|
|
|
} else {
|
|
|
|
CK_ULONG i;
|
|
|
|
|
|
|
|
/* walk from the back to front, incrementing
|
|
|
|
* the CKA_ID until we no longer have a carry,
|
|
|
|
* or have hit the front of the id. */
|
|
|
|
for (i=len; i != 0; i--) {
|
|
|
|
buf[i-1]++;
|
|
|
|
if (buf[i-1] != 0) {
|
|
|
|
/* no more carries, the increment is complete */
|
|
|
|
return SECSuccess;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* we've now overflowed, fall through and expand the CKA_ID by
|
|
|
|
* one byte */
|
|
|
|
}
|
|
|
|
/* if we are here we've run the counter to zero (indicating an overflow).
|
|
|
|
* create an CKA_ID that is all zeros, but has one more zero than
|
|
|
|
* the previous CKA_ID */
|
|
|
|
buf = PORT_ArenaZAlloc(arena, len+1);
|
|
|
|
if (buf == NULL) {
|
|
|
|
return SECFailure;
|
|
|
|
}
|
|
|
|
ptemplate->pValue = buf;
|
|
|
|
ptemplate->ulValueLen = len+1;
|
|
|
|
return SECSuccess;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static CK_FLAGS
|
|
|
|
pk11_getSecretKeyFlags(PK11SlotInfo *slot, CK_OBJECT_HANDLE id)
|
|
|
|
{
|
|
|
|
CK_FLAGS flags = 0;
|
|
|
|
|
|
|
|
if (PK11_HasAttributeSet(slot, id, CKA_UNWRAP)) {
|
|
|
|
flags |= CKF_UNWRAP;
|
|
|
|
}
|
|
|
|
if (PK11_HasAttributeSet(slot, id, CKA_WRAP)) {
|
|
|
|
flags |= CKF_WRAP;
|
|
|
|
}
|
|
|
|
if (PK11_HasAttributeSet(slot, id, CKA_ENCRYPT)) {
|
|
|
|
flags |= CKF_ENCRYPT;
|
|
|
|
}
|
|
|
|
if (PK11_HasAttributeSet(slot, id, CKA_DECRYPT)) {
|
|
|
|
flags |= CKF_DECRYPT;
|
|
|
|
}
|
|
|
|
if (PK11_HasAttributeSet(slot, id, CKA_DERIVE)) {
|
|
|
|
flags |= CKF_DERIVE;
|
|
|
|
}
|
|
|
|
if (PK11_HasAttributeSet(slot, id, CKA_SIGN)) {
|
|
|
|
flags |= CKF_SIGN;
|
|
|
|
}
|
|
|
|
if (PK11_HasAttributeSet(slot, id, CKA_SIGN_RECOVER)) {
|
|
|
|
flags |= CKF_SIGN_RECOVER;
|
|
|
|
}
|
|
|
|
if (PK11_HasAttributeSet(slot, id, CKA_VERIFY)) {
|
|
|
|
flags |= CKF_VERIFY;
|
|
|
|
}
|
|
|
|
if (PK11_HasAttributeSet(slot, id, CKA_VERIFY_RECOVER)) {
|
|
|
|
flags |= CKF_VERIFY_RECOVER;
|
|
|
|
}
|
|
|
|
return flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char testString[] =
|
|
|
|
"My Encrytion Test Data (should be at least 32 bytes long)";
|
|
|
|
/*
|
|
|
|
* merge a secret key,
|
|
|
|
*
|
|
|
|
* Secret keys may collide by CKA_ID as we merge 2 token. If we collide
|
|
|
|
* on the CKA_ID, we need to make sure we are dealing with different keys.
|
|
|
|
* The reason for this is it is possible that we've merged this database
|
|
|
|
* before, and this key could have been merged already. If the keys are
|
|
|
|
* the same, we are done. If they are not, we need to update the CKA_ID of
|
|
|
|
* the source key and try again.
|
|
|
|
*
|
|
|
|
* Once we know we have a unique key to merge in, we use NSS's underlying
|
|
|
|
* key Move function which will do a key exchange if necessary to move
|
|
|
|
* the key from one token to another. Then we set the CKA_ID and additional
|
|
|
|
* pkcs #11 attributes.
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_mergeSecretKey(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
|
|
|
|
CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
|
|
|
|
{
|
|
|
|
PK11SymKey *sourceKey = NULL;
|
|
|
|
PK11SymKey *targetKey = NULL;
|
|
|
|
SECItem *sourceOutput = NULL;
|
|
|
|
SECItem *targetOutput = NULL;
|
|
|
|
SECItem *param = NULL;
|
|
|
|
SECItem input;
|
|
|
|
CK_OBJECT_HANDLE targetKeyID;
|
|
|
|
CK_FLAGS flags;
|
|
|
|
PRArenaPool *arena = NULL;
|
|
|
|
SECStatus rv = SECSuccess;
|
|
|
|
CK_MECHANISM_TYPE keyMechType, cryptoMechType;
|
|
|
|
CK_KEY_TYPE sourceKeyType, targetKeyType;
|
|
|
|
CK_ATTRIBUTE symTemplate[] = {
|
|
|
|
{ CKA_ID, NULL, 0 },
|
|
|
|
{ CKA_CLASS, NULL, 0 }
|
|
|
|
};
|
|
|
|
CK_ULONG symTemplateCount = sizeof(symTemplate)/sizeof(symTemplate[0]);
|
|
|
|
CK_ATTRIBUTE symCopyTemplate[] = {
|
|
|
|
{ CKA_LABEL, NULL, 0 }
|
|
|
|
};
|
|
|
|
CK_ULONG symCopyTemplateCount =
|
|
|
|
sizeof(symCopyTemplate)/sizeof(symCopyTemplate[0]);
|
|
|
|
|
|
|
|
arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if (arena == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
sourceKeyType = PK11_ReadULongAttribute(sourceSlot, id, CKA_KEY_TYPE);
|
|
|
|
if (sourceKeyType == (CK_ULONG) -1) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get the key mechanism */
|
|
|
|
keyMechType = PK11_GetKeyMechanism(sourceKeyType);
|
|
|
|
/* get a mechanism suitable to encryption.
|
|
|
|
* PK11_GetKeyMechanism returns a mechanism that is unique to the key
|
|
|
|
* type. It tries to return encryption/decryption mechanisms, however
|
|
|
|
* CKM_DES3_CBC uses and abmiguous keyType, so keyMechType is returned as
|
|
|
|
* 'keygen' mechanism. Detect that case here */
|
|
|
|
cryptoMechType = keyMechType;
|
|
|
|
if ((keyMechType == CKM_DES3_KEY_GEN) ||
|
|
|
|
(keyMechType == CKM_DES2_KEY_GEN)) {
|
|
|
|
cryptoMechType = CKM_DES3_CBC;
|
|
|
|
}
|
|
|
|
|
|
|
|
sourceKey = PK11_SymKeyFromHandle(sourceSlot, NULL, PK11_OriginDerive,
|
|
|
|
keyMechType , id, PR_FALSE, sourcePwArg);
|
|
|
|
if (sourceKey == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check to see a key with the same CKA_ID already exists in
|
|
|
|
* the target slot. If it does, then we need to verify if the keys
|
|
|
|
* really matches. If they don't import the key with a new CKA_ID
|
|
|
|
* value. */
|
|
|
|
rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot,
|
|
|
|
symTemplate, symTemplateCount, id, &targetKeyID);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set up the input test */
|
|
|
|
input.data = (unsigned char *)testString;
|
|
|
|
input.len = PK11_GetBlockSize(cryptoMechType, NULL);
|
|
|
|
if (input.len < 0) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
if (input.len == 0) {
|
|
|
|
input.len = sizeof (testString);
|
|
|
|
}
|
|
|
|
while (targetKeyID != CK_INVALID_HANDLE) {
|
|
|
|
/* test to see if the keys are identical */
|
|
|
|
targetKeyType = PK11_ReadULongAttribute(sourceSlot, id, CKA_KEY_TYPE);
|
|
|
|
if (targetKeyType == sourceKeyType) {
|
|
|
|
/* same keyType - see if it's the same key */
|
|
|
|
targetKey = PK11_SymKeyFromHandle(targetSlot, NULL,
|
|
|
|
PK11_OriginDerive, keyMechType, targetKeyID, PR_FALSE,
|
|
|
|
targetPwArg);
|
|
|
|
/* get a parameter if we don't already have one */
|
|
|
|
if (!param) {
|
|
|
|
param = PK11_GenerateNewParam(cryptoMechType, sourceKey);
|
|
|
|
if (param == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* use the source key to encrypt a reference */
|
|
|
|
if (!sourceOutput) {
|
|
|
|
rv = pk11_encrypt(sourceKey, cryptoMechType, param, &input,
|
|
|
|
&sourceOutput);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* encrypt the reference with the target key */
|
|
|
|
rv = pk11_encrypt(targetKey, cryptoMechType, param, &input,
|
|
|
|
&targetOutput);
|
|
|
|
if (rv == SECSuccess) {
|
|
|
|
if (SECITEM_ItemsAreEqual(sourceOutput, targetOutput)) {
|
|
|
|
/* they produce the same output, they must be the
|
|
|
|
* same key */
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
SECITEM_FreeItem(targetOutput, PR_TRUE);
|
|
|
|
targetOutput = NULL;
|
|
|
|
}
|
|
|
|
PK11_FreeSymKey(targetKey);
|
|
|
|
targetKey = NULL;
|
|
|
|
}
|
|
|
|
/* keys aren't equal, update the KEY_ID and look again */
|
|
|
|
rv = pk11_incrementID(arena, &symTemplate[0]);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
targetKeyID = pk11_FindObjectByTemplate(targetSlot,
|
|
|
|
symTemplate, symTemplateCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* we didn't find a matching key, import this one with the new
|
|
|
|
* CKAID */
|
|
|
|
flags = pk11_getSecretKeyFlags(sourceSlot, id);
|
|
|
|
targetKey = PK11_MoveSymKey(targetSlot, PK11_OriginDerive, flags, PR_TRUE,
|
|
|
|
sourceKey);
|
|
|
|
if (targetKey == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
/* set the key new CKAID */
|
|
|
|
rv = pk11_setAttributes(targetSlot, targetKey->objectID, symTemplate, 1);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* fill in remaining attributes */
|
|
|
|
rv = pk11_copyAttributes(arena, targetSlot, targetKey->objectID,
|
|
|
|
sourceSlot, id, symCopyTemplate, symCopyTemplateCount);
|
|
|
|
done:
|
|
|
|
if (sourceKey) {
|
|
|
|
PK11_FreeSymKey(sourceKey);
|
|
|
|
}
|
|
|
|
if (targetKey) {
|
|
|
|
PK11_FreeSymKey(targetKey);
|
|
|
|
}
|
|
|
|
if (sourceOutput) {
|
|
|
|
SECITEM_FreeItem(sourceOutput, PR_TRUE);
|
|
|
|
}
|
|
|
|
if (targetOutput) {
|
|
|
|
SECITEM_FreeItem(targetOutput, PR_TRUE);
|
|
|
|
}
|
|
|
|
if (param) {
|
|
|
|
SECITEM_FreeItem(param, PR_TRUE);
|
|
|
|
}
|
|
|
|
if (arena) {
|
|
|
|
PORT_FreeArena(arena,PR_FALSE);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* Public Keys
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Merge public key
|
|
|
|
*
|
|
|
|
* Use the high level NSS calls to extract the public key and import it
|
|
|
|
* into the token. Extra attributes are then copied to the new token.
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_mergePublicKey(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
|
|
|
|
CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
|
|
|
|
{
|
|
|
|
SECKEYPublicKey *sourceKey = NULL;
|
|
|
|
CK_OBJECT_HANDLE targetKeyID;
|
|
|
|
PRArenaPool *arena = NULL;
|
|
|
|
SECStatus rv = SECSuccess;
|
|
|
|
CK_ATTRIBUTE pubTemplate[] = {
|
|
|
|
{ CKA_ID, NULL, 0 },
|
|
|
|
{ CKA_CLASS, NULL, 0 }
|
|
|
|
};
|
|
|
|
CK_ULONG pubTemplateCount = sizeof(pubTemplate)/sizeof(pubTemplate[0]);
|
|
|
|
CK_ATTRIBUTE pubCopyTemplate[] = {
|
|
|
|
{ CKA_ID, NULL, 0 },
|
|
|
|
{ CKA_LABEL, NULL, 0 },
|
|
|
|
{ CKA_SUBJECT, NULL, 0 }
|
|
|
|
};
|
|
|
|
CK_ULONG pubCopyTemplateCount =
|
|
|
|
sizeof(pubCopyTemplate)/sizeof(pubCopyTemplate[0]);
|
|
|
|
|
|
|
|
arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if (arena == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* check to see if the key is already in the target slot */
|
|
|
|
rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot, pubTemplate,
|
|
|
|
pubTemplateCount, id, &targetKeyID);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Key is already in the target slot */
|
|
|
|
if (targetKeyID != CK_INVALID_HANDLE) {
|
|
|
|
/* not an error ... */
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* fetch an NSS representation of the public key */
|
|
|
|
sourceKey = PK11_ExtractPublicKey(sourceSlot, nullKey, id);
|
|
|
|
if (sourceKey== NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* load the public key into the target token. */
|
|
|
|
targetKeyID = PK11_ImportPublicKey(targetSlot, sourceKey, PR_TRUE);
|
|
|
|
if (targetKeyID == CK_INVALID_HANDLE) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* fill in remaining attributes */
|
|
|
|
rv = pk11_copyAttributes(arena, targetSlot, targetKeyID, sourceSlot, id,
|
|
|
|
pubCopyTemplate, pubCopyTemplateCount);
|
|
|
|
|
|
|
|
|
|
|
|
done:
|
|
|
|
if (sourceKey) {
|
|
|
|
SECKEY_DestroyPublicKey(sourceKey);
|
|
|
|
}
|
|
|
|
if (arena) {
|
|
|
|
PORT_FreeArena(arena,PR_FALSE);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* Certificates
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
|
2009-01-20 19:43:31 -08:00
|
|
|
/*
|
|
|
|
* Two copies of the source code for this algorithm exist in NSS.
|
|
|
|
* Changes must be made in both copies.
|
|
|
|
* The other copy is in sftkdb_resolveConflicts() in softoken/sftkdb.c.
|
|
|
|
*/
|
|
|
|
static char *
|
|
|
|
pk11_IncrementNickname(char *nickname)
|
|
|
|
{
|
|
|
|
char *newNickname = NULL;
|
|
|
|
int end;
|
|
|
|
int digit;
|
|
|
|
int len = strlen(nickname);
|
|
|
|
|
|
|
|
/* does nickname end with " #n*" ? */
|
|
|
|
for (end = len - 1;
|
|
|
|
end >= 2 && (digit = nickname[end]) <= '9' && digit >= '0';
|
|
|
|
end--) /* just scan */ ;
|
|
|
|
if (len >= 3 &&
|
|
|
|
end < (len - 1) /* at least one digit */ &&
|
|
|
|
nickname[end] == '#' &&
|
|
|
|
nickname[end - 1] == ' ') {
|
|
|
|
/* Already has a suitable suffix string */
|
|
|
|
} else {
|
|
|
|
/* ... append " #2" to the name */
|
|
|
|
static const char num2[] = " #2";
|
|
|
|
newNickname = PORT_Realloc(nickname, len + sizeof(num2));
|
|
|
|
if (newNickname) {
|
|
|
|
PORT_Strcat(newNickname, num2);
|
|
|
|
} else {
|
|
|
|
PORT_Free(nickname);
|
|
|
|
}
|
|
|
|
return newNickname;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (end = len - 1;
|
|
|
|
end >= 0 && (digit = nickname[end]) <= '9' && digit >= '0';
|
|
|
|
end--) {
|
|
|
|
if (digit < '9') {
|
|
|
|
nickname[end]++;
|
|
|
|
return nickname;
|
|
|
|
}
|
|
|
|
nickname[end] = '0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/* we overflowed, insert a new '1' for a carry in front of the number */
|
|
|
|
newNickname = PORT_Realloc(nickname, len + 2);
|
|
|
|
if (newNickname) {
|
|
|
|
newNickname[++end] = '1';
|
|
|
|
PORT_Memset(&newNickname[end + 1], '0', len - end);
|
|
|
|
newNickname[len + 1] = 0;
|
|
|
|
} else {
|
|
|
|
PORT_Free(nickname);
|
|
|
|
}
|
|
|
|
return newNickname;
|
|
|
|
}
|
|
|
|
|
2008-06-06 05:40:11 -07:00
|
|
|
/*
|
|
|
|
* merge a certificate object
|
|
|
|
*
|
|
|
|
* Use the high level NSS calls to extract and import the certificate.
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_mergeCert(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
|
|
|
|
CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
|
|
|
|
{
|
|
|
|
CERTCertificate *sourceCert = NULL;
|
|
|
|
CK_OBJECT_HANDLE targetCertID = CK_INVALID_HANDLE;
|
|
|
|
char *nickname = NULL;
|
|
|
|
SECStatus rv = SECSuccess;
|
|
|
|
PRArenaPool *arena = NULL;
|
|
|
|
CK_ATTRIBUTE sourceCKAID = {CKA_ID, NULL, 0};
|
|
|
|
CK_ATTRIBUTE targetCKAID = {CKA_ID, NULL, 0};
|
|
|
|
SECStatus lrv = SECSuccess;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
|
|
|
|
sourceCert = PK11_MakeCertFromHandle(sourceSlot, id, NULL);
|
|
|
|
if (sourceCert == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
nickname = PK11_GetObjectNickname(sourceSlot, id);
|
|
|
|
|
2009-01-20 19:43:31 -08:00
|
|
|
/* The database code will prevent nickname collisions for certs with
|
|
|
|
* different subjects. This code will prevent us from getting
|
|
|
|
* actual import errors */
|
|
|
|
if (nickname) {
|
|
|
|
const char *tokenName = PK11_GetTokenName(targetSlot);
|
|
|
|
char *tokenNickname = NULL;
|
|
|
|
|
|
|
|
do {
|
|
|
|
tokenNickname = PR_smprintf("%s:%s",tokenName, nickname);
|
|
|
|
if (!tokenNickname) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!SEC_CertNicknameConflict(tokenNickname,
|
|
|
|
&sourceCert->derSubject, CERT_GetDefaultCertDB())) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
nickname = pk11_IncrementNickname(nickname);
|
|
|
|
if (!nickname) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
PR_smprintf_free(tokenNickname);
|
|
|
|
} while (1);
|
|
|
|
if (tokenNickname) {
|
|
|
|
PR_smprintf_free(tokenNickname);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2008-06-06 05:40:11 -07:00
|
|
|
/* see if the cert is already there */
|
|
|
|
targetCertID = PK11_FindCertInSlot(targetSlot, sourceCert, targetPwArg);
|
|
|
|
if (targetCertID == CK_INVALID_HANDLE) {
|
|
|
|
/* cert doesn't exist load the cert in. */
|
|
|
|
/* OK for the nickname to be NULL, not all certs have nicknames */
|
|
|
|
rv = PK11_ImportCert(targetSlot, sourceCert, CK_INVALID_HANDLE,
|
|
|
|
nickname, PR_FALSE);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* the cert already exists, see if the nickname and/or CKA_ID need
|
|
|
|
* to be updated */
|
|
|
|
|
|
|
|
arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if (arena == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* does our source have a CKA_ID ? */
|
|
|
|
rv = PK11_GetAttributes(arena, sourceSlot, id, &sourceCKAID, 1);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
sourceCKAID.ulValueLen = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if we have a source CKA_ID, see of we need to update the
|
|
|
|
* target's CKA_ID */
|
|
|
|
if (sourceCKAID.ulValueLen != 0) {
|
|
|
|
rv = PK11_GetAttributes(arena, targetSlot, targetCertID,
|
|
|
|
&targetCKAID, 1);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
targetCKAID.ulValueLen = 0;
|
|
|
|
}
|
|
|
|
/* if the target has no CKA_ID, update it from the source */
|
|
|
|
if (targetCKAID.ulValueLen == 0) {
|
|
|
|
lrv=pk11_setAttributes(targetSlot, targetCertID, &sourceCKAID, 1);
|
|
|
|
if (lrv != SECSuccess) {
|
|
|
|
error = PORT_GetError();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rv = SECSuccess;
|
|
|
|
|
|
|
|
/* now check if we need to update the nickname */
|
|
|
|
if (nickname && *nickname) {
|
|
|
|
char *targetname;
|
|
|
|
targetname = PK11_GetObjectNickname(targetSlot, targetCertID);
|
|
|
|
if (!targetname || !*targetname) {
|
|
|
|
/* target has no nickname, or it's empty, update it */
|
|
|
|
rv = PK11_SetObjectNickname(targetSlot, targetCertID, nickname);
|
|
|
|
}
|
|
|
|
if (targetname) {
|
|
|
|
PORT_Free(targetname);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* restore the error code if CKA_ID failed, but nickname didn't */
|
|
|
|
if ((rv == SECSuccess) && (lrv != SECSuccess)) {
|
|
|
|
rv = lrv;
|
|
|
|
PORT_SetError(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
|
|
|
if (nickname) {
|
|
|
|
PORT_Free(nickname);
|
|
|
|
}
|
|
|
|
if (sourceCert) {
|
|
|
|
CERT_DestroyCertificate(sourceCert);
|
|
|
|
}
|
|
|
|
if (arena) {
|
|
|
|
PORT_FreeArena(arena,PR_FALSE);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* Crls
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Use the raw PKCS #11 interface to merge the CRLs.
|
|
|
|
*
|
|
|
|
* In the case where of collision, choose the newest CRL that is valid.
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_mergeCrl(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
|
|
|
|
CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
|
|
|
|
{
|
|
|
|
CK_OBJECT_HANDLE targetCrlID;
|
|
|
|
PRArenaPool *arena = NULL;
|
|
|
|
SECStatus rv = SECSuccess;
|
|
|
|
CK_ATTRIBUTE crlTemplate[] = {
|
|
|
|
{ CKA_SUBJECT, NULL, 0 },
|
|
|
|
{ CKA_CLASS, NULL, 0 },
|
|
|
|
{ CKA_NSS_KRL, NULL, 0 }
|
|
|
|
};
|
|
|
|
CK_ULONG crlTemplateCount = sizeof(crlTemplate)/sizeof(crlTemplate[0]);
|
|
|
|
CK_ATTRIBUTE crlCopyTemplate[] = {
|
|
|
|
{ CKA_CLASS, NULL, 0 },
|
|
|
|
{ CKA_TOKEN, NULL, 0 },
|
|
|
|
{ CKA_LABEL, NULL, 0 },
|
|
|
|
{ CKA_PRIVATE, NULL, 0 },
|
|
|
|
{ CKA_MODIFIABLE, NULL, 0 },
|
|
|
|
{ CKA_SUBJECT, NULL, 0 },
|
|
|
|
{ CKA_NSS_KRL, NULL, 0 },
|
|
|
|
{ CKA_NSS_URL, NULL, 0 },
|
|
|
|
{ CKA_VALUE, NULL, 0 }
|
|
|
|
};
|
|
|
|
CK_ULONG crlCopyTemplateCount =
|
|
|
|
sizeof(crlCopyTemplate)/sizeof(crlCopyTemplate[0]);
|
|
|
|
|
|
|
|
arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if (arena == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
/* check to see if the crl is already in the target slot */
|
|
|
|
rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot, crlTemplate,
|
|
|
|
crlTemplateCount, id, &targetCrlID);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
if (targetCrlID != CK_INVALID_HANDLE) {
|
|
|
|
/* we already have a CRL, check to see which is more up-to-date. */
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* load the CRL into the target token. */
|
|
|
|
rv = pk11_copyAttributes(arena, targetSlot, targetCrlID, sourceSlot, id,
|
|
|
|
crlCopyTemplate, crlCopyTemplateCount);
|
|
|
|
done:
|
|
|
|
if (arena) {
|
|
|
|
PORT_FreeArena(arena,PR_FALSE);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* SMIME objects
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* use the raw PKCS #11 interface to merge the S/MIME records
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_mergeSmime(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
|
|
|
|
CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
|
|
|
|
{
|
|
|
|
CK_OBJECT_HANDLE targetSmimeID;
|
|
|
|
PRArenaPool *arena = NULL;
|
|
|
|
SECStatus rv = SECSuccess;
|
|
|
|
CK_ATTRIBUTE smimeTemplate[] = {
|
|
|
|
{ CKA_SUBJECT, NULL, 0 },
|
|
|
|
{ CKA_NSS_EMAIL, NULL, 0 },
|
|
|
|
{ CKA_CLASS, NULL, 0 },
|
|
|
|
};
|
|
|
|
CK_ULONG smimeTemplateCount =
|
|
|
|
sizeof(smimeTemplate)/sizeof(smimeTemplate[0]);
|
|
|
|
CK_ATTRIBUTE smimeCopyTemplate[] = {
|
|
|
|
{ CKA_CLASS, NULL, 0 },
|
|
|
|
{ CKA_TOKEN, NULL, 0 },
|
|
|
|
{ CKA_LABEL, NULL, 0 },
|
|
|
|
{ CKA_PRIVATE, NULL, 0 },
|
|
|
|
{ CKA_MODIFIABLE, NULL, 0 },
|
|
|
|
{ CKA_SUBJECT, NULL, 0 },
|
|
|
|
{ CKA_NSS_EMAIL, NULL, 0 },
|
|
|
|
{ CKA_NSS_SMIME_TIMESTAMP, NULL, 0 },
|
|
|
|
{ CKA_VALUE, NULL, 0 }
|
|
|
|
};
|
|
|
|
CK_ULONG smimeCopyTemplateCount =
|
|
|
|
sizeof(smimeCopyTemplate)/sizeof(smimeCopyTemplate[0]);
|
|
|
|
|
|
|
|
arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if (arena == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
/* check to see if the crl is already in the target slot */
|
|
|
|
rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot, smimeTemplate,
|
|
|
|
smimeTemplateCount, id, &targetSmimeID);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
if (targetSmimeID != CK_INVALID_HANDLE) {
|
|
|
|
/* we already have a SMIME record */
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* load the SMime Record into the target token. */
|
|
|
|
rv = pk11_copyAttributes(arena, targetSlot, targetSmimeID, sourceSlot, id,
|
|
|
|
smimeCopyTemplate, smimeCopyTemplateCount);
|
|
|
|
done:
|
|
|
|
if (arena) {
|
|
|
|
PORT_FreeArena(arena,PR_FALSE);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* Trust Objects
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* decide which trust record entry wins. PR_TRUE (source) or PR_FALSE (target)
|
|
|
|
*/
|
|
|
|
#define USE_TARGET PR_FALSE
|
|
|
|
#define USE_SOURCE PR_TRUE
|
|
|
|
PRBool
|
|
|
|
pk11_mergeTrustEntry(CK_ATTRIBUTE *target, CK_ATTRIBUTE *source)
|
|
|
|
{
|
|
|
|
CK_ULONG targetTrust = (target->ulValueLen == sizeof (CK_LONG)) ?
|
|
|
|
*(CK_ULONG *)target->pValue : CKT_NSS_TRUST_UNKNOWN;
|
|
|
|
CK_ULONG sourceTrust = (source->ulValueLen == sizeof (CK_LONG)) ?
|
|
|
|
*(CK_ULONG *)source->pValue : CKT_NSS_TRUST_UNKNOWN;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Examine a single entry and deside if the source or target version
|
|
|
|
* should win out. When all the entries have been checked, if there is
|
|
|
|
* any case we need to update, we will write the whole source record
|
|
|
|
* to the target database. That means for each individual record, if the
|
|
|
|
* target wins, we need to update the source (in case later we have a
|
|
|
|
* case where the source wins). If the source wins, it already
|
|
|
|
*/
|
|
|
|
if (sourceTrust == targetTrust) {
|
|
|
|
return USE_TARGET; /* which equates to 'do nothing' */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sourceTrust == CKT_NSS_TRUST_UNKNOWN) {
|
|
|
|
return USE_TARGET;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* target has no idea, use the source's idea of the trust value */
|
|
|
|
if (targetTrust == CKT_NSS_TRUST_UNKNOWN) {
|
|
|
|
/* source overwrites the target */
|
|
|
|
return USE_SOURCE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* so both the target and the source have some idea of what this
|
|
|
|
* trust attribute should be, and neither agree exactly.
|
|
|
|
* At this point, we prefer 'hard' attributes over 'soft' ones.
|
|
|
|
* 'hard' ones are CKT_NSS_TRUSTED, CKT_NSS_TRUSTED_DELEGATOR, and
|
|
|
|
* CKT_NSS_UNTRUTED. Soft ones are ones which don't change the
|
|
|
|
* actual trust of the cert (CKT_MUST_VERIFY, CKT_NSS_VALID,
|
|
|
|
* CKT_NSS_VALID_DELEGATOR).
|
|
|
|
*/
|
|
|
|
if ((sourceTrust == CKT_NSS_MUST_VERIFY)
|
|
|
|
|| (sourceTrust == CKT_NSS_VALID)
|
|
|
|
|| (sourceTrust == CKT_NSS_VALID_DELEGATOR)) {
|
|
|
|
return USE_TARGET;
|
|
|
|
}
|
|
|
|
if ((targetTrust == CKT_NSS_MUST_VERIFY)
|
|
|
|
|| (targetTrust == CKT_NSS_VALID)
|
|
|
|
|| (targetTrust == CKT_NSS_VALID_DELEGATOR)) {
|
|
|
|
/* source overrites the target */
|
|
|
|
return USE_SOURCE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* both have hard attributes, we have a conflict, let the target win. */
|
|
|
|
return USE_TARGET;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* use the raw PKCS #11 interface to merge the S/MIME records
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_mergeTrust(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
|
|
|
|
CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
|
|
|
|
{
|
|
|
|
CK_OBJECT_HANDLE targetTrustID;
|
|
|
|
PRArenaPool *arena = NULL;
|
|
|
|
SECStatus rv = SECSuccess;
|
|
|
|
int error = 0;
|
|
|
|
CK_ATTRIBUTE trustTemplate[] = {
|
|
|
|
{ CKA_ISSUER, NULL, 0 },
|
|
|
|
{ CKA_SERIAL_NUMBER, NULL, 0 },
|
|
|
|
{ CKA_CLASS, NULL, 0 },
|
|
|
|
};
|
|
|
|
CK_ULONG trustTemplateCount =
|
|
|
|
sizeof(trustTemplate)/sizeof(trustTemplate[0]);
|
|
|
|
CK_ATTRIBUTE trustCopyTemplate[] = {
|
|
|
|
{ CKA_CLASS, NULL, 0 },
|
|
|
|
{ CKA_TOKEN, NULL, 0 },
|
|
|
|
{ CKA_LABEL, NULL, 0 },
|
|
|
|
{ CKA_PRIVATE, NULL, 0 },
|
|
|
|
{ CKA_MODIFIABLE, NULL, 0 },
|
|
|
|
{ CKA_ISSUER, NULL, 0},
|
|
|
|
{ CKA_SERIAL_NUMBER, NULL, 0},
|
|
|
|
{ CKA_CERT_SHA1_HASH, NULL, 0 },
|
|
|
|
{ CKA_CERT_MD5_HASH, NULL, 0 },
|
|
|
|
{ CKA_TRUST_SERVER_AUTH, NULL, 0 },
|
|
|
|
{ CKA_TRUST_CLIENT_AUTH, NULL, 0 },
|
|
|
|
{ CKA_TRUST_CODE_SIGNING, NULL, 0 },
|
|
|
|
{ CKA_TRUST_EMAIL_PROTECTION, NULL, 0 },
|
|
|
|
{ CKA_TRUST_STEP_UP_APPROVED, NULL, 0 }
|
|
|
|
};
|
|
|
|
CK_ULONG trustCopyTemplateCount =
|
|
|
|
sizeof(trustCopyTemplate)/sizeof(trustCopyTemplate[0]);
|
|
|
|
|
|
|
|
arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if (arena == NULL) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
/* check to see if the crl is already in the target slot */
|
|
|
|
rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot, trustTemplate,
|
|
|
|
trustTemplateCount, id, &targetTrustID);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
if (targetTrustID != CK_INVALID_HANDLE) {
|
|
|
|
/* a matching trust record already exists, merge it in */
|
|
|
|
CK_ATTRIBUTE_TYPE trustAttrs[] = {
|
|
|
|
CKA_TRUST_SERVER_AUTH, CKA_TRUST_CLIENT_AUTH,
|
|
|
|
CKA_TRUST_CODE_SIGNING, CKA_TRUST_EMAIL_PROTECTION,
|
|
|
|
CKA_TRUST_IPSEC_TUNNEL, CKA_TRUST_IPSEC_USER,
|
|
|
|
CKA_TRUST_TIME_STAMPING
|
|
|
|
};
|
|
|
|
CK_ULONG trustAttrsCount =
|
|
|
|
sizeof(trustAttrs)/sizeof(trustAttrs[0]);
|
|
|
|
|
|
|
|
int i;
|
|
|
|
CK_ATTRIBUTE targetTemplate, sourceTemplate;
|
|
|
|
|
|
|
|
/* existing trust record, merge the two together */
|
|
|
|
for (i=0; i < trustAttrsCount; i++) {
|
|
|
|
targetTemplate.type = sourceTemplate.type = trustAttrs[i];
|
|
|
|
targetTemplate.pValue = sourceTemplate.pValue = NULL;
|
|
|
|
targetTemplate.ulValueLen = sourceTemplate.ulValueLen = 0;
|
|
|
|
PK11_GetAttributes(arena, sourceSlot, id, &sourceTemplate, 1);
|
|
|
|
PK11_GetAttributes(arena, targetSlot, targetTrustID,
|
|
|
|
&targetTemplate, 1);
|
|
|
|
if (pk11_mergeTrustEntry(&targetTemplate, &sourceTemplate)) {
|
|
|
|
/* source wins, write out the source attribute to the target */
|
|
|
|
SECStatus lrv = pk11_setAttributes(targetSlot, targetTrustID,
|
|
|
|
&sourceTemplate, 1);
|
|
|
|
if (lrv != SECSuccess) {
|
|
|
|
rv = SECFailure;
|
|
|
|
error = PORT_GetError();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* handle step */
|
|
|
|
sourceTemplate.type = CKA_TRUST_STEP_UP_APPROVED;
|
|
|
|
sourceTemplate.pValue = NULL;
|
|
|
|
sourceTemplate.ulValueLen = 0;
|
|
|
|
|
|
|
|
/* if the source has steup set, then set it in the target */
|
|
|
|
PK11_GetAttributes(arena, sourceSlot, id, &sourceTemplate, 1);
|
|
|
|
if ((sourceTemplate.ulValueLen == sizeof(CK_BBOOL)) &&
|
|
|
|
(sourceTemplate.pValue) &&
|
|
|
|
(*(CK_BBOOL *)sourceTemplate.pValue == CK_TRUE)) {
|
|
|
|
SECStatus lrv = pk11_setAttributes(targetSlot, targetTrustID,
|
|
|
|
&sourceTemplate, 1);
|
|
|
|
if (lrv != SECSuccess) {
|
|
|
|
rv = SECFailure;
|
|
|
|
error = PORT_GetError();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* load the new trust Record into the target token. */
|
|
|
|
rv = pk11_copyAttributes(arena, targetSlot, targetTrustID, sourceSlot, id,
|
|
|
|
trustCopyTemplate, trustCopyTemplateCount);
|
|
|
|
done:
|
|
|
|
if (arena) {
|
|
|
|
PORT_FreeArena(arena,PR_FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* restore the error code */
|
|
|
|
if (rv == SECFailure && error) {
|
|
|
|
PORT_SetError(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* Central merge code
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
/*
|
|
|
|
* merge a single object from sourceToken to targetToken
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_mergeObject(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
|
|
|
|
CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
|
|
|
|
{
|
|
|
|
|
|
|
|
CK_OBJECT_CLASS objClass;
|
|
|
|
|
|
|
|
|
|
|
|
objClass = PK11_ReadULongAttribute(sourceSlot, id, CKA_CLASS);
|
|
|
|
if (objClass == (CK_ULONG) -1) {
|
|
|
|
PORT_SetError( SEC_ERROR_UNKNOWN_OBJECT_TYPE );
|
|
|
|
return SECFailure;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (objClass) {
|
|
|
|
case CKO_CERTIFICATE:
|
|
|
|
return pk11_mergeCert(targetSlot, sourceSlot, id,
|
|
|
|
targetPwArg, sourcePwArg);
|
|
|
|
case CKO_NSS_TRUST:
|
|
|
|
return pk11_mergeTrust(targetSlot, sourceSlot, id,
|
|
|
|
targetPwArg, sourcePwArg);
|
|
|
|
case CKO_PUBLIC_KEY:
|
|
|
|
return pk11_mergePublicKey(targetSlot, sourceSlot, id,
|
|
|
|
targetPwArg, sourcePwArg);
|
|
|
|
case CKO_PRIVATE_KEY:
|
|
|
|
return pk11_mergePrivateKey(targetSlot, sourceSlot, id,
|
|
|
|
targetPwArg, sourcePwArg);
|
|
|
|
case CKO_SECRET_KEY:
|
|
|
|
return pk11_mergeSecretKey(targetSlot, sourceSlot, id,
|
|
|
|
targetPwArg, sourcePwArg);
|
|
|
|
case CKO_NSS_CRL:
|
|
|
|
return pk11_mergeCrl(targetSlot, sourceSlot, id,
|
|
|
|
targetPwArg, sourcePwArg);
|
|
|
|
case CKO_NSS_SMIME:
|
|
|
|
return pk11_mergeSmime(targetSlot, sourceSlot, id,
|
|
|
|
targetPwArg, sourcePwArg);
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
PORT_SetError( SEC_ERROR_UNKNOWN_OBJECT_TYPE );
|
|
|
|
return SECFailure;
|
|
|
|
}
|
|
|
|
|
|
|
|
PK11MergeLogNode *
|
|
|
|
pk11_newMergeLogNode(PRArenaPool *arena,
|
|
|
|
PK11SlotInfo *slot, CK_OBJECT_HANDLE id, int error)
|
|
|
|
{
|
|
|
|
PK11MergeLogNode *newLog;
|
|
|
|
PK11GenericObject *obj;
|
|
|
|
|
|
|
|
newLog = PORT_ArenaZNew(arena, PK11MergeLogNode);
|
|
|
|
if (newLog == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
obj = PORT_ArenaZNew(arena, PK11GenericObject);
|
|
|
|
if ( !obj ) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize it */
|
|
|
|
obj->slot = slot;
|
|
|
|
obj->objectID = id;
|
|
|
|
|
|
|
|
newLog->object= obj;
|
|
|
|
newLog->error = error;
|
|
|
|
return newLog;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* walk down each entry and merge it. keep track of the errors in the log
|
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
pk11_mergeByObjectIDs(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
|
|
|
|
CK_OBJECT_HANDLE *objectIDs, int count,
|
|
|
|
PK11MergeLog *log, void *targetPwArg, void *sourcePwArg)
|
|
|
|
{
|
|
|
|
SECStatus rv = SECSuccess;
|
|
|
|
int error, i;
|
|
|
|
|
|
|
|
for (i=0; i < count; i++) {
|
|
|
|
/* try to update the entire database. On failure, keep going,
|
|
|
|
* but remember the error to report back to the caller */
|
|
|
|
SECStatus lrv;
|
|
|
|
PK11MergeLogNode *newLog;
|
|
|
|
|
|
|
|
lrv= pk11_mergeObject(targetSlot, sourceSlot, objectIDs[i],
|
|
|
|
targetPwArg, sourcePwArg);
|
|
|
|
if (lrv == SECSuccess) {
|
|
|
|
/* merged with no problem, go to next object */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* remember that we failed and why */
|
|
|
|
rv = SECFailure;
|
|
|
|
error = PORT_GetError();
|
|
|
|
|
|
|
|
/* log the errors */
|
|
|
|
if (!log) {
|
|
|
|
/* not logging, go to next entry */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
newLog = pk11_newMergeLogNode(log->arena, sourceSlot,
|
|
|
|
objectIDs[i], error);
|
|
|
|
if (!newLog) {
|
|
|
|
/* failed to allocate entry, just keep going */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* link in the errorlog entry */
|
|
|
|
newLog->next = NULL;
|
|
|
|
if (log->tail) {
|
|
|
|
log->tail->next = newLog;
|
|
|
|
} else {
|
|
|
|
log->head = newLog;
|
|
|
|
}
|
|
|
|
newLog->prev = log->tail;
|
|
|
|
log->tail = newLog;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* restore the last error code */
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
PORT_SetError(error);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Merge all the records in sourceSlot that aren't in targetSlot
|
|
|
|
*
|
|
|
|
* This function will return failure if not all the objects
|
|
|
|
* successfully merged.
|
|
|
|
*
|
|
|
|
* Applications can pass in an optional error log which will record
|
|
|
|
* each failing object and why it failed to import. PK11MergeLog
|
|
|
|
* is modelled after the CERTVerifyLog.
|
|
|
|
*/
|
|
|
|
SECStatus
|
|
|
|
PK11_MergeTokens(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
|
|
|
|
PK11MergeLog *log, void *targetPwArg, void *sourcePwArg)
|
|
|
|
{
|
|
|
|
SECStatus rv = SECSuccess, lrv = SECSuccess;
|
|
|
|
int error, count = 0;
|
|
|
|
CK_ATTRIBUTE search[2];
|
|
|
|
CK_OBJECT_HANDLE *objectIDs = NULL;
|
|
|
|
CK_BBOOL ck_true = CK_TRUE;
|
|
|
|
CK_OBJECT_CLASS privKey = CKO_PRIVATE_KEY;
|
|
|
|
|
|
|
|
PK11_SETATTRS(&search[0], CKA_TOKEN, &ck_true, sizeof(ck_true));
|
|
|
|
PK11_SETATTRS(&search[1], CKA_CLASS, &privKey, sizeof(privKey));
|
|
|
|
/*
|
|
|
|
* make sure both tokens are already authenticated if need be.
|
|
|
|
*/
|
|
|
|
rv = PK11_Authenticate(targetSlot, PR_TRUE, targetPwArg);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto loser;
|
|
|
|
}
|
|
|
|
rv = PK11_Authenticate(sourceSlot, PR_TRUE, sourcePwArg);
|
|
|
|
if (rv != SECSuccess) {
|
|
|
|
goto loser;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* turns out the old DB's are rather fragile if the private keys aren't
|
|
|
|
* merged in first, so do the private keys explicity. */
|
|
|
|
objectIDs = pk11_FindObjectsByTemplate(sourceSlot, search, 2, &count);
|
|
|
|
if (objectIDs) {
|
|
|
|
lrv = pk11_mergeByObjectIDs(targetSlot, sourceSlot,
|
|
|
|
objectIDs, count, log,
|
|
|
|
targetPwArg, sourcePwArg);
|
|
|
|
if (lrv != SECSuccess) {
|
|
|
|
error = PORT_GetError();
|
|
|
|
}
|
|
|
|
PORT_Free(objectIDs);
|
|
|
|
count = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* now do the rest (NOTE: this will repeat the private keys, but
|
|
|
|
* that shouldnt' be an issue as we will notice they are already
|
|
|
|
* merged in */
|
|
|
|
objectIDs = pk11_FindObjectsByTemplate(sourceSlot, search, 1, &count);
|
|
|
|
if (!objectIDs) {
|
|
|
|
rv = SECFailure;
|
|
|
|
goto loser;
|
|
|
|
}
|
|
|
|
|
|
|
|
rv = pk11_mergeByObjectIDs(targetSlot, sourceSlot, objectIDs, count, log,
|
|
|
|
targetPwArg, sourcePwArg);
|
|
|
|
if (rv == SECSuccess) {
|
|
|
|
/* if private keys failed, but the rest succeeded, be sure to let
|
|
|
|
* the caller know that private keys failed and why.
|
|
|
|
* NOTE: this is highly unlikely since the same keys that failed
|
|
|
|
* in the previous merge call will most likely fail in this one */
|
|
|
|
if (lrv != SECSuccess) {
|
|
|
|
rv = lrv;
|
|
|
|
PORT_SetError(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
loser:
|
|
|
|
if (objectIDs) {
|
|
|
|
PORT_Free(objectIDs);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
PK11MergeLog *
|
2008-10-22 17:38:29 -07:00
|
|
|
PK11_CreateMergeLog(void)
|
2008-06-06 05:40:11 -07:00
|
|
|
{
|
|
|
|
PRArenaPool *arena;
|
|
|
|
PK11MergeLog *log;
|
|
|
|
|
|
|
|
arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if (arena == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
log = PORT_ArenaZNew(arena, PK11MergeLog);
|
|
|
|
if (log == NULL) {
|
|
|
|
PORT_FreeArena(arena,PR_FALSE);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
log->arena = arena;
|
|
|
|
log->version = 1;
|
|
|
|
return log;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
PK11_DestroyMergeLog(PK11MergeLog *log)
|
|
|
|
{
|
|
|
|
if (log && log->arena) {
|
|
|
|
PORT_FreeArena(log->arena, PR_FALSE);
|
|
|
|
}
|
|
|
|
}
|