diff options
Diffstat (limited to 'security/nss/lib/softoken/sftkdb.c')
-rw-r--r-- | security/nss/lib/softoken/sftkdb.c | 2716 |
1 files changed, 2716 insertions, 0 deletions
diff --git a/security/nss/lib/softoken/sftkdb.c b/security/nss/lib/softoken/sftkdb.c new file mode 100644 index 000000000..52e516117 --- /dev/null +++ b/security/nss/lib/softoken/sftkdb.c @@ -0,0 +1,2716 @@ +/* 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" + +/* + * 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 + +static 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 i; + int ulongCount = 0; + unsigned char *data; + CK_ATTRIBUTE *ntemplate; + + *dataOut = NULL; + + /* 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; + /* 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_getAttributeSignature(SFTKDBHandle *handle, SFTKDBHandle *keyHandle, + CK_OBJECT_HANDLE objectID, CK_ATTRIBUTE_TYPE type, + SECItem *signText) +{ + SDB *db; + char id[30]; + CK_RV crv; + + db = SFTK_GET_SDB(keyHandle); + + 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; +} + +/* + * 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->passwordKey, + &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_FreeItem(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_FreeItem(plainText, PR_TRUE); + } + /* make sure signed attributes are valid */ + if (checkSig && sftkdb_isAuthenticatedAttribute(ntemplate[i].type)) { + SECStatus rv; + SECItem signText; + SECItem plainText; + unsigned char signData[SDB_MAX_META_DATA_LEN]; + + signText.data = signData; + signText.len = sizeof(signData); + + rv = sftkdb_getAttributeSignature(handle, keyHandle, + objectID, ntemplate[i].type, &signText); + if (rv != SECSuccess) { + PORT_Memset(template[i].pValue, 0, template[i].ulValueLen); + template[i].ulValueLen = -1; + crv = CKR_DATA_INVALID; /* better error code? */ + 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->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->passwordKey, + objectID, template[i].type, + &plainText, &signText); + PZ_Unlock(keyHandle->passwordLock); + if (rv != SECSuccess) { + crv = CKR_GENERAL_ERROR; /* better error code here? */ + goto loser; + } + rv = sftkdb_PutAttributeSignature(handle, keyTarget, + objectID, template[i].type, signText); + if (rv != SECSuccess) { + crv = CKR_GENERAL_ERROR; /* better error code here? */ + 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; +} + +CK_ATTRIBUTE * +sftk_ExtractTemplate(PLArenaPool *arena, SFTKObject *object, + SFTKDBHandle *handle, 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->passwordKey, + &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; + + *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; + } + + template = sftk_ExtractTemplate(arena, object, handle, &count, &crv); + if (!template) { + goto loser; + } + + crv = (*db->sdb_Begin)(db); + if (crv != CKR_OK) { + goto loser; + } + inTransaction = PR_TRUE; + + /* + * 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) { + crv = sftkdb_CreateObject(arena, handle, db, objectID, template, count); + } else { + /* object already exists, modify it's attributes */ + *objectID = id; + 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_FALSE); + } + 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; + SDB *db; + + if (handle == NULL) { + return CKR_OK; + } + db = SFTK_GET_SDB(handle); + + if (count != 0) { + ntemplate = sftkdb_fixupTemplateIn(template, count, &data); + if (ntemplate == NULL) { + return CKR_HOST_MEMORY; + } + } + + crv = (*db->sdb_FindObjectsInit)(db, ntemplate, + count, find); + if (data) { + PORT_Free(ntemplate); + PORT_Free(data); + } + 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; + 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); + 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_Free(data); + } + 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; + + 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); + 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, template, 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, template, 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_Free(data); + } + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + return crv; +} + +CK_RV +sftkdb_DestroyObject(SFTKDBHandle *handle, CK_OBJECT_HANDLE objectID) +{ + 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) { + goto loser; + } + crv = (*db->sdb_DestroyObject)(db, objectID); + 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_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) { + PORT_ZFree(handle->passwordKey.data, handle->passwordKey.len); + } + if (handle->passwordLock) { + SKIP_AFTER_FORK(PZ_DestroyLock(handle->passwordLock)); + } + if (handle->updatePasswordKey) { + SECITEM_FreeItem(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_NETSCAPE_DB, CKA_NETSCAPE_TRUST, CKA_NSS_OVERRIDE_EXTENSIONS +}; + +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; +} + +#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_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, &id, + ptemplate, max_attributes); + } else { + sftkdbUpdateStatus update_status; + update_status = sftkdb_updateObjectTemplate(arena, target, + objectType, ptemplate, &max_attributes, &id); + switch (update_status) { + case SFTKDB_ADD_OBJECT: + crv = sftkdb_CreateObject(arena, handle, target, &id, + ptemplate, max_attributes); + break; + case SFTKDB_MODIFY_OBJECT: + crv = sftkdb_setAttributeValue(arena, handle, target, + id, ptemplate, max_attributes); + break; + case SFTKDB_DO_NOTHING: + case SFTKDB_DROP_ATTRIBUTE: + break; + } + } + +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) { + goto loser; + } + 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) { + 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) +{ + 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->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; + } + return SECSuccess; +} + +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; +} + +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; +} + +/* + * 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; + PRBool needUpdate = PR_FALSE; + + 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) { + 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; + 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); + + /* + * if we failed to open the DB's read only, use the old ones if + * the exists. + */ + if (crv != CKR_OK) { + if (((flags & SDB_RDONLY) == SDB_RDONLY) && + sftk_hasLegacyDB(confdir, 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(confdir, 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 */ + if (sftk_hasLegacyDB(confdir, certPrefix, keyPrefix, 8, 3)) { + needUpdate = PR_TRUE; + } + } + 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); + } else { + *certDB = NULL; + } + if (!noKeyDB) { + *keyDB = sftk_NewDBHandle(keySDB, SFTK_KEYDB_TYPE); + } else { + *keyDB = NULL; + } + + /* link them together */ + if (*certDB) { + (*certDB)->peerDB = *keyDB; + } + if (*keyDB) { + (*keyDB)->peerDB = *certDB; + } + + /* + * 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_CheckPassword((*keyDB), "", &tokenRemoved); + } else { + /* we don't have a key DB, update the certificate DB now */ + sftkdb_Update(*certDB, NULL); + } + } + } +done: + if (appName) { + PORT_Free(appName); + } + return forceOpen ? CKR_OK : crv; +} + +CK_RV +sftkdb_Shutdown(void) +{ + s_shutdown(); + sftkdbCall_Shutdown(); + return CKR_OK; +} |