/* * 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_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 * *************************************************************************/ /* * 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; 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); /* 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; 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 * PK11_CreateMergeLog(void) { 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); } }