/* 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/. */
/*
 * Internal PKCS #11 functions. Should only be called by pkcs11.c
 */
#include "pkcs11.h"
#include "lgdb.h"

#include "pcertt.h"
#include "lowkeyi.h"
#include "pcert.h"
#include "blapi.h"
#include "secerr.h"
#include "secasn1.h"

/*
 * Cache the object we are working on during Set's and Get's
 */
typedef struct LGObjectCacheStr {
    CK_OBJECT_CLASS objclass;
    CK_OBJECT_HANDLE handle;
    SDB *sdb;
    void *objectInfo;
    LGFreeFunc infoFree;
    SECItem dbKey;
} LGObjectCache;

static const CK_OBJECT_HANDLE lg_classArray[] = {
    CKO_TRUST, CKO_PRIVATE_KEY, CKO_PUBLIC_KEY, CKO_SECRET_KEY,
    CKO_NSS_TRUST, CKO_NSS_CRL, CKO_NSS_SMIME,
    CKO_CERTIFICATE
};

#define handleToClass(handle) \
    lg_classArray[((handle & LG_TOKEN_TYPE_MASK)) >> LG_TOKEN_TYPE_SHIFT]

static void lg_DestroyObjectCache(LGObjectCache *obj);

static LGObjectCache *
lg_NewObjectCache(SDB *sdb, const SECItem *dbKey, CK_OBJECT_HANDLE handle)
{
    LGObjectCache *obj = NULL;
    SECStatus rv;

    obj = PORT_New(LGObjectCache);
    if (obj == NULL) {
        return NULL;
    }

    obj->objclass = handleToClass(handle);
    obj->handle = handle;
    obj->sdb = sdb;
    obj->objectInfo = NULL;
    obj->infoFree = NULL;
    obj->dbKey.data = NULL;
    obj->dbKey.len = 0;
    lg_DBLock(sdb);
    if (dbKey == NULL) {
        dbKey = lg_lookupTokenKeyByHandle(sdb, handle);
    }
    if (dbKey == NULL) {
        lg_DBUnlock(sdb);
        goto loser;
    }
    rv = SECITEM_CopyItem(NULL, &obj->dbKey, dbKey);
    lg_DBUnlock(sdb);
    if (rv != SECSuccess) {
        goto loser;
    }

    return obj;
loser:
    (void)lg_DestroyObjectCache(obj);
    return NULL;
}

/*
 * free all the data associated with an object. Object reference count must
 * be 'zero'.
 */
static void
lg_DestroyObjectCache(LGObjectCache *obj)
{
    if (obj->dbKey.data) {
        PORT_Free(obj->dbKey.data);
        obj->dbKey.data = NULL;
    }
    if (obj->objectInfo) {
        (*obj->infoFree)(obj->objectInfo);
        obj->objectInfo = NULL;
        obj->infoFree = NULL;
    }
    PORT_Free(obj);
}
/*
 * ******************** Attribute Utilities *******************************
 */

static CK_RV
lg_ULongAttribute(CK_ATTRIBUTE *attr, CK_ATTRIBUTE_TYPE type, CK_ULONG value)
{
    unsigned char *data;
    int i;

    if (attr->pValue == NULL) {
        attr->ulValueLen = 4;
        return CKR_OK;
    }
    if (attr->ulValueLen < 4) {
        attr->ulValueLen = (CK_ULONG)-1;
        return CKR_BUFFER_TOO_SMALL;
    }

    data = (unsigned char *)attr->pValue;
    for (i = 0; i < 4; i++) {
        data[i] = (value >> ((3 - i) * 8)) & 0xff;
    }
    attr->ulValueLen = 4;
    return CKR_OK;
}

static CK_RV
lg_CopyAttribute(CK_ATTRIBUTE *attr, CK_ATTRIBUTE_TYPE type,
                 CK_VOID_PTR value, CK_ULONG len)
{

    if (attr->pValue == NULL) {
        attr->ulValueLen = len;
        return CKR_OK;
    }
    if (attr->ulValueLen < len) {
        attr->ulValueLen = (CK_ULONG)-1;
        return CKR_BUFFER_TOO_SMALL;
    }
    if (len > 0 && value != NULL) {
        PORT_Memcpy(attr->pValue, value, len);
    }
    attr->ulValueLen = len;
    return CKR_OK;
}

static CK_RV
lg_CopyAttributeSigned(CK_ATTRIBUTE *attribute, CK_ATTRIBUTE_TYPE type,
                       void *value, CK_ULONG len)
{
    unsigned char *dval = (unsigned char *)value;
    if (*dval == 0) {
        dval++;
        len--;
    }
    return lg_CopyAttribute(attribute, type, dval, len);
}

static CK_RV
lg_CopyPrivAttribute(CK_ATTRIBUTE *attribute, CK_ATTRIBUTE_TYPE type,
                     void *value, CK_ULONG len, SDB *sdbpw)
{
    SECItem plainText, *cipherText = NULL;
    CK_RV crv = CKR_USER_NOT_LOGGED_IN;
    SECStatus rv;

    plainText.data = value;
    plainText.len = len;
    rv = lg_util_encrypt(NULL, sdbpw, &plainText, &cipherText);
    if (rv != SECSuccess) {
        goto loser;
    }
    crv = lg_CopyAttribute(attribute, type, cipherText->data, cipherText->len);
loser:
    if (cipherText) {
        SECITEM_FreeItem(cipherText, PR_TRUE);
    }
    return crv;
}

static CK_RV
lg_CopyPrivAttrSigned(CK_ATTRIBUTE *attribute, CK_ATTRIBUTE_TYPE type,
                      void *value, CK_ULONG len, SDB *sdbpw)
{
    unsigned char *dval = (unsigned char *)value;

    if (*dval == 0) {
        dval++;
        len--;
    }
    return lg_CopyPrivAttribute(attribute, type, dval, len, sdbpw);
}

static CK_RV
lg_invalidAttribute(CK_ATTRIBUTE *attr)
{
    attr->ulValueLen = (CK_ULONG)-1;
    return CKR_ATTRIBUTE_TYPE_INVALID;
}

#define LG_DEF_ATTRIBUTE(value, len) \
    {                                \
        0, value, len                \
    }

#define LG_CLONE_ATTR(attribute, type, staticAttr) \
    lg_CopyAttribute(attribute, type, staticAttr.pValue, staticAttr.ulValueLen)

CK_BBOOL lg_staticTrueValue = CK_TRUE;
CK_BBOOL lg_staticFalseValue = CK_FALSE;
static const CK_ATTRIBUTE lg_StaticTrueAttr =
    LG_DEF_ATTRIBUTE(&lg_staticTrueValue, sizeof(lg_staticTrueValue));
static const CK_ATTRIBUTE lg_StaticFalseAttr =
    LG_DEF_ATTRIBUTE(&lg_staticFalseValue, sizeof(lg_staticFalseValue));
static const CK_ATTRIBUTE lg_StaticNullAttr = LG_DEF_ATTRIBUTE(NULL, 0);
char lg_StaticOneValue = 1;

/*
 * helper functions which get the database and call the underlying
 * low level database function.
 */
static char *
lg_FindKeyNicknameByPublicKey(SDB *sdb, SECItem *dbKey)
{
    NSSLOWKEYDBHandle *keyHandle;
    char *label;

    keyHandle = lg_getKeyDB(sdb);
    if (!keyHandle) {
        return NULL;
    }

    label = nsslowkey_FindKeyNicknameByPublicKey(keyHandle, dbKey,
                                                 sdb);
    return label;
}

NSSLOWKEYPrivateKey *
lg_FindKeyByPublicKey(SDB *sdb, SECItem *dbKey)
{
    NSSLOWKEYPrivateKey *privKey;
    NSSLOWKEYDBHandle *keyHandle;

    keyHandle = lg_getKeyDB(sdb);
    if (keyHandle == NULL) {
        return NULL;
    }
    privKey = nsslowkey_FindKeyByPublicKey(keyHandle, dbKey, sdb);
    if (privKey == NULL) {
        return NULL;
    }
    return privKey;
}

