/* 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/. */

#include "pkcs11.h"

#ifndef DEVM_H
#include "devm.h"
#endif /* DEVM_H */

#ifndef CKHELPER_H
#include "ckhelper.h"
#endif /* CKHELPER_H */

#include "pk11func.h"
#include "dev3hack.h"
#include "secerr.h"

extern const NSSError NSS_ERROR_NOT_FOUND;
extern const NSSError NSS_ERROR_INVALID_ARGUMENT;
extern const NSSError NSS_ERROR_PKCS11;

/* The number of object handles to grab during each call to C_FindObjects */
#define OBJECT_STACK_SIZE 16

NSS_IMPLEMENT PRStatus
nssToken_Destroy(
    NSSToken *tok)
{
    if (tok) {
        if (PR_ATOMIC_DECREMENT(&tok->base.refCount) == 0) {
            PK11_FreeSlot(tok->pk11slot);
            PZ_DestroyLock(tok->base.lock);
            nssTokenObjectCache_Destroy(tok->cache);

            /* We're going away, let the nssSlot know in case it's held
             * alive by someone else. Usually we should hold the last ref. */
            nssSlot_EnterMonitor(tok->slot);
            tok->slot->token = NULL;
            nssSlot_ExitMonitor(tok->slot);

            (void)nssSlot_Destroy(tok->slot);
            return nssArena_Destroy(tok->base.arena);
        }
    }
    return PR_SUCCESS;
}

NSS_IMPLEMENT void
nssToken_Remove(
    NSSToken *tok)
{
    nssTokenObjectCache_Clear(tok->cache);
}

NSS_IMPLEMENT void
NSSToken_Destroy(
    NSSToken *tok)
{
    (void)nssToken_Destroy(tok);
}

NSS_IMPLEMENT NSSToken *
nssToken_AddRef(
    NSSToken *tok)
{
    PR_ATOMIC_INCREMENT(&tok->base.refCount);
    return tok;
}

NSS_IMPLEMENT NSSSlot *
nssToken_GetSlot(
    NSSToken *tok)
{
    return nssSlot_AddRef(tok->slot);
}

NSS_IMPLEMENT void *
nssToken_GetCryptokiEPV(
    NSSToken *token)
{
    return nssSlot_GetCryptokiEPV(token->slot);
}

NSS_IMPLEMENT nssSession *
nssToken_GetDefaultSession(
    NSSToken *token)
{
    return token->defaultSession;
}

NSS_IMPLEMENT NSSUTF8 *
nssToken_GetName(
    NSSToken *tok)
{
    if (tok == NULL) {
        return "";
    }
    if (tok->base.name[0] == 0) {
        (void)nssSlot_IsTokenPresent(tok->slot);
    }
    return tok->base.name;
}

NSS_IMPLEMENT NSSUTF8 *
NSSToken_GetName(
    NSSToken *token)
{
    return nssToken_GetName(token);
}

NSS_IMPLEMENT PRBool
nssToken_IsLoginRequired(
    NSSToken *token)
{
    return (token->ckFlags & CKF_LOGIN_REQUIRED);
}

NSS_IMPLEMENT PRBool
nssToken_NeedsPINInitialization(
    NSSToken *token)
{
    return (!(token->ckFlags & CKF_USER_PIN_INITIALIZED));
}

NSS_IMPLEMENT PRStatus
nssToken_DeleteStoredObject(
    nssCryptokiObject *instance)
{
    CK_RV ckrv;
    PRStatus status;
    PRBool createdSession = PR_FALSE;
    NSSToken *token = instance->token;
    nssSession *session = NULL;
    void *epv = nssToken_GetCryptokiEPV(instance->token);
    if (token->cache) {
        nssTokenObjectCache_RemoveObject(token->cache, instance);
    }
    if (instance->isTokenObject) {
        if (token->defaultSession &&
            nssSession_IsReadWrite(token->defaultSession)) {
            session = token->defaultSession;
        } else {
            session = nssSlot_CreateSession(token->slot, NULL, PR_TRUE);
            createdSession = PR_TRUE;
        }
    }
    if (session == NULL) {
        return PR_FAILURE;
    }
    nssSession_EnterMonitor(session);
    ckrv = CKAPI(epv)->C_DestroyObject(session->handle, instance->handle);
    nssSession_ExitMonitor(session);
    if (createdSession) {
        nssSession_Destroy(session);
    }
    status = PR_SUCCESS;
    if (ckrv != CKR_OK) {
        status = PR_FAILURE;
        /* use the error stack to pass the PKCS #11 error out  */
        nss_SetError(ckrv);
        nss_SetError(NSS_ERROR_PKCS11);
    }
    return status;
}

static nssCryptokiObject *
import_object(
    NSSToken *tok,
    nssSession *sessionOpt,
    CK_ATTRIBUTE_PTR objectTemplate,
    CK_ULONG otsize)
{
    nssSession *session = NULL;
    PRBool createdSession = PR_FALSE;
    nssCryptokiObject *object = NULL;
    CK_OBJECT_HANDLE handle;
    CK_RV ckrv;
    void *epv = nssToken_GetCryptokiEPV(tok);
    if (nssCKObject_IsTokenObjectTemplate(objectTemplate, otsize)) {
        if (sessionOpt) {
            if (!nssSession_IsReadWrite(sessionOpt)) {
                nss_SetError(NSS_ERROR_INVALID_ARGUMENT);
                return NULL;
            }
            session = sessionOpt;
        } else if (tok->defaultSession &&
                   nssSession_IsReadWrite(tok->defaultSession)) {
            session = tok->defaultSession;
        } else {
            session = nssSlot_CreateSession(tok->slot, NULL, PR_TRUE);
            createdSession = PR_TRUE;
        }
    } else {
        session = (sessionOpt) ? sessionOpt : tok->defaultSession;
    }
    if (session == NULL) {
        nss_SetError(NSS_ERROR_INVALID_ARGUMENT);
        return NULL;
    }
    nssSession_EnterMonitor(session);
    ckrv = CKAPI(epv)->C_CreateObject(session->handle,
                                      objectTemplate, otsize,
                                      &handle);
    nssSession_ExitMonitor(session);
    if (ckrv == CKR_OK) {
        object = nssCryptokiObject_Create(tok, session, handle);
    } else {
        nss_SetError(ckrv);
        nss_SetError(NSS_ERROR_PKCS11);
    }
    if (createdSession) {
        nssSession_Destroy(session);
    }
    return object;
}

