<commit_before>/* 
 * 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$ $Revision$ $Date$ $Name$";
#endif /* DEBUG */

#ifndef DEV_H
#include "dev.h"
#endif /* DEV_H */

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

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

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

#ifndef BASE_H
#include "base.h"
#endif /* BASE_H */

/* XXX */
#ifndef PKI_H
#include "pki.h"
#endif /* PKI_H */

/* XXX */
#ifndef NSSPKI_H
#include "nsspki.h"
#endif /* NSSPKI_H */

#ifdef NSS_3_4_CODE
#include "pkim.h" /* for cert decoding */
#include "pk11func.h" /* for PK11_HasRootCerts */
#endif

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

NSS_IMPLEMENT PRStatus
nssToken_DeleteStoredObject
(
  nssCryptokiInstance *instance
)
{
    CK_RV ckrv;
    PRStatus nssrv;
    PRBool createdSession = PR_FALSE;
    NSSToken *token = instance->token;
    nssSession *session = NULL;
    if (nssCKObject_IsAttributeTrue(instance->handle, CKA_TOKEN, 
                                    token->defaultSession,
	                            token->slot, &nssrv)) {
       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(token)->C_DestroyObject(session->handle, instance->handle);
    nssSession_ExitMonitor(session);
    if (createdSession) {
	nssSession_Destroy(session);
    }
    if (ckrv != CKR_OK) {
	return PR_FAILURE;
    }
    return PR_SUCCESS;
}

static CK_OBJECT_HANDLE
import_object
(
  NSSToken *tok,
  nssSession *sessionOpt,
  CK_ATTRIBUTE_PTR objectTemplate,
  CK_ULONG otsize
)
{
    nssSession *session = NULL;
    PRBool createdSession = PR_FALSE;
    CK_OBJECT_HANDLE object;
    CK_RV ckrv;
    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(tok->slot)->C_CreateObject(session->handle, 
                                            objectTemplate, otsize,
                                            &object);
    nssSession_ExitMonitor(session);
    if (createdSession) {
	nssSession_Destroy(session);
    }
    if (ckrv != CKR_OK) {
	return CK_INVALID_HANDLE;
    }
    return object;
}

static CK_OBJECT_HANDLE
find_object_by_template
(
  NSSToken *tok,
  nssSession *sessionOpt,
  CK_ATTRIBUTE_PTR cktemplate,
  CK_ULONG ctsize
)
{
    CK_SESSION_HANDLE hSession;
    CK_OBJECT_HANDLE rvObject;
    CK_ULONG count;
    CK_RV ckrv;
    nssSession *session;
    session = (sessionOpt) ? sessionOpt : tok->defaultSession;
    hSession = session->handle;
    nssSession_EnterMonitor(session);
    ckrv = CKAPI(tok)->C_FindObjectsInit(hSession, cktemplate, ctsize);
    if (ckrv != CKR_OK) {
	nssSession_ExitMonitor(session);
	return CK_INVALID_HANDLE;
    }
    ckrv = CKAPI(tok)->C_FindObjects(hSession, &rvObject, 1, &count);
    if (ckrv != CKR_OK) {
	nssSession_ExitMonitor(session);
	return CK_INVALID_HANDLE;
    }
    ckrv = CKAPI(tok)->C_FindObjectsFinal(hSession);
    nssSession_ExitMonitor(session);
    if (ckrv != CKR_OK) {
	return CK_INVALID_HANDLE;
    }
    return rvObject;
}

static PRStatus 
traverse_objects_by_template
(
  NSSToken *tok,
  nssSession *sessionOpt,
  CK_ATTRIBUTE_PTR obj_template,
  CK_ULONG otsize,
  PRStatus (*callback)(NSSToken *t, nssSession *session,
                       CK_OBJECT_HANDLE h, void *arg),
  void *arg
)
{
    NSSSlot *slot;
    PRStatus cbrv;
    PRUint32 i;
    CK_RV ckrv;
    CK_ULONG count;
    CK_OBJECT_HANDLE *objectStack;
    CK_OBJECT_HANDLE startOS[OBJECT_STACK_SIZE];
    CK_SESSION_HANDLE hSession;
    NSSArena *objectArena = NULL;
    nssSession *session;
    nssList *objectList = NULL;
    slot = tok->slot;
    objectStack = startOS;
    session = (sessionOpt) ? sessionOpt : tok->defaultSession;
    hSession = session->handle;
    nssSession_EnterMonitor(session);
    ckrv = CKAPI(slot)->C_FindObjectsInit(hSession, obj_template, otsize);
    if (ckrv != CKR_OK) {
	nssSession_ExitMonitor(session);
	goto loser;
    }
    while (PR_TRUE) {
	ckrv = CKAPI(slot)->C_FindObjects(hSession, objectStack, 
	                                  OBJECT_STACK_SIZE, &count);
	if (ckrv != CKR_OK) {
	    nssSession_ExitMonitor(session);
	    goto loser;
	}
	if (count == OBJECT_STACK_SIZE) {
	    if (!objectList) {
		objectArena = NSSArena_Create();
		objectList = nssList_Create(objectArena, PR_FALSE);
	    }
	    objectStack = nss_ZNEWARRAY(objectArena, CK_OBJECT_HANDLE, 
	                                OBJECT_STACK_SIZE);
	    nssList_Add(objectList, objectStack);
	} else {
	    break;
	}
    }
    ckrv = CKAPI(slot)->C_FindObjectsFinal(hSession);
    nssSession_ExitMonitor(session);
    if (ckrv != CKR_OK) {
	goto loser;
    }
    if (objectList) {
	nssListIterator *objects;
	objects = nssList_CreateIterator(objectList);
	for (objectStack = (CK_OBJECT_HANDLE *)nssListIterator_Start(objects);
	     objectStack != NULL;
	     objectStack = (CK_OBJECT_HANDLE *)nssListIterator_Next(objects)) {
	    for (i=0; i<count; i++) {
		cbrv = (*callback)(tok, session, objectStack[i], arg);
	    }
	}
	nssListIterator_Finish(objects);
	count = OBJECT_STACK_SIZE;
    }
    for (i=0; i<count; i++) {
	cbrv = (*callback)(tok, session, startOS[i], arg);
    }
    if (objectArena)
	NSSArena_Destroy(objectArena);
    return PR_SUCCESS;
loser:
    if (objectArena)
	NSSArena_Destroy(objectArena);
    return PR_FAILURE;
}

static nssCryptokiInstance *
create_cryptoki_instance
(
  NSSArena *arena,
  NSSToken *t, 
  CK_OBJECT_HANDLE h,
  PRBool isTokenObject
)
{
    nssCryptokiInstance *instance;
    instance = nss_ZNEW(arena, nssCryptokiInstance);
    if (!instance) {
	return NULL;
    }
    instance->handle = h;
    instance->token = t;
    instance->isTokenObject = isTokenObject;
    return instance;
}

static NSSCertificateType
nss_cert_type_from_ck_attrib(CK_ATTRIBUTE_PTR attrib)
{
    CK_CERTIFICATE_TYPE ckCertType;
    ckCertType = *((CK_ULONG *)attrib->pValue);
    switch (ckCertType) {
    case CKC_X_509:
	return NSSCertificateType_PKIX;
	break;
    default:
	return NSSCertificateType_Unknown;
    }
}

/* Create a certificate from an object handle. */
static NSSCertificate *
get_token_cert
(
  NSSToken *token,
  nssSession *sessionOpt,
  CK_OBJECT_HANDLE handle
)
{
    NSSCertificate *rvCert;
    NSSArena *arena;
    nssSession *session;
    PRStatus nssrv;
    CK_ULONG template_size;
    CK_ATTRIBUTE cert_template[] = {
	{ CKA_CERTIFICATE_TYPE, NULL, 0 },
	{ CKA_ID,               NULL, 0 },
	{ CKA_VALUE,            NULL, 0 },
	{ CKA_ISSUER,           NULL, 0 },
	{ CKA_SERIAL_NUMBER,    NULL, 0 },
	{ CKA_LABEL,            NULL, 0 },
	{ CKA_SUBJECT,          NULL, 0 },
	{ CKA_NETSCAPE_EMAIL,   NULL, 0 }
    };
    template_size = sizeof(cert_template) / sizeof(cert_template[0]);
    session = (sessionOpt) ? sessionOpt : token->defaultSession;
    arena = nssArena_Create();
    if (!arena) {
	return NULL;
    }
    rvCert = nss_ZNEW(arena, NSSCertificate);
    if (!rvCert) {
	NSSArena_Destroy(arena);
	return NULL;
    }
    nssrv = nssPKIObject_Initialize(&rvCert->object, arena, 
                                    token->trustDomain, NULL);
    if (nssrv != PR_SUCCESS) {
	goto loser;
    }
    nssrv = nssCKObject_GetAttributes(handle, 
                                      cert_template, template_size,
                                      arena, session, token->slot);
    if (nssrv != PR_SUCCESS) {
	goto loser;
    }
    rvCert->type = nss_cert_type_from_ck_attrib(&cert_template[0]);
    NSS_CK_ATTRIBUTE_TO_ITEM(&cert_template[1], &rvCert->id);
    NSS_CK_ATTRIBUTE_TO_ITEM(&cert_template[2], &rvCert->encoding);
    NSS_CK_ATTRIBUTE_TO_ITEM(&cert_template[3], &rvCert->issuer);
    NSS_CK_ATTRIBUTE_TO_ITEM(&cert_template[4], &rvCert->serial);
    NSS_CK_ATTRIBUTE_TO_UTF8(&cert_template[5],  rvCert->nickname);
    NSS_CK_ATTRIBUTE_TO_ITEM(&cert_template[6], &rvCert->subject);
    NSS_CK_ATTRIBUTE_TO_UTF8(&cert_template[7],  rvCert->email);
#ifdef NSS_3_4_CODE
    /* nss 3.4 database doesn't associate email address with cert */
    if (!rvCert->email) {
	nssDecodedCert *dc;
	NSSASCII7 *email;
	dc = nssCertificate_GetDecoding(rvCert);
	if (dc) {
	    email = dc->getEmailAddress(dc);
	    if (email) 
	    	rvCert->email = nssUTF8_Duplicate(email, arena);
	}
    }
#endif
    return rvCert;
loser:
    nssPKIObject_Destroy(&rvCert->object);
    return (NSSCertificate *)NULL;
}

NSS_IMPLEMENT PRStatus
nssToken_ImportCertificate
(
  NSSToken *tok,
  nssSession *sessionOpt,
  NSSCertificate *cert,
  PRBool asTokenObject
)
{
    nssCryptokiInstance *instance;
    CK_CERTIFICATE_TYPE cert_type = CKC_X_509;
    CK_OBJECT_HANDLE handle;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE cert_tmpl[9];
    CK_ULONG ctsize;
    NSS_CK_TEMPLATE_START(cert_tmpl, attr, ctsize);
    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_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,               &cert->id);
    NSS_CK_SET_ATTRIBUTE_UTF8(attr, CKA_LABEL,             cert->nickname);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_VALUE,            &cert->encoding);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ISSUER,           &cert->issuer);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SUBJECT,          &cert->subject);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SERIAL_NUMBER,    &cert->serial);
    NSS_CK_TEMPLATE_FINISH(cert_tmpl, attr, ctsize);
    /* Import the certificate onto the token */
    handle = import_object(tok, sessionOpt, cert_tmpl, ctsize);
    if (handle == CK_INVALID_HANDLE) {
	return PR_FAILURE;
    }
    instance = create_cryptoki_instance(cert->object.arena, 
                                        tok, handle, asTokenObject);
    if (!instance) {
	/* XXX destroy object */
	return PR_FAILURE;
    }
    nssList_Add(cert->object.instanceList, instance);
    /* XXX Fix this! */
    nssListIterator_Destroy(cert->object.instances);
    cert->object.instances = nssList_CreateIterator(cert->object.instanceList);
    return PR_SUCCESS;
}