static certDBEntrySMime *
lg_getSMime(LGObjectCache *obj)
{
    certDBEntrySMime *entry;
    NSSLOWCERTCertDBHandle *certHandle;

    if (obj->objclass != CKO_NSS_SMIME) {
        return NULL;
    }
    if (obj->objectInfo) {
        return (certDBEntrySMime *)obj->objectInfo;
    }

    certHandle = lg_getCertDB(obj->sdb);
    if (!certHandle) {
        return NULL;
    }
    entry = nsslowcert_ReadDBSMimeEntry(certHandle, (char *)obj->dbKey.data);
    obj->objectInfo = (void *)entry;
    obj->infoFree = (LGFreeFunc)nsslowcert_DestroyDBEntry;
    return entry;
}

static certDBEntryRevocation *
lg_getCrl(LGObjectCache *obj)
{
    certDBEntryRevocation *crl;
    PRBool isKrl;
    NSSLOWCERTCertDBHandle *certHandle;

    if (obj->objclass != CKO_NSS_CRL) {
        return NULL;
    }
    if (obj->objectInfo) {
        return (certDBEntryRevocation *)obj->objectInfo;
    }

    isKrl = (PRBool)(obj->handle == LG_TOKEN_KRL_HANDLE);
    certHandle = lg_getCertDB(obj->sdb);
    if (!certHandle) {
        return NULL;
    }

    crl = nsslowcert_FindCrlByKey(certHandle, &obj->dbKey, isKrl);
    obj->objectInfo = (void *)crl;
    obj->infoFree = (LGFreeFunc)nsslowcert_DestroyDBEntry;
    return crl;
}

static NSSLOWCERTCertificate *
lg_getCert(LGObjectCache *obj, NSSLOWCERTCertDBHandle *certHandle)
{
    NSSLOWCERTCertificate *cert;
    CK_OBJECT_CLASS objClass = obj->objclass;

    if ((objClass != CKO_CERTIFICATE) && (objClass != CKO_NSS_TRUST) &&
        (objClass != CKO_TRUST)) {
        return NULL;
    }
    if (objClass == CKO_CERTIFICATE && obj->objectInfo) {
        return (NSSLOWCERTCertificate *)obj->objectInfo;
    }
    cert = nsslowcert_FindCertByKey(certHandle, &obj->dbKey);
    if (objClass == CKO_CERTIFICATE) {
        obj->objectInfo = (void *)cert;
        obj->infoFree = (LGFreeFunc)nsslowcert_DestroyCertificate;
    }
    return cert;
}

static NSSLOWCERTTrust *
lg_getTrust(LGObjectCache *obj, NSSLOWCERTCertDBHandle *certHandle)
{
    NSSLOWCERTTrust *trust;

    if ((obj->objclass != CKO_NSS_TRUST) && (obj->objclass != CKO_TRUST)) {
        return NULL;
    }
    if (obj->objectInfo) {
        return (NSSLOWCERTTrust *)obj->objectInfo;
    }
    trust = nsslowcert_FindTrustByKey(certHandle, &obj->dbKey);
    obj->objectInfo = (void *)trust;
    obj->infoFree = (LGFreeFunc)nsslowcert_DestroyTrust;
    return trust;
}

static NSSLOWKEYPublicKey *
lg_GetPublicKey(LGObjectCache *obj)
{
    NSSLOWKEYPublicKey *pubKey;
    NSSLOWKEYPrivateKey *privKey;

    if (obj->objclass != CKO_PUBLIC_KEY) {
        return NULL;
    }
    if (obj->objectInfo) {
        return (NSSLOWKEYPublicKey *)obj->objectInfo;
    }
    privKey = lg_FindKeyByPublicKey(obj->sdb, &obj->dbKey);
    if (privKey == NULL) {
        return NULL;
    }
    pubKey = lg_nsslowkey_ConvertToPublicKey(privKey);
    lg_nsslowkey_DestroyPrivateKey(privKey);
    obj->objectInfo = (void *)pubKey;
    obj->infoFree = (LGFreeFunc)lg_nsslowkey_DestroyPublicKey;
    return pubKey;
}

/*
 * we need two versions of lg_GetPrivateKey. One version that takes the
 * DB handle so we can pass the handle we have already acquired in,
 *  rather than going through the 'getKeyDB' code again,
 *  which may fail the second time and another which just aquires
 *  the key handle from the sdb (where we don't already have a key handle.
 * This version does the former.
 */
static NSSLOWKEYPrivateKey *
lg_GetPrivateKeyWithDB(LGObjectCache *obj, NSSLOWKEYDBHandle *keyHandle)
{
    NSSLOWKEYPrivateKey *privKey;

    if ((obj->objclass != CKO_PRIVATE_KEY) &&
        (obj->objclass != CKO_SECRET_KEY)) {
        return NULL;
    }
    if (obj->objectInfo) {
        return (NSSLOWKEYPrivateKey *)obj->objectInfo;
    }
    privKey = nsslowkey_FindKeyByPublicKey(keyHandle, &obj->dbKey, obj->sdb);
    if (privKey == NULL) {
        return NULL;
    }
    obj->objectInfo = (void *)privKey;
    obj->infoFree = (LGFreeFunc)lg_nsslowkey_DestroyPrivateKey;
    return privKey;
}

/* this version does the latter */
static NSSLOWKEYPrivateKey *
lg_GetPrivateKey(LGObjectCache *obj)
{
    NSSLOWKEYDBHandle *keyHandle;
    NSSLOWKEYPrivateKey *privKey;

    keyHandle = lg_getKeyDB(obj->sdb);
    if (!keyHandle) {
        return NULL;
    }
    privKey = lg_GetPrivateKeyWithDB(obj, keyHandle);
    return privKey;
}

/* lg_GetPubItem returns data associated with the public key.
 * one only needs to free the public key. This comment is here
 * because this sematic would be non-obvious otherwise. All callers
 * should include this comment.
 */
static SECItem *
lg_GetPubItem(NSSLOWKEYPublicKey *pubKey)
{
    SECItem *pubItem = NULL;
    /* get value to compare from the cert's public key */
    switch (pubKey->keyType) {
        case NSSLOWKEYRSAKey:
            pubItem = &pubKey->u.rsa.modulus;
            break;
        case NSSLOWKEYDSAKey:
            pubItem = &pubKey->u.dsa.publicValue;
            break;
        case NSSLOWKEYDHKey:
            pubItem = &pubKey->u.dh.publicValue;
            break;
        case NSSLOWKEYECKey:
            pubItem = &pubKey->u.ec.publicValue;
            break;
        default:
            break;
    }
    return pubItem;
}