static nssCryptokiObject **
create_objects_from_handles(
    NSSToken *tok,
    nssSession *session,
    CK_OBJECT_HANDLE *handles,
    PRUint32 numH)
{
    nssCryptokiObject **objects;
    objects = nss_ZNEWARRAY(NULL, nssCryptokiObject *, numH + 1);
    if (objects) {
        PRInt32 i;
        for (i = 0; i < (PRInt32)numH; i++) {
            objects[i] = nssCryptokiObject_Create(tok, session, handles[i]);
            if (!objects[i]) {
                for (--i; i > 0; --i) {
                    nssCryptokiObject_Destroy(objects[i]);
                }
                nss_ZFreeIf(objects);
                objects = NULL;
                break;
            }
        }
    }
    return objects;
}

static nssCryptokiObject **
find_objects(
    NSSToken *tok,
    nssSession *sessionOpt,
    CK_ATTRIBUTE_PTR obj_template,
    CK_ULONG otsize,
    PRUint32 maximumOpt,
    PRStatus *statusOpt)
{
    CK_RV ckrv = CKR_OK;
    CK_ULONG count;
    CK_OBJECT_HANDLE *objectHandles = NULL;
    CK_OBJECT_HANDLE staticObjects[OBJECT_STACK_SIZE];
    PRUint32 arraySize, numHandles;
    void *epv = nssToken_GetCryptokiEPV(tok);
    nssCryptokiObject **objects;
    nssSession *session = (sessionOpt) ? sessionOpt : tok->defaultSession;

    /* Don't ask the module to use an invalid session handle. */
    if (!session || session->handle == CK_INVALID_HANDLE) {
        ckrv = CKR_SESSION_HANDLE_INVALID;
        goto loser;
    }

    /* the arena is only for the array of object handles */
    if (maximumOpt > 0) {
        arraySize = maximumOpt;
    } else {
        arraySize = OBJECT_STACK_SIZE;
    }
    numHandles = 0;
    if (arraySize <= OBJECT_STACK_SIZE) {
        objectHandles = staticObjects;
    } else {
        objectHandles = nss_ZNEWARRAY(NULL, CK_OBJECT_HANDLE, arraySize);
    }
    if (!objectHandles) {
        ckrv = CKR_HOST_MEMORY;
        goto loser;
    }
    nssSession_EnterMonitor(session); /* ==== session lock === */
    /* Initialize the find with the template */
    ckrv = CKAPI(epv)->C_FindObjectsInit(session->handle,
                                         obj_template, otsize);
    if (ckrv != CKR_OK) {
        nssSession_ExitMonitor(session);
        goto loser;
    }
    while (PR_TRUE) {
        /* Issue the find for up to arraySize - numHandles objects */
        ckrv = CKAPI(epv)->C_FindObjects(session->handle,
                                         objectHandles + numHandles,
                                         arraySize - numHandles,
                                         &count);
        if (ckrv != CKR_OK) {
            nssSession_ExitMonitor(session);
            goto loser;
        }
        /* bump the number of found objects */
        numHandles += count;
        if (maximumOpt > 0 || numHandles < arraySize) {
            /* When a maximum is provided, the search is done all at once,
             * so the search is finished.  If the number returned was less
             * than the number sought, the search is finished.
             */
            break;
        }
        /* the array is filled, double it and continue */
        arraySize *= 2;
        if (objectHandles == staticObjects) {
            objectHandles = nss_ZNEWARRAY(NULL, CK_OBJECT_HANDLE, arraySize);
            if (objectHandles) {
                PORT_Memcpy(objectHandles, staticObjects,
                            OBJECT_STACK_SIZE * sizeof(objectHandles[1]));
            }
        } else {
            objectHandles = nss_ZREALLOCARRAY(objectHandles,
                                              CK_OBJECT_HANDLE,
                                              arraySize);
        }
        if (!objectHandles) {
            nssSession_ExitMonitor(session);
            ckrv = CKR_HOST_MEMORY;
            goto loser;
        }
    }
    ckrv = CKAPI(epv)->C_FindObjectsFinal(session->handle);
    nssSession_ExitMonitor(session); /* ==== end session lock === */
    if (ckrv != CKR_OK) {
        goto loser;
    }
    if (numHandles > 0) {
        objects = create_objects_from_handles(tok, session,
                                              objectHandles, numHandles);
    } else {
        nss_SetError(NSS_ERROR_NOT_FOUND);
        objects = NULL;
    }
    if (objectHandles && objectHandles != staticObjects) {
        nss_ZFreeIf(objectHandles);
    }
    if (statusOpt)
        *statusOpt = PR_SUCCESS;
    return objects;
loser:
    if (objectHandles && objectHandles != staticObjects) {
        nss_ZFreeIf(objectHandles);
    }
    /*
     * These errors should be treated the same as if the objects just weren't
     * found..
     */
    if ((ckrv == CKR_ATTRIBUTE_TYPE_INVALID) ||
        (ckrv == CKR_ATTRIBUTE_VALUE_INVALID) ||
        (ckrv == CKR_DATA_INVALID) ||
        (ckrv == CKR_DATA_LEN_RANGE) ||
        (ckrv == CKR_FUNCTION_NOT_SUPPORTED) ||
        (ckrv == CKR_TEMPLATE_INCOMPLETE) ||
        (ckrv == CKR_TEMPLATE_INCONSISTENT)) {

        nss_SetError(NSS_ERROR_NOT_FOUND);
        if (statusOpt)
            *statusOpt = PR_SUCCESS;
    } else {
        nss_SetError(ckrv);
        nss_SetError(NSS_ERROR_PKCS11);
        if (statusOpt)
            *statusOpt = PR_FAILURE;
    }
    return (nssCryptokiObject **)NULL;
}

NSS_IMPLEMENT nssCryptokiObject **
nssToken_FindObjectsByTemplate(
    NSSToken *token,
    nssSession *sessionOpt,
    CK_ATTRIBUTE_PTR obj_template,
    CK_ULONG otsize,
    PRUint32 maximumOpt,
    PRStatus *statusOpt)
{
    CK_OBJECT_CLASS objclass = (CK_OBJECT_CLASS)-1;
    nssCryptokiObject **objects = NULL;
    PRUint32 i;

    if (!token) {
        PORT_SetError(SEC_ERROR_NO_TOKEN);
        if (statusOpt)
            *statusOpt = PR_FAILURE;
        return NULL;
    }
    for (i = 0; i < otsize; i++) {
        if (obj_template[i].type == CKA_CLASS) {
            objclass = *(CK_OBJECT_CLASS *)obj_template[i].pValue;
            break;
        }
    }
    PR_ASSERT(i < otsize);
    if (i == otsize) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        if (statusOpt)
            *statusOpt = PR_FAILURE;
        return NULL;
    }
    /* If these objects are being cached, try looking there first */
    if (token->cache &&
        nssTokenObjectCache_HaveObjectClass(token->cache, objclass)) {
        PRStatus status;
        objects = nssTokenObjectCache_FindObjectsByTemplate(token->cache,
                                                            objclass,
                                                            obj_template,
                                                            otsize,
                                                            maximumOpt,
                                                            &status);
        if (status == PR_SUCCESS) {
            if (statusOpt)
                *statusOpt = status;
            return objects;
        }
    }
    /* Either they are not cached, or cache failed; look on token. */
    objects = find_objects(token, sessionOpt,
                           obj_template, otsize,
                           maximumOpt, statusOpt);
    return objects;
}