static PRBool 
compare_cert_by_issuer_sn(void *a, void *b)
{
    NSSCertificate *c1 = (NSSCertificate *)a;
    NSSCertificate *c2 = (NSSCertificate *)b;
    return  (nssItem_Equal(&c1->issuer, &c2->issuer, NULL) &&
             nssItem_Equal(&c1->serial, &c2->serial, NULL));
}

static PRStatus
retrieve_cert(NSSToken *t, nssSession *session, CK_OBJECT_HANDLE h, void *arg)
{
    PRStatus nssrv;
    PRBool found;
    nssTokenCertSearch *search = (nssTokenCertSearch *)arg;
    NSSCertificate *cert = NULL;
    nssListIterator *instances;
    nssCryptokiInstance *ci;
    CK_ATTRIBUTE issuersn_tmpl[] = {
	{ CKA_ISSUER,        NULL, 0 },
	{ CKA_SERIAL_NUMBER, NULL, 0 }
    };
    CK_ULONG ist_size = sizeof(issuersn_tmpl) / sizeof(issuersn_tmpl[0]);
    if (search->cached) {
	NSSCertificate csi; /* a fake cert for indexing */
	nssrv = nssCKObject_GetAttributes(h, issuersn_tmpl, ist_size,
	                                  NULL, session, t->slot);
	NSS_CK_ATTRIBUTE_TO_ITEM(&issuersn_tmpl[0], &csi.issuer);
	NSS_CK_ATTRIBUTE_TO_ITEM(&issuersn_tmpl[1], &csi.serial);
	cert = (NSSCertificate *)nssList_Get(search->cached, &csi);
	nss_ZFreeIf(csi.issuer.data);
	nss_ZFreeIf(csi.serial.data);
    }
    found = PR_FALSE;
    if (cert) {
	nssCertificate_AddRef(cert);
	instances = cert->object.instances;
	for (ci  = (nssCryptokiInstance *)nssListIterator_Start(instances);
	     ci != (nssCryptokiInstance *)NULL;
	     ci  = (nssCryptokiInstance *)nssListIterator_Next(instances))
	{
	    if (ci->handle == h && ci->token == t) {
		found = PR_TRUE;
		break;
	    }
	}
	nssListIterator_Finish(instances);
    } else {
	cert = get_token_cert(t, session, h);
	if (!cert) return PR_FAILURE;
    }
    if (!found) {
	PRBool isTokenObject;
	/* XXX this is incorrect if the search is over both types */
	isTokenObject = (search->searchType == nssTokenSearchType_TokenOnly) ?
	                PR_TRUE : PR_FALSE;
	ci = create_cryptoki_instance(cert->object.arena, t, h, isTokenObject);
	if (!ci) {
	    NSSCertificate_Destroy(cert);
	    return PR_FAILURE;
	}
	nssList_Add(cert->object.instanceList, ci);
	/* XXX Fix this! */
	nssListIterator_Destroy(cert->object.instances);
	cert->object.instances = nssList_CreateIterator(cert->object.instanceList);
    }
    nssrv = (*search->callback)(cert, search->cbarg);
    NSSCertificate_Destroy(cert);
    return nssrv;
}