static CK_RV
lg_FindRSAPublicKeyAttribute(NSSLOWKEYPublicKey *key, CK_ATTRIBUTE_TYPE type,
                             CK_ATTRIBUTE *attribute)
{
    unsigned char hash[SHA1_LENGTH];
    CK_KEY_TYPE keyType = CKK_RSA;

    switch (type) {
        case CKA_KEY_TYPE:
            return lg_ULongAttribute(attribute, type, keyType);
        case CKA_ID:
            SHA1_HashBuf(hash, key->u.rsa.modulus.data, key->u.rsa.modulus.len);
            return lg_CopyAttribute(attribute, type, hash, SHA1_LENGTH);
        case CKA_DERIVE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_ENCRYPT:
        case CKA_VERIFY:
        case CKA_VERIFY_RECOVER:
        case CKA_WRAP:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_MODULUS:
            return lg_CopyAttributeSigned(attribute, type, key->u.rsa.modulus.data,
                                          key->u.rsa.modulus.len);
        case CKA_PUBLIC_EXPONENT:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.rsa.publicExponent.data,
                                          key->u.rsa.publicExponent.len);
        default:
            break;
    }
    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindDSAPublicKeyAttribute(NSSLOWKEYPublicKey *key, CK_ATTRIBUTE_TYPE type,
                             CK_ATTRIBUTE *attribute)
{
    unsigned char hash[SHA1_LENGTH];
    CK_KEY_TYPE keyType = CKK_DSA;

    switch (type) {
        case CKA_KEY_TYPE:
            return lg_ULongAttribute(attribute, type, keyType);
        case CKA_ID:
            SHA1_HashBuf(hash, key->u.dsa.publicValue.data,
                         key->u.dsa.publicValue.len);
            return lg_CopyAttribute(attribute, type, hash, SHA1_LENGTH);
        case CKA_DERIVE:
        case CKA_ENCRYPT:
        case CKA_VERIFY_RECOVER:
        case CKA_WRAP:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_VERIFY:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_VALUE:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.dsa.publicValue.data,
                                          key->u.dsa.publicValue.len);
        case CKA_PRIME:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.dsa.params.prime.data,
                                          key->u.dsa.params.prime.len);
        case CKA_SUBPRIME:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.dsa.params.subPrime.data,
                                          key->u.dsa.params.subPrime.len);
        case CKA_BASE:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.dsa.params.base.data,
                                          key->u.dsa.params.base.len);
        default:
            break;
    }
    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindDHPublicKeyAttribute(NSSLOWKEYPublicKey *key, CK_ATTRIBUTE_TYPE type,
                            CK_ATTRIBUTE *attribute)
{
    unsigned char hash[SHA1_LENGTH];
    CK_KEY_TYPE keyType = CKK_DH;

    switch (type) {
        case CKA_KEY_TYPE:
            return lg_ULongAttribute(attribute, type, keyType);
        case CKA_ID:
            SHA1_HashBuf(hash, key->u.dh.publicValue.data, key->u.dh.publicValue.len);
            return lg_CopyAttribute(attribute, type, hash, SHA1_LENGTH);
        case CKA_DERIVE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_ENCRYPT:
        case CKA_VERIFY:
        case CKA_VERIFY_RECOVER:
        case CKA_WRAP:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_VALUE:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.dh.publicValue.data,
                                          key->u.dh.publicValue.len);
        case CKA_PRIME:
            return lg_CopyAttributeSigned(attribute, type, key->u.dh.prime.data,
                                          key->u.dh.prime.len);
        case CKA_BASE:
            return lg_CopyAttributeSigned(attribute, type, key->u.dh.base.data,
                                          key->u.dh.base.len);
        default:
            break;
    }
    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindECPublicKeyAttribute(NSSLOWKEYPublicKey *key, CK_ATTRIBUTE_TYPE type,
                            CK_ATTRIBUTE *attribute)
{
    unsigned char hash[SHA1_LENGTH];
    CK_KEY_TYPE keyType = CKK_EC;

    switch (type) {
        case CKA_KEY_TYPE:
            return lg_ULongAttribute(attribute, type, keyType);
        case CKA_ID:
            SHA1_HashBuf(hash, key->u.ec.publicValue.data,
                         key->u.ec.publicValue.len);
            return lg_CopyAttribute(attribute, type, hash, SHA1_LENGTH);
        case CKA_DERIVE:
        case CKA_VERIFY:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_ENCRYPT:
        case CKA_VERIFY_RECOVER:
        case CKA_WRAP:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_EC_PARAMS:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.ec.ecParams.DEREncoding.data,
                                          key->u.ec.ecParams.DEREncoding.len);
        case CKA_EC_POINT:
            if (PR_GetEnvSecure("NSS_USE_DECODED_CKA_EC_POINT")) {
                return lg_CopyAttributeSigned(attribute, type,
                                              key->u.ec.publicValue.data,
                                              key->u.ec.publicValue.len);
            } else {
                SECItem *pubValue = SEC_ASN1EncodeItem(NULL, NULL,
                                                       &(key->u.ec.publicValue),
                                                       SEC_ASN1_GET(SEC_OctetStringTemplate));
                CK_RV crv;
                if (!pubValue) {
                    return CKR_HOST_MEMORY;
                }
                crv = lg_CopyAttributeSigned(attribute, type,
                                             pubValue->data,
                                             pubValue->len);
                SECITEM_FreeItem(pubValue, PR_TRUE);
                return crv;
            }
        default:
            break;
    }
    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindPublicKeyAttribute(LGObjectCache *obj, CK_ATTRIBUTE_TYPE type,
                          CK_ATTRIBUTE *attribute)
{
    NSSLOWKEYPublicKey *key;
    CK_RV crv;
    char *label;

    switch (type) {
        case CKA_PRIVATE:
        case CKA_SENSITIVE:
        case CKA_ALWAYS_SENSITIVE:
        case CKA_NEVER_EXTRACTABLE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_MODIFIABLE:
        case CKA_EXTRACTABLE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_SUBJECT:
            return LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
        case CKA_START_DATE:
        case CKA_END_DATE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
        case CKA_LABEL:
            label = lg_FindKeyNicknameByPublicKey(obj->sdb, &obj->dbKey);
            if (label == NULL) {
                return LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
            }
            crv = lg_CopyAttribute(attribute, type, label, PORT_Strlen(label));
            PORT_Free(label);
            return crv;
        default:
            break;
    }

    key = lg_GetPublicKey(obj);
    if (key == NULL) {
        if (type == CKA_ID) {
            return LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
        }
        return CKR_OBJECT_HANDLE_INVALID;
    }

    switch (key->keyType) {
        case NSSLOWKEYRSAKey:
            return lg_FindRSAPublicKeyAttribute(key, type, attribute);
        case NSSLOWKEYDSAKey:
            return lg_FindDSAPublicKeyAttribute(key, type, attribute);
        case NSSLOWKEYDHKey:
            return lg_FindDHPublicKeyAttribute(key, type, attribute);
        case NSSLOWKEYECKey:
            return lg_FindECPublicKeyAttribute(key, type, attribute);
        default:
            break;
    }

    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindSecretKeyAttribute(LGObjectCache *obj, CK_ATTRIBUTE_TYPE type,
                          CK_ATTRIBUTE *attribute)
{
    NSSLOWKEYPrivateKey *key;
    char *label;
    unsigned char *keyString;
    CK_RV crv;
    int keyTypeLen;
    CK_ULONG keyLen;
    CK_KEY_TYPE keyType;
    PRUint32 keyTypeStorage;

    switch (type) {
        case CKA_PRIVATE:
        case CKA_SENSITIVE:
        case CKA_ALWAYS_SENSITIVE:
        case CKA_EXTRACTABLE:
        case CKA_DERIVE:
        case CKA_ENCRYPT:
        case CKA_DECRYPT:
        case CKA_SIGN:
        case CKA_VERIFY:
        case CKA_WRAP:
        case CKA_UNWRAP:
        case CKA_MODIFIABLE:
        case CKA_LOCAL:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_NEVER_EXTRACTABLE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_START_DATE:
        case CKA_END_DATE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
        case CKA_LABEL:
            label = lg_FindKeyNicknameByPublicKey(obj->sdb, &obj->dbKey);
            if (label == NULL) {
                return LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
            }
            crv = lg_CopyAttribute(attribute, type, label, PORT_Strlen(label));
            PORT_Free(label);
            return crv;
        case CKA_ID:
            return lg_CopyAttribute(attribute, type, obj->dbKey.data,
                                    obj->dbKey.len);
        case CKA_KEY_TYPE:
        case CKA_VALUE_LEN:
        case CKA_VALUE:
            break;
        default:
            return lg_invalidAttribute(attribute);
    }

    key = lg_GetPrivateKey(obj);
    if (key == NULL) {
        return CKR_OBJECT_HANDLE_INVALID;
    }
    switch (type) {
        case CKA_KEY_TYPE:
            /* handle legacy databases. In legacy databases key_type was stored
             * in host order, with any leading zeros stripped off. Only key types
             * under 0x1f (AES) were stored. We assume that any values which are
             * either 1 byte long (big endian), or have byte[0] between 0 and
             * 0x7f and bytes[1]-bytes[3] equal to '0' (little endian). All other
             * values are assumed to be from the new database, which is always 4
             * bytes in network order */
            keyType = 0;
            keyString = key->u.rsa.coefficient.data;
            keyTypeLen = key->u.rsa.coefficient.len;

            /*
             * Because of various endian and word lengths The database may have
             * stored the keyType value in one of the following formats:
             *   (kt) <= 0x1f
             *                                   length data
             * Big Endian,     pre-3.9, all lengths: 1  (kt)
             * Little Endian,  pre-3.9, 32 bits:     4  (kt) 0  0  0
             * Little Endian,  pre-3.9, 64 bits:     8  (kt) 0  0  0   0  0  0  0
             * All platforms,      3.9, 32 bits:     4    0  0  0 (kt)
             * Big Endian,         3.9, 64 bits:     8    0  0  0 (kt) 0  0  0  0
             * Little  Endian,     3.9, 64 bits:     8    0  0  0  0   0  0  0 (kt)
             * All platforms, >= 3.9.1, all lengths: 4   (a) k1 k2 k3
             * where (a) is 0 or >= 0x80. currently (a) can only be 0.
             */
            /*
             * this key was written on a 64 bit platform with a using NSS 3.9
             * or earlier. Reduce the 64 bit possibilities above. When we are
             * through, we will only have:
             *
             * Big Endian,     pre-3.9, all lengths: 1  (kt)
             * Little Endian,  pre-3.9, all lengths: 4  (kt) 0  0  0
             * All platforms,      3.9, all lengths: 4    0  0  0 (kt)
             * All platforms, => 3.9.1, all lengths: 4   (a) k1 k2 k3
             */
            if (keyTypeLen == 8) {
                keyTypeStorage = *(PRUint32 *)keyString;
                if (keyTypeStorage == 0) {
                    keyString += sizeof(PRUint32);
                }
                keyTypeLen = 4;
            }
            /*
             * Now Handle:
             *
             * All platforms,      3.9, all lengths: 4    0  0  0 (kt)
             * All platforms, => 3.9.1, all lengths: 4   (a) k1 k2 k3
             *
             * NOTE: if  kt == 0 or ak1k2k3 == 0, the test fails and
             * we handle it as:
             *
             * Little Endian,  pre-3.9, all lengths: 4  (kt) 0  0  0
             */
            if (keyTypeLen == sizeof(keyTypeStorage) &&
                (((keyString[0] & 0x80) == 0x80) ||
                 !((keyString[1] == 0) && (keyString[2] == 0) && (keyString[3] == 0)))) {
                PORT_Memcpy(&keyTypeStorage, keyString, sizeof(keyTypeStorage));
                keyType = (CK_KEY_TYPE)PR_ntohl(keyTypeStorage);
            } else {
                /*
                 * Now Handle:
                 *
                 * Big Endian,     pre-3.9, all lengths: 1  (kt)
                 * Little Endian,  pre-3.9, all lengths: 4  (kt) 0  0  0
                 *  -- KeyType == 0 all other cases ---: 4    0  0  0  0
                 */
                keyType = (CK_KEY_TYPE)keyString[0];
            }
            return lg_ULongAttribute(attribute, type, keyType);
        case CKA_VALUE:
            return lg_CopyPrivAttribute(attribute, type, key->u.rsa.privateExponent.data,
                                        key->u.rsa.privateExponent.len, obj->sdb);
        case CKA_VALUE_LEN:
            keyLen = key->u.rsa.privateExponent.len;
            return lg_ULongAttribute(attribute, type, keyLen);
    }
    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindRSAPrivateKeyAttribute(NSSLOWKEYPrivateKey *key, CK_ATTRIBUTE_TYPE type,
                              CK_ATTRIBUTE *attribute, SDB *sdbpw)
{
    unsigned char hash[SHA1_LENGTH];
    CK_KEY_TYPE keyType = CKK_RSA;

    switch (type) {
        case CKA_KEY_TYPE:
            return lg_ULongAttribute(attribute, type, keyType);
        case CKA_ID:
            SHA1_HashBuf(hash, key->u.rsa.modulus.data, key->u.rsa.modulus.len);
            return lg_CopyAttribute(attribute, type, hash, SHA1_LENGTH);
        case CKA_DERIVE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_DECRYPT:
        case CKA_SIGN:
        case CKA_SIGN_RECOVER:
        case CKA_UNWRAP:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_MODULUS:
            return lg_CopyAttributeSigned(attribute, type, key->u.rsa.modulus.data,
                                          key->u.rsa.modulus.len);
        case CKA_PUBLIC_EXPONENT:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.rsa.publicExponent.data,
                                          key->u.rsa.publicExponent.len);
        case CKA_PRIVATE_EXPONENT:
            return lg_CopyPrivAttrSigned(attribute, type,
                                         key->u.rsa.privateExponent.data,
                                         key->u.rsa.privateExponent.len, sdbpw);
        case CKA_PRIME_1:
            return lg_CopyPrivAttrSigned(attribute, type, key->u.rsa.prime1.data,
                                         key->u.rsa.prime1.len, sdbpw);
        case CKA_PRIME_2:
            return lg_CopyPrivAttrSigned(attribute, type, key->u.rsa.prime2.data,
                                         key->u.rsa.prime2.len, sdbpw);
        case CKA_EXPONENT_1:
            return lg_CopyPrivAttrSigned(attribute, type,
                                         key->u.rsa.exponent1.data,
                                         key->u.rsa.exponent1.len, sdbpw);
        case CKA_EXPONENT_2:
            return lg_CopyPrivAttrSigned(attribute, type,
                                         key->u.rsa.exponent2.data,
                                         key->u.rsa.exponent2.len, sdbpw);
        case CKA_COEFFICIENT:
            return lg_CopyPrivAttrSigned(attribute, type,
                                         key->u.rsa.coefficient.data,
                                         key->u.rsa.coefficient.len, sdbpw);
        default:
            break;
    }
    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindDSAPrivateKeyAttribute(NSSLOWKEYPrivateKey *key, CK_ATTRIBUTE_TYPE type,
                              CK_ATTRIBUTE *attribute, SDB *sdbpw)
{
    unsigned char hash[SHA1_LENGTH];
    CK_KEY_TYPE keyType = CKK_DSA;

    switch (type) {
        case CKA_KEY_TYPE:
            return lg_ULongAttribute(attribute, type, keyType);
        case CKA_ID:
            SHA1_HashBuf(hash, key->u.dsa.publicValue.data,
                         key->u.dsa.publicValue.len);
            return lg_CopyAttribute(attribute, type, hash, SHA1_LENGTH);
        case CKA_DERIVE:
        case CKA_DECRYPT:
        case CKA_SIGN_RECOVER:
        case CKA_UNWRAP:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_SIGN:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_VALUE:
            return lg_CopyPrivAttrSigned(attribute, type,
                                         key->u.dsa.privateValue.data,
                                         key->u.dsa.privateValue.len, sdbpw);
        case CKA_PRIME:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.dsa.params.prime.data,
                                          key->u.dsa.params.prime.len);
        case CKA_SUBPRIME:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.dsa.params.subPrime.data,
                                          key->u.dsa.params.subPrime.len);
        case CKA_BASE:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.dsa.params.base.data,
                                          key->u.dsa.params.base.len);
        case CKA_NSS_DB:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.dsa.publicValue.data,
                                          key->u.dsa.publicValue.len);
        default:
            break;
    }
    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindDHPrivateKeyAttribute(NSSLOWKEYPrivateKey *key, CK_ATTRIBUTE_TYPE type,
                             CK_ATTRIBUTE *attribute, SDB *sdbpw)
{
    unsigned char hash[SHA1_LENGTH];
    CK_KEY_TYPE keyType = CKK_DH;

    switch (type) {
        case CKA_KEY_TYPE:
            return lg_ULongAttribute(attribute, type, keyType);
        case CKA_ID:
            SHA1_HashBuf(hash, key->u.dh.publicValue.data, key->u.dh.publicValue.len);
            return lg_CopyAttribute(attribute, type, hash, SHA1_LENGTH);
        case CKA_DERIVE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_DECRYPT:
        case CKA_SIGN:
        case CKA_SIGN_RECOVER:
        case CKA_UNWRAP:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_VALUE:
            return lg_CopyPrivAttrSigned(attribute, type,
                                         key->u.dh.privateValue.data,
                                         key->u.dh.privateValue.len, sdbpw);
        case CKA_PRIME:
            return lg_CopyAttributeSigned(attribute, type, key->u.dh.prime.data,
                                          key->u.dh.prime.len);
        case CKA_BASE:
            return lg_CopyAttributeSigned(attribute, type, key->u.dh.base.data,
                                          key->u.dh.base.len);
        case CKA_NSS_DB:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.dh.publicValue.data,
                                          key->u.dh.publicValue.len);
        default:
            break;
    }
    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindECPrivateKeyAttribute(NSSLOWKEYPrivateKey *key, CK_ATTRIBUTE_TYPE type,
                             CK_ATTRIBUTE *attribute, SDB *sdbpw)
{
    unsigned char hash[SHA1_LENGTH];
    CK_KEY_TYPE keyType = CKK_EC;

    switch (type) {
        case CKA_KEY_TYPE:
            return lg_ULongAttribute(attribute, type, keyType);
        case CKA_ID:
            SHA1_HashBuf(hash, key->u.ec.publicValue.data, key->u.ec.publicValue.len);
            return lg_CopyAttribute(attribute, type, hash, SHA1_LENGTH);
        case CKA_DERIVE:
        case CKA_SIGN:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_DECRYPT:
        case CKA_SIGN_RECOVER:
        case CKA_UNWRAP:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_VALUE:
            return lg_CopyPrivAttribute(attribute, type,
                                        key->u.ec.privateValue.data,
                                        key->u.ec.privateValue.len, sdbpw);
        case CKA_EC_PARAMS:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.ec.ecParams.DEREncoding.data,
                                          key->u.ec.ecParams.DEREncoding.len);
        case CKA_NSS_DB:
            return lg_CopyAttributeSigned(attribute, type,
                                          key->u.ec.publicValue.data,
                                          key->u.ec.publicValue.len);
        default:
            break;
    }
    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindPrivateKeyAttribute(LGObjectCache *obj, CK_ATTRIBUTE_TYPE type,
                           CK_ATTRIBUTE *attribute)
{
    NSSLOWKEYPrivateKey *key;
    char *label;
    CK_RV crv;

    switch (type) {
        case CKA_PRIVATE:
        case CKA_SENSITIVE:
        case CKA_ALWAYS_SENSITIVE:
        case CKA_EXTRACTABLE:
        case CKA_MODIFIABLE:
        case CKA_LOCAL:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_NEVER_EXTRACTABLE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_SUBJECT:
            return LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
        case CKA_START_DATE:
        case CKA_END_DATE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
        case CKA_LABEL:
            label = lg_FindKeyNicknameByPublicKey(obj->sdb, &obj->dbKey);
            if (label == NULL) {
                return LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
            }
            crv = lg_CopyAttribute(attribute, type, label, PORT_Strlen(label));
            PORT_Free(label);
            return crv;
        default:
            break;
    }
    key = lg_GetPrivateKey(obj);
    if (key == NULL) {
        return CKR_OBJECT_HANDLE_INVALID;
    }
    switch (key->keyType) {
        case NSSLOWKEYRSAKey:
            return lg_FindRSAPrivateKeyAttribute(key, type, attribute, obj->sdb);
        case NSSLOWKEYDSAKey:
            return lg_FindDSAPrivateKeyAttribute(key, type, attribute, obj->sdb);
        case NSSLOWKEYDHKey:
            return lg_FindDHPrivateKeyAttribute(key, type, attribute, obj->sdb);
        case NSSLOWKEYECKey:
            return lg_FindECPrivateKeyAttribute(key, type, attribute, obj->sdb);
        default:
            break;
    }

    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindSMIMEAttribute(LGObjectCache *obj, CK_ATTRIBUTE_TYPE type,
                      CK_ATTRIBUTE *attribute)
{
    certDBEntrySMime *entry;
    switch (type) {
        case CKA_PRIVATE:
        case CKA_MODIFIABLE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_NSS_EMAIL:
            return lg_CopyAttribute(attribute, type, obj->dbKey.data,
                                    obj->dbKey.len - 1);
        case CKA_NSS_SMIME_TIMESTAMP:
        case CKA_SUBJECT:
        case CKA_VALUE:
            break;
        default:
            return lg_invalidAttribute(attribute);
    }
    entry = lg_getSMime(obj);
    if (entry == NULL) {
        return CKR_OBJECT_HANDLE_INVALID;
    }
    switch (type) {
        case CKA_NSS_SMIME_TIMESTAMP:
            return lg_CopyAttribute(attribute, type, entry->optionsDate.data,
                                    entry->optionsDate.len);
        case CKA_SUBJECT:
            return lg_CopyAttribute(attribute, type, entry->subjectName.data,
                                    entry->subjectName.len);
        case CKA_VALUE:
            return lg_CopyAttribute(attribute, type, entry->smimeOptions.data,
                                    entry->smimeOptions.len);
        default:
            break;
    }
    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindTrustAttribute(LGObjectCache *obj, CK_ATTRIBUTE_TYPE type,
                      CK_ATTRIBUTE *attribute)
{
    NSSLOWCERTTrust *trust;
    NSSLOWCERTCertDBHandle *certHandle;
    NSSLOWCERTCertificate *cert;
    unsigned char hash[SHA1_LENGTH];
    unsigned int trustFlags;
    CK_RV crv = CKR_CANCEL;
    PRBool isPKCS;

    switch (type) {
        case CKA_PRIVATE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_MODIFIABLE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_NSS_CERT_SHA1_HASH:
        case CKA_NSS_CERT_MD5_HASH:
        case CKA_HASH_OF_CERTIFICATE:
        case CKA_NAME_HASH_ALGORITHM:
        case CKA_NSS_TRUST_CLIENT_AUTH:
        case CKA_NSS_TRUST_SERVER_AUTH:
        case CKA_NSS_TRUST_EMAIL_PROTECTION:
        case CKA_NSS_TRUST_CODE_SIGNING:
        case CKA_NSS_TRUST_STEP_UP_APPROVED:
        case CKA_PKCS_TRUST_CLIENT_AUTH:
        case CKA_PKCS_TRUST_SERVER_AUTH:
        case CKA_PKCS_TRUST_EMAIL_PROTECTION:
        case CKA_PKCS_TRUST_CODE_SIGNING:
        case CKA_ISSUER:
        case CKA_SERIAL_NUMBER:
            break;
        default:
            return lg_invalidAttribute(attribute);
    }
    certHandle = lg_getCertDB(obj->sdb);
    if (!certHandle) {
        return CKR_OBJECT_HANDLE_INVALID;
    }
    trust = lg_getTrust(obj, certHandle);
    if (trust == NULL) {
        return CKR_OBJECT_HANDLE_INVALID;
    }
    isPKCS = PR_TRUE;
    switch (type) {
        case CKA_NAME_HASH_ALGORITHM:
            return lg_ULongAttribute(attribute, type, CKM_SHA_1);
        case CKA_NSS_CERT_SHA1_HASH:
        case CKA_HASH_OF_CERTIFICATE:
            SHA1_HashBuf(hash, trust->derCert->data, trust->derCert->len);
            return lg_CopyAttribute(attribute, type, hash, SHA1_LENGTH);
        case CKA_NSS_CERT_MD5_HASH:
            MD5_HashBuf(hash, trust->derCert->data, trust->derCert->len);
            return lg_CopyAttribute(attribute, type, hash, MD5_LENGTH);
        case CKA_NSS_TRUST_CLIENT_AUTH:
            isPKCS = PR_FALSE;
        case CKA_PKCS_TRUST_CLIENT_AUTH:
            trustFlags = trust->trust->sslFlags &
                                 CERTDB_TRUSTED_CLIENT_CA
                             ? trust->trust->sslFlags | CERTDB_TRUSTED_CA
                             : 0;
            goto trust;
        case CKA_NSS_TRUST_SERVER_AUTH:
            isPKCS = PR_FALSE;
        case CKA_PKCS_TRUST_SERVER_AUTH:
            trustFlags = trust->trust->sslFlags;
            goto trust;
        case CKA_NSS_TRUST_EMAIL_PROTECTION:
            isPKCS = PR_FALSE;
        case CKA_PKCS_TRUST_EMAIL_PROTECTION:
            trustFlags = trust->trust->emailFlags;
            goto trust;
        case CKA_NSS_TRUST_CODE_SIGNING:
            isPKCS = PR_FALSE;
        case CKA_PKCS_TRUST_CODE_SIGNING:
            trustFlags = trust->trust->objectSigningFlags;
        trust:
            if (trustFlags & CERTDB_TRUSTED_CA) {
                return lg_ULongAttribute(attribute, type,
                                         isPKCS ? CKT_TRUST_ANCHOR : CKT_NSS_TRUSTED_DELEGATOR);
            }
            if (trustFlags & CERTDB_TRUSTED) {
                return lg_ULongAttribute(attribute, type, isPKCS ? CKT_TRUSTED : CKT_NSS_TRUSTED);
            }
            if (trustFlags & CERTDB_MUST_VERIFY) {
                return lg_ULongAttribute(attribute, type,
                                         isPKCS ? CKT_TRUST_MUST_VERIFY_TRUST : CKT_NSS_MUST_VERIFY_TRUST);
            }
            if (trustFlags & CERTDB_TRUSTED_UNKNOWN) {
                return lg_ULongAttribute(attribute, type,
                                         isPKCS ? CKT_TRUST_UNKNOWN : CKT_NSS_TRUST_UNKNOWN);
            }
            if (trustFlags & CERTDB_VALID_CA) {
                return lg_ULongAttribute(attribute, type,
                                         isPKCS ? CKT_TRUST_MUST_VERIFY_TRUST : CKT_NSS_VALID_DELEGATOR);
            }
            if (trustFlags & CERTDB_TERMINAL_RECORD) {
                return lg_ULongAttribute(attribute, type,
                                         isPKCS ? CKT_NOT_TRUSTED : CKT_NSS_NOT_TRUSTED);
            }
            return lg_ULongAttribute(attribute, type, isPKCS ? CKT_TRUST_UNKNOWN : CKT_NSS_TRUST_UNKNOWN);
        case CKA_NSS_TRUST_STEP_UP_APPROVED:
            if (trust->trust->sslFlags & CERTDB_GOVT_APPROVED_CA) {
                return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
            } else {
                return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
            }
        default:
            break;
    }

    switch (type) {
        case CKA_ISSUER:
            cert = lg_getCert(obj, certHandle);
            if (cert == NULL)
                break;
            crv = lg_CopyAttribute(attribute, type, cert->derIssuer.data,
                                   cert->derIssuer.len);
            break;
        case CKA_SERIAL_NUMBER:
            cert = lg_getCert(obj, certHandle);
            if (cert == NULL)
                break;
            crv = lg_CopyAttribute(attribute, type, cert->derSN.data,
                                   cert->derSN.len);
            break;
        default:
            cert = NULL;
            break;
    }
    if (cert) {
        nsslowcert_DestroyCertificate(cert);
        return crv;
    }
    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindCrlAttribute(LGObjectCache *obj, CK_ATTRIBUTE_TYPE type,
                    CK_ATTRIBUTE *attribute)
{
    certDBEntryRevocation *crl;

    switch (type) {
        case CKA_PRIVATE:
        case CKA_MODIFIABLE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_NSS_KRL:
            return ((obj->handle == LG_TOKEN_KRL_HANDLE)
                        ? LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr)
                        : LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr));
        case CKA_SUBJECT:
            return lg_CopyAttribute(attribute, type, obj->dbKey.data,
                                    obj->dbKey.len);
        case CKA_NSS_URL:
        case CKA_VALUE:
            break;
        default:
            return lg_invalidAttribute(attribute);
    }
    crl = lg_getCrl(obj);
    if (!crl) {
        return CKR_OBJECT_HANDLE_INVALID;
    }
    switch (type) {
        case CKA_NSS_URL:
            if (crl->url == NULL) {
                return LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
            }
            return lg_CopyAttribute(attribute, type, crl->url,
                                    PORT_Strlen(crl->url) + 1);
        case CKA_VALUE:
            return lg_CopyAttribute(attribute, type, crl->derCrl.data,
                                    crl->derCrl.len);
        default:
            break;
    }
    return lg_invalidAttribute(attribute);
}

static CK_RV
lg_FindCertAttribute(LGObjectCache *obj, CK_ATTRIBUTE_TYPE type,
                     CK_ATTRIBUTE *attribute)
{
    NSSLOWCERTCertificate *cert;
    NSSLOWCERTCertDBHandle *certHandle;
    NSSLOWKEYPublicKey *pubKey;
    unsigned char hash[SHA1_LENGTH];
    SECItem *item;

    switch (type) {
        case CKA_PRIVATE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticFalseAttr);
        case CKA_MODIFIABLE:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_CERTIFICATE_TYPE:
            /* hardcoding X.509 into here */
            return lg_ULongAttribute(attribute, type, CKC_X_509);
        case CKA_VALUE:
        case CKA_ID:
        case CKA_LABEL:
        case CKA_SUBJECT:
        case CKA_ISSUER:
        case CKA_SERIAL_NUMBER:
        case CKA_NSS_EMAIL:
            break;
        default:
            return lg_invalidAttribute(attribute);
    }

    certHandle = lg_getCertDB(obj->sdb);
    if (certHandle == NULL) {
        return CKR_OBJECT_HANDLE_INVALID;
    }

    cert = lg_getCert(obj, certHandle);
    if (cert == NULL) {
        return CKR_OBJECT_HANDLE_INVALID;
    }
    switch (type) {
        case CKA_VALUE:
            return lg_CopyAttribute(attribute, type, cert->derCert.data,
                                    cert->derCert.len);
        case CKA_ID:
            if (((cert->trust->sslFlags & CERTDB_USER) == 0) &&
                ((cert->trust->emailFlags & CERTDB_USER) == 0) &&
                ((cert->trust->objectSigningFlags & CERTDB_USER) == 0)) {
                return LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
            }
            pubKey = nsslowcert_ExtractPublicKey(cert);
            if (pubKey == NULL)
                break;
            item = lg_GetPubItem(pubKey);
            if (item == NULL) {
                lg_nsslowkey_DestroyPublicKey(pubKey);
                break;
            }
            SHA1_HashBuf(hash, item->data, item->len);
            /* item is imbedded in pubKey, just free the key */
            lg_nsslowkey_DestroyPublicKey(pubKey);
            return lg_CopyAttribute(attribute, type, hash, SHA1_LENGTH);
        case CKA_LABEL:
            return cert->nickname
                       ? lg_CopyAttribute(attribute, type, cert->nickname,
                                          PORT_Strlen(cert->nickname))
                       : LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
        case CKA_SUBJECT:
            return lg_CopyAttribute(attribute, type, cert->derSubject.data,
                                    cert->derSubject.len);
        case CKA_ISSUER:
            return lg_CopyAttribute(attribute, type, cert->derIssuer.data,
                                    cert->derIssuer.len);
        case CKA_SERIAL_NUMBER:
            return lg_CopyAttribute(attribute, type, cert->derSN.data,
                                    cert->derSN.len);
        case CKA_NSS_EMAIL:
            return (cert->emailAddr && cert->emailAddr[0])
                       ? lg_CopyAttribute(attribute, type, cert->emailAddr,
                                          PORT_Strlen(cert->emailAddr))
                       : LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
        default:
            break;
    }
    return lg_invalidAttribute(attribute);
}

