diff options
Diffstat (limited to 'security/nss/lib/pk11wrap/pk11merge.c')
-rw-r--r-- | security/nss/lib/pk11wrap/pk11merge.c | 1437 |
1 files changed, 1437 insertions, 0 deletions
diff --git a/security/nss/lib/pk11wrap/pk11merge.c b/security/nss/lib/pk11wrap/pk11merge.c new file mode 100644 index 0000000000..d6d9da718b --- /dev/null +++ b/security/nss/lib/pk11wrap/pk11merge.c @@ -0,0 +1,1437 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * 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" +#include "certdb.h" + +/************************************************************************* + * + * 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_HANDLE) { + 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(PLArenaPool *arena, + PK11SlotInfo *targetSlot, CK_OBJECT_HANDLE targetID, + PK11SlotInfo *sourceSlot, CK_OBJECT_HANDLE sourceID, + CK_ATTRIBUTE *copyTemplate, CK_ULONG copyTemplateCount) +{ + SECStatus rv; + CK_ATTRIBUTE *newTemplate = NULL; + CK_RV crv; + + crv = PK11_GetAttributes(arena, sourceSlot, sourceID, + copyTemplate, copyTemplateCount); + /* if we have missing attributes, just skip them and create the object */ + if (crv == CKR_ATTRIBUTE_TYPE_INVALID) { + CK_ULONG i, j; + newTemplate = PORT_NewArray(CK_ATTRIBUTE, copyTemplateCount); + if (!newTemplate) { + return SECFailure; + } + /* remove the unknown attributes. If we don't have enough attributes + * PK11_CreateNewObject() will fail */ + for (i = 0, j = 0; i < copyTemplateCount; i++) { + if (copyTemplate[i].ulValueLen != -1) { + newTemplate[j] = copyTemplate[i]; + j++; + } + } + copyTemplate = newTemplate; + copyTemplateCount = j; + crv = PK11_GetAttributes(arena, sourceSlot, sourceID, + copyTemplate, copyTemplateCount); + } + if (crv != CKR_OK) { + PORT_SetError(PK11_MapError(crv)); + PORT_Free(newTemplate); + return SECFailure; + } + if (targetID == CK_INVALID_HANDLE) { + /* we need to create the object */ + rv = PK11_CreateNewObject(targetSlot, CK_INVALID_HANDLE, + copyTemplate, copyTemplateCount, PR_TRUE, &targetID); + } else { + /* update the existing object with the new attributes */ + rv = pk11_setAttributes(targetSlot, targetID, + copyTemplate, copyTemplateCount); + } + if (newTemplate) { + PORT_Free(newTemplate); + } + return rv; +} + +/* + * look for a matching object across tokens. + */ +static SECStatus +pk11_matchAcrossTokens(PLArenaPool *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, PR_FALSE) || + PK11_HasAttributeSet(slot, id, CKA_DECRYPT, PR_FALSE))) { + usage |= KU_KEY_ENCIPHERMENT; + } + if (PK11_HasAttributeSet(slot, id, CKA_DERIVE, PR_FALSE)) { + usage |= KU_KEY_AGREEMENT; + } + if ((PK11_HasAttributeSet(slot, id, CKA_SIGN_RECOVER, PR_FALSE) || + PK11_HasAttributeSet(slot, id, CKA_SIGN, PR_FALSE))) { + 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; + PLArenaPool *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-existent, 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(PLArenaPool *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, PR_FALSE)) { + flags |= CKF_UNWRAP; + } + if (PK11_HasAttributeSet(slot, id, CKA_WRAP, PR_FALSE)) { + flags |= CKF_WRAP; + } + if (PK11_HasAttributeSet(slot, id, CKA_ENCRYPT, PR_FALSE)) { + flags |= CKF_ENCRYPT; + } + if (PK11_HasAttributeSet(slot, id, CKA_DECRYPT, PR_FALSE)) { + flags |= CKF_DECRYPT; + } + if (PK11_HasAttributeSet(slot, id, CKA_DERIVE, PR_FALSE)) { + flags |= CKF_DERIVE; + } + if (PK11_HasAttributeSet(slot, id, CKA_SIGN, PR_FALSE)) { + flags |= CKF_SIGN; + } + if (PK11_HasAttributeSet(slot, id, CKA_SIGN_RECOVER, PR_FALSE)) { + flags |= CKF_SIGN_RECOVER; + } + if (PK11_HasAttributeSet(slot, id, CKA_VERIFY, PR_FALSE)) { + flags |= CKF_VERIFY; + } + if (PK11_HasAttributeSet(slot, id, CKA_VERIFY_RECOVER, PR_FALSE)) { + 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; + int blockSize; + SECItem input; + CK_OBJECT_HANDLE targetKeyID; + CK_FLAGS flags; + PLArenaPool *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 } + }; + const 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; + blockSize = PK11_GetBlockSize(cryptoMechType, NULL); + if (blockSize < 0) { + rv = SECFailure; + goto done; + } + input.len = blockSize; + 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; + PLArenaPool *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 + * + *************************************************************************/ + +/* + * 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; +} + +/* + * 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; + PLArenaPool *arena = NULL; + CK_ATTRIBUTE sourceCKAID = { CKA_ID, NULL, 0 }; + CK_ATTRIBUTE targetCKAID = { CKA_ID, NULL, 0 }; + SECStatus lrv = SECSuccess; + int error = SEC_ERROR_LIBRARY_FAILURE; + + sourceCert = PK11_MakeCertFromHandle(sourceSlot, id, NULL); + if (sourceCert == NULL) { + rv = SECFailure; + goto done; + } + + nickname = PK11_GetObjectNickname(sourceSlot, id); + + /* 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); + } + } + + /* 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; + PLArenaPool *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; + PLArenaPool *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_TRUST) || + (sourceTrust == CKT_NSS_VALID_DELEGATOR)) { + return USE_TARGET; + } + if ((targetTrust == CKT_NSS_MUST_VERIFY_TRUST) || + (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; + PLArenaPool *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]); + + CK_ULONG 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(PLArenaPool *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; + obj->owner = PR_FALSE; + + 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 = SEC_ERROR_LIBRARY_FAILURE; + int 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 = SEC_ERROR_LIBRARY_FAILURE; + int 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 * +PK11_CreateMergeLog(void) +{ + PLArenaPool *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); + } +} |