extern const NSSError NSS_ERROR_INVALID_CERTIFICATE;

NSS_IMPLEMENT nssCryptokiObject *
nssToken_ImportCertificate(
    NSSToken *tok,
    nssSession *sessionOpt,
    NSSCertificateType certType,
    NSSItem *id,
    const NSSUTF8 *nickname,
    NSSDER *encoding,
    NSSDER *issuer,
    NSSDER *subject,
    NSSDER *serial,
    NSSASCII7 *email,
    PRBool asTokenObject)
{
    PRStatus status;
    CK_CERTIFICATE_TYPE cert_type;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE cert_tmpl[10];
    CK_ULONG ctsize;
    nssTokenSearchType searchType;
    nssCryptokiObject *rvObject = NULL;

    if (!tok) {
        PORT_SetError(SEC_ERROR_NO_TOKEN);
        return NULL;
    }
    if (certType == NSSCertificateType_PKIX) {
        cert_type = CKC_X_509;
    } else {
        return (nssCryptokiObject *)NULL;
    }
    NSS_CK_TEMPLATE_START(cert_tmpl, attr, ctsize);
    if (asTokenObject) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
        searchType = nssTokenSearchType_TokenOnly;
    } else {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
        searchType = nssTokenSearchType_SessionOnly;
    }
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_cert);
    NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_CERTIFICATE_TYPE, cert_type);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ID, id);
    NSS_CK_SET_ATTRIBUTE_UTF8(attr, CKA_LABEL, nickname);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_VALUE, encoding);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ISSUER, issuer);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SUBJECT, subject);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SERIAL_NUMBER, serial);
    if (email) {
        NSS_CK_SET_ATTRIBUTE_UTF8(attr, CKA_NSS_EMAIL, email);
    }
    NSS_CK_TEMPLATE_FINISH(cert_tmpl, attr, ctsize);
    /* see if the cert is already there */
    rvObject = nssToken_FindCertificateByIssuerAndSerialNumber(tok,
                                                               sessionOpt,
                                                               issuer,
                                                               serial,
                                                               searchType,
                                                               NULL);
    if (rvObject) {
        NSSItem existingDER;
        NSSSlot *slot = nssToken_GetSlot(tok);
        nssSession *session = nssSlot_CreateSession(slot, NULL, PR_TRUE);
        if (!session) {
            nssCryptokiObject_Destroy(rvObject);
            nssSlot_Destroy(slot);
            return (nssCryptokiObject *)NULL;
        }
        /* Reject any attempt to import a new cert that has the same
         * issuer/serial as an existing cert, but does not have the
         * same encoding
         */
        NSS_CK_TEMPLATE_START(cert_tmpl, attr, ctsize);
        NSS_CK_SET_ATTRIBUTE_NULL(attr, CKA_VALUE);
        NSS_CK_TEMPLATE_FINISH(cert_tmpl, attr, ctsize);
        status = nssCKObject_GetAttributes(rvObject->handle,
                                           cert_tmpl, ctsize, NULL,
                                           session, slot);
        NSS_CK_ATTRIBUTE_TO_ITEM(cert_tmpl, &existingDER);
        if (status == PR_SUCCESS) {
            if (!nssItem_Equal(encoding, &existingDER, NULL)) {
                nss_SetError(NSS_ERROR_INVALID_CERTIFICATE);
                status = PR_FAILURE;
            }
            nss_ZFreeIf(existingDER.data);
        }
        if (status == PR_FAILURE) {
            nssCryptokiObject_Destroy(rvObject);
            nssSession_Destroy(session);
            nssSlot_Destroy(slot);
            return (nssCryptokiObject *)NULL;
        }
        /* according to PKCS#11, label, ID, issuer, and serial number
         * may change after the object has been created.  For PKIX, the
         * last two attributes can't change, so for now we'll only worry
         * about the first two.
         */
        NSS_CK_TEMPLATE_START(cert_tmpl, attr, ctsize);
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ID, id);
        if (!rvObject->label && nickname) {
            NSS_CK_SET_ATTRIBUTE_UTF8(attr, CKA_LABEL, nickname);
        }
        NSS_CK_TEMPLATE_FINISH(cert_tmpl, attr, ctsize);
        /* reset the mutable attributes on the token */
        nssCKObject_SetAttributes(rvObject->handle,
                                  cert_tmpl, ctsize,
                                  session, slot);
        if (!rvObject->label && nickname) {
            rvObject->label = nssUTF8_Duplicate(nickname, NULL);
        }
        nssSession_Destroy(session);
        nssSlot_Destroy(slot);
    } else {
        /* Import the certificate onto the token */
        rvObject = import_object(tok, sessionOpt, cert_tmpl, ctsize);
    }
    if (rvObject && tok->cache) {
        /* The cache will overwrite the attributes if the object already
         * exists.
         */
        nssTokenObjectCache_ImportObject(tok->cache, rvObject,
                                         CKO_CERTIFICATE,
                                         cert_tmpl, ctsize);
    }
    return rvObject;
}

/* traverse all objects of the given class - this should only happen
 * if the token has been marked as "traversable"
 */
NSS_IMPLEMENT nssCryptokiObject **
nssToken_FindObjects(
    NSSToken *token,
    nssSession *sessionOpt,
    CK_OBJECT_CLASS objclass,
    nssTokenSearchType searchType,
    PRUint32 maximumOpt,
    PRStatus *statusOpt)
{
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE obj_template[2];
    CK_ULONG obj_size;
    nssCryptokiObject **objects;
    NSS_CK_TEMPLATE_START(obj_template, attr, obj_size);
    /* Set the search to token/session only if provided */
    if (searchType == nssTokenSearchType_SessionOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (searchType == nssTokenSearchType_TokenOnly ||
               searchType == nssTokenSearchType_TokenForced) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    }
    NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_CLASS, objclass);
    NSS_CK_TEMPLATE_FINISH(obj_template, attr, obj_size);

    if (searchType == nssTokenSearchType_TokenForced) {
        objects = find_objects(token, sessionOpt,
                               obj_template, obj_size,
                               maximumOpt, statusOpt);
    } else {
        objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
                                                 obj_template, obj_size,
                                                 maximumOpt, statusOpt);
    }
    return objects;
}