CK_RV
lg_GetSingleAttribute(LGObjectCache *obj, CK_ATTRIBUTE *attribute)
{
    /* handle the common ones */
    CK_ATTRIBUTE_TYPE type = attribute->type;
    switch (type) {
        case CKA_CLASS:
            return lg_ULongAttribute(attribute, type, obj->objclass);
        case CKA_TOKEN:
            return LG_CLONE_ATTR(attribute, type, lg_StaticTrueAttr);
        case CKA_LABEL:
            if ((obj->objclass == CKO_CERTIFICATE) ||
                (obj->objclass == CKO_PRIVATE_KEY) ||
                (obj->objclass == CKO_PUBLIC_KEY) ||
                (obj->objclass == CKO_SECRET_KEY)) {
                break;
            }
            return LG_CLONE_ATTR(attribute, type, lg_StaticNullAttr);
        default:
            break;
    }
    switch (obj->objclass) {
        case CKO_CERTIFICATE:
            return lg_FindCertAttribute(obj, type, attribute);
        case CKO_NSS_CRL:
            return lg_FindCrlAttribute(obj, type, attribute);
        case CKO_NSS_TRUST:
        case CKO_TRUST:
            return lg_FindTrustAttribute(obj, type, attribute);
        case CKO_NSS_SMIME:
            return lg_FindSMIMEAttribute(obj, type, attribute);
        case CKO_PUBLIC_KEY:
            return lg_FindPublicKeyAttribute(obj, type, attribute);
        case CKO_PRIVATE_KEY:
            return lg_FindPrivateKeyAttribute(obj, type, attribute);
        case CKO_SECRET_KEY:
            return lg_FindSecretKeyAttribute(obj, type, attribute);
        default:
            break;
    }
    return lg_invalidAttribute(attribute);
}

