/* 
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Netscape security libraries.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

#ifdef DEBUG
static const char CVS_ID[] = "@(#) $RCSfile: devtoken.c,v $ $Revision: 1.36 $ $Date: 2003/09/23 20:34:15 $ $Name: MOZILLA_1_7_5_RELEASE $";
#endif /* DEBUG */

#ifndef NSSCKEPV_H
#include "nssckepv.h"
#endif /* NSSCKEPV_H */

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

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

#ifdef NSS_3_4_CODE
#include "pk11func.h"
#include "dev3hack.h"
#include "secerr.h"
#endif

extern const NSSError NSS_ERROR_NOT_FOUND;

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

#ifdef PURE_STAN_BUILD
struct NSSTokenStr
{
  struct nssDeviceBaseStr base;
  NSSSlot *slot;  /* Peer */
  CK_FLAGS ckFlags; /* from CK_TOKEN_INFO.flags */
  nssSession *defaultSession;
  nssTokenObjectCache *cache;
};

NSS_IMPLEMENT NSSToken *
nssToken_Create (
  CK_SLOT_ID slotID,
  NSSSlot *peer
)
{
    NSSArena *arena;
    NSSToken *rvToken;
    nssSession *session = NULL;
    NSSUTF8 *tokenName = NULL;
    PRUint32 length;
    PRBool readWrite;
    CK_TOKEN_INFO tokenInfo;
    CK_RV ckrv;
    void *epv = nssSlot_GetCryptokiEPV(peer);
    arena = NSSArena_Create();
    if(!arena) {
	return (NSSToken *)NULL;
    }
    rvToken = nss_ZNEW(arena, NSSToken);
    if (!rvToken) {
	goto loser;
    }
    /* Get token information */
    ckrv = CKAPI(epv)->C_GetTokenInfo(slotID, &tokenInfo);
    if (ckrv != CKR_OK) {
	/* set an error here, eh? */
	goto loser;
    }
    /* Grab the slot description from the PKCS#11 fixed-length buffer */
    length = nssPKCS11String_Length(tokenInfo.label, sizeof(tokenInfo.label));
    if (length > 0) {
	tokenName = nssUTF8_Create(arena, nssStringType_UTF8String, 
	                           (void *)tokenInfo.label, length);
	if (!tokenName) {
	    goto loser;
	}
    }
    /* Open a default session handle for the token. */
    if (tokenInfo.ulMaxSessionCount == 1) {
	/* if the token can only handle one session, it must be RW. */
	readWrite = PR_TRUE;
    } else {
	readWrite = PR_FALSE;
    }
    session = nssSlot_CreateSession(peer, arena, readWrite);
    if (session == NULL) {
	goto loser;
    }
    /* TODO: seed the RNG here */
    rvToken->base.arena = arena;
    rvToken->base.refCount = 1;
    rvToken->base.name = tokenName;
    rvToken->base.lock = PZ_NewLock(nssNSSILockOther); /* XXX */
    if (!rvToken->base.lock) {
	goto loser;
    }
    rvToken->slot = peer; /* slot owns ref to token */
    rvToken->ckFlags = tokenInfo.flags;
    rvToken->defaultSession = session;
    if (nssSlot_IsHardware(peer)) {
	rvToken->cache = nssTokenObjectCache_Create(rvToken, 
	                                            PR_TRUE, PR_TRUE, PR_TRUE);
	if (!rvToken->cache) {
	    nssSlot_Destroy(peer);
	    goto loser;
	}
    }
    return rvToken;
loser:
    if (session) {
	nssSession_Destroy(session);
    }
    nssArena_Destroy(arena);
    return (NSSToken *)NULL;
}
#endif /* PURE_STAN_BUILD */