NSS_IMPLEMENT nssCryptokiObject **
nssToken_FindCertificatesBySubject(
    NSSToken *token,
    nssSession *sessionOpt,
    NSSDER *subject,
    nssTokenSearchType searchType,
    PRUint32 maximumOpt,
    PRStatus *statusOpt)
{
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE subj_template[3];
    CK_ULONG stsize;
    nssCryptokiObject **objects;
    NSS_CK_TEMPLATE_START(subj_template, attr, stsize);
    /* Set the search to token/session only if provided */
    if (searchType == nssTokenSearchType_SessionOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (searchType == nssTokenSearchType_TokenOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    }
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_cert);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SUBJECT, subject);
    NSS_CK_TEMPLATE_FINISH(subj_template, attr, stsize);
    /* now locate the token certs matching this template */
    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
                                             subj_template, stsize,
                                             maximumOpt, statusOpt);
    return objects;
}

NSS_IMPLEMENT nssCryptokiObject **
nssToken_FindCertificatesByNickname(
    NSSToken *token,
    nssSession *sessionOpt,
    const NSSUTF8 *name,
    nssTokenSearchType searchType,
    PRUint32 maximumOpt,
    PRStatus *statusOpt)
{
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE nick_template[3];
    CK_ULONG ntsize;
    nssCryptokiObject **objects;
    NSS_CK_TEMPLATE_START(nick_template, attr, ntsize);
    NSS_CK_SET_ATTRIBUTE_UTF8(attr, CKA_LABEL, name);
    /* Set the search to token/session only if provided */
    if (searchType == nssTokenSearchType_SessionOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (searchType == nssTokenSearchType_TokenOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    }
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_cert);
    NSS_CK_TEMPLATE_FINISH(nick_template, attr, ntsize);
    /* now locate the token certs matching this template */
    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
                                             nick_template, ntsize,
                                             maximumOpt, statusOpt);
    if (!objects) {
        /* This is to workaround the fact that PKCS#11 doesn't specify
         * whether the '\0' should be included.  XXX Is that still true?
         * im - this is not needed by the current softoken.  However, I'm
         * leaving it in until I have surveyed more tokens to see if it needed.
         * well, its needed by the builtin token...
         */
        nick_template[0].ulValueLen++;
        objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
                                                 nick_template, ntsize,
                                                 maximumOpt, statusOpt);
    }
    return objects;
}

/* XXX
 * This function *does not* use the token object cache, because not even
 * the softoken will return a value for CKA_NSS_EMAIL from a call
 * to GetAttributes.  The softoken does allow searches with that attribute,
 * it just won't return a value for it.
 */
NSS_IMPLEMENT nssCryptokiObject **
nssToken_FindCertificatesByEmail(
    NSSToken *token,
    nssSession *sessionOpt,
    NSSASCII7 *email,
    nssTokenSearchType searchType,
    PRUint32 maximumOpt,
    PRStatus *statusOpt)
{
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE email_template[3];
    CK_ULONG etsize;
    nssCryptokiObject **objects;
    NSS_CK_TEMPLATE_START(email_template, attr, etsize);
    NSS_CK_SET_ATTRIBUTE_UTF8(attr, CKA_NSS_EMAIL, email);
    /* Set the search to token/session only if provided */
    if (searchType == nssTokenSearchType_SessionOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (searchType == nssTokenSearchType_TokenOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    }
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_cert);
    NSS_CK_TEMPLATE_FINISH(email_template, attr, etsize);
    /* now locate the token certs matching this template */
    objects = find_objects(token, sessionOpt,
                           email_template, etsize,
                           maximumOpt, statusOpt);
    if (!objects) {
        /* This is to workaround the fact that PKCS#11 doesn't specify
         * whether the '\0' should be included.  XXX Is that still true?
         * im - this is not needed by the current softoken.  However, I'm
         * leaving it in until I have surveyed more tokens to see if it needed.
         * well, its needed by the builtin token...
         */
        email_template[0].ulValueLen++;
        objects = find_objects(token, sessionOpt,
                               email_template, etsize,
                               maximumOpt, statusOpt);
    }
    return objects;
}

NSS_IMPLEMENT nssCryptokiObject **
nssToken_FindCertificatesByID(
    NSSToken *token,
    nssSession *sessionOpt,
    NSSItem *id,
    nssTokenSearchType searchType,
    PRUint32 maximumOpt,
    PRStatus *statusOpt)
{
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE id_template[3];
    CK_ULONG idtsize;
    nssCryptokiObject **objects;
    NSS_CK_TEMPLATE_START(id_template, attr, idtsize);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ID, id);
    /* Set the search to token/session only if provided */
    if (searchType == nssTokenSearchType_SessionOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (searchType == nssTokenSearchType_TokenOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    }
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_cert);
    NSS_CK_TEMPLATE_FINISH(id_template, attr, idtsize);
    /* now locate the token certs matching this template */
    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
                                             id_template, idtsize,
                                             maximumOpt, statusOpt);
    return objects;
}

/*
 * decode the serial item and return our result.
 * NOTE serialDecode's data is really stored in serial. Don't free it.
 */
static PRStatus
nssToken_decodeSerialItem(NSSItem *serial, NSSItem *serialDecode)
{
    unsigned char *data = (unsigned char *)serial->data;
    int data_left, data_len, index;

    if ((serial->size >= 3) && (data[0] == 0x2)) {
        /* remove the der encoding of the serial number before generating the
         * key.. */
        data_left = serial->size - 2;
        data_len = data[1];
        index = 2;

        /* extended length ? (not very likely for a serial number) */
        if (data_len & 0x80) {
            int len_count = data_len & 0x7f;

            data_len = 0;
            data_left -= len_count;
            if (data_left > 0) {
                while (len_count--) {
                    data_len = (data_len << 8) | data[index++];
                }
            }
        }
        /* XXX leaving any leading zeros on the serial number for backwards
         * compatibility
         */
        /* not a valid der, must be just an unlucky serial number value */
        if (data_len == data_left) {
            serialDecode->size = data_len;
            serialDecode->data = &data[index];
            return PR_SUCCESS;
        }
    }
    return PR_FAILURE;
}