/*
 * Fill in the attribute template based on the data in the database.
 */
CK_RV
lg_GetAttributeValue(SDB *sdb, CK_OBJECT_HANDLE handle, CK_ATTRIBUTE *templ,
                     CK_ULONG count)
{
    LGObjectCache *obj = lg_NewObjectCache(sdb, NULL, handle & ~LG_TOKEN_MASK);
    CK_RV crv, crvCollect = CKR_OK;
    unsigned int i;

    if (obj == NULL) {
        return CKR_OBJECT_HANDLE_INVALID;
    }

    for (i = 0; i < count; i++) {
        crv = lg_GetSingleAttribute(obj, &templ[i]);
        if (crvCollect == CKR_OK)
            crvCollect = crv;
    }

    lg_DestroyObjectCache(obj);
    return crvCollect;
}

PRBool
lg_cmpAttribute(LGObjectCache *obj, const CK_ATTRIBUTE *attribute)
{
    unsigned char buf[LG_BUF_SPACE];
    CK_ATTRIBUTE testAttr;
    unsigned char *tempBuf = NULL;
    PRBool match = PR_TRUE;
    CK_RV crv;

    /* we're going to compare 'attribute' with the actual attribute from
     * the object. We'll use the length of 'attribute' to decide how much
     * space we need to read the test attribute. If 'attribute' doesn't give
     * enough space, then we know the values don't match and that will
     * show up as ckr != CKR_OK */
    testAttr = *attribute;
    testAttr.pValue = buf;

    /* if we don't have enough space, malloc it */
    if (attribute->ulValueLen > LG_BUF_SPACE) {
        tempBuf = PORT_Alloc(attribute->ulValueLen);
        if (!tempBuf) {
            return PR_FALSE;
        }
        testAttr.pValue = tempBuf;
    }

    /* get the attribute */
    crv = lg_GetSingleAttribute(obj, &testAttr);
    /* if the attribute was read OK, compare it */
    if ((crv != CKR_OK) ||
        (attribute->pValue == NULL) ||
        (attribute->ulValueLen != testAttr.ulValueLen) ||
        (PORT_Memcmp(attribute->pValue, testAttr.pValue, testAttr.ulValueLen) != 0)) {
        /* something didn't match, this isn't the object we are looking for */
        match = PR_FALSE;
    }
    /* free the buffer we may have allocated */
    if (tempBuf) {
        PORT_Free(tempBuf);
    }
    return match;
}