/* traverse all certificates - this should only happen if the token
 * has been marked as "traversable"
 */
NSS_IMPLEMENT PRStatus 
nssToken_TraverseCertificates
(
  NSSToken *token,
  nssSession *sessionOpt,
  nssTokenCertSearch *search
)
{
    PRStatus nssrv;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE cert_template[2];
    CK_ULONG ctsize;
    NSS_CK_TEMPLATE_START(cert_template, attr, ctsize);
    /* Set the search to token/session only if provided */
    if (search->searchType == nssTokenSearchType_SessionOnly) {
	NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (search->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(cert_template, attr, ctsize);
    if (search->cached) {
	nssList_SetCompareFunction(search->cached, compare_cert_by_issuer_sn);
    }
    nssrv = traverse_objects_by_template(token, sessionOpt,
                                         cert_template, ctsize,
                                         retrieve_cert, search);
    return nssrv;
}

NSS_IMPLEMENT PRStatus
nssToken_TraverseCertificatesBySubject
(
  NSSToken *token,
  nssSession *sessionOpt,
  NSSDER *subject,
  nssTokenCertSearch *search
)
{
    PRStatus nssrv;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE subj_template[3];
    CK_ULONG stsize;
    NSS_CK_TEMPLATE_START(subj_template, attr, stsize);
    /* Set the search to token/session only if provided */
    if (search->searchType == nssTokenSearchType_SessionOnly) {
	NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (search->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);
    if (search->cached) {
	nssList_SetCompareFunction(search->cached, compare_cert_by_issuer_sn);
    }
    /* now traverse the token certs matching this template */
    nssrv = traverse_objects_by_template(token, sessionOpt,
	                                 subj_template, stsize,
                                         retrieve_cert, search);
    return nssrv;
}

NSS_IMPLEMENT PRStatus
nssToken_TraverseCertificatesByNickname
(
  NSSToken *token,
  nssSession *sessionOpt,
  NSSUTF8 *name,
  nssTokenCertSearch *search
)
{
    PRStatus nssrv;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE nick_template[3];
    CK_ULONG ntsize;
    NSS_CK_TEMPLATE_START(nick_template, attr, ntsize);
    /* Set the search to token/session only if provided */
    if (search->searchType == nssTokenSearchType_SessionOnly) {
	NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (search->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_UTF8(attr, CKA_LABEL, name);
    NSS_CK_TEMPLATE_FINISH(nick_template, attr, ntsize);
    if (search->cached) {
	nssList_SetCompareFunction(search->cached, compare_cert_by_issuer_sn);
    }
    /* now traverse the token certs matching this template */
    nssrv = traverse_objects_by_template(token, sessionOpt,
	                                 nick_template, ntsize, 
                                         retrieve_cert, search);
    if (nssrv != PR_SUCCESS) {
	return nssrv;
    }
    /* 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[1].ulValueLen++;
    nssrv = traverse_objects_by_template(token, sessionOpt,
	                                 nick_template, ntsize,
                                         retrieve_cert, search);
    return nssrv;
}

NSS_IMPLEMENT PRStatus
nssToken_TraverseCertificatesByEmail
(
  NSSToken *token,
  nssSession *sessionOpt,
  NSSASCII7 *email,<commit_msg>Increment length on LABEL, not on CLASS to catch the added NULL case.<commit_after>/* 
 * 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$ $Revision$ $Date$ $Name$";
#endif /* DEBUG */

#ifndef DEV_H
#include "dev.h"
#endif /* DEV_H */

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

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

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

#ifndef BASE_H
#include "base.h"
#endif /* BASE_H */

/* XXX */
#ifndef PKI_H
#include "pki.h"
#endif /* PKI_H */

/* XXX */
#ifndef NSSPKI_H
#include "nsspki.h"
#endif /* NSSPKI_H */

#ifdef NSS_3_4_CODE
#include "pkim.h" /* for cert decoding */
#include "pk11func.h" /* for PK11_HasRootCerts */
#endif

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

NSS_IMPLEMENT PRStatus
nssToken_DeleteStoredObject
(
  nssCryptokiInstance *instance
)
{
    CK_RV ckrv;
    PRStatus nssrv;
    PRBool createdSession = PR_FALSE;
    NSSToken *token = instance->token;
    nssSession *session = NULL;
    if (nssCKObject_IsAttributeTrue(instance->handle, CKA_TOKEN, 
                                    token->defaultSession,
	                            token->slot, &nssrv)) {
       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(token)->C_DestroyObject(session->handle, instance->handle);
    nssSession_ExitMonitor(session);
    if (createdSession) {
	nssSession_Destroy(session);
    }
    if (ckrv != CKR_OK) {
	return PR_FAILURE;
    }
    return PR_SUCCESS;
}

static CK_OBJECT_HANDLE
import_object
(
  NSSToken *tok,
  nssSession *sessionOpt,
  CK_ATTRIBUTE_PTR objectTemplate,
  CK_ULONG otsize
)
{
    nssSession *session = NULL;
    PRBool createdSession = PR_FALSE;
    CK_OBJECT_HANDLE object;
    CK_RV ckrv;
    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(tok->slot)->C_CreateObject(session->handle, 
                                            objectTemplate, otsize,
                                            &object);
    nssSession_ExitMonitor(session);
    if (createdSession) {
	nssSession_Destroy(session);
    }
    if (ckrv != CKR_OK) {
	return CK_INVALID_HANDLE;
    }
    return object;
}

static CK_OBJECT_HANDLE
find_object_by_template
(
  NSSToken *tok,
  nssSession *sessionOpt,
  CK_ATTRIBUTE_PTR cktemplate,
  CK_ULONG ctsize
)
{
    CK_SESSION_HANDLE hSession;
    CK_OBJECT_HANDLE rvObject;
    CK_ULONG count;
    CK_RV ckrv;
    nssSession *session;
    session = (sessionOpt) ? sessionOpt : tok->defaultSession;
    hSession = session->handle;
    nssSession_EnterMonitor(session);
    ckrv = CKAPI(tok)->C_FindObjectsInit(hSession, cktemplate, ctsize);
    if (ckrv != CKR_OK) {
	nssSession_ExitMonitor(session);
	return CK_INVALID_HANDLE;
    }
    ckrv = CKAPI(tok)->C_FindObjects(hSession, &rvObject, 1, &count);
    if (ckrv != CKR_OK) {
	nssSession_ExitMonitor(session);
	return CK_INVALID_HANDLE;
    }
    ckrv = CKAPI(tok)->C_FindObjectsFinal(hSession);
    nssSession_ExitMonitor(session);
    if (ckrv != CKR_OK) {
	return CK_INVALID_HANDLE;
    }
    return rvObject;
}

static PRStatus 
traverse_objects_by_template
(
  NSSToken *tok,
  nssSession *sessionOpt,
  CK_ATTRIBUTE_PTR obj_template,
  CK_ULONG otsize,
  PRStatus (*callback)(NSSToken *t, nssSession *session,
                       CK_OBJECT_HANDLE h, void *arg),
  void *arg
)
{
    NSSSlot *slot;
    PRStatus cbrv;
    PRUint32 i;
    CK_RV ckrv;
    CK_ULONG count;
    CK_OBJECT_HANDLE *objectStack;
    CK_OBJECT_HANDLE startOS[OBJECT_STACK_SIZE];
    CK_SESSION_HANDLE hSession;
    NSSArena *objectArena = NULL;
    nssSession *session;
    nssList *objectList = NULL;
    slot = tok->slot;
    objectStack = startOS;
    session = (sessionOpt) ? sessionOpt : tok->defaultSession;
    hSession = session->handle;
    nssSession_EnterMonitor(session);
    ckrv = CKAPI(slot)->C_FindObjectsInit(hSession, obj_template, otsize);
    if (ckrv != CKR_OK) {
	nssSession_ExitMonitor(session);
	goto loser;
    }
    while (PR_TRUE) {
	ckrv = CKAPI(slot)->C_FindObjects(hSession, objectStack, 
	                                  OBJECT_STACK_SIZE, &count);
	if (ckrv != CKR_OK) {
	    nssSession_ExitMonitor(session);
	    goto loser;
	}
	if (count == OBJECT_STACK_SIZE) {
	    if (!objectList) {
		objectArena = NSSArena_Create();
		objectList = nssList_Create(objectArena, PR_FALSE);
	    }
	    objectStack = nss_ZNEWARRAY(objectArena, CK_OBJECT_HANDLE, 
	                                OBJECT_STACK_SIZE);
	    nssList_Add(objectList, objectStack);
	} else {
	    break;
	}
    }
    ckrv = CKAPI(slot)->C_FindObjectsFinal(hSession);
    nssSession_ExitMonitor(session);
    if (ckrv != CKR_OK) {
	goto loser;
    }
    if (objectList) {
	nssListIterator *objects;
	objects = nssList_CreateIterator(objectList);
	for (objectStack = (CK_OBJECT_HANDLE *)nssListIterator_Start(objects);
	     objectStack != NULL;
	     objectStack = (CK_OBJECT_HANDLE *)nssListIterator_Next(objects)) {
	    for (i=0; i<count; i++) {
		cbrv = (*callback)(tok, session, objectStack[i], arg);
	    }
	}
	nssListIterator_Finish(objects);
	count = OBJECT_STACK_SIZE;
    }
    for (i=0; i<count; i++) {
	cbrv = (*callback)(tok, session, startOS[i], arg);
    }
    if (objectArena)
	NSSArena_Destroy(objectArena);
    return PR_SUCCESS;
loser:
    if (objectArena)
	NSSArena_Destroy(objectArena);
    return PR_FAILURE;
}

static nssCryptokiInstance *
create_cryptoki_instance
(
  NSSArena *arena,
  NSSToken *t, 
  CK_OBJECT_HANDLE h,
  PRBool isTokenObject
)
{
    nssCryptokiInstance *instance;
    instance = nss_ZNEW(arena, nssCryptokiInstance);
    if (!instance) {
	return NULL;
    }
    instance->handle = h;
    instance->token = t;
    instance->isTokenObject = isTokenObject;
    return instance;
}

static NSSCertificateType
nss_cert_type_from_ck_attrib(CK_ATTRIBUTE_PTR attrib)
{
    CK_CERTIFICATE_TYPE ckCertType;
    ckCertType = *((CK_ULONG *)attrib->pValue);
    switch (ckCertType) {
    case CKC_X_509:
	return NSSCertificateType_PKIX;
	break;
    default:
	return NSSCertificateType_Unknown;
    }
}

/* Create a certificate from an object handle. */
static NSSCertificate *
get_token_cert
(
  NSSToken *token,
  nssSession *sessionOpt,
  CK_OBJECT_HANDLE handle
)
{
    NSSCertificate *rvCert;
    NSSArena *arena;
    nssSession *session;
    PRStatus nssrv;
    CK_ULONG template_size;
    CK_ATTRIBUTE cert_template[] = {
	{ CKA_CERTIFICATE_TYPE, NULL, 0 },
	{ CKA_ID,               NULL, 0 },
	{ CKA_VALUE,            NULL, 0 },
	{ CKA_ISSUER,           NULL, 0 },
	{ CKA_SERIAL_NUMBER,    NULL, 0 },
	{ CKA_LABEL,            NULL, 0 },
	{ CKA_SUBJECT,          NULL, 0 },
	{ CKA_NETSCAPE_EMAIL,   NULL, 0 }
    };
    template_size = sizeof(cert_template) / sizeof(cert_template[0]);
    session = (sessionOpt) ? sessionOpt : token->defaultSession;
    arena = nssArena_Create();
    if (!arena) {
	return NULL;
    }
    rvCert = nss_ZNEW(arena, NSSCertificate);
    if (!rvCert) {
	NSSArena_Destroy(arena);
	return NULL;
    }
    nssrv = nssPKIObject_Initialize(&rvCert->object, arena, 
                                    token->trustDomain, NULL);
    if (nssrv != PR_SUCCESS) {
	goto loser;
    }
    nssrv = nssCKObject_GetAttributes(handle, 
                                      cert_template, template_size,
                                      arena, session, token->slot);
    if (nssrv != PR_SUCCESS) {
	goto loser;
    }
    rvCert->type = nss_cert_type_from_ck_attrib(&cert_template[0]);
    NSS_CK_ATTRIBUTE_TO_ITEM(&cert_template[1], &rvCert->id);
    NSS_CK_ATTRIBUTE_TO_ITEM(&cert_template[2], &rvCert->encoding);
    NSS_CK_ATTRIBUTE_TO_ITEM(&cert_template[3], &rvCert->issuer);
    NSS_CK_ATTRIBUTE_TO_ITEM(&cert_template[4], &rvCert->serial);
    NSS_CK_ATTRIBUTE_TO_UTF8(&cert_template[5],  rvCert->nickname);
    NSS_CK_ATTRIBUTE_TO_ITEM(&cert_template[6], &rvCert->subject);
    NSS_CK_ATTRIBUTE_TO_UTF8(&cert_template[7],  rvCert->email);
#ifdef NSS_3_4_CODE
    /* nss 3.4 database doesn't associate email address with cert */
    if (!rvCert->email) {
	nssDecodedCert *dc;
	NSSASCII7 *email;
	dc = nssCertificate_GetDecoding(rvCert);
	if (dc) {
	    email = dc->getEmailAddress(dc);
	    if (email) 
	    	rvCert->email = nssUTF8_Duplicate(email, arena);
	}
    }
#endif
    return rvCert;
loser:
    nssPKIObject_Destroy(&rvCert->object);
    return (NSSCertificate *)NULL;
}

NSS_IMPLEMENT PRStatus
nssToken_ImportCertificate
(
  NSSToken *tok,
  nssSession *sessionOpt,
  NSSCertificate *cert,
  PRBool asTokenObject
)
{
    nssCryptokiInstance *instance;
    CK_CERTIFICATE_TYPE cert_type = CKC_X_509;
    CK_OBJECT_HANDLE handle;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE cert_tmpl[9];
    CK_ULONG ctsize;
    NSS_CK_TEMPLATE_START(cert_tmpl, attr, ctsize);
    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_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,               &cert->id);
    NSS_CK_SET_ATTRIBUTE_UTF8(attr, CKA_LABEL,             cert->nickname);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_VALUE,            &cert->encoding);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ISSUER,           &cert->issuer);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SUBJECT,          &cert->subject);
    NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SERIAL_NUMBER,    &cert->serial);
    NSS_CK_TEMPLATE_FINISH(cert_tmpl, attr, ctsize);
    /* Import the certificate onto the token */
    handle = import_object(tok, sessionOpt, cert_tmpl, ctsize);
    if (handle == CK_INVALID_HANDLE) {
	return PR_FAILURE;
    }
    instance = create_cryptoki_instance(cert->object.arena, 
                                        tok, handle, asTokenObject);
    if (!instance) {
	/* XXX destroy object */
	return PR_FAILURE;
    }
    nssList_Add(cert->object.instanceList, instance);
    /* XXX Fix this! */
    nssListIterator_Destroy(cert->object.instances);
    cert->object.instances = nssList_CreateIterator(cert->object.instanceList);
    return PR_SUCCESS;
}

static PRBool 
compare_cert_by_issuer_sn(void *a, void *b)
{
    NSSCertificate *c1 = (NSSCertificate *)a;
    NSSCertificate *c2 = (NSSCertificate *)b;
    return  (nssItem_Equal(&c1->issuer, &c2->issuer, NULL) &&
             nssItem_Equal(&c1->serial, &c2->serial, NULL));
}

static PRStatus
retrieve_cert(NSSToken *t, nssSession *session, CK_OBJECT_HANDLE h, void *arg)
{
    PRStatus nssrv;
    PRBool found;
    nssTokenCertSearch *search = (nssTokenCertSearch *)arg;
    NSSCertificate *cert = NULL;
    nssListIterator *instances;
    nssCryptokiInstance *ci;
    CK_ATTRIBUTE issuersn_tmpl[] = {
	{ CKA_ISSUER,        NULL, 0 },
	{ CKA_SERIAL_NUMBER, NULL, 0 }
    };
    CK_ULONG ist_size = sizeof(issuersn_tmpl) / sizeof(issuersn_tmpl[0]);
    if (search->cached) {
	NSSCertificate csi; /* a fake cert for indexing */
	nssrv = nssCKObject_GetAttributes(h, issuersn_tmpl, ist_size,
	                                  NULL, session, t->slot);
	NSS_CK_ATTRIBUTE_TO_ITEM(&issuersn_tmpl[0], &csi.issuer);
	NSS_CK_ATTRIBUTE_TO_ITEM(&issuersn_tmpl[1], &csi.serial);
	cert = (NSSCertificate *)nssList_Get(search->cached, &csi);
	nss_ZFreeIf(csi.issuer.data);
	nss_ZFreeIf(csi.serial.data);
    }
    found = PR_FALSE;
    if (cert) {
	nssCertificate_AddRef(cert);
	instances = cert->object.instances;
	for (ci  = (nssCryptokiInstance *)nssListIterator_Start(instances);
	     ci != (nssCryptokiInstance *)NULL;
	     ci  = (nssCryptokiInstance *)nssListIterator_Next(instances))
	{
	    if (ci->handle == h && ci->token == t) {
		found = PR_TRUE;
		break;
	    }
	}
	nssListIterator_Finish(instances);
    } else {
	cert = get_token_cert(t, session, h);
	if (!cert) return PR_FAILURE;
    }
    if (!found) {
	PRBool isTokenObject;
	/* XXX this is incorrect if the search is over both types */
	isTokenObject = (search->searchType == nssTokenSearchType_TokenOnly) ?
	                PR_TRUE : PR_FALSE;
	ci = create_cryptoki_instance(cert->object.arena, t, h, isTokenObject);
	if (!ci) {
	    NSSCertificate_Destroy(cert);
	    return PR_FAILURE;
	}
	nssList_Add(cert->object.instanceList, ci);
	/* XXX Fix this! */
	nssListIterator_Destroy(cert->object.instances);
	cert->object.instances = nssList_CreateIterator(cert->object.instanceList);
    }
    nssrv = (*search->callback)(cert, search->cbarg);
    NSSCertificate_Destroy(cert);
    return nssrv;
}

/* traverse all certificates - this should only happen if the token
 * has been marked as "traversable"
 */
NSS_IMPLEMENT PRStatus 
nssToken_TraverseCertificates
(
  NSSToken *token,
  nssSession *sessionOpt,
  nssTokenCertSearch *search
)
{
    PRStatus nssrv;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE cert_template[2];
    CK_ULONG ctsize;
    NSS_CK_TEMPLATE_START(cert_template, attr, ctsize);
    /* Set the search to token/session only if provided */
    if (search->searchType == nssTokenSearchType_SessionOnly) {
	NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (search->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(cert_template, attr, ctsize);
    if (search->cached) {
	nssList_SetCompareFunction(search->cached, compare_cert_by_issuer_sn);
    }
    nssrv = traverse_objects_by_template(token, sessionOpt,
                                         cert_template, ctsize,
                                         retrieve_cert, search);
    return nssrv;
}

NSS_IMPLEMENT PRStatus
nssToken_TraverseCertificatesBySubject
(
  NSSToken *token,
  nssSession *sessionOpt,
  NSSDER *subject,
  nssTokenCertSearch *search
)
{
    PRStatus nssrv;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE subj_template[3];
    CK_ULONG stsize;
    NSS_CK_TEMPLATE_START(subj_template, attr, stsize);
    /* Set the search to token/session only if provided */
    if (search->searchType == nssTokenSearchType_SessionOnly) {
	NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (search->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);
    if (search->cached) {
	nssList_SetCompareFunction(search->cached, compare_cert_by_issuer_sn);
    }
    /* now traverse the token certs matching this template */
    nssrv = traverse_objects_by_template(token, sessionOpt,
	                                 subj_template, stsize,
                                         retrieve_cert, search);
    return nssrv;
}

NSS_IMPLEMENT PRStatus
nssToken_TraverseCertificatesByNickname
(
  NSSToken *token,
  nssSession *sessionOpt,
  NSSUTF8 *name,
  nssTokenCertSearch *search
)
{
    PRStatus nssrv;
    CK_ATTRIBUTE_PTR attr;
    CK_ATTRIBUTE nick_template[3];
    CK_ULONG ntsize;
    NSS_CK_TEMPLATE_START(nick_template, attr, ntsize);
    /* Set the search to token/session only if provided */
    if (search->searchType == nssTokenSearchType_SessionOnly) {
	NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
    } else if (search->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_UTF8(attr, CKA_LABEL, name);
    NSS_CK_TEMPLATE_FINISH(nick_template, attr, ntsize);
    if (search->cached) {
	nssList_SetCompareFunction(search->cached, compare_cert_by_issuer_sn);
    }
    /* now traverse the token certs matching this template */
    nssrv = traverse_objects_by_template(token, sessionOpt,
	                                 nick_template, ntsize, 
                                         retrieve_cert, search);
    if (nssrv != PR_SUCCESS) {
	return nssrv;
    }
    /* 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[2].ulValueLen++;
    nssrv = traverse_objects_by_template(token, sessionOpt,
	                                 nick_template, ntsize,
                                         retrieve_cert, search);
    return nssrv;
}

NSS_IMPLEMENT PRStatus
nssToken_TraverseCertificatesByEmail
(
  NSSToken *token,
  nssSession *sessionOpt,
  NSSASCII7 *email,
{'fraction_non_alphanumeric': 0.10063914501257334, 'fraction_numerical': 0.004086336965632859, 'mean_word_length': 3.361092072195568, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 181, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 52, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3288882', 'n_tokens_mistral': 13688, 'n_tokens_neox': 13265, 'n_words': 2815}