NSS_IMPLEMENT nssCryptokiObject *
nssToken_FindCertificateByIssuerAndSerialNumber(
    NSSToken *token,
    nssSession *sessionOpt,
    NSSDER *issuer,
    NSSDER *serial,
    nssTokenSearchType searchType,
    PRStatus *statusOpt)
{
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE_PTR serialAttr;
    CK_ATTRIBUTE cert_template[4];
    CK_ULONG ctsize;
    nssCryptokiObject **objects;
    nssCryptokiObject *rvObject = NULL;
    NSS_CK_TEMPLATE_START(cert_template, attr, ctsize);

    if (!token) {
        PORT_SetError(SEC_ERROR_NO_TOKEN);
        if (statusOpt)
            *statusOpt = PR_FAILURE;
        return NULL;
    }
    /* Set the search to token/session only if provided */
    if (searchType == nssTokenSearchType_SessionOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if ((searchType == nssTokenSearchType_TokenOnly) ||
               (searchType == nssTokenSearchType_TokenForced)) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    }
    /* Set the unique id */
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_cert);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ISSUER, issuer);
    serialAttr = attr;
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SERIAL_NUMBER, serial);
    NSS_CK_TEMPLATE_FINISH(cert_template, attr, ctsize);
    /* get the object handle */
    if (searchType == nssTokenSearchType_TokenForced) {
        objects = find_objects(token, sessionOpt,
                               cert_template, ctsize,
                               1, statusOpt);
    } else {
        objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
                                                 cert_template, ctsize,
                                                 1, statusOpt);
    }
    if (objects) {
        rvObject = objects[0];
        nss_ZFreeIf(objects);
    }

    /*
     * NSS used to incorrectly store serial numbers in their decoded form.
     * because of this old tokens have decoded serial numbers.
     */
    if (!objects) {
        NSSItem serialDecode;
        PRStatus status;

        status = nssToken_decodeSerialItem(serial, &serialDecode);
        if (status != PR_SUCCESS) {
            return NULL;
        }
        NSS_CK_SET_ATTRIBUTE_ITEM(serialAttr, CKA_SERIAL_NUMBER, &serialDecode);
        if (searchType == nssTokenSearchType_TokenForced) {
            objects = find_objects(token, sessionOpt,
                                   cert_template, ctsize,
                                   1, statusOpt);
        } else {
            objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
                                                     cert_template, ctsize,
                                                     1, statusOpt);
        }
        if (objects) {
            rvObject = objects[0];
            nss_ZFreeIf(objects);
        }
    }
    return rvObject;
}

NSS_IMPLEMENT nssCryptokiObject *
nssToken_FindCertificateByEncodedCertificate(
    NSSToken *token,
    nssSession *sessionOpt,
    NSSBER *encodedCertificate,
    nssTokenSearchType searchType,
    PRStatus *statusOpt)
{
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE cert_template[3];
    CK_ULONG ctsize;
    nssCryptokiObject **objects;
    nssCryptokiObject *rvObject = NULL;
    NSS_CK_TEMPLATE_START(cert_template, attr, ctsize);
    /* Set the search to token/session only if provided */
    if (searchType == nssTokenSearchType_SessionOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (searchType == nssTokenSearchType_TokenOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    }
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_cert);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_VALUE, encodedCertificate);
    NSS_CK_TEMPLATE_FINISH(cert_template, attr, ctsize);
    /* get the object handle */
    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
                                             cert_template, ctsize,
                                             1, statusOpt);
    if (objects) {
        rvObject = objects[0];
        nss_ZFreeIf(objects);
    }
    return rvObject;
}

NSS_IMPLEMENT nssCryptokiObject **
nssToken_FindPrivateKeys(
    NSSToken *token,
    nssSession *sessionOpt,
    nssTokenSearchType searchType,
    PRUint32 maximumOpt,
    PRStatus *statusOpt)
{
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE key_template[2];
    CK_ULONG ktsize;
    nssCryptokiObject **objects;

    NSS_CK_TEMPLATE_START(key_template, attr, ktsize);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_privkey);
    if (searchType == nssTokenSearchType_SessionOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (searchType == nssTokenSearchType_TokenOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    }
    NSS_CK_TEMPLATE_FINISH(key_template, attr, ktsize);

    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
                                             key_template, ktsize,
                                             maximumOpt, statusOpt);
    return objects;
}

/* XXX ?there are no session cert objects, so only search token objects */
NSS_IMPLEMENT nssCryptokiObject *
nssToken_FindPrivateKeyByID(
    NSSToken *token,
    nssSession *sessionOpt,
    NSSItem *keyID)
{
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE key_template[3];
    CK_ULONG ktsize;
    nssCryptokiObject **objects;
    nssCryptokiObject *rvKey = NULL;

    NSS_CK_TEMPLATE_START(key_template, attr, ktsize);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_privkey);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ID, keyID);
    NSS_CK_TEMPLATE_FINISH(key_template, attr, ktsize);

    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
                                             key_template, ktsize,
                                             1, NULL);
    if (objects) {
        rvKey = objects[0];
        nss_ZFreeIf(objects);
    }
    return rvKey;
}

/* XXX ?there are no session cert objects, so only search token objects */
NSS_IMPLEMENT nssCryptokiObject *
nssToken_FindPublicKeyByID(
    NSSToken *token,
    nssSession *sessionOpt,
    NSSItem *keyID)
{
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE key_template[3];
    CK_ULONG ktsize;
    nssCryptokiObject **objects;
    nssCryptokiObject *rvKey = NULL;

    NSS_CK_TEMPLATE_START(key_template, attr, ktsize);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_pubkey);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ID, keyID);
    NSS_CK_TEMPLATE_FINISH(key_template, attr, ktsize);

    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
                                             key_template, ktsize,
                                             1, NULL);
    if (objects) {
        rvKey = objects[0];
        nss_ZFreeIf(objects);
    }
    return rvKey;
}

static void
sha1_hash(NSSItem *input, NSSItem *output)
{
    NSSAlgorithmAndParameters *ap;
    PK11SlotInfo *internal = PK11_GetInternalSlot();
    NSSToken *token = PK11Slot_GetNSSToken(internal);
    ap = NSSAlgorithmAndParameters_CreateSHA1Digest(NULL);
    (void)nssToken_Digest(token, NULL, ap, input, output, NULL);
    PK11_FreeSlot(token->pk11slot);
    nss_ZFreeIf(ap);
}