PRBool
lg_tokenMatch(SDB *sdb, const SECItem *dbKey, CK_OBJECT_HANDLE class,
              const CK_ATTRIBUTE *templ, CK_ULONG count)
{
    PRBool match = PR_TRUE;
    LGObjectCache *obj = lg_NewObjectCache(sdb, dbKey, class);
    unsigned int i;

    if (obj == NULL) {
        return PR_FALSE;
    }

    for (i = 0; i < count; i++) {
        match = lg_cmpAttribute(obj, &templ[i]);
        if (!match) {
            break;
        }
    }

    /* done looking, free up our cache */
    lg_DestroyObjectCache(obj);

    /* if we get through the whole list without finding a mismatched attribute,
     * then this object fits the criteria we are matching */
    return match;
}

static CK_RV
lg_SetCertAttribute(LGObjectCache *obj, CK_ATTRIBUTE_TYPE type,
                    const void *value, unsigned int len)
{
    NSSLOWCERTCertificate *cert;
    NSSLOWCERTCertDBHandle *certHandle;
    char *nickname = NULL;
    SECStatus rv;
    CK_RV crv;

    /* we can't change  the EMAIL values, but let the
     * upper layers feel better about the fact we tried to set these */
    if (type == CKA_NSS_EMAIL) {
        return CKR_OK;
    }

    certHandle = lg_getCertDB(obj->sdb);
    if (certHandle == NULL) {
        crv = CKR_TOKEN_WRITE_PROTECTED;
        goto done;
    }

    if ((type != CKA_LABEL) && (type != CKA_ID)) {
        crv = CKR_ATTRIBUTE_READ_ONLY;
        goto done;
    }

    cert = lg_getCert(obj, certHandle);
    if (cert == NULL) {
        crv = CKR_OBJECT_HANDLE_INVALID;
        goto done;
    }

    /* if the app is trying to set CKA_ID, it's probably because it just
     * imported the key. Look to see if we need to set the CERTDB_USER bits.
     */
    if (type == CKA_ID) {
        if (((cert->trust->sslFlags & CERTDB_USER) == 0) &&
            ((cert->trust->emailFlags & CERTDB_USER) == 0) &&
            ((cert->trust->objectSigningFlags & CERTDB_USER) == 0)) {
            NSSLOWKEYDBHandle *keyHandle;

            keyHandle = lg_getKeyDB(obj->sdb);
            if (keyHandle) {
                if (nsslowkey_KeyForCertExists(keyHandle, cert)) {
                    NSSLOWCERTCertTrust trust = *cert->trust;
                    trust.sslFlags |= CERTDB_USER;
                    trust.emailFlags |= CERTDB_USER;
                    trust.objectSigningFlags |= CERTDB_USER;
                    nsslowcert_ChangeCertTrust(certHandle, cert, &trust);
                }
            }
        }
        crv = CKR_OK;
        goto done;
    }

    /* must be CKA_LABEL */
    if (value != NULL) {
        nickname = PORT_ZAlloc(len + 1);
        if (nickname == NULL) {
            crv = CKR_HOST_MEMORY;
            goto done;
        }
        PORT_Memcpy(nickname, value, len);
        nickname[len] = 0;
    }
    rv = nsslowcert_AddPermNickname(certHandle, cert, nickname);
    crv = (rv == SECSuccess) ? CKR_OK : CKR_DEVICE_ERROR;

done:
    if (nickname) {
        PORT_Free(nickname);
    }
    return crv;
}