NSS_IMPLEMENT PRStatus
nssToken_Destroy (
  NSSToken *tok
)
{
    if (tok) {
	if (PR_AtomicDecrement(&tok->base.refCount) == 0) {
	    PZ_DestroyLock(tok->base.lock);
	    nssTokenObjectCache_Destroy(tok->cache);
	    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_AtomicIncrement(&tok->base.refCount);
    return tok;
}

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

#ifdef PURE_STAN_BUILD
NSS_IMPLEMENT NSSModule *
nssToken_GetModule (
  NSSToken *token
)
{
    return nssSlot_GetModule(token->slot);
}
#endif

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 (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 = (ckrv == CKR_OK) ? PR_SUCCESS : PR_FAILURE;
    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)) {
		return CK_INVALID_HANDLE;
	    } else {
		session = sessionOpt;
	    }
	} else if (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) {
	return CK_INVALID_HANDLE;
    }
    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);
    }
    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]);
		}
		return (nssCryptokiObject **)NULL;
	    }
	}
    }
    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;
    CK_OBJECT_HANDLE staticObjects[OBJECT_STACK_SIZE];
    PRUint32 arraySize, numHandles;
    void *epv = nssToken_GetCryptokiEPV(tok);
    nssCryptokiObject **objects;
    nssSession *session = (sessionOpt) ? sessionOpt : tok->defaultSession;

    /* 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 {
	if (statusOpt) *statusOpt = PR_FAILURE;
    }
    return (nssCryptokiObject **)NULL;
}

static nssCryptokiObject **
find_objects_by_template (
  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;
    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) {
#ifdef NSS_3_4_CODE
	PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
#endif
	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,
  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 (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_NETSCAPE_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);
	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 certificates - this should only happen if the token
 * has been marked as "traversable"
 */