static void
md5_hash(NSSItem *input, NSSItem *output)
{
    NSSAlgorithmAndParameters *ap;
    PK11SlotInfo *internal = PK11_GetInternalSlot();
    NSSToken *token = PK11Slot_GetNSSToken(internal);
    ap = NSSAlgorithmAndParameters_CreateMD5Digest(NULL);
    (void)nssToken_Digest(token, NULL, ap, input, output, NULL);
    PK11_FreeSlot(token->pk11slot);
    nss_ZFreeIf(ap);
}

static CK_TRUST
get_ck_trust(
    nssTrustLevel nssTrust)
{
    CK_TRUST t;
    switch (nssTrust) {
        case nssTrustLevel_NotTrusted:
            t = CKT_NSS_NOT_TRUSTED;
            break;
        case nssTrustLevel_TrustedDelegator:
            t = CKT_NSS_TRUSTED_DELEGATOR;
            break;
        case nssTrustLevel_ValidDelegator:
            t = CKT_NSS_VALID_DELEGATOR;
            break;
        case nssTrustLevel_Trusted:
            t = CKT_NSS_TRUSTED;
            break;
        case nssTrustLevel_MustVerify:
            t = CKT_NSS_MUST_VERIFY_TRUST;
            break;
        case nssTrustLevel_Unknown:
        default:
            t = CKT_NSS_TRUST_UNKNOWN;
            break;
    }
    return t;
}

NSS_IMPLEMENT nssCryptokiObject *
nssToken_ImportTrust(
    NSSToken *tok,
    nssSession *sessionOpt,
    NSSDER *certEncoding,
    NSSDER *certIssuer,
    NSSDER *certSerial,
    nssTrustLevel serverAuth,
    nssTrustLevel clientAuth,
    nssTrustLevel codeSigning,
    nssTrustLevel emailProtection,
    PRBool stepUpApproved,
    PRBool asTokenObject)
{
    nssCryptokiObject *object;
    CK_OBJECT_CLASS tobjc = CKO_NSS_TRUST;
    CK_TRUST ckSA, ckCA, ckCS, ckEP;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE trust_tmpl[11];
    CK_ULONG tsize;
    PRUint8 sha1[20]; /* this is cheating... */
    PRUint8 md5[16];
    NSSItem sha1_result, md5_result;
    sha1_result.data = sha1;
    sha1_result.size = sizeof sha1;
    md5_result.data = md5;
    md5_result.size = sizeof md5;
    sha1_hash(certEncoding, &sha1_result);
    md5_hash(certEncoding, &md5_result);
    ckSA = get_ck_trust(serverAuth);
    ckCA = get_ck_trust(clientAuth);
    ckCS = get_ck_trust(codeSigning);
    ckEP = get_ck_trust(emailProtection);
    NSS_CK_TEMPLATE_START(trust_tmpl, attr, tsize);
    if (asTokenObject) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    } else {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    }
    NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_CLASS, tobjc);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ISSUER, certIssuer);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SERIAL_NUMBER, certSerial);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CERT_SHA1_HASH, &sha1_result);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CERT_MD5_HASH, &md5_result);
    /* now set the trust values */
    NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_TRUST_SERVER_AUTH, ckSA);
    NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_TRUST_CLIENT_AUTH, ckCA);
    NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_TRUST_CODE_SIGNING, ckCS);
    NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_TRUST_EMAIL_PROTECTION, ckEP);
    if (stepUpApproved) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TRUST_STEP_UP_APPROVED,
                                  &g_ck_true);
    } else {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TRUST_STEP_UP_APPROVED,
                                  &g_ck_false);
    }
    NSS_CK_TEMPLATE_FINISH(trust_tmpl, attr, tsize);
    /* import the trust object onto the token */
    object = import_object(tok, sessionOpt, trust_tmpl, tsize);
    if (object && tok->cache) {
        nssTokenObjectCache_ImportObject(tok->cache, object, tobjc,
                                         trust_tmpl, tsize);
    }
    return object;
}

NSS_IMPLEMENT nssCryptokiObject *
nssToken_FindTrustForCertificate(
    NSSToken *token,
    nssSession *sessionOpt,
    NSSDER *certEncoding,
    NSSDER *certIssuer,
    NSSDER *certSerial,
    nssTokenSearchType searchType)
{
    CK_OBJECT_CLASS tobjc = CKO_NSS_TRUST;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE tobj_template[5];
    CK_ULONG tobj_size;
    nssSession *session = sessionOpt ? sessionOpt : token->defaultSession;
    nssCryptokiObject *object = NULL, **objects;

    /* Don't ask the module to use an invalid session handle. */
    if (!session || session->handle == CK_INVALID_HANDLE) {
        PORT_SetError(SEC_ERROR_NO_TOKEN);
        return object;
    }

    NSS_CK_TEMPLATE_START(tobj_template, attr, tobj_size);
    if (searchType == nssTokenSearchType_TokenOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    }
    NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_CLASS, tobjc);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ISSUER, certIssuer);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SERIAL_NUMBER, certSerial);
    NSS_CK_TEMPLATE_FINISH(tobj_template, attr, tobj_size);
    objects = nssToken_FindObjectsByTemplate(token, session,
                                             tobj_template, tobj_size,
                                             1, NULL);
    if (objects) {
        object = objects[0];
        nss_ZFreeIf(objects);
    }
    return object;
}

NSS_IMPLEMENT nssCryptokiObject *
nssToken_ImportCRL(
    NSSToken *token,
    nssSession *sessionOpt,
    NSSDER *subject,
    NSSDER *encoding,
    PRBool isKRL,
    NSSUTF8 *url,
    PRBool asTokenObject)
{
    nssCryptokiObject *object;
    CK_OBJECT_CLASS crlobjc = CKO_NSS_CRL;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE crl_tmpl[6];
    CK_ULONG crlsize;

    NSS_CK_TEMPLATE_START(crl_tmpl, attr, crlsize);
    if (asTokenObject) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    } else {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    }
    NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_CLASS, crlobjc);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SUBJECT, subject);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_VALUE, encoding);
    NSS_CK_SET_ATTRIBUTE_UTF8(attr, CKA_NSS_URL, url);
    if (isKRL) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_NSS_KRL, &g_ck_true);
    } else {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_NSS_KRL, &g_ck_false);
    }
    NSS_CK_TEMPLATE_FINISH(crl_tmpl, attr, crlsize);

    /* import the crl object onto the token */
    object = import_object(token, sessionOpt, crl_tmpl, crlsize);
    if (object && token->cache) {
        nssTokenObjectCache_ImportObject(token->cache, object, crlobjc,
                                         crl_tmpl, crlsize);
    }
    return object;
}

NSS_IMPLEMENT nssCryptokiObject **
nssToken_FindCRLsBySubject(
    NSSToken *token,
    nssSession *sessionOpt,
    NSSDER *subject,
    nssTokenSearchType searchType,
    PRUint32 maximumOpt,
    PRStatus *statusOpt)
{
    CK_OBJECT_CLASS crlobjc = CKO_NSS_CRL;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE crlobj_template[3];
    CK_ULONG crlobj_size;
    nssCryptokiObject **objects = NULL;
    nssSession *session = sessionOpt ? sessionOpt : token->defaultSession;

    /* Don't ask the module to use an invalid session handle. */
    if (!session || session->handle == CK_INVALID_HANDLE) {
        PORT_SetError(SEC_ERROR_NO_TOKEN);
        return objects;
    }

    NSS_CK_TEMPLATE_START(crlobj_template, attr, crlobj_size);
    if (searchType == nssTokenSearchType_SessionOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (searchType == nssTokenSearchType_TokenOnly ||
               searchType == nssTokenSearchType_TokenForced) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    }
    NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_CLASS, crlobjc);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SUBJECT, subject);
    NSS_CK_TEMPLATE_FINISH(crlobj_template, attr, crlobj_size);

    objects = nssToken_FindObjectsByTemplate(token, session,
                                             crlobj_template, crlobj_size,
                                             maximumOpt, statusOpt);
    return objects;
}

NSS_IMPLEMENT PRStatus
nssToken_GetCachedObjectAttributes(
    NSSToken *token,
    NSSArena *arenaOpt,
    nssCryptokiObject *object,
    CK_OBJECT_CLASS objclass,
    CK_ATTRIBUTE_PTR atemplate,
    CK_ULONG atlen)
{
    if (!token->cache) {
        return PR_FAILURE;
    }
    return nssTokenObjectCache_GetObjectAttributes(token->cache, arenaOpt,
                                                   object, objclass,
                                                   atemplate, atlen);
}

NSS_IMPLEMENT NSSItem *
nssToken_Digest(
    NSSToken *tok,
    nssSession *sessionOpt,
    NSSAlgorithmAndParameters *ap,
    NSSItem *data,
    NSSItem *rvOpt,
    NSSArena *arenaOpt)
{
    CK_RV ckrv;
    CK_ULONG digestLen;
    CK_BYTE_PTR digest;
    NSSItem *rvItem = NULL;
    void *epv = nssToken_GetCryptokiEPV(tok);
    nssSession *session = (sessionOpt) ? sessionOpt : tok->defaultSession;

    /* Don't ask the module to use an invalid session handle. */
    if (!session || session->handle == CK_INVALID_HANDLE) {
        PORT_SetError(SEC_ERROR_NO_TOKEN);
        return rvItem;
    }

    nssSession_EnterMonitor(session);
    ckrv = CKAPI(epv)->C_DigestInit(session->handle, &ap->mechanism);
    if (ckrv != CKR_OK) {
        nssSession_ExitMonitor(session);
        return NULL;
    }
#if 0
    /* XXX the standard says this should work, but it doesn't */
    ckrv = CKAPI(epv)->C_Digest(session->handle, NULL, 0, NULL, &digestLen);
    if (ckrv != CKR_OK) {
	nssSession_ExitMonitor(session);
	return NULL;
    }
#endif
    digestLen = 0; /* XXX for now */
    digest = NULL;
    if (rvOpt) {
        if (rvOpt->size > 0 && rvOpt->size < digestLen) {
            nssSession_ExitMonitor(session);
            /* the error should be bad args */
            return NULL;
        }
        if (rvOpt->data) {
            digest = rvOpt->data;
        }
        digestLen = rvOpt->size;
    }
    if (!digest) {
        digest = (CK_BYTE_PTR)nss_ZAlloc(arenaOpt, digestLen);
        if (!digest) {
            nssSession_ExitMonitor(session);
            return NULL;
        }
    }
    ckrv = CKAPI(epv)->C_Digest(session->handle,
                                (CK_BYTE_PTR)data->data,
                                (CK_ULONG)data->size,
                                (CK_BYTE_PTR)digest,
                                &digestLen);
    nssSession_ExitMonitor(session);
    if (ckrv != CKR_OK) {
        nss_ZFreeIf(digest);
        return NULL;
    }
    if (!rvOpt) {
        rvItem = nssItem_Create(arenaOpt, NULL, digestLen, (void *)digest);
    }
    return rvItem;
}

NSS_IMPLEMENT PRStatus
nssToken_BeginDigest(
    NSSToken *tok,
    nssSession *sessionOpt,
    NSSAlgorithmAndParameters *ap)
{
    CK_RV ckrv;
    void *epv = nssToken_GetCryptokiEPV(tok);
    nssSession *session = (sessionOpt) ? sessionOpt : tok->defaultSession;

    /* Don't ask the module to use an invalid session handle. */
    if (!session || session->handle == CK_INVALID_HANDLE) {
        PORT_SetError(SEC_ERROR_NO_TOKEN);
        return PR_FAILURE;
    }

    nssSession_EnterMonitor(session);
    ckrv = CKAPI(epv)->C_DigestInit(session->handle, &ap->mechanism);
    nssSession_ExitMonitor(session);
    return (ckrv == CKR_OK) ? PR_SUCCESS : PR_FAILURE;
}

NSS_IMPLEMENT PRStatus
nssToken_ContinueDigest(
    NSSToken *tok,
    nssSession *sessionOpt,
    NSSItem *item)
{
    CK_RV ckrv;
    void *epv = nssToken_GetCryptokiEPV(tok);
    nssSession *session = (sessionOpt) ? sessionOpt : tok->defaultSession;

    /* Don't ask the module to use an invalid session handle. */
    if (!session || session->handle == CK_INVALID_HANDLE) {
        PORT_SetError(SEC_ERROR_NO_TOKEN);
        return PR_FAILURE;
    }

    nssSession_EnterMonitor(session);
    ckrv = CKAPI(epv)->C_DigestUpdate(session->handle,
                                      (CK_BYTE_PTR)item->data,
                                      (CK_ULONG)item->size);
    nssSession_ExitMonitor(session);
    return (ckrv == CKR_OK) ? PR_SUCCESS : PR_FAILURE;
}