static CK_RV
lg_SetPrivateKeyAttribute(LGObjectCache *obj, CK_ATTRIBUTE_TYPE type,
                          const void *value, unsigned int len,
                          PRBool *writePrivate)
{
    NSSLOWKEYPrivateKey *privKey;
    NSSLOWKEYDBHandle *keyHandle;
    char *nickname = NULL;
    SECStatus rv;
    CK_RV crv;

    /* we can't change the ID and we don't store the subject, but let the
     * upper layers feel better about the fact we tried to set these */
    if ((type == CKA_ID) || (type == CKA_SUBJECT) ||
        (type == CKA_LOCAL) || (type == CKA_NEVER_EXTRACTABLE) ||
        (type == CKA_ALWAYS_SENSITIVE)) {
        return CKR_OK;
    }

    keyHandle = lg_getKeyDB(obj->sdb);
    if (keyHandle == NULL) {
        crv = CKR_TOKEN_WRITE_PROTECTED;
        goto done;
    }

    privKey = lg_GetPrivateKeyWithDB(obj, keyHandle);
    if (privKey == NULL) {
        crv = CKR_OBJECT_HANDLE_INVALID;
        goto done;
    }

    crv = CKR_ATTRIBUTE_READ_ONLY;
    switch (type) {
        case CKA_LABEL:
            if (value != NULL) {
                nickname = PORT_ZAlloc(len + 1);
                if (nickname == NULL) {
                    crv = CKR_HOST_MEMORY;
                    goto done;
                }
                PORT_Memcpy(nickname, value, len);
                nickname[len] = 0;
            }
            rv = nsslowkey_UpdateNickname(keyHandle, privKey, &obj->dbKey,
                                          nickname, obj->sdb);
            crv = (rv == SECSuccess) ? CKR_OK : CKR_DEVICE_ERROR;
            break;
        case CKA_UNWRAP:
        case CKA_SIGN:
        case CKA_DERIVE:
        case CKA_SIGN_RECOVER:
        case CKA_DECRYPT:
            /* ignore attempts to change restrict these.
             * legacyDB ignore these flags and always presents all of them
             * that are valid as true.
             * NOTE: We only get here if the current value and the new value do
             * not match. */
            if (*(char *)value == 0) {
                crv = CKR_OK;
            }
            break;
        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:
            /* We aren't really changing these values, we are just triggering
             * the database to update it's entry */
            *writePrivate = PR_TRUE;
            crv = CKR_OK;
            break;
        default:
            crv = CKR_ATTRIBUTE_READ_ONLY;
            break;
    }
done:
    if (nickname) {
        PORT_Free(nickname);
    }
    return crv;
}

