/* 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/. */ /* * The following code handles the storage of PKCS 11 modules used by the * NSS. For the rest of NSS, only one kind of database handle exists: * * SFTKDBHandle * * There is one SFTKDBHandle for the each key database and one for each cert * database. These databases are opened as associated pairs, one pair per * slot. SFTKDBHandles are reference counted objects. * * Each SFTKDBHandle points to a low level database handle (SDB). This handle * represents the underlying physical database. These objects are not * reference counted, an are 'owned' by their respective SFTKDBHandles. * * */ #include "sftkdb.h" #include "sftkdbti.h" #include "pkcs11t.h" #include "pkcs11i.h" #include "sdb.h" #include "prprf.h" #include "pratom.h" #include "lgglue.h" #include "utilpars.h" #include "secerr.h" #include "softoken.h" #if defined(_WIN32) #include #endif /* * We want all databases to have the same binary representation independent of * endianness or length of the host architecture. In general PKCS #11 attributes * are endian/length independent except those attributes that pass CK_ULONG. * * The following functions fixes up the CK_ULONG type attributes so that the data * base sees a machine independent view. CK_ULONGs are stored as 4 byte network * byte order values (big endian). */ #define BBP 8 PRBool sftkdb_isULONGAttribute(CK_ATTRIBUTE_TYPE type) { switch (type) { case CKA_CERTIFICATE_CATEGORY: case CKA_CERTIFICATE_TYPE: case CKA_CLASS: case CKA_JAVA_MIDP_SECURITY_DOMAIN: case CKA_KEY_GEN_MECHANISM: case CKA_KEY_TYPE: case CKA_MECHANISM_TYPE: case CKA_MODULUS_BITS: case CKA_PRIME_BITS: case CKA_SUBPRIME_BITS: case CKA_VALUE_BITS: case CKA_VALUE_LEN: case CKA_TRUST_DIGITAL_SIGNATURE: case CKA_TRUST_NON_REPUDIATION: case CKA_TRUST_KEY_ENCIPHERMENT: case CKA_TRUST_DATA_ENCIPHERMENT: case CKA_TRUST_KEY_AGREEMENT: case CKA_TRUST_KEY_CERT_SIGN: case CKA_TRUST_CRL_SIGN: case CKA_TRUST_SERVER_AUTH: case CKA_TRUST_CLIENT_AUTH: case CKA_TRUST_CODE_SIGNING: case CKA_TRUST_EMAIL_PROTECTION: case CKA_TRUST_IPSEC_END_SYSTEM: case CKA_TRUST_IPSEC_TUNNEL: case CKA_TRUST_IPSEC_USER: case CKA_TRUST_TIME_STAMPING: case CKA_TRUST_STEP_UP_APPROVED: return PR_TRUE; default: break; } return PR_FALSE; } /* are the attributes private? */ static PRBool sftkdb_isPrivateAttribute(CK_ATTRIBUTE_TYPE type) { switch (type) { case CKA_VALUE: case CKA_PRIVATE_EXPONENT: case CKA_PRIME_1: case CKA_PRIME_2: case CKA_EXPONENT_1: case CKA_EXPONENT_2: case CKA_COEFFICIENT: return PR_TRUE; default: break; } return PR_FALSE; } /* These attributes must be authenticated with an hmac. */ static PRBool sftkdb_isAuthenticatedAttribute(CK_ATTRIBUTE_TYPE type) { switch (type) { case CKA_MODULUS: case CKA_PUBLIC_EXPONENT: case CKA_CERT_SHA1_HASH: case CKA_CERT_MD5_HASH: case CKA_TRUST_SERVER_AUTH: case CKA_TRUST_CLIENT_AUTH: case CKA_TRUST_EMAIL_PROTECTION: case CKA_TRUST_CODE_SIGNING: case CKA_TRUST_STEP_UP_APPROVED: case CKA_NSS_OVERRIDE_EXTENSIONS: return PR_TRUE; default: break; } return PR_FALSE; } /* * convert a native ULONG to a database ulong. Database ulong's * are all 4 byte big endian values. */ void sftk_ULong2SDBULong(unsigned char *data, CK_ULONG value) { int i; for (i = 0; i < SDB_ULONG_SIZE; i++) { data[i] = (value >> (SDB_ULONG_SIZE - 1 - i) * BBP) & 0xff; } } /* * convert a database ulong back to a native ULONG. (reverse of the above * function). */ static CK_ULONG sftk_SDBULong2ULong(unsigned char *data) { int i; CK_ULONG value = 0; for (i = 0; i < SDB_ULONG_SIZE; i++) { value |= (((CK_ULONG)data[i]) << (SDB_ULONG_SIZE - 1 - i) * BBP); } return value; } /* certain trust records are default values, which are the values * returned if the signature check fails anyway. * In those cases, we can skip the signature check. */ PRBool sftkdb_isNullTrust(const CK_ATTRIBUTE *template) { switch (template->type) { case CKA_TRUST_SERVER_AUTH: case CKA_TRUST_CLIENT_AUTH: case CKA_TRUST_EMAIL_PROTECTION: case CKA_TRUST_CODE_SIGNING: if (template->ulValueLen != SDB_ULONG_SIZE) { break; } if (sftk_SDBULong2ULong(template->pValue) == CKT_NSS_TRUST_UNKNOWN) { return PR_TRUE; } break; case CKA_TRUST_STEP_UP_APPROVED: if (template->ulValueLen != 1) { break; } if (*((unsigned char *)(template->pValue)) == 0) { return PR_TRUE; } break; default: break; } return PR_FALSE; } /* * fix up the input templates. Our fixed up ints are stored in data and must * be freed by the caller. The new template must also be freed. If there are no * CK_ULONG attributes, the orignal template is passed in as is. */ static CK_ATTRIBUTE * sftkdb_fixupTemplateIn(const CK_ATTRIBUTE *template, int count, unsigned char **dataOut, int *dataOutSize) { int i; int ulongCount = 0; unsigned char *data; CK_ATTRIBUTE *ntemplate; *dataOut = NULL; *dataOutSize = 0; /* first count the number of CK_ULONG attributes */ for (i = 0; i < count; i++) { /* Don't 'fixup' NULL values */ if (!template[i].pValue) { continue; } if (template[i].ulValueLen == sizeof(CK_ULONG)) { if (sftkdb_isULONGAttribute(template[i].type)) { ulongCount++; } } } /* no attributes to fixup, just call on through */ if (ulongCount == 0) { return (CK_ATTRIBUTE *)template; } /* allocate space for new ULONGS */ data = (unsigned char *)PORT_Alloc(SDB_ULONG_SIZE * ulongCount); if (!data) { return NULL; } /* allocate new template */ ntemplate = PORT_NewArray(CK_ATTRIBUTE, count); if (!ntemplate) { PORT_Free(data); return NULL; } *dataOut = data; *dataOutSize = SDB_ULONG_SIZE * ulongCount; /* copy the old template, fixup the actual ulongs */ for (i = 0; i < count; i++) { ntemplate[i] = template[i]; /* Don't 'fixup' NULL values */ if (!template[i].pValue) { continue; } if (template[i].ulValueLen == sizeof(CK_ULONG)) { if (sftkdb_isULONGAttribute(template[i].type)) { CK_ULONG value = *(CK_ULONG *)template[i].pValue; sftk_ULong2SDBULong(data, value); ntemplate[i].pValue = data; ntemplate[i].ulValueLen = SDB_ULONG_SIZE; data += SDB_ULONG_SIZE; } } } return ntemplate; } static const char SFTKDB_META_SIG_TEMPLATE[] = "sig_%s_%08x_%08x"; /* * return a string describing the database type (key or cert) */ const char * sftkdb_TypeString(SFTKDBHandle *handle) { return (handle->type == SFTK_KEYDB_TYPE) ? "key" : "cert"; } /* * Some attributes are signed with an Hmac and a pbe key generated from * the password. This signature is stored indexed by object handle and * attribute type in the meta data table in the key database. * * Signature entries are indexed by the string * sig_[cert/key]_{ObjectID}_{Attribute} * * This function fetches that pkcs5 signature. Caller supplies a SECItem * pre-allocated to the appropriate size if the SECItem is too small the * function will fail with CKR_BUFFER_TOO_SMALL. */ static CK_RV sftkdb_getRawAttributeSignature(SFTKDBHandle *handle, SDB *db, CK_OBJECT_HANDLE objectID, CK_ATTRIBUTE_TYPE type, SECItem *signText) { char id[30]; CK_RV crv; snprintf(id, sizeof(id), SFTKDB_META_SIG_TEMPLATE, sftkdb_TypeString(handle), (unsigned int)objectID, (unsigned int)type); crv = (*db->sdb_GetMetaData)(db, id, signText, NULL); return crv; } CK_RV sftkdb_GetAttributeSignature(SFTKDBHandle *handle, SFTKDBHandle *keyHandle, CK_OBJECT_HANDLE objectID, CK_ATTRIBUTE_TYPE type, SECItem *signText) { SDB *db = SFTK_GET_SDB(keyHandle); return sftkdb_getRawAttributeSignature(handle, db, objectID, type, signText); } CK_RV sftkdb_DestroyAttributeSignature(SFTKDBHandle *handle, SDB *db, CK_OBJECT_HANDLE objectID, CK_ATTRIBUTE_TYPE type) { char id[30]; CK_RV crv; snprintf(id, sizeof(id), SFTKDB_META_SIG_TEMPLATE, sftkdb_TypeString(handle), (unsigned int)objectID, (unsigned int)type); crv = (*db->sdb_DestroyMetaData)(db, id); return crv; } /* * Some attributes are signed with an Hmac and a pbe key generated from * the password. This signature is stored indexed by object handle and * attribute type in the meta data table in the key database. * * Signature entries are indexed by the string * sig_[cert/key]_{ObjectID}_{Attribute} * * This function stores that pkcs5 signature. */ CK_RV sftkdb_PutAttributeSignature(SFTKDBHandle *handle, SDB *keyTarget, CK_OBJECT_HANDLE objectID, CK_ATTRIBUTE_TYPE type, SECItem *signText) { char id[30]; CK_RV crv; snprintf(id, sizeof(id), SFTKDB_META_SIG_TEMPLATE, sftkdb_TypeString(handle), (unsigned int)objectID, (unsigned int)type); crv = (*keyTarget->sdb_PutMetaData)(keyTarget, id, signText, NULL); return crv; } /* * fix up returned data. NOTE: sftkdb_fixupTemplateIn has already allocated * separate data sections for the database ULONG values. */ static CK_RV sftkdb_fixupTemplateOut(CK_ATTRIBUTE *template, CK_OBJECT_HANDLE objectID, CK_ATTRIBUTE *ntemplate, int count, SFTKDBHandle *handle) { int i; CK_RV crv = CKR_OK; SFTKDBHandle *keyHandle; PRBool checkSig = PR_TRUE; PRBool checkEnc = PR_TRUE; PORT_Assert(handle); /* find the key handle */ keyHandle = handle; if (handle->type != SFTK_KEYDB_TYPE) { checkEnc = PR_FALSE; keyHandle = handle->peerDB; } if ((keyHandle == NULL) || ((SFTK_GET_SDB(keyHandle)->sdb_flags & SDB_HAS_META) == 0) || (sftkdb_PWCached(keyHandle) != SECSuccess)) { checkSig = PR_FALSE; } for (i = 0; i < count; i++) { CK_ULONG length = template[i].ulValueLen; template[i].ulValueLen = ntemplate[i].ulValueLen; /* fixup ulongs */ if (ntemplate[i].ulValueLen == SDB_ULONG_SIZE) { if (sftkdb_isULONGAttribute(template[i].type)) { if (template[i].pValue) { CK_ULONG value; value = sftk_SDBULong2ULong(ntemplate[i].pValue); if (length < sizeof(CK_ULONG)) { template[i].ulValueLen = -1; crv = CKR_BUFFER_TOO_SMALL; continue; } PORT_Memcpy(template[i].pValue, &value, sizeof(CK_ULONG)); } template[i].ulValueLen = sizeof(CK_ULONG); } } /* if no data was retrieved, no need to process encrypted or signed * attributes */ if ((template[i].pValue == NULL) || (template[i].ulValueLen == -1)) { continue; } /* fixup private attributes */ if (checkEnc && sftkdb_isPrivateAttribute(ntemplate[i].type)) { /* we have a private attribute */ /* This code depends on the fact that the cipherText is bigger * than the plain text */ SECItem cipherText; SECItem *plainText; SECStatus rv; cipherText.data = ntemplate[i].pValue; cipherText.len = ntemplate[i].ulValueLen; PZ_Lock(handle->passwordLock); if (handle->passwordKey.data == NULL) { PZ_Unlock(handle->passwordLock); template[i].ulValueLen = -1; crv = CKR_USER_NOT_LOGGED_IN; continue; } rv = sftkdb_DecryptAttribute(handle, &handle->passwordKey, objectID, ntemplate[i].type, &cipherText, &plainText); PZ_Unlock(handle->passwordLock); if (rv != SECSuccess) { PORT_Memset(template[i].pValue, 0, template[i].ulValueLen); template[i].ulValueLen = -1; crv = CKR_GENERAL_ERROR; continue; } PORT_Assert(template[i].ulValueLen >= plainText->len); if (template[i].ulValueLen < plainText->len) { SECITEM_ZfreeItem(plainText, PR_TRUE); PORT_Memset(template[i].pValue, 0, template[i].ulValueLen); template[i].ulValueLen = -1; crv = CKR_GENERAL_ERROR; continue; } /* copy the plain text back into the template */ PORT_Memcpy(template[i].pValue, plainText->data, plainText->len); template[i].ulValueLen = plainText->len; SECITEM_ZfreeItem(plainText, PR_TRUE); } /* make sure signed attributes are valid */ if (checkSig && sftkdb_isAuthenticatedAttribute(ntemplate[i].type) && !sftkdb_isNullTrust(&ntemplate[i])) { SECStatus rv; CK_RV local_crv; SECItem signText; SECItem plainText; unsigned char signData[SDB_MAX_META_DATA_LEN]; signText.data = signData; signText.len = sizeof(signData); /* Use a local variable so that we don't clobber any already * set error. This function returns either CKR_OK or the last * found error in the template */ local_crv = sftkdb_GetAttributeSignature(handle, keyHandle, objectID, ntemplate[i].type, &signText); if (local_crv != CKR_OK) { PORT_Memset(template[i].pValue, 0, template[i].ulValueLen); template[i].ulValueLen = -1; crv = local_crv; continue; } plainText.data = ntemplate[i].pValue; plainText.len = ntemplate[i].ulValueLen; /* * we do a second check holding the lock just in case the user * loggout while we were trying to get the signature. */ PZ_Lock(keyHandle->passwordLock); if (keyHandle->passwordKey.data == NULL) { /* if we are no longer logged in, no use checking the other * Signatures either. */ checkSig = PR_FALSE; PZ_Unlock(keyHandle->passwordLock); continue; } rv = sftkdb_VerifyAttribute(keyHandle, &keyHandle->passwordKey, objectID, ntemplate[i].type, &plainText, &signText); PZ_Unlock(keyHandle->passwordLock); if (rv != SECSuccess) { PORT_Memset(template[i].pValue, 0, template[i].ulValueLen); template[i].ulValueLen = -1; crv = CKR_SIGNATURE_INVALID; /* better error code? */ } /* This Attribute is fine */ } } return crv; } /* * Some attributes are signed with an HMAC and a pbe key generated from * the password. This signature is stored indexed by object handle and * * Those attributes are: * 1) Trust object hashes and trust values. * 2) public key values. * * Certs themselves are considered properly authenticated by virtue of their * signature, or their matching hash with the trust object. * * These signature is only checked for objects coming from shared databases. * Older dbm style databases have such no signature checks. HMACs are also * only checked when the token is logged in, as it requires a pbe generated * from the password. * * Tokens which have no key database (and therefore no master password) do not * have any stored signature values. Signature values are stored in the key * database, since the signature data is tightly coupled to the key database * password. * * This function takes a template of attributes that were either created or * modified. These attributes are checked to see if the need to be signed. * If they do, then this function signs the attributes and writes them * to the meta data store. * * This function can fail if there are attributes that must be signed, but * the token is not logged in. * * The caller is expected to abort any transaction he was in in the * event of a failure of this function. */ static CK_RV sftk_signTemplate(PLArenaPool *arena, SFTKDBHandle *handle, PRBool mayBeUpdateDB, CK_OBJECT_HANDLE objectID, const CK_ATTRIBUTE *template, CK_ULONG count) { unsigned int i; CK_RV crv; SFTKDBHandle *keyHandle = handle; SDB *keyTarget = NULL; PRBool usingPeerDB = PR_FALSE; PRBool inPeerDBTransaction = PR_FALSE; PORT_Assert(handle); if (handle->type != SFTK_KEYDB_TYPE) { keyHandle = handle->peerDB; usingPeerDB = PR_TRUE; } /* no key DB defined? then no need to sign anything */ if (keyHandle == NULL) { crv = CKR_OK; goto loser; } /* When we are in a middle of an update, we have an update database set, * but we want to write to the real database. The bool mayBeUpdateDB is * set to TRUE if it's possible that we want to write an update database * rather than a primary */ keyTarget = (mayBeUpdateDB && keyHandle->update) ? keyHandle->update : keyHandle->db; /* skip the the database does not support meta data */ if ((keyTarget->sdb_flags & SDB_HAS_META) == 0) { crv = CKR_OK; goto loser; } /* If we had to switch databases, we need to initialize a transaction. */ if (usingPeerDB) { crv = (*keyTarget->sdb_Begin)(keyTarget); if (crv != CKR_OK) { goto loser; } inPeerDBTransaction = PR_TRUE; } for (i = 0; i < count; i++) { if (sftkdb_isAuthenticatedAttribute(template[i].type)) { SECStatus rv; SECItem *signText; SECItem plainText; plainText.data = template[i].pValue; plainText.len = template[i].ulValueLen; PZ_Lock(keyHandle->passwordLock); if (keyHandle->passwordKey.data == NULL) { PZ_Unlock(keyHandle->passwordLock); crv = CKR_USER_NOT_LOGGED_IN; goto loser; } rv = sftkdb_SignAttribute(arena, keyHandle, keyTarget, &keyHandle->passwordKey, keyHandle->defaultIterationCount, objectID, template[i].type, &plainText, &signText); PZ_Unlock(keyHandle->passwordLock); if (rv != SECSuccess) { crv = CKR_GENERAL_ERROR; /* better error code here? */ goto loser; } crv = sftkdb_PutAttributeSignature(handle, keyTarget, objectID, template[i].type, signText); if (crv != CKR_OK) { goto loser; } } } crv = CKR_OK; /* If necessary, commit the transaction */ if (inPeerDBTransaction) { crv = (*keyTarget->sdb_Commit)(keyTarget); if (crv != CKR_OK) { goto loser; } inPeerDBTransaction = PR_FALSE; } loser: if (inPeerDBTransaction) { /* The transaction must have failed. Abort. */ (*keyTarget->sdb_Abort)(keyTarget); PORT_Assert(crv != CKR_OK); if (crv == CKR_OK) crv = CKR_GENERAL_ERROR; } return crv; } static CK_RV sftkdb_CreateObject(PLArenaPool *arena, SFTKDBHandle *handle, SDB *db, CK_OBJECT_HANDLE *objectID, CK_ATTRIBUTE *template, CK_ULONG count) { CK_RV crv; crv = (*db->sdb_CreateObject)(db, objectID, template, count); if (crv != CKR_OK) { goto loser; } crv = sftk_signTemplate(arena, handle, (db == handle->update), *objectID, template, count); loser: return crv; } static CK_RV sftkdb_fixupSignatures(SFTKDBHandle *handle, SDB *db, CK_OBJECT_HANDLE oldID, CK_OBJECT_HANDLE newID, CK_ATTRIBUTE *ptemplate, CK_ULONG max_attributes) { unsigned int i; CK_RV crv = CKR_OK; /* if we don't have a meta table, we didn't write any signature objects */ if ((db->sdb_flags & SDB_HAS_META) == 0) { return CKR_OK; } for (i = 0; i < max_attributes; i++) { CK_ATTRIBUTE *att = &ptemplate[i]; CK_ATTRIBUTE_TYPE type = att->type; if (sftkdb_isPrivateAttribute(type)) { /* move the signature from one object handle to another and delete * the old entry */ SECItem signature; unsigned char signData[SDB_MAX_META_DATA_LEN]; signature.data = signData; signature.len = sizeof(signData); crv = sftkdb_getRawAttributeSignature(handle, db, oldID, type, &signature); if (crv != CKR_OK) { /* NOTE: if we ever change our default write from AES_CBC * to AES_KW, We'll need to change this to a continue as * we won't need the integrity record for AES_KW */ break; } crv = sftkdb_PutAttributeSignature(handle, db, newID, type, &signature); if (crv != CKR_OK) { break; } /* now get rid of the old one */ crv = sftkdb_DestroyAttributeSignature(handle, db, oldID, type); if (crv != CKR_OK) { break; } } } return crv; } CK_ATTRIBUTE * sftk_ExtractTemplate(PLArenaPool *arena, SFTKObject *object, SFTKDBHandle *handle, CK_OBJECT_HANDLE objectID, SDB *db, CK_ULONG *pcount, CK_RV *crv) { unsigned int count; CK_ATTRIBUTE *template; unsigned int i, templateIndex; SFTKSessionObject *sessObject = sftk_narrowToSessionObject(object); PRBool doEnc = PR_TRUE; *crv = CKR_OK; if (sessObject == NULL) { *crv = CKR_GENERAL_ERROR; /* internal programming error */ return NULL; } PORT_Assert(handle); /* find the key handle */ if (handle->type != SFTK_KEYDB_TYPE) { doEnc = PR_FALSE; } PZ_Lock(sessObject->attributeLock); count = 0; for (i = 0; i < sessObject->hashSize; i++) { SFTKAttribute *attr; for (attr = sessObject->head[i]; attr; attr = attr->next) { count++; } } template = PORT_ArenaNewArray(arena, CK_ATTRIBUTE, count); if (template == NULL) { PZ_Unlock(sessObject->attributeLock); *crv = CKR_HOST_MEMORY; return NULL; } templateIndex = 0; for (i = 0; i < sessObject->hashSize; i++) { SFTKAttribute *attr; for (attr = sessObject->head[i]; attr; attr = attr->next) { CK_ATTRIBUTE *tp = &template[templateIndex++]; /* copy the attribute */ *tp = attr->attrib; /* fixup ULONG s */ if ((tp->ulValueLen == sizeof(CK_ULONG)) && (sftkdb_isULONGAttribute(tp->type))) { CK_ULONG value = *(CK_ULONG *)tp->pValue; unsigned char *data; tp->pValue = PORT_ArenaAlloc(arena, SDB_ULONG_SIZE); data = (unsigned char *)tp->pValue; if (data == NULL) { *crv = CKR_HOST_MEMORY; break; } sftk_ULong2SDBULong(data, value); tp->ulValueLen = SDB_ULONG_SIZE; } /* encrypt private attributes */ if (doEnc && sftkdb_isPrivateAttribute(tp->type)) { /* we have a private attribute */ SECItem *cipherText; SECItem plainText; SECStatus rv; plainText.data = tp->pValue; plainText.len = tp->ulValueLen; PZ_Lock(handle->passwordLock); if (handle->passwordKey.data == NULL) { PZ_Unlock(handle->passwordLock); *crv = CKR_USER_NOT_LOGGED_IN; break; } rv = sftkdb_EncryptAttribute(arena, handle, db, &handle->passwordKey, handle->defaultIterationCount, objectID, tp->type, &plainText, &cipherText); PZ_Unlock(handle->passwordLock); if (rv == SECSuccess) { tp->pValue = cipherText->data; tp->ulValueLen = cipherText->len; } else { *crv = CKR_GENERAL_ERROR; /* better error code here? */ break; } PORT_Memset(plainText.data, 0, plainText.len); } } } PORT_Assert(templateIndex <= count); PZ_Unlock(sessObject->attributeLock); if (*crv != CKR_OK) { return NULL; } if (pcount) { *pcount = count; } return template; } /* * return a pointer to the attribute in the give template. * The return value is not const, as the caller may modify * the given attribute value, but such modifications will * modify the actual value in the template. */ static CK_ATTRIBUTE * sftkdb_getAttributeFromTemplate(CK_ATTRIBUTE_TYPE attribute, CK_ATTRIBUTE *ptemplate, CK_ULONG len) { CK_ULONG i; for (i = 0; i < len; i++) { if (attribute == ptemplate[i].type) { return &ptemplate[i]; } } return NULL; } static const CK_ATTRIBUTE * sftkdb_getAttributeFromConstTemplate(CK_ATTRIBUTE_TYPE attribute, const CK_ATTRIBUTE *ptemplate, CK_ULONG len) { CK_ULONG i; for (i = 0; i < len; i++) { if (attribute == ptemplate[i].type) { return &ptemplate[i]; } } return NULL; } /* * fetch a template which identifies 'unique' entries based on object type */ static CK_RV sftkdb_getFindTemplate(CK_OBJECT_CLASS objectType, unsigned char *objTypeData, CK_ATTRIBUTE *findTemplate, CK_ULONG *findCount, CK_ATTRIBUTE *ptemplate, int len) { CK_ATTRIBUTE *attr; CK_ULONG count = 1; sftk_ULong2SDBULong(objTypeData, objectType); findTemplate[0].type = CKA_CLASS; findTemplate[0].pValue = objTypeData; findTemplate[0].ulValueLen = SDB_ULONG_SIZE; switch (objectType) { case CKO_CERTIFICATE: case CKO_NSS_TRUST: attr = sftkdb_getAttributeFromTemplate(CKA_ISSUER, ptemplate, len); if (attr == NULL) { return CKR_TEMPLATE_INCOMPLETE; } findTemplate[1] = *attr; attr = sftkdb_getAttributeFromTemplate(CKA_SERIAL_NUMBER, ptemplate, len); if (attr == NULL) { return CKR_TEMPLATE_INCOMPLETE; } findTemplate[2] = *attr; count = 3; break; case CKO_PRIVATE_KEY: case CKO_PUBLIC_KEY: case CKO_SECRET_KEY: attr = sftkdb_getAttributeFromTemplate(CKA_ID, ptemplate, len); if (attr == NULL) { return CKR_TEMPLATE_INCOMPLETE; } if (attr->ulValueLen == 0) { /* key is too generic to determine that it's unique, usually * happens in the key gen case */ return CKR_OBJECT_HANDLE_INVALID; } findTemplate[1] = *attr; count = 2; break; case CKO_NSS_CRL: attr = sftkdb_getAttributeFromTemplate(CKA_SUBJECT, ptemplate, len); if (attr == NULL) { return CKR_TEMPLATE_INCOMPLETE; } findTemplate[1] = *attr; count = 2; break; case CKO_NSS_SMIME: attr = sftkdb_getAttributeFromTemplate(CKA_SUBJECT, ptemplate, len); if (attr == NULL) { return CKR_TEMPLATE_INCOMPLETE; } findTemplate[1] = *attr; attr = sftkdb_getAttributeFromTemplate(CKA_NSS_EMAIL, ptemplate, len); if (attr == NULL) { return CKR_TEMPLATE_INCOMPLETE; } findTemplate[2] = *attr; count = 3; break; default: attr = sftkdb_getAttributeFromTemplate(CKA_VALUE, ptemplate, len); if (attr == NULL) { return CKR_TEMPLATE_INCOMPLETE; } findTemplate[1] = *attr; count = 2; break; } *findCount = count; return CKR_OK; } /* * look to see if this object already exists and return its object ID if * it does. */ static CK_RV sftkdb_lookupObject(SDB *db, CK_OBJECT_CLASS objectType, CK_OBJECT_HANDLE *id, CK_ATTRIBUTE *ptemplate, CK_ULONG len) { CK_ATTRIBUTE findTemplate[3]; CK_ULONG count = 1; CK_ULONG objCount = 0; SDBFind *find = NULL; unsigned char objTypeData[SDB_ULONG_SIZE]; CK_RV crv; *id = CK_INVALID_HANDLE; if (objectType == CKO_NSS_CRL) { return CKR_OK; } crv = sftkdb_getFindTemplate(objectType, objTypeData, findTemplate, &count, ptemplate, len); if (crv == CKR_OBJECT_HANDLE_INVALID) { /* key is too generic to determine that it's unique, usually * happens in the key gen case, tell the caller to go ahead * and just create it */ return CKR_OK; } if (crv != CKR_OK) { return crv; } /* use the raw find, so we get the correct database */ crv = (*db->sdb_FindObjectsInit)(db, findTemplate, count, &find); if (crv != CKR_OK) { return crv; } (*db->sdb_FindObjects)(db, find, id, 1, &objCount); (*db->sdb_FindObjectsFinal)(db, find); if (objCount == 0) { *id = CK_INVALID_HANDLE; } return CKR_OK; } /* * check to see if this template conflicts with others in our current database. */ static CK_RV sftkdb_checkConflicts(SDB *db, CK_OBJECT_CLASS objectType, const CK_ATTRIBUTE *ptemplate, CK_ULONG len, CK_OBJECT_HANDLE sourceID) { CK_ATTRIBUTE findTemplate[2]; unsigned char objTypeData[SDB_ULONG_SIZE]; /* we may need to allocate some temporaries. Keep track of what was * allocated so we can free it in the end */ unsigned char *temp1 = NULL; unsigned char *temp2 = NULL; CK_ULONG objCount = 0; SDBFind *find = NULL; CK_OBJECT_HANDLE id; const CK_ATTRIBUTE *attr, *attr2; CK_RV crv; CK_ATTRIBUTE subject; /* Currently the only conflict is with nicknames pointing to the same * subject when creating or modifying a certificate. */ /* If the object is not a cert, no problem. */ if (objectType != CKO_CERTIFICATE) { return CKR_OK; } /* if not setting a nickname then there's still no problem */ attr = sftkdb_getAttributeFromConstTemplate(CKA_LABEL, ptemplate, len); if ((attr == NULL) || (attr->ulValueLen == 0)) { return CKR_OK; } /* fetch the subject of the source. For creation and merge, this should * be found in the template */ attr2 = sftkdb_getAttributeFromConstTemplate(CKA_SUBJECT, ptemplate, len); if (sourceID == CK_INVALID_HANDLE) { if ((attr2 == NULL) || ((CK_LONG)attr2->ulValueLen < 0)) { crv = CKR_TEMPLATE_INCOMPLETE; goto done; } } else if ((attr2 == NULL) || ((CK_LONG)attr2->ulValueLen <= 0)) { /* sourceID is set if we are trying to modify an existing entry instead * of creating a new one. In this case the subject may not be (probably * isn't) in the template, we have to read it from the database */ subject.type = CKA_SUBJECT; subject.pValue = NULL; subject.ulValueLen = 0; crv = (*db->sdb_GetAttributeValue)(db, sourceID, &subject, 1); if (crv != CKR_OK) { goto done; } if ((CK_LONG)subject.ulValueLen < 0) { crv = CKR_DEVICE_ERROR; /* closest pkcs11 error to corrupted DB */ goto done; } temp1 = subject.pValue = PORT_Alloc(++subject.ulValueLen); if (temp1 == NULL) { crv = CKR_HOST_MEMORY; goto done; } crv = (*db->sdb_GetAttributeValue)(db, sourceID, &subject, 1); if (crv != CKR_OK) { goto done; } attr2 = &subject; } /* check for another cert in the database with the same nickname */ sftk_ULong2SDBULong(objTypeData, objectType); findTemplate[0].type = CKA_CLASS; findTemplate[0].pValue = objTypeData; findTemplate[0].ulValueLen = SDB_ULONG_SIZE; findTemplate[1] = *attr; crv = (*db->sdb_FindObjectsInit)(db, findTemplate, 2, &find); if (crv != CKR_OK) { goto done; } (*db->sdb_FindObjects)(db, find, &id, 1, &objCount); (*db->sdb_FindObjectsFinal)(db, find); /* object count == 0 means no conflicting certs found, * go on with the operation */ if (objCount == 0) { crv = CKR_OK; goto done; } /* There is a least one cert that shares the nickname, make sure it also * matches the subject. */ findTemplate[0] = *attr2; /* we know how big the source subject was. Use that length to create the * space for the target. If it's not enough space, then it means the * source subject is too big, and therefore not a match. GetAttributeValue * will return CKR_BUFFER_TOO_SMALL. Otherwise it should be exactly enough * space (or enough space to be able to compare the result. */ temp2 = findTemplate[0].pValue = PORT_Alloc(++findTemplate[0].ulValueLen); if (temp2 == NULL) { crv = CKR_HOST_MEMORY; goto done; } crv = (*db->sdb_GetAttributeValue)(db, id, findTemplate, 1); if (crv != CKR_OK) { if (crv == CKR_BUFFER_TOO_SMALL) { /* if our buffer is too small, then the Subjects clearly do * not match */ crv = CKR_ATTRIBUTE_VALUE_INVALID; goto loser; } /* otherwise we couldn't get the value, just fail */ goto done; } /* Ok, we have both subjects, make sure they are the same. * Compare the subjects */ if ((findTemplate[0].ulValueLen != attr2->ulValueLen) || (attr2->ulValueLen > 0 && PORT_Memcmp(findTemplate[0].pValue, attr2->pValue, attr2->ulValueLen) != 0)) { crv = CKR_ATTRIBUTE_VALUE_INVALID; goto loser; } crv = CKR_OK; done: /* If we've failed for some other reason than a conflict, make sure we * return an error code other than CKR_ATTRIBUTE_VALUE_INVALID. * (NOTE: neither sdb_FindObjectsInit nor sdb_GetAttributeValue should * return CKR_ATTRIBUTE_VALUE_INVALID, so the following is paranoia). */ if (crv == CKR_ATTRIBUTE_VALUE_INVALID) { crv = CKR_GENERAL_ERROR; /* clearly a programming error */ } /* exit point if we found a conflict */ loser: PORT_Free(temp1); PORT_Free(temp2); return crv; } /* * try to update the template to fix any errors. This is only done * during update. * * NOTE: we must update the template or return an error, or the update caller * will loop forever! * * Two copies of the source code for this algorithm exist in NSS. * Changes must be made in both copies. * The other copy is in pk11_IncrementNickname() in pk11wrap/pk11merge.c. * */ static CK_RV sftkdb_resolveConflicts(PLArenaPool *arena, CK_OBJECT_CLASS objectType, CK_ATTRIBUTE *ptemplate, CK_ULONG *plen) { CK_ATTRIBUTE *attr; char *nickname, *newNickname; unsigned int end, digit; /* sanity checks. We should never get here with these errors */ if (objectType != CKO_CERTIFICATE) { return CKR_GENERAL_ERROR; /* shouldn't happen */ } attr = sftkdb_getAttributeFromTemplate(CKA_LABEL, ptemplate, *plen); if ((attr == NULL) || (attr->ulValueLen == 0)) { return CKR_GENERAL_ERROR; /* shouldn't happen */ } /* update the nickname */ /* is there a number at the end of the nickname already? * if so just increment that number */ nickname = (char *)attr->pValue; /* does nickname end with " #n*" ? */ for (end = attr->ulValueLen - 1; end >= 2 && (digit = nickname[end]) <= '9' && digit >= '0'; end--) /* just scan */ ; if (attr->ulValueLen >= 3 && end < (attr->ulValueLen - 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_ArenaAlloc(arena, attr->ulValueLen + sizeof(num2)); if (!newNickname) { return CKR_HOST_MEMORY; } PORT_Memcpy(newNickname, nickname, attr->ulValueLen); PORT_Memcpy(&newNickname[attr->ulValueLen], num2, sizeof(num2)); attr->pValue = newNickname; /* modifies ptemplate */ attr->ulValueLen += 3; /* 3 is strlen(num2) */ return CKR_OK; } for (end = attr->ulValueLen; end-- > 0;) { digit = nickname[end]; if (digit > '9' || digit < '0') { break; } if (digit < '9') { nickname[end]++; return CKR_OK; } nickname[end] = '0'; } /* we overflowed, insert a new '1' for a carry in front of the number */ newNickname = PORT_ArenaAlloc(arena, attr->ulValueLen + 1); if (!newNickname) { return CKR_HOST_MEMORY; } /* PORT_Memcpy should handle len of '0' */ PORT_Memcpy(newNickname, nickname, ++end); newNickname[end] = '1'; PORT_Memset(&newNickname[end + 1], '0', attr->ulValueLen - end); attr->pValue = newNickname; attr->ulValueLen++; return CKR_OK; } /* * set an attribute and sign it if necessary */ static CK_RV sftkdb_setAttributeValue(PLArenaPool *arena, SFTKDBHandle *handle, SDB *db, CK_OBJECT_HANDLE objectID, const CK_ATTRIBUTE *template, CK_ULONG count) { CK_RV crv; crv = (*db->sdb_SetAttributeValue)(db, objectID, template, count); if (crv != CKR_OK) { return crv; } crv = sftk_signTemplate(arena, handle, db == handle->update, objectID, template, count); return crv; } /* * write a softoken object out to the database. */ CK_RV sftkdb_write(SFTKDBHandle *handle, SFTKObject *object, CK_OBJECT_HANDLE *objectID) { CK_ATTRIBUTE *template; PLArenaPool *arena; CK_ULONG count; CK_RV crv; SDB *db; PRBool inTransaction = PR_FALSE; CK_OBJECT_HANDLE id, candidateID; *objectID = CK_INVALID_HANDLE; if (handle == NULL) { return CKR_TOKEN_WRITE_PROTECTED; } db = SFTK_GET_SDB(handle); /* * we have opened a new database, but we have not yet updated it. We are * still running pointing to the old database (so the application can * still read). We don't want to write to the old database at this point, * however, since it leads to user confusion. So at this point we simply * require a user login. Let NSS know this so it can prompt the user. */ if (db == handle->update) { return CKR_USER_NOT_LOGGED_IN; } arena = PORT_NewArena(256); if (arena == NULL) { return CKR_HOST_MEMORY; } crv = (*db->sdb_Begin)(db); if (crv != CKR_OK) { goto loser; } inTransaction = PR_TRUE; crv = (*db->sdb_GetNewObjectID)(db, &candidateID); if (crv != CKR_OK) { goto loser; } template = sftk_ExtractTemplate(arena, object, handle, candidateID, db, &count, &crv); if (!template) { goto loser; } /* * We want to make the base database as free from object specific knowledge * as possible. To maintain compatibility, keep some of the desirable * object specific semantics of the old database. * * These were 2 fold: * 1) there were certain conflicts (like trying to set the same nickname * on two different subjects) that would return an error. * 2) Importing the 'same' object would silently update that object. * * The following 2 functions mimic the desirable effects of these two * semantics without pushing any object knowledge to the underlying database * code. */ /* make sure we don't have attributes that conflict with the existing DB */ crv = sftkdb_checkConflicts(db, object->objclass, template, count, CK_INVALID_HANDLE); if (crv != CKR_OK) { goto loser; } /* Find any copies that match this particular object */ crv = sftkdb_lookupObject(db, object->objclass, &id, template, count); if (crv != CKR_OK) { goto loser; } if (id == CK_INVALID_HANDLE) { *objectID = candidateID; crv = sftkdb_CreateObject(arena, handle, db, objectID, template, count); } else { /* object already exists, modify it's attributes */ *objectID = id; /* The object ID changed from our candidate, we need to move any * signature attribute signatures to the new object ID. */ crv = sftkdb_fixupSignatures(handle, db, candidateID, id, template, count); if (crv != CKR_OK) { goto loser; } crv = sftkdb_setAttributeValue(arena, handle, db, id, template, count); } if (crv != CKR_OK) { goto loser; } crv = (*db->sdb_Commit)(db); inTransaction = PR_FALSE; loser: if (inTransaction) { (*db->sdb_Abort)(db); /* It is trivial to show the following code cannot * happen unless something is horribly wrong with our compilier or * hardware */ PORT_Assert(crv != CKR_OK); if (crv == CKR_OK) crv = CKR_GENERAL_ERROR; } if (arena) { PORT_FreeArena(arena, PR_TRUE); } if (crv == CKR_OK) { *objectID |= (handle->type | SFTK_TOKEN_TYPE); } return crv; } CK_RV sftkdb_FindObjectsInit(SFTKDBHandle *handle, const CK_ATTRIBUTE *template, CK_ULONG count, SDBFind **find) { unsigned char *data = NULL; CK_ATTRIBUTE *ntemplate = NULL; CK_RV crv; int dataSize; SDB *db; if (handle == NULL) { return CKR_OK; } db = SFTK_GET_SDB(handle); if (count != 0) { ntemplate = sftkdb_fixupTemplateIn(template, count, &data, &dataSize); if (ntemplate == NULL) { return CKR_HOST_MEMORY; } } crv = (*db->sdb_FindObjectsInit)(db, ntemplate, count, find); if (data) { PORT_Free(ntemplate); PORT_ZFree(data, dataSize); } return crv; } CK_RV sftkdb_FindObjects(SFTKDBHandle *handle, SDBFind *find, CK_OBJECT_HANDLE *ids, int arraySize, CK_ULONG *count) { CK_RV crv; SDB *db; if (handle == NULL) { *count = 0; return CKR_OK; } db = SFTK_GET_SDB(handle); crv = (*db->sdb_FindObjects)(db, find, ids, arraySize, count); if (crv == CKR_OK) { unsigned int i; for (i = 0; i < *count; i++) { ids[i] |= (handle->type | SFTK_TOKEN_TYPE); } } return crv; } CK_RV sftkdb_FindObjectsFinal(SFTKDBHandle *handle, SDBFind *find) { SDB *db; if (handle == NULL) { return CKR_OK; } db = SFTK_GET_SDB(handle); return (*db->sdb_FindObjectsFinal)(db, find); } CK_RV sftkdb_GetAttributeValue(SFTKDBHandle *handle, CK_OBJECT_HANDLE objectID, CK_ATTRIBUTE *template, CK_ULONG count) { CK_RV crv, crv2; CK_ATTRIBUTE *ntemplate; unsigned char *data = NULL; int dataSize = 0; SDB *db; if (handle == NULL) { return CKR_GENERAL_ERROR; } /* short circuit common attributes */ if (count == 1 && (template[0].type == CKA_TOKEN || template[0].type == CKA_PRIVATE || template[0].type == CKA_SENSITIVE)) { CK_BBOOL boolVal = CK_TRUE; if (template[0].pValue == NULL) { template[0].ulValueLen = sizeof(CK_BBOOL); return CKR_OK; } if (template[0].ulValueLen < sizeof(CK_BBOOL)) { template[0].ulValueLen = -1; return CKR_BUFFER_TOO_SMALL; } if ((template[0].type == CKA_PRIVATE) && (handle->type != SFTK_KEYDB_TYPE)) { boolVal = CK_FALSE; } if ((template[0].type == CKA_SENSITIVE) && (handle->type != SFTK_KEYDB_TYPE)) { boolVal = CK_FALSE; } *(CK_BBOOL *)template[0].pValue = boolVal; template[0].ulValueLen = sizeof(CK_BBOOL); return CKR_OK; } db = SFTK_GET_SDB(handle); /* nothing to do */ if (count == 0) { return CKR_OK; } ntemplate = sftkdb_fixupTemplateIn(template, count, &data, &dataSize); if (ntemplate == NULL) { return CKR_HOST_MEMORY; } objectID &= SFTK_OBJ_ID_MASK; crv = (*db->sdb_GetAttributeValue)(db, objectID, ntemplate, count); crv2 = sftkdb_fixupTemplateOut(template, objectID, ntemplate, count, handle); if (crv == CKR_OK) crv = crv2; if (data) { PORT_Free(ntemplate); PORT_ZFree(data, dataSize); } return crv; } CK_RV sftkdb_SetAttributeValue(SFTKDBHandle *handle, SFTKObject *object, const CK_ATTRIBUTE *template, CK_ULONG count) { CK_ATTRIBUTE *ntemplate; unsigned char *data = NULL; PLArenaPool *arena = NULL; SDB *db; CK_RV crv = CKR_OK; CK_OBJECT_HANDLE objectID = (object->handle & SFTK_OBJ_ID_MASK); PRBool inTransaction = PR_FALSE; int dataSize; if (handle == NULL) { return CKR_TOKEN_WRITE_PROTECTED; } db = SFTK_GET_SDB(handle); /* nothing to do */ if (count == 0) { return CKR_OK; } /* * we have opened a new database, but we have not yet updated it. We are * still running pointing to the old database (so the application can * still read). We don't want to write to the old database at this point, * however, since it leads to user confusion. So at this point we simply * require a user login. Let NSS know this so it can prompt the user. */ if (db == handle->update) { return CKR_USER_NOT_LOGGED_IN; } ntemplate = sftkdb_fixupTemplateIn(template, count, &data, &dataSize); if (ntemplate == NULL) { return CKR_HOST_MEMORY; } /* make sure we don't have attributes that conflict with the existing DB */ crv = sftkdb_checkConflicts(db, object->objclass, ntemplate, count, objectID); if (crv != CKR_OK) { goto loser; } arena = PORT_NewArena(256); if (arena == NULL) { crv = CKR_HOST_MEMORY; goto loser; } crv = (*db->sdb_Begin)(db); if (crv != CKR_OK) { goto loser; } inTransaction = PR_TRUE; crv = sftkdb_setAttributeValue(arena, handle, db, objectID, ntemplate, count); if (crv != CKR_OK) { goto loser; } crv = (*db->sdb_Commit)(db); loser: if (crv != CKR_OK && inTransaction) { (*db->sdb_Abort)(db); } if (data) { PORT_Free(ntemplate); PORT_ZFree(data, dataSize); } if (arena) { PORT_FreeArena(arena, PR_FALSE); } return crv; } CK_RV sftkdb_DestroyObject(SFTKDBHandle *handle, CK_OBJECT_HANDLE objectID, CK_OBJECT_CLASS objclass) { CK_RV crv = CKR_OK; SDB *db; if (handle == NULL) { return CKR_TOKEN_WRITE_PROTECTED; } db = SFTK_GET_SDB(handle); objectID &= SFTK_OBJ_ID_MASK; crv = (*db->sdb_Begin)(db); if (crv != CKR_OK) { return crv; } crv = (*db->sdb_DestroyObject)(db, objectID); if (crv != CKR_OK) { goto loser; } /* if the database supports meta data, delete any old signatures * that we may have added */ if ((db->sdb_flags & SDB_HAS_META) == SDB_HAS_META) { SDB *keydb = db; if (handle->type == SFTK_KEYDB_TYPE) { /* delete any private attribute signatures that might exist */ (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_VALUE); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_PRIVATE_EXPONENT); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_PRIME_1); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_PRIME_2); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_EXPONENT_1); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_EXPONENT_2); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_COEFFICIENT); } else { keydb = SFTK_GET_SDB(handle->peerDB); } /* now destroy any authenticated attributes that may exist */ (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_MODULUS); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_PUBLIC_EXPONENT); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_CERT_SHA1_HASH); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_CERT_MD5_HASH); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_TRUST_SERVER_AUTH); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_TRUST_CLIENT_AUTH); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_TRUST_EMAIL_PROTECTION); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_TRUST_CODE_SIGNING); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_TRUST_STEP_UP_APPROVED); (void)sftkdb_DestroyAttributeSignature(handle, keydb, objectID, CKA_NSS_OVERRIDE_EXTENSIONS); } crv = (*db->sdb_Commit)(db); loser: if (crv != CKR_OK) { (*db->sdb_Abort)(db); } return crv; } CK_RV sftkdb_CloseDB(SFTKDBHandle *handle) { #ifdef NO_FORK_CHECK PRBool parentForkedAfterC_Initialize = PR_FALSE; #endif if (handle == NULL) { return CKR_OK; } if (handle->update) { if (handle->db->sdb_SetForkState) { (*handle->db->sdb_SetForkState)(parentForkedAfterC_Initialize); } (*handle->update->sdb_Close)(handle->update); } if (handle->db) { if (handle->db->sdb_SetForkState) { (*handle->db->sdb_SetForkState)(parentForkedAfterC_Initialize); } (*handle->db->sdb_Close)(handle->db); } if (handle->passwordLock) { PZ_Lock(handle->passwordLock); } if (handle->passwordKey.data) { SECITEM_ZfreeItem(&handle->passwordKey, PR_FALSE); } if (handle->passwordLock) { PZ_Unlock(handle->passwordLock); SKIP_AFTER_FORK(PZ_DestroyLock(handle->passwordLock)); } if (handle->updatePasswordKey) { SECITEM_ZfreeItem(handle->updatePasswordKey, PR_TRUE); } if (handle->updateID) { PORT_Free(handle->updateID); } PORT_Free(handle); return CKR_OK; } /* * reset a database to it's uninitialized state. */ static CK_RV sftkdb_ResetDB(SFTKDBHandle *handle) { CK_RV crv = CKR_OK; SDB *db; if (handle == NULL) { return CKR_TOKEN_WRITE_PROTECTED; } db = SFTK_GET_SDB(handle); crv = (*db->sdb_Begin)(db); if (crv != CKR_OK) { goto loser; } crv = (*db->sdb_Reset)(db); if (crv != CKR_OK) { goto loser; } crv = (*db->sdb_Commit)(db); loser: if (crv != CKR_OK) { (*db->sdb_Abort)(db); } return crv; } CK_RV sftkdb_Begin(SFTKDBHandle *handle) { CK_RV crv = CKR_OK; SDB *db; if (handle == NULL) { return CKR_OK; } db = SFTK_GET_SDB(handle); if (db) { crv = (*db->sdb_Begin)(db); } return crv; } CK_RV sftkdb_Commit(SFTKDBHandle *handle) { CK_RV crv = CKR_OK; SDB *db; if (handle == NULL) { return CKR_OK; } db = SFTK_GET_SDB(handle); if (db) { (*db->sdb_Commit)(db); } return crv; } CK_RV sftkdb_Abort(SFTKDBHandle *handle) { CK_RV crv = CKR_OK; SDB *db; if (handle == NULL) { return CKR_OK; } db = SFTK_GET_SDB(handle); if (db) { crv = (db->sdb_Abort)(db); } return crv; } /* * functions to update the database from an old database */ /* * known attributes */ static const CK_ATTRIBUTE_TYPE known_attributes[] = { CKA_CLASS, CKA_TOKEN, CKA_PRIVATE, CKA_LABEL, CKA_APPLICATION, CKA_VALUE, CKA_OBJECT_ID, CKA_CERTIFICATE_TYPE, CKA_ISSUER, CKA_SERIAL_NUMBER, CKA_AC_ISSUER, CKA_OWNER, CKA_ATTR_TYPES, CKA_TRUSTED, CKA_CERTIFICATE_CATEGORY, CKA_JAVA_MIDP_SECURITY_DOMAIN, CKA_URL, CKA_HASH_OF_SUBJECT_PUBLIC_KEY, CKA_HASH_OF_ISSUER_PUBLIC_KEY, CKA_CHECK_VALUE, CKA_KEY_TYPE, CKA_SUBJECT, CKA_ID, CKA_SENSITIVE, CKA_ENCRYPT, CKA_DECRYPT, CKA_WRAP, CKA_UNWRAP, CKA_SIGN, CKA_SIGN_RECOVER, CKA_VERIFY, CKA_VERIFY_RECOVER, CKA_DERIVE, CKA_START_DATE, CKA_END_DATE, CKA_MODULUS, CKA_MODULUS_BITS, CKA_PUBLIC_EXPONENT, CKA_PRIVATE_EXPONENT, CKA_PRIME_1, CKA_PRIME_2, CKA_EXPONENT_1, CKA_EXPONENT_2, CKA_COEFFICIENT, CKA_PRIME, CKA_SUBPRIME, CKA_BASE, CKA_PRIME_BITS, CKA_SUB_PRIME_BITS, CKA_VALUE_BITS, CKA_VALUE_LEN, CKA_EXTRACTABLE, CKA_LOCAL, CKA_NEVER_EXTRACTABLE, CKA_ALWAYS_SENSITIVE, CKA_KEY_GEN_MECHANISM, CKA_MODIFIABLE, CKA_EC_PARAMS, CKA_EC_POINT, CKA_SECONDARY_AUTH, CKA_AUTH_PIN_FLAGS, CKA_ALWAYS_AUTHENTICATE, CKA_WRAP_WITH_TRUSTED, CKA_WRAP_TEMPLATE, CKA_UNWRAP_TEMPLATE, CKA_HW_FEATURE_TYPE, CKA_RESET_ON_INIT, CKA_HAS_RESET, CKA_PIXEL_X, CKA_PIXEL_Y, CKA_RESOLUTION, CKA_CHAR_ROWS, CKA_CHAR_COLUMNS, CKA_COLOR, CKA_BITS_PER_PIXEL, CKA_CHAR_SETS, CKA_ENCODING_METHODS, CKA_MIME_TYPES, CKA_MECHANISM_TYPE, CKA_REQUIRED_CMS_ATTRIBUTES, CKA_DEFAULT_CMS_ATTRIBUTES, CKA_SUPPORTED_CMS_ATTRIBUTES, CKA_NSS_URL, CKA_NSS_EMAIL, CKA_NSS_SMIME_INFO, CKA_NSS_SMIME_TIMESTAMP, CKA_NSS_PKCS8_SALT, CKA_NSS_PASSWORD_CHECK, CKA_NSS_EXPIRES, CKA_NSS_KRL, CKA_NSS_PQG_COUNTER, CKA_NSS_PQG_SEED, CKA_NSS_PQG_H, CKA_NSS_PQG_SEED_BITS, CKA_NSS_MODULE_SPEC, CKA_TRUST_DIGITAL_SIGNATURE, CKA_TRUST_NON_REPUDIATION, CKA_TRUST_KEY_ENCIPHERMENT, CKA_TRUST_DATA_ENCIPHERMENT, CKA_TRUST_KEY_AGREEMENT, CKA_TRUST_KEY_CERT_SIGN, CKA_TRUST_CRL_SIGN, CKA_TRUST_SERVER_AUTH, CKA_TRUST_CLIENT_AUTH, CKA_TRUST_CODE_SIGNING, CKA_TRUST_EMAIL_PROTECTION, CKA_TRUST_IPSEC_END_SYSTEM, CKA_TRUST_IPSEC_TUNNEL, CKA_TRUST_IPSEC_USER, CKA_TRUST_TIME_STAMPING, CKA_TRUST_STEP_UP_APPROVED, CKA_CERT_SHA1_HASH, CKA_CERT_MD5_HASH, CKA_NSS_DB, CKA_NSS_TRUST, CKA_NSS_OVERRIDE_EXTENSIONS, CKA_PUBLIC_KEY_INFO }; static unsigned int known_attributes_size = sizeof(known_attributes) / sizeof(known_attributes[0]); static CK_RV sftkdb_GetObjectTemplate(SDB *source, CK_OBJECT_HANDLE id, CK_ATTRIBUTE *ptemplate, CK_ULONG *max) { unsigned int i, j; CK_RV crv; if (*max < known_attributes_size) { *max = known_attributes_size; return CKR_BUFFER_TOO_SMALL; } for (i = 0; i < known_attributes_size; i++) { ptemplate[i].type = known_attributes[i]; ptemplate[i].pValue = NULL; ptemplate[i].ulValueLen = 0; } crv = (*source->sdb_GetAttributeValue)(source, id, ptemplate, known_attributes_size); if ((crv != CKR_OK) && (crv != CKR_ATTRIBUTE_TYPE_INVALID)) { return crv; } for (i = 0, j = 0; i < known_attributes_size; i++, j++) { while (i < known_attributes_size && (ptemplate[i].ulValueLen == -1)) { i++; } if (i >= known_attributes_size) { break; } /* cheap optimization */ if (i == j) { continue; } ptemplate[j] = ptemplate[i]; } *max = j; return CKR_OK; } static const char SFTKDB_META_UPDATE_TEMPLATE[] = "upd_%s_%s"; /* * check to see if we have already updated this database. * a NULL updateID means we are trying to do an in place * single database update. In that case we have already * determined that an update was necessary. */ static PRBool sftkdb_hasUpdate(const char *typeString, SDB *db, const char *updateID) { char *id; CK_RV crv; SECItem dummy = { 0, NULL, 0 }; unsigned char dummyData[SDB_MAX_META_DATA_LEN]; if (!updateID) { return PR_FALSE; } id = PR_smprintf(SFTKDB_META_UPDATE_TEMPLATE, typeString, updateID); if (id == NULL) { return PR_FALSE; } dummy.data = dummyData; dummy.len = sizeof(dummyData); crv = (*db->sdb_GetMetaData)(db, id, &dummy, NULL); PR_smprintf_free(id); return crv == CKR_OK ? PR_TRUE : PR_FALSE; } /* * we just completed an update, store the update id * so we don't need to do it again. If non was given, * there is nothing to do. */ static CK_RV sftkdb_putUpdate(const char *typeString, SDB *db, const char *updateID) { char *id; CK_RV crv; SECItem dummy = { 0, NULL, 0 }; /* if no id was given, nothing to do */ if (updateID == NULL) { return CKR_OK; } dummy.data = (unsigned char *)updateID; dummy.len = PORT_Strlen(updateID); id = PR_smprintf(SFTKDB_META_UPDATE_TEMPLATE, typeString, updateID); if (id == NULL) { return PR_FALSE; } crv = (*db->sdb_PutMetaData)(db, id, &dummy, NULL); PR_smprintf_free(id); return crv; } /* * get a ULong attribute from a template: * NOTE: this is a raw templated stored in database order! */ static CK_ULONG sftkdb_getULongFromTemplate(CK_ATTRIBUTE_TYPE type, CK_ATTRIBUTE *ptemplate, CK_ULONG len) { CK_ATTRIBUTE *attr = sftkdb_getAttributeFromTemplate(type, ptemplate, len); if (attr && attr->pValue && attr->ulValueLen == SDB_ULONG_SIZE) { return sftk_SDBULong2ULong(attr->pValue); } return (CK_ULONG)-1; } /* * 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 CK_RV sftkdb_incrementCKAID(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 CKR_OK; } } /* we've now overflowed, fall through and expand the CKA_ID by * one byte */ } buf = PORT_ArenaAlloc(arena, len + 1); if (!buf) { return CKR_HOST_MEMORY; } if (len > 0) { PORT_Memcpy(buf, ptemplate->pValue, len); } buf[len] = 0; ptemplate->pValue = buf; ptemplate->ulValueLen = len + 1; return CKR_OK; } /* * drop an attribute from a template. */ void sftkdb_dropAttribute(CK_ATTRIBUTE *attr, CK_ATTRIBUTE *ptemplate, CK_ULONG *plen) { CK_ULONG count = *plen; CK_ULONG i; for (i = 0; i < count; i++) { if (attr->type == ptemplate[i].type) { break; } } if (i == count) { /* attribute not found */ return; } /* copy the remaining attributes up */ for (i++; i < count; i++) { ptemplate[i - 1] = ptemplate[i]; } /* decrement the template size */ *plen = count - 1; } /* * create some defines for the following functions to document the meaning * of true/false. (make's it easier to remember what means what. */ typedef enum { SFTKDB_DO_NOTHING = 0, SFTKDB_ADD_OBJECT, SFTKDB_MODIFY_OBJECT, SFTKDB_DROP_ATTRIBUTE } sftkdbUpdateStatus; /* * helper function to reconcile a single trust entry. * Identify which trust entry we want to keep. * If we don't need to do anything (the records are already equal). * return SFTKDB_DO_NOTHING. * If we want to use the source version, * return SFTKDB_MODIFY_OBJECT * If we want to use the target version, * return SFTKDB_DROP_ATTRIBUTE * * In the end the caller will remove any attributes in the source * template when SFTKDB_DROP_ATTRIBUTE is specified, then use do a * set attributes with that template on the target if we received * any SFTKDB_MODIFY_OBJECT returns. */ sftkdbUpdateStatus sftkdb_reconcileTrustEntry(PLArenaPool *arena, CK_ATTRIBUTE *target, CK_ATTRIBUTE *source) { CK_ULONG targetTrust = sftkdb_getULongFromTemplate(target->type, target, 1); CK_ULONG sourceTrust = sftkdb_getULongFromTemplate(target->type, source, 1); /* * try to pick the best solution between the source and the * target. Update the source template if we want the target value * to win out. Prefer cases where we don't actually update the * trust entry. */ /* they are the same, everything is already kosher */ if (targetTrust == sourceTrust) { return SFTKDB_DO_NOTHING; } /* handle the case where the source Trust attribute may be a bit * flakey */ if (sourceTrust == (CK_ULONG)-1) { /* * The source Trust is invalid. We know that the target Trust * must be valid here, otherwise the above * targetTrust == sourceTrust check would have succeeded. */ return SFTKDB_DROP_ATTRIBUTE; } /* target is invalid, use the source's idea of the trust value */ if (targetTrust == (CK_ULONG)-1) { /* overwriting the target in this case is OK */ return SFTKDB_MODIFY_OBJECT; } /* at this point we know that both attributes exist and have the * appropriate length (SDB_ULONG_SIZE). We no longer need to check * ulValueLen for either attribute. */ if (sourceTrust == CKT_NSS_TRUST_UNKNOWN) { return SFTKDB_DROP_ATTRIBUTE; } /* target has no idea, use the source's idea of the trust value */ if (targetTrust == CKT_NSS_TRUST_UNKNOWN) { /* overwriting the target in this case is OK */ return SFTKDB_MODIFY_OBJECT; } /* 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_NOT_TRUTED. Soft ones are ones which don't change the * actual trust of the cert (CKT_MUST_VERIFY_TRUST, * CKT_NSS_VALID_DELEGATOR). */ if ((sourceTrust == CKT_NSS_MUST_VERIFY_TRUST) || (sourceTrust == CKT_NSS_VALID_DELEGATOR)) { return SFTKDB_DROP_ATTRIBUTE; } if ((targetTrust == CKT_NSS_MUST_VERIFY_TRUST) || (targetTrust == CKT_NSS_VALID_DELEGATOR)) { /* again, overwriting the target in this case is OK */ return SFTKDB_MODIFY_OBJECT; } /* both have hard attributes, we have a conflict, let the target win. */ return SFTKDB_DROP_ATTRIBUTE; } const CK_ATTRIBUTE_TYPE sftkdb_trustList[] = { 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 }; #define SFTK_TRUST_TEMPLATE_COUNT \ (sizeof(sftkdb_trustList) / sizeof(sftkdb_trustList[0])) /* * Run through the list of known trust types, and reconcile each trust * entry one by one. Keep track of we really need to write out the source * trust object (overwriting the existing one). */ static sftkdbUpdateStatus sftkdb_reconcileTrust(PLArenaPool *arena, SDB *db, CK_OBJECT_HANDLE id, CK_ATTRIBUTE *ptemplate, CK_ULONG *plen) { CK_ATTRIBUTE trustTemplate[SFTK_TRUST_TEMPLATE_COUNT]; unsigned char trustData[SFTK_TRUST_TEMPLATE_COUNT * SDB_ULONG_SIZE]; sftkdbUpdateStatus update = SFTKDB_DO_NOTHING; CK_ULONG i; CK_RV crv; for (i = 0; i < SFTK_TRUST_TEMPLATE_COUNT; i++) { trustTemplate[i].type = sftkdb_trustList[i]; trustTemplate[i].pValue = &trustData[i * SDB_ULONG_SIZE]; trustTemplate[i].ulValueLen = SDB_ULONG_SIZE; } crv = (*db->sdb_GetAttributeValue)(db, id, trustTemplate, SFTK_TRUST_TEMPLATE_COUNT); if ((crv != CKR_OK) && (crv != CKR_ATTRIBUTE_TYPE_INVALID)) { /* target trust has some problems, update it */ update = SFTKDB_MODIFY_OBJECT; goto done; } for (i = 0; i < SFTK_TRUST_TEMPLATE_COUNT; i++) { CK_ATTRIBUTE *attr = sftkdb_getAttributeFromTemplate( trustTemplate[i].type, ptemplate, *plen); sftkdbUpdateStatus status; /* if target trust value doesn't exist, nothing to merge */ if (trustTemplate[i].ulValueLen == (CK_ULONG)-1) { /* if the source exists, then we want the source entry, * go ahead and update */ if (attr && attr->ulValueLen != (CK_ULONG)-1) { update = SFTKDB_MODIFY_OBJECT; } continue; } /* * the source doesn't have the attribute, go to the next attribute */ if (attr == NULL) { continue; } status = sftkdb_reconcileTrustEntry(arena, &trustTemplate[i], attr); if (status == SFTKDB_MODIFY_OBJECT) { update = SFTKDB_MODIFY_OBJECT; } else if (status == SFTKDB_DROP_ATTRIBUTE) { /* drop the source copy of the attribute, we are going with * the target's version */ sftkdb_dropAttribute(attr, ptemplate, plen); } } /* finally manage stepup */ if (update == SFTKDB_MODIFY_OBJECT) { CK_BBOOL stepUpBool = CK_FALSE; /* if we are going to write from the source, make sure we don't * overwrite the stepup bit if it's on*/ trustTemplate[0].type = CKA_TRUST_STEP_UP_APPROVED; trustTemplate[0].pValue = &stepUpBool; trustTemplate[0].ulValueLen = sizeof(stepUpBool); crv = (*db->sdb_GetAttributeValue)(db, id, trustTemplate, 1); if ((crv == CKR_OK) && (stepUpBool == CK_TRUE)) { sftkdb_dropAttribute(trustTemplate, ptemplate, plen); } } else { /* we currently aren't going to update. If the source stepup bit is * on however, do an update so the target gets it as well */ CK_ATTRIBUTE *attr; attr = sftkdb_getAttributeFromTemplate(CKA_TRUST_STEP_UP_APPROVED, ptemplate, *plen); if (attr && (attr->ulValueLen == sizeof(CK_BBOOL)) && (*(CK_BBOOL *)(attr->pValue) == CK_TRUE)) { update = SFTKDB_MODIFY_OBJECT; } } done: return update; } static sftkdbUpdateStatus sftkdb_handleIDAndName(PLArenaPool *arena, SDB *db, CK_OBJECT_HANDLE id, CK_ATTRIBUTE *ptemplate, CK_ULONG *plen) { sftkdbUpdateStatus update = SFTKDB_DO_NOTHING; CK_ATTRIBUTE *attr1, *attr2; CK_ATTRIBUTE ttemplate[2] = { { CKA_ID, NULL, 0 }, { CKA_LABEL, NULL, 0 } }; attr1 = sftkdb_getAttributeFromTemplate(CKA_LABEL, ptemplate, *plen); attr2 = sftkdb_getAttributeFromTemplate(CKA_ID, ptemplate, *plen); /* if the source has neither an id nor label, don't bother updating */ if ((!attr1 || attr1->ulValueLen == 0) && (!attr2 || attr2->ulValueLen == 0)) { return SFTKDB_DO_NOTHING; } /* the source has either an id or a label, see what the target has */ (void)(*db->sdb_GetAttributeValue)(db, id, ttemplate, 2); /* if the target has neither, update from the source */ if (((ttemplate[0].ulValueLen == 0) || (ttemplate[0].ulValueLen == (CK_ULONG)-1)) && ((ttemplate[1].ulValueLen == 0) || (ttemplate[1].ulValueLen == (CK_ULONG)-1))) { return SFTKDB_MODIFY_OBJECT; } /* check the CKA_ID */ if ((ttemplate[0].ulValueLen != 0) && (ttemplate[0].ulValueLen != (CK_ULONG)-1)) { /* we have a CKA_ID in the target, don't overwrite * the target with an empty CKA_ID from the source*/ if (attr1 && attr1->ulValueLen == 0) { sftkdb_dropAttribute(attr1, ptemplate, plen); } } else if (attr1 && attr1->ulValueLen != 0) { /* source has a CKA_ID, but the target doesn't, update the target */ update = SFTKDB_MODIFY_OBJECT; } /* check the nickname */ if ((ttemplate[1].ulValueLen != 0) && (ttemplate[1].ulValueLen != (CK_ULONG)-1)) { /* we have a nickname in the target, and we don't have to update * the CKA_ID. We are done. NOTE: if we add addition attributes * in this check, this shortcut can only go on the last of them. */ if (update == SFTKDB_DO_NOTHING) { return update; } /* we have a nickname in the target, don't overwrite * the target with an empty nickname from the source */ if (attr2 && attr2->ulValueLen == 0) { sftkdb_dropAttribute(attr2, ptemplate, plen); } } else if (attr2 && attr2->ulValueLen != 0) { /* source has a nickname, but the target doesn't, update the target */ update = SFTKDB_MODIFY_OBJECT; } return update; } /* * This function updates the template before we write the object out. * * If we are going to skip updating this object, return PR_FALSE. * If it should be updated we return PR_TRUE. * To help readability, these have been defined * as SFTK_DONT_UPDATE and SFTK_UPDATE respectively. */ static PRBool sftkdb_updateObjectTemplate(PLArenaPool *arena, SDB *db, CK_OBJECT_CLASS objectType, CK_ATTRIBUTE *ptemplate, CK_ULONG *plen, CK_OBJECT_HANDLE *targetID) { PRBool done; /* should we repeat the loop? */ CK_OBJECT_HANDLE id; CK_RV crv = CKR_OK; do { crv = sftkdb_checkConflicts(db, objectType, ptemplate, *plen, CK_INVALID_HANDLE); if (crv != CKR_ATTRIBUTE_VALUE_INVALID) { break; } crv = sftkdb_resolveConflicts(arena, objectType, ptemplate, plen); } while (crv == CKR_OK); if (crv != CKR_OK) { return SFTKDB_DO_NOTHING; } do { done = PR_TRUE; crv = sftkdb_lookupObject(db, objectType, &id, ptemplate, *plen); if (crv != CKR_OK) { return SFTKDB_DO_NOTHING; } /* This object already exists, merge it, don't update */ if (id != CK_INVALID_HANDLE) { CK_ATTRIBUTE *attr = NULL; /* special post processing for attributes */ switch (objectType) { case CKO_CERTIFICATE: case CKO_PUBLIC_KEY: case CKO_PRIVATE_KEY: /* update target's CKA_ID and labels if they don't already * exist */ *targetID = id; return sftkdb_handleIDAndName(arena, db, id, ptemplate, plen); case CKO_NSS_TRUST: /* if we have conflicting trust object types, * we need to reconcile them */ *targetID = id; return sftkdb_reconcileTrust(arena, db, id, ptemplate, plen); case CKO_SECRET_KEY: /* secret keys in the old database are all sdr keys, * unfortunately they all appear to have the same CKA_ID, * even though they are truly different keys, so we always * want to update these keys, but we need to * give them a new CKA_ID */ /* NOTE: this changes ptemplate */ attr = sftkdb_getAttributeFromTemplate(CKA_ID, ptemplate, *plen); crv = attr ? sftkdb_incrementCKAID(arena, attr) : CKR_HOST_MEMORY; /* in the extremely rare event that we needed memory and * couldn't get it, just drop the key */ if (crv != CKR_OK) { return SFTKDB_DO_NOTHING; } done = PR_FALSE; /* repeat this find loop */ break; default: /* for all other objects, if we found the equivalent object, * don't update it */ return SFTKDB_DO_NOTHING; } } } while (!done); /* this object doesn't exist, update it */ return SFTKDB_ADD_OBJECT; } static CK_RV sftkdb_updateIntegrity(PLArenaPool *arena, SFTKDBHandle *handle, SDB *source, CK_OBJECT_HANDLE sourceID, SDB *target, CK_OBJECT_HANDLE targetID, CK_ATTRIBUTE *ptemplate, CK_ULONG max_attributes) { unsigned int i; CK_RV global_crv = CKR_OK; /* if the target doesn't have META data, don't need to do anything */ if ((target->sdb_flags & SDB_HAS_META) == 0) { return CKR_OK; } /* if the source doesn't have meta data, then the record won't require * integrity */ if ((source->sdb_flags & SDB_HAS_META) == 0) { return CKR_OK; } for (i = 0; i < max_attributes; i++) { CK_ATTRIBUTE *att = &ptemplate[i]; CK_ATTRIBUTE_TYPE type = att->type; if (sftkdb_isPrivateAttribute(type)) { /* copy integrity signatures associated with this record (if any) */ SECItem signature; unsigned char signData[SDB_MAX_META_DATA_LEN]; CK_RV crv; signature.data = signData; signature.len = sizeof(signData); crv = sftkdb_getRawAttributeSignature(handle, source, sourceID, type, &signature); if (crv != CKR_OK) { /* old databases don't have signature IDs because they are * 3DES encrypted. Since we know not to look for integrity * for 3DES records it's OK not to find one here. A new record * will be created when we reencrypt using AES CBC */ continue; } crv = sftkdb_PutAttributeSignature(handle, target, targetID, type, &signature); if (crv != CKR_OK) { /* we had a signature in the source db, but we couldn't store * it in the target, remember the error so we can report it. */ global_crv = crv; } } } return global_crv; } #define MAX_ATTRIBUTES 500 static CK_RV sftkdb_mergeObject(SFTKDBHandle *handle, CK_OBJECT_HANDLE id, SECItem *key) { CK_ATTRIBUTE template[MAX_ATTRIBUTES]; CK_ATTRIBUTE *ptemplate; CK_ULONG max_attributes = MAX_ATTRIBUTES; CK_OBJECT_CLASS objectType; SDB *source = handle->update; SDB *target = handle->db; unsigned int i; CK_OBJECT_HANDLE newID = CK_INVALID_HANDLE; CK_RV crv; PLArenaPool *arena = NULL; arena = PORT_NewArena(256); if (arena == NULL) { return CKR_HOST_MEMORY; } ptemplate = &template[0]; id &= SFTK_OBJ_ID_MASK; crv = sftkdb_GetObjectTemplate(source, id, ptemplate, &max_attributes); if (crv == CKR_BUFFER_TOO_SMALL) { ptemplate = PORT_ArenaNewArray(arena, CK_ATTRIBUTE, max_attributes); if (ptemplate == NULL) { crv = CKR_HOST_MEMORY; } else { crv = sftkdb_GetObjectTemplate(source, id, ptemplate, &max_attributes); } } if (crv != CKR_OK) { goto loser; } for (i = 0; i < max_attributes; i++) { ptemplate[i].pValue = PORT_ArenaAlloc(arena, ptemplate[i].ulValueLen); if (ptemplate[i].pValue == NULL) { crv = CKR_HOST_MEMORY; goto loser; } } crv = (*source->sdb_GetAttributeValue)(source, id, ptemplate, max_attributes); if (crv != CKR_OK) { goto loser; } objectType = sftkdb_getULongFromTemplate(CKA_CLASS, ptemplate, max_attributes); /* * Update Object updates the object template if necessary then returns * whether or not we need to actually write the object out to our target * database. */ if (!handle->updateID) { crv = sftkdb_CreateObject(arena, handle, target, &newID, ptemplate, max_attributes); } else { sftkdbUpdateStatus update_status; update_status = sftkdb_updateObjectTemplate(arena, target, objectType, ptemplate, &max_attributes, &newID); switch (update_status) { case SFTKDB_ADD_OBJECT: crv = sftkdb_CreateObject(arena, handle, target, &newID, ptemplate, max_attributes); break; case SFTKDB_MODIFY_OBJECT: crv = sftkdb_setAttributeValue(arena, handle, target, newID, ptemplate, max_attributes); break; case SFTKDB_DO_NOTHING: case SFTKDB_DROP_ATTRIBUTE: break; } } /* if keyDB copy any meta data hashes to target, Update for the new * object ID */ if (crv == CKR_OK) { crv = sftkdb_updateIntegrity(arena, handle, source, id, target, newID, ptemplate, max_attributes); } loser: if (arena) { PORT_FreeArena(arena, PR_TRUE); } return crv; } #define MAX_IDS 10 /* * update a new database from an old one, now that we have the key */ CK_RV sftkdb_Update(SFTKDBHandle *handle, SECItem *key) { SDBFind *find = NULL; CK_ULONG idCount = MAX_IDS; CK_OBJECT_HANDLE ids[MAX_IDS]; SECItem *updatePasswordKey = NULL; CK_RV crv, crv2; PRBool inTransaction = PR_FALSE; unsigned int i; if (handle == NULL) { return CKR_OK; } if (handle->update == NULL) { return CKR_OK; } /* * put the whole update under a transaction. This allows us to handle * any possible race conditions between with the updateID check. */ crv = (*handle->db->sdb_Begin)(handle->db); if (crv != CKR_OK) { return crv; } inTransaction = PR_TRUE; /* some one else has already updated this db */ if (sftkdb_hasUpdate(sftkdb_TypeString(handle), handle->db, handle->updateID)) { crv = CKR_OK; goto done; } updatePasswordKey = sftkdb_GetUpdatePasswordKey(handle); if (updatePasswordKey) { /* pass the source DB key to the legacy code, * so it can decrypt things */ handle->oldKey = updatePasswordKey; } /* find all the objects */ crv = sftkdb_FindObjectsInit(handle, NULL, 0, &find); if (crv != CKR_OK) { goto loser; } while ((crv == CKR_OK) && (idCount == MAX_IDS)) { crv = sftkdb_FindObjects(handle, find, ids, MAX_IDS, &idCount); for (i = 0; (crv == CKR_OK) && (i < idCount); i++) { crv = sftkdb_mergeObject(handle, ids[i], key); } } crv2 = sftkdb_FindObjectsFinal(handle, find); if (crv == CKR_OK) crv = crv2; loser: /* no longer need the old key value */ handle->oldKey = NULL; /* update the password - even if we didn't update objects */ if (handle->type == SFTK_KEYDB_TYPE) { SECItem item1, item2; unsigned char data1[SDB_MAX_META_DATA_LEN]; unsigned char data2[SDB_MAX_META_DATA_LEN]; item1.data = data1; item1.len = sizeof(data1); item2.data = data2; item2.len = sizeof(data2); /* if the target db already has a password, skip this. */ crv = (*handle->db->sdb_GetMetaData)(handle->db, "password", &item1, &item2); if (crv == CKR_OK) { goto done; } /* nope, update it from the source */ crv = (*handle->update->sdb_GetMetaData)(handle->update, "password", &item1, &item2); if (crv != CKR_OK) { /* if we get here, neither the source, nor the target has been initialized * with a password entry. Create a metadata table now so that we don't * mistake this for a partially updated database */ item1.data[0] = 0; item2.data[0] = 0; item1.len = item2.len = 1; crv = (*handle->db->sdb_PutMetaData)(handle->db, "empty", &item1, &item2); goto done; } crv = (*handle->db->sdb_PutMetaData)(handle->db, "password", &item1, &item2); if (crv != CKR_OK) { goto done; } } done: /* finally mark this up to date db up to date */ /* some one else has already updated this db */ if (crv == CKR_OK) { crv = sftkdb_putUpdate(sftkdb_TypeString(handle), handle->db, handle->updateID); } if (inTransaction) { if (crv == CKR_OK) { crv = (*handle->db->sdb_Commit)(handle->db); } else { (*handle->db->sdb_Abort)(handle->db); } } if (handle->update) { (*handle->update->sdb_Close)(handle->update); handle->update = NULL; } if (handle->updateID) { PORT_Free(handle->updateID); handle->updateID = NULL; } sftkdb_FreeUpdatePasswordKey(handle); if (updatePasswordKey) { SECITEM_ZfreeItem(updatePasswordKey, PR_TRUE); } handle->updateDBIsInit = PR_FALSE; return crv; } /****************************************************************** * DB handle managing functions. * * These functions are called by softoken to initialize, acquire, * and release database handles. */ const char * sftkdb_GetUpdateID(SFTKDBHandle *handle) { return handle->updateID; } /* release a database handle */ void sftk_freeDB(SFTKDBHandle *handle) { PRInt32 ref; if (!handle) return; ref = PR_ATOMIC_DECREMENT(&handle->ref); if (ref == 0) { sftkdb_CloseDB(handle); } return; } /* * acquire a database handle for a certificate db * (database for public objects) */ SFTKDBHandle * sftk_getCertDB(SFTKSlot *slot) { SFTKDBHandle *dbHandle; PZ_Lock(slot->slotLock); dbHandle = slot->certDB; if (dbHandle) { (void)PR_ATOMIC_INCREMENT(&dbHandle->ref); } PZ_Unlock(slot->slotLock); return dbHandle; } /* * acquire a database handle for a key database * (database for private objects) */ SFTKDBHandle * sftk_getKeyDB(SFTKSlot *slot) { SFTKDBHandle *dbHandle; SKIP_AFTER_FORK(PZ_Lock(slot->slotLock)); dbHandle = slot->keyDB; if (dbHandle) { (void)PR_ATOMIC_INCREMENT(&dbHandle->ref); } SKIP_AFTER_FORK(PZ_Unlock(slot->slotLock)); return dbHandle; } /* * acquire the database for a specific object. NOTE: objectID must point * to a Token object! */ SFTKDBHandle * sftk_getDBForTokenObject(SFTKSlot *slot, CK_OBJECT_HANDLE objectID) { SFTKDBHandle *dbHandle; PZ_Lock(slot->slotLock); dbHandle = objectID & SFTK_KEYDB_TYPE ? slot->keyDB : slot->certDB; if (dbHandle) { (void)PR_ATOMIC_INCREMENT(&dbHandle->ref); } PZ_Unlock(slot->slotLock); return dbHandle; } /* * initialize a new database handle */ static SFTKDBHandle * sftk_NewDBHandle(SDB *sdb, int type, PRBool legacy) { SFTKDBHandle *handle = PORT_New(SFTKDBHandle); handle->ref = 1; handle->db = sdb; handle->update = NULL; handle->peerDB = NULL; handle->newKey = NULL; handle->oldKey = NULL; handle->updatePasswordKey = NULL; handle->updateID = NULL; handle->type = type; handle->usesLegacyStorage = legacy; handle->passwordKey.data = NULL; handle->passwordKey.len = 0; handle->passwordLock = NULL; if (type == SFTK_KEYDB_TYPE) { handle->passwordLock = PZ_NewLock(nssILockAttribute); } sdb->app_private = handle; return handle; } /* * reset the key database to it's uninitialized state. This call * will clear all the key entried. */ SECStatus sftkdb_ResetKeyDB(SFTKDBHandle *handle) { CK_RV crv; /* only rest the key db */ if (handle->type != SFTK_KEYDB_TYPE) { return SECFailure; } crv = sftkdb_ResetDB(handle); if (crv != CKR_OK) { /* set error */ return SECFailure; } PZ_Lock(handle->passwordLock); if (handle->passwordKey.data) { SECITEM_ZfreeItem(&handle->passwordKey, PR_FALSE); handle->passwordKey.data = NULL; } PZ_Unlock(handle->passwordLock); return SECSuccess; } #ifndef NSS_DISABLE_DBM static PRBool sftk_oldVersionExists(const char *dir, int version) { int i; PRStatus exists = PR_FAILURE; char *file = NULL; for (i = version; i > 1; i--) { file = PR_smprintf("%s%d.db", dir, i); if (file == NULL) { continue; } exists = PR_Access(file, PR_ACCESS_EXISTS); PR_smprintf_free(file); if (exists == PR_SUCCESS) { return PR_TRUE; } } return PR_FALSE; } #if defined(_WIN32) /* * Convert an sdb path (encoded in UTF-8) to a legacy path (encoded in the * current system codepage). Fails if the path contains a character outside * the current system codepage. */ static char * sftk_legacyPathFromSDBPath(const char *confdir) { wchar_t *confdirWide; DWORD size; char *nconfdir; BOOL unmappable; if (!confdir) { return NULL; } confdirWide = _NSSUTIL_UTF8ToWide(confdir); if (!confdirWide) { return NULL; } size = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, confdirWide, -1, NULL, 0, NULL, &unmappable); if (size == 0 || unmappable) { PORT_Free(confdirWide); return NULL; } nconfdir = PORT_Alloc(sizeof(char) * size); if (!nconfdir) { PORT_Free(confdirWide); return NULL; } size = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, confdirWide, -1, nconfdir, size, NULL, &unmappable); PORT_Free(confdirWide); if (size == 0 || unmappable) { PORT_Free(nconfdir); return NULL; } return nconfdir; } #else #define sftk_legacyPathFromSDBPath(confdir) PORT_Strdup((confdir)) #endif static PRBool sftk_hasLegacyDB(const char *confdir, const char *certPrefix, const char *keyPrefix, int certVersion, int keyVersion) { char *dir; PRBool exists; if (certPrefix == NULL) { certPrefix = ""; } if (keyPrefix == NULL) { keyPrefix = ""; } dir = PR_smprintf("%s/%scert", confdir, certPrefix); if (dir == NULL) { return PR_FALSE; } exists = sftk_oldVersionExists(dir, certVersion); PR_smprintf_free(dir); if (exists) { return PR_TRUE; } dir = PR_smprintf("%s/%skey", confdir, keyPrefix); if (dir == NULL) { return PR_FALSE; } exists = sftk_oldVersionExists(dir, keyVersion); PR_smprintf_free(dir); return exists; } #endif /* NSS_DISABLE_DBM */ /* * initialize certificate and key database handles as a pair. * * This function figures out what type of database we are opening and * calls the appropriate low level function to open the database. * It also figures out whether or not to setup up automatic update. */ CK_RV sftk_DBInit(const char *configdir, const char *certPrefix, const char *keyPrefix, const char *updatedir, const char *updCertPrefix, const char *updKeyPrefix, const char *updateID, PRBool readOnly, PRBool noCertDB, PRBool noKeyDB, PRBool forceOpen, PRBool isFIPS, SFTKDBHandle **certDB, SFTKDBHandle **keyDB) { const char *confdir; NSSDBType dbType = NSS_DB_TYPE_NONE; char *appName = NULL; SDB *keySDB, *certSDB; CK_RV crv = CKR_OK; int flags = SDB_RDONLY; PRBool newInit = PR_FALSE; #ifndef NSS_DISABLE_DBM PRBool needUpdate = PR_FALSE; #endif /* NSS_DISABLE_DBM */ char *nconfdir = NULL; PRBool legacy = PR_TRUE; if (!readOnly) { flags = SDB_CREATE; } if (isFIPS) { flags |= SDB_FIPS; } *certDB = NULL; *keyDB = NULL; if (noKeyDB && noCertDB) { return CKR_OK; } confdir = _NSSUTIL_EvaluateConfigDir(configdir, &dbType, &appName); /* * now initialize the appropriate database */ switch (dbType) { #ifndef NSS_DISABLE_DBM case NSS_DB_TYPE_LEGACY: crv = sftkdbCall_open(confdir, certPrefix, keyPrefix, 8, 3, flags, noCertDB ? NULL : &certSDB, noKeyDB ? NULL : &keySDB); break; case NSS_DB_TYPE_MULTIACCESS: crv = sftkdbCall_open(configdir, certPrefix, keyPrefix, 8, 3, flags, noCertDB ? NULL : &certSDB, noKeyDB ? NULL : &keySDB); break; #endif /* NSS_DISABLE_DBM */ case NSS_DB_TYPE_SQL: case NSS_DB_TYPE_EXTERN: /* SHOULD open a loadable db */ crv = s_open(confdir, certPrefix, keyPrefix, 9, 4, flags, noCertDB ? NULL : &certSDB, noKeyDB ? NULL : &keySDB, &newInit); legacy = PR_FALSE; #ifndef NSS_DISABLE_DBM /* * if we failed to open the DB's read only, use the old ones if * the exists. */ if (crv != CKR_OK) { legacy = PR_TRUE; if ((flags & SDB_RDONLY) == SDB_RDONLY) { nconfdir = sftk_legacyPathFromSDBPath(confdir); } if (nconfdir && sftk_hasLegacyDB(nconfdir, certPrefix, keyPrefix, 8, 3)) { /* we have legacy databases, if we failed to open the new format * DB's read only, just use the legacy ones */ crv = sftkdbCall_open(nconfdir, certPrefix, keyPrefix, 8, 3, flags, noCertDB ? NULL : &certSDB, noKeyDB ? NULL : &keySDB); } /* Handle the database merge case. * * For the merge case, we need help from the application. Only * the application knows where the old database is, and what unique * identifier it has associated with it. * * If the client supplies these values, we use them to determine * if we need to update. */ } else if ( /* both update params have been supplied */ updatedir && *updatedir && updateID && *updateID /* old dbs exist? */ && sftk_hasLegacyDB(updatedir, updCertPrefix, updKeyPrefix, 8, 3) /* and they have not yet been updated? */ && ((noKeyDB || !sftkdb_hasUpdate("key", keySDB, updateID)) || (noCertDB || !sftkdb_hasUpdate("cert", certSDB, updateID)))) { /* we need to update */ confdir = updatedir; certPrefix = updCertPrefix; keyPrefix = updKeyPrefix; needUpdate = PR_TRUE; } else if (newInit) { /* if the new format DB was also a newly created DB, and we * succeeded, then need to update that new database with data * from the existing legacy DB */ nconfdir = sftk_legacyPathFromSDBPath(confdir); if (nconfdir && sftk_hasLegacyDB(nconfdir, certPrefix, keyPrefix, 8, 3)) { confdir = nconfdir; needUpdate = PR_TRUE; } } #endif /* NSS_DISABLE_DBM */ break; default: crv = CKR_GENERAL_ERROR; /* can't happen, EvaluationConfigDir MUST * return one of the types we already * specified. */ } if (crv != CKR_OK) { goto done; } if (!noCertDB) { *certDB = sftk_NewDBHandle(certSDB, SFTK_CERTDB_TYPE, legacy); } else { *certDB = NULL; } if (!noKeyDB) { *keyDB = sftk_NewDBHandle(keySDB, SFTK_KEYDB_TYPE, legacy); } else { *keyDB = NULL; } /* link them together */ if (*certDB) { (*certDB)->peerDB = *keyDB; } if (*keyDB) { (*keyDB)->peerDB = *certDB; } #ifndef NSS_DISABLE_DBM /* * if we need to update, open the legacy database and * mark the handle as needing update. */ if (needUpdate) { SDB *updateCert = NULL; SDB *updateKey = NULL; CK_RV crv2; crv2 = sftkdbCall_open(confdir, certPrefix, keyPrefix, 8, 3, flags, noCertDB ? NULL : &updateCert, noKeyDB ? NULL : &updateKey); if (crv2 == CKR_OK) { if (*certDB) { (*certDB)->update = updateCert; (*certDB)->updateID = updateID && *updateID ? PORT_Strdup(updateID) : NULL; updateCert->app_private = (*certDB); } if (*keyDB) { PRBool tokenRemoved = PR_FALSE; (*keyDB)->update = updateKey; (*keyDB)->updateID = updateID && *updateID ? PORT_Strdup(updateID) : NULL; updateKey->app_private = (*keyDB); (*keyDB)->updateDBIsInit = PR_TRUE; (*keyDB)->updateDBIsInit = (sftkdb_HasPasswordSet(*keyDB) == SECSuccess) ? PR_TRUE : PR_FALSE; /* if the password on the key db is NULL, kick off our update * chain of events */ sftkdb_CheckPasswordNull((*keyDB), &tokenRemoved); } else { /* we don't have a key DB, update the certificate DB now */ sftkdb_Update(*certDB, NULL); } } } #endif /* NSS_DISABLE_DBM */ done: if (appName) { PORT_Free(appName); } if (nconfdir) { PORT_Free(nconfdir); } return forceOpen ? CKR_OK : crv; } CK_RV sftkdb_Shutdown(void) { s_shutdown(); #ifndef NSS_DISABLE_DBM sftkdbCall_Shutdown(); #endif /* NSS_DISABLE_DBM */ return CKR_OK; }