NSS_IMPLEMENT NSSItem *
nssToken_FinishDigest(
    NSSToken *tok,
    nssSession *sessionOpt,
    NSSItem *rvOpt,
    NSSArena *arenaOpt)
{
    CK_RV ckrv;
    CK_ULONG digestLen;
    CK_BYTE_PTR digest;
    NSSItem *rvItem = NULL;
    void *epv = nssToken_GetCryptokiEPV(tok);
    nssSession *session = (sessionOpt) ? sessionOpt : tok->defaultSession;

    /* Don't ask the module to use an invalid session handle. */
    if (!session || session->handle == CK_INVALID_HANDLE) {
        PORT_SetError(SEC_ERROR_NO_TOKEN);
        return NULL;
    }

    nssSession_EnterMonitor(session);
    ckrv = CKAPI(epv)->C_DigestFinal(session->handle, NULL, &digestLen);
    if (ckrv != CKR_OK || digestLen == 0) {
        nssSession_ExitMonitor(session);
        return NULL;
    }
    digest = NULL;
    if (rvOpt) {
        if (rvOpt->size > 0 && rvOpt->size < digestLen) {
            nssSession_ExitMonitor(session);
            /* the error should be bad args */
            return NULL;
        }
        if (rvOpt->data) {
            digest = rvOpt->data;
        }
        digestLen = rvOpt->size;
    }
    if (!digest) {
        digest = (CK_BYTE_PTR)nss_ZAlloc(arenaOpt, digestLen);
        if (!digest) {
            nssSession_ExitMonitor(session);
            return NULL;
        }
    }
    ckrv = CKAPI(epv)->C_DigestFinal(session->handle, digest, &digestLen);
    nssSession_ExitMonitor(session);
    if (ckrv != CKR_OK) {
        nss_ZFreeIf(digest);
        return NULL;
    }
    if (!rvOpt) {
        rvItem = nssItem_Create(arenaOpt, NULL, digestLen, (void *)digest);
    }
    return rvItem;
}

NSS_IMPLEMENT PRBool
nssToken_IsPresent(
    NSSToken *token)
{
    return nssSlot_IsTokenPresent(token->slot);
}

/* Sigh.  The methods to find objects declared above cause problems with
 * the low-level object cache in the softoken -- the objects are found in
 * toto, then one wave of GetAttributes is done, then another.  Having a
 * large number of objects causes the cache to be thrashed, as the objects
 * are gone before there's any chance to ask for their attributes.
 * So, for now, bringing back traversal methods for certs.  This way all of
 * the cert's attributes can be grabbed immediately after finding it,
 * increasing the likelihood that the cache takes care of it.
 */
NSS_IMPLEMENT PRStatus
nssToken_TraverseCertificates(
    NSSToken *token,
    nssSession *sessionOpt,
    nssTokenSearchType searchType,
    PRStatus (*callback)(nssCryptokiObject *instance, void *arg),
    void *arg)
{
    CK_RV ckrv;
    CK_ULONG count;
    CK_OBJECT_HANDLE *objectHandles;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE cert_template[2];
    CK_ULONG ctsize;
    NSSArena *arena;
    PRUint32 arraySize, numHandles;
    nssCryptokiObject **objects;
    void *epv = nssToken_GetCryptokiEPV(token);
    nssSession *session = (sessionOpt) ? sessionOpt : token->defaultSession;

    /* Don't ask the module to use an invalid session handle. */
    if (!session || session->handle == CK_INVALID_HANDLE) {
        PORT_SetError(SEC_ERROR_NO_TOKEN);
        return PR_FAILURE;
    }

    /* template for all certs */
    NSS_CK_TEMPLATE_START(cert_template, attr, ctsize);
    if (searchType == nssTokenSearchType_SessionOnly) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (searchType == nssTokenSearchType_TokenOnly ||
               searchType == nssTokenSearchType_TokenForced) {
        NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
    }
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_cert);
    NSS_CK_TEMPLATE_FINISH(cert_template, attr, ctsize);

    /* the arena is only for the array of object handles */
    arena = nssArena_Create();
    if (!arena) {
        return PR_FAILURE;
    }
    arraySize = OBJECT_STACK_SIZE;
    numHandles = 0;
    objectHandles = nss_ZNEWARRAY(arena, CK_OBJECT_HANDLE, arraySize);
    if (!objectHandles) {
        goto loser;
    }
    nssSession_EnterMonitor(session); /* ==== session lock === */
    /* Initialize the find with the template */
    ckrv = CKAPI(epv)->C_FindObjectsInit(session->handle,
                                         cert_template, ctsize);
    if (ckrv != CKR_OK) {
        nssSession_ExitMonitor(session);
        goto loser;
    }
    while (PR_TRUE) {
        /* Issue the find for up to arraySize - numHandles objects */
        ckrv = CKAPI(epv)->C_FindObjects(session->handle,
                                         objectHandles + numHandles,
                                         arraySize - numHandles,
                                         &count);
        if (ckrv != CKR_OK) {
            nssSession_ExitMonitor(session);
            goto loser;
        }
        /* bump the number of found objects */
        numHandles += count;
        if (numHandles < arraySize) {
            break;
        }
        /* the array is filled, double it and continue */
        arraySize *= 2;
        objectHandles = nss_ZREALLOCARRAY(objectHandles,
                                          CK_OBJECT_HANDLE,
                                          arraySize);
        if (!objectHandles) {
            nssSession_ExitMonitor(session);
            goto loser;
        }
    }
    ckrv = CKAPI(epv)->C_FindObjectsFinal(session->handle);
    nssSession_ExitMonitor(session); /* ==== end session lock === */
    if (ckrv != CKR_OK) {
        goto loser;
    }
    if (numHandles > 0) {
        objects = create_objects_from_handles(token, session,
                                              objectHandles, numHandles);
        if (objects) {
            nssCryptokiObject **op;
            for (op = objects; *op; op++) {
                (void)(*callback)(*op, arg);
            }
            nss_ZFreeIf(objects);
        }
    }
    nssArena_Destroy(arena);
    return PR_SUCCESS;
loser:
    nssArena_Destroy(arena);
    return PR_FAILURE;
}

NSS_IMPLEMENT PRBool
nssToken_IsPrivateKeyAvailable(
    NSSToken *token,
    NSSCertificate *c,
    nssCryptokiObject *instance)
{
    CK_OBJECT_CLASS theClass;

    if (token == NULL)
        return PR_FALSE;
    if (c == NULL)
        return PR_FALSE;

    theClass = CKO_PRIVATE_KEY;
    if (!nssSlot_IsLoggedIn(token->slot)) {
        theClass = CKO_PUBLIC_KEY;
    }
    if (PK11_MatchItem(token->pk11slot, instance->handle, theClass) !=
        CK_INVALID_HANDLE) {
        return PR_TRUE;
    }
    return PR_FALSE;
}