static CK_RV
lg_SetPublicKeyAttribute(LGObjectCache *obj, CK_ATTRIBUTE_TYPE type,
                         const void *value, unsigned int len,
                         PRBool *writePrivate)
{
    /* we can't change the ID and we don't store the subject, but let the
     * upper layers feel better about the fact we tried to set these */
    if ((type == CKA_ID) || (type == CKA_SUBJECT) || (type == CKA_LABEL)) {
        return CKR_OK;
    }
    return CKR_ATTRIBUTE_READ_ONLY;
}

static CK_RV
lg_SetTrustAttribute(LGObjectCache *obj, const CK_ATTRIBUTE *attr)
{
    unsigned int flags;
    CK_TRUST trust;
    NSSLOWCERTCertificate *cert = NULL;
    NSSLOWCERTCertDBHandle *certHandle;
    NSSLOWCERTCertTrust dbTrust;
    SECStatus rv;
    CK_RV crv;

    if (attr->type == CKA_LABEL) {
        return CKR_OK;
    }

    crv = lg_GetULongAttribute(attr->type, attr, 1, &trust);
    if (crv != CKR_OK) {
        return crv;
    }
    flags = lg_MapTrust(trust, (PRBool)((attr->type == CKA_NSS_TRUST_CLIENT_AUTH) ||
                                        (attr->type == CKA_PKCS_TRUST_CLIENT_AUTH)));

    certHandle = lg_getCertDB(obj->sdb);

    if (certHandle == NULL) {
        crv = CKR_TOKEN_WRITE_PROTECTED;
        goto done;
    }

    cert = lg_getCert(obj, certHandle);
    if (cert == NULL) {
        crv = CKR_OBJECT_HANDLE_INVALID;
        goto done;
    }
    dbTrust = *cert->trust;

    switch (attr->type) {
        case CKA_TRUST_EMAIL_PROTECTION:
            dbTrust.emailFlags = flags |
                                 (cert->trust->emailFlags & CERTDB_PRESERVE_TRUST_BITS);
            break;
        case CKA_TRUST_CODE_SIGNING:
            dbTrust.objectSigningFlags = flags |
                                         (cert->trust->objectSigningFlags & CERTDB_PRESERVE_TRUST_BITS);
            break;
        case CKA_TRUST_CLIENT_AUTH:
            dbTrust.sslFlags = flags | (cert->trust->sslFlags &
                                        (CERTDB_PRESERVE_TRUST_BITS | CERTDB_TRUSTED_CA));
            break;
        case CKA_TRUST_SERVER_AUTH:
            dbTrust.sslFlags = flags | (cert->trust->sslFlags &
                                        (CERTDB_PRESERVE_TRUST_BITS | CERTDB_TRUSTED_CLIENT_CA));
            break;
        default:
            crv = CKR_ATTRIBUTE_READ_ONLY;
            goto done;
    }

    rv = nsslowcert_ChangeCertTrust(certHandle, cert, &dbTrust);
    crv = (rv == SECSuccess) ? CKR_OK : CKR_DEVICE_ERROR;
done:
    if (cert) {
        nsslowcert_DestroyCertificate(cert);
    }
    return crv;
}

static CK_RV
lg_SetSingleAttribute(LGObjectCache *obj, const CK_ATTRIBUTE *attr,
                      PRBool *writePrivate)
{
    CK_ATTRIBUTE attribLocal;
    CK_RV crv;

    if ((attr->type == CKA_NSS_DB) && (obj->objclass == CKO_PRIVATE_KEY)) {
        *writePrivate = PR_TRUE;
        return CKR_OK;
    }

    /* Make sure the attribute exists first */
    attribLocal.type = attr->type;
    attribLocal.pValue = NULL;
    attribLocal.ulValueLen = 0;
    crv = lg_GetSingleAttribute(obj, &attribLocal);
    if (crv != CKR_OK) {
        return crv;
    }

    /* if we are just setting it to the value we already have,
     * allow it to happen. Let label setting go through so
     * we have the opportunity to repair any database corruption. */
    if (attr->type != CKA_LABEL) {
        if (lg_cmpAttribute(obj, attr)) {
            return CKR_OK;
        }
    }

    crv = CKR_ATTRIBUTE_READ_ONLY;
    switch (obj->objclass) {
        case CKO_CERTIFICATE:
            /* change NICKNAME, EMAIL,  */
            crv = lg_SetCertAttribute(obj, attr->type,
                                      attr->pValue, attr->ulValueLen);
            break;
        case CKO_NSS_CRL:
            /* change URL */
            break;
        case CKO_NSS_TRUST:
        case CKO_TRUST:
            crv = lg_SetTrustAttribute(obj, attr);
            break;
        case CKO_PRIVATE_KEY:
        case CKO_SECRET_KEY:
            crv = lg_SetPrivateKeyAttribute(obj, attr->type,
                                            attr->pValue, attr->ulValueLen, writePrivate);
            break;
        case CKO_PUBLIC_KEY:
            crv = lg_SetPublicKeyAttribute(obj, attr->type,
                                           attr->pValue, attr->ulValueLen, writePrivate);
            break;
    }
    return crv;
}

/*
 * Fill in the attribute template based on the data in the database.
 */
CK_RV
lg_SetAttributeValue(SDB *sdb, CK_OBJECT_HANDLE handle,
                     const CK_ATTRIBUTE *templ, CK_ULONG count)
{
    LGObjectCache *obj = lg_NewObjectCache(sdb, NULL, handle & ~LG_TOKEN_MASK);
    CK_RV crv, crvCollect = CKR_OK;
    PRBool writePrivate = PR_FALSE;
    unsigned int i;

    if (obj == NULL) {
        return CKR_OBJECT_HANDLE_INVALID;
    }

    for (i = 0; i < count; i++) {
        crv = lg_SetSingleAttribute(obj, &templ[i], &writePrivate);
        if (crvCollect == CKR_OK)
            crvCollect = crv;
    }

    /* Write any collected changes out for private and secret keys.
     *  don't do the write for just the label */
    if (writePrivate) {
        NSSLOWKEYPrivateKey *privKey = lg_GetPrivateKey(obj);
        SECStatus rv = SECFailure;
        char *label = lg_FindKeyNicknameByPublicKey(obj->sdb, &obj->dbKey);

        if (privKey) {
            rv = nsslowkey_StoreKeyByPublicKeyAlg(lg_getKeyDB(sdb), privKey,
                                                  &obj->dbKey, label, sdb, PR_TRUE);
        }
        if (rv != SECSuccess) {
            crv = CKR_DEVICE_ERROR;
        }
        PORT_Free(label);
    }

    lg_DestroyObjectCache(obj);
    return crvCollect;
}