NSS_IMPLEMENT nssCryptokiObject **
nssToken_FindCertificates (
  NSSToken *token,
  nssSession *sessionOpt,
  nssTokenSearchType searchType,
  PRUint32 maximumOpt,
  PRStatus *statusOpt
)
{
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE cert_template[2];
    CK_ULONG ctsize;
    nssCryptokiObject **objects;
    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 ||
               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);

    if (searchType == nssTokenSearchType_TokenForced) {
	objects = find_objects(token, sessionOpt,
	                       cert_template, ctsize,
	                       maximumOpt, statusOpt);
    } else {
	objects = find_objects_by_template(token, sessionOpt,
	                                   cert_template, ctsize,
	                                   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 = find_objects_by_template(token, sessionOpt,
                                       subj_template, stsize,
                                       maximumOpt, statusOpt);
    return objects;
}

NSS_IMPLEMENT nssCryptokiObject **
nssToken_FindCertificatesByNickname (
  NSSToken *token,
  nssSession *sessionOpt,
  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 = find_objects_by_template(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 = find_objects_by_template(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_NETSCAPE_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_NETSCAPE_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 = find_objects_by_template(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);
    /* 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 = find_objects_by_template(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 = find_objects_by_template(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 = find_objects_by_template(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 = find_objects_by_template(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 = find_objects_by_template(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 = find_objects_by_template(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;
#ifdef NSS_3_4_CODE
    PK11SlotInfo *internal = PK11_GetInternalSlot();
    NSSToken *token = PK11Slot_GetNSSToken(internal);
#else
    NSSToken *token = nss_GetDefaultCryptoToken();
#endif
    ap = NSSAlgorithmAndParameters_CreateSHA1Digest(NULL);
    (void)nssToken_Digest(token, NULL, ap, input, output, NULL);
#ifdef NSS_3_4_CODE
    PK11_FreeSlot(token->pk11slot);
#endif
    nss_ZFreeIf(ap);
}

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

static CK_TRUST
get_ck_trust (
  nssTrustLevel nssTrust
)
{
    CK_TRUST t;
    switch (nssTrust) {
    case nssTrustLevel_NotTrusted: t = CKT_NETSCAPE_UNTRUSTED; break;
    case nssTrustLevel_TrustedDelegator: t = CKT_NETSCAPE_TRUSTED_DELEGATOR; 
	break;
    case nssTrustLevel_ValidDelegator: t = CKT_NETSCAPE_VALID_DELEGATOR; break;
    case nssTrustLevel_Trusted: t = CKT_NETSCAPE_TRUSTED; break;
    case nssTrustLevel_Valid: t = CKT_NETSCAPE_VALID; break;
    case nssTrustLevel_Unknown:
    default: t = CKT_NETSCAPE_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 asTokenObject
)
{
    nssCryptokiObject *object;
    CK_OBJECT_CLASS tobjc = CKO_NETSCAPE_TRUST;
    CK_TRUST ckSA, ckCA, ckCS, ckEP;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE trust_tmpl[10];
    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);
    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_FindTrustObjects (
  NSSToken *token,
  nssSession *sessionOpt,
  nssTokenSearchType searchType,
  PRUint32 maximumOpt,
  PRStatus *statusOpt
)
{
    CK_OBJECT_CLASS tobjc = CKO_NETSCAPE_TRUST;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE tobj_template[2];
    CK_ULONG tobj_size;
    nssCryptokiObject **objects;
    nssSession *session = sessionOpt ? sessionOpt : token->defaultSession;

    NSS_CK_TEMPLATE_START(tobj_template, attr, tobj_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, tobjc);
    NSS_CK_TEMPLATE_FINISH(tobj_template, attr, tobj_size);

    if (searchType == nssTokenSearchType_TokenForced) {
	objects = find_objects(token, session,
	                       tobj_template, tobj_size,
	                       maximumOpt, statusOpt);
    } else {
	objects = find_objects_by_template(token, session,
	                                   tobj_template, tobj_size,
	                                   maximumOpt, statusOpt);
    }
    return objects;
}

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

    NSS_CK_TEMPLATE_START(tobj_template, attr, tobj_size);
    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_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);
    object = NULL;
    objects = find_objects_by_template(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_NETSCAPE_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_NETSCAPE_URL, url);
    if (isKRL) {
	NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_NETSCAPE_KRL, &g_ck_true);
    } else {
	NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_NETSCAPE_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_FindCRLs (
  NSSToken *token,
  nssSession *sessionOpt,
  nssTokenSearchType searchType,
  PRUint32 maximumOpt,
  PRStatus *statusOpt
)
{
    CK_OBJECT_CLASS crlobjc = CKO_NETSCAPE_CRL;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE crlobj_template[2];
    CK_ULONG crlobj_size;
    nssCryptokiObject **objects;
    nssSession *session = sessionOpt ? sessionOpt : token->defaultSession;

    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_TEMPLATE_FINISH(crlobj_template, attr, crlobj_size);

    if (searchType == nssTokenSearchType_TokenForced) {
	objects = find_objects(token, session,
	                       crlobj_template, crlobj_size,
	                       maximumOpt, statusOpt);
    } else {
	objects = find_objects_by_template(token, session,
	                                   crlobj_template, crlobj_size,
	                                   maximumOpt, statusOpt);
    }
    return objects;
}

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

    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 = find_objects_by_template(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;
    session = (sessionOpt) ? sessionOpt : tok->defaultSession;
    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;
    nssSession *session;
    void *epv = nssToken_GetCryptokiEPV(tok);
    session = (sessionOpt) ? sessionOpt : tok->defaultSession;
    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;
    nssSession *session;
    void *epv = nssToken_GetCryptokiEPV(tok);
    session = (sessionOpt) ? sessionOpt : tok->defaultSession;
    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;
    session = (sessionOpt) ? sessionOpt : tok->defaultSession;
    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;
    PRStatus status;
    PRUint32 arraySize, numHandles;
    nssCryptokiObject **objects;
    void *epv = nssToken_GetCryptokiEPV(token);
    nssSession *session = (sessionOpt) ? sessionOpt : token->defaultSession;

    /* 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++) {
		status = (*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;
}
