| /* 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 <windows.h> |
| #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; |
| } |
| |
| /* |
| * 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; |
| |
| sprintf(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; |
| |
| sprintf(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; |
| |
| sprintf(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) || |
| (keyHandle->passwordKey.data == NULL)) { |
| 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)) { |
| 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->passwordKey.data) { |
| SECITEM_ZfreeItem(&handle->passwordKey, PR_FALSE); |
| } |
| if (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; |
| } |
| if (handle->passwordKey.data) { |
| SECITEM_ZfreeItem(&handle->passwordKey, PR_FALSE); |
| handle->passwordKey.data = NULL; |
| } |
| 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; |
| } |