/*
 * 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.
 */
#include "nspr.h"
#include "secerr.h"
#include "secasn1.h"
#include "seccomon.h"
#include "pk11func.h"
#include "certdb.h"
#include "certt.h"
#include "cert.h"
#include "certxutl.h"

/*
 * Find all user certificates that match the given criteria.
 * 
 *	"handle" - database to search
 *	"usage" - certificate usage to match
 *	"oneCertPerName" - if set then only return the "best" cert per
 *			name
 *	"validOnly" - only return certs that are curently valid
 *	"proto_win" - window handle passed to pkcs11
 */
CERTCertList *
CERT_FindUserCertsByUsage(CERTCertDBHandle *handle,
			  SECCertUsage usage,
			  PRBool oneCertPerName,
			  PRBool validOnly,
			  void *proto_win)
{
    CERTCertNicknames *nicknames = NULL;
    char **nnptr;
    int nn;
    CERTCertificate *cert = NULL;
    CERTCertList *certList = NULL;
    SECStatus rv;
    int64 time;
    CERTCertListNode *node = NULL;
    CERTCertListNode *freenode = NULL;
    int n;
    
    time = PR_Now();
    
    nicknames = CERT_GetCertNicknames(handle, SEC_CERT_NICKNAMES_USER,
				      proto_win);
    
    if ( ( nicknames == NULL ) || ( nicknames->numnicknames == 0 ) ) {
	goto loser;
    }

    nnptr = nicknames->nicknames;
    nn = nicknames->numnicknames;

    while ( nn > 0 ) {
	cert = NULL;
	/* use the pk11 call so that we pick up any certs on tokens,
	 * which may require login
	 */
	if ( proto_win != NULL ) {
	    cert = PK11_FindCertFromNickname(*nnptr,proto_win);
	}

	/* Sigh, It turns out if the cert is already in the temp db, because
	 * it's in the perm db, then the nickname lookup doesn't work.
	 * since we already have the cert here, though, than we can just call
	 * CERT_CreateSubjectCertList directly. For those cases where we didn't
	 * find the cert in pkcs #11 (because we didn't have a password arg,
	 * or because the nickname is for a peer, server, or CA cert, then we
	 * go look the cert up.
	 */
	if (cert == NULL) { 
	    cert = CERT_FindCertByNickname(handle,*nnptr);
	}

	if ( cert != NULL ) {
	   /* collect certs for this nickname, sorting them into the list */
	    certList = CERT_CreateSubjectCertList(certList, handle, 
				&cert->derSubject, time, validOnly);
	
	    /* drop the extra reference */
	    CERT_DestroyCertificate(cert);
	}
	
	nnptr++;
	nn--;
    }

    /* remove certs with incorrect usage */
    rv = CERT_FilterCertListByUsage(certList, usage, PR_FALSE);

    if ( rv != SECSuccess ) {
	goto loser;
    }

    /* remove any extra certs for each name */
    if ( oneCertPerName ) {
	PRBool *flags;

	nn = nicknames->numnicknames;
	nnptr = nicknames->nicknames;
	
	flags = (PRBool *)PORT_ZAlloc(sizeof(PRBool) * nn);
	if ( flags == NULL ) {
	    goto loser;
	}
	
	node = CERT_LIST_HEAD(certList);
	
	/* treverse all certs in the list */
	while ( !CERT_LIST_END(node, certList) ) {

	    /* find matching nickname index */
	    for ( n = 0; n < nn; n++ ) {
		if ( PORT_Strcmp(nnptr[n], node->cert->nickname) == 0 ) {
		    /* We found a match.  If this is the first one, then
		     * set the flag and move on to the next cert.  If this
		     * is not the first one then delete it from the list.
		     */
		    if ( flags[n] ) {
			/* We have already seen a cert with this nickname,
			 * so delete this one.
			 */
			freenode = node;
			node = CERT_LIST_NEXT(node);
			CERT_RemoveCertListNode(freenode);
		    } else {
			/* keep the first cert for each nickname, but set the
			 * flag so we know to delete any others with the same
			 * nickname.
			 */
			flags[n] = PR_TRUE;
			node = CERT_LIST_NEXT(node);
		    }
		    break;
		}
	    }
	    if ( n == nn ) {
		/* if we get here it means that we didn't find a matching
		 * nickname, which should not happen.
		 */
		PORT_Assert(0);
		node = CERT_LIST_NEXT(node);
	    }
	}
	PORT_Free(flags);
    }

    goto done;
    
loser:
    if ( certList != NULL ) {
	CERT_DestroyCertList(certList);
	certList = NULL;
    }

done:
    if ( nicknames != NULL ) {
	CERT_FreeNicknames(nicknames);
    }

    return(certList);
}

/*
 * Find a user certificate that matchs the given criteria.
 * 
 *	"handle" - database to search
 *	"nickname" - nickname to match
 *	"usage" - certificate usage to match
 *	"validOnly" - only return certs that are curently valid
 *	"proto_win" - window handle passed to pkcs11
 */
CERTCertificate *
CERT_FindUserCertByUsage(CERTCertDBHandle *handle,
			 char *nickname,
			 SECCertUsage usage,
			 PRBool validOnly,
			 void *proto_win)
{
    CERTCertificate *cert = NULL;
    CERTCertList *certList = NULL;
    SECStatus rv;
    int64 time;
    
    time = PR_Now();
    
    /* use the pk11 call so that we pick up any certs on tokens,
     * which may require login
     */
    /* XXX - why is this restricted? */
    if ( proto_win != NULL ) {
	cert = PK11_FindCertFromNickname(nickname,proto_win);
    }


    /* sigh, There are still problems find smart cards from the temp
     * db. This will get smart cards working again. The real fix
     * is to make sure we can search the temp db by their token nickname.
     */
    if (cert == NULL) {
	cert = CERT_FindCertByNickname(handle,nickname);
    }

    if ( cert != NULL ) {
 	/* collect certs for this nickname, sorting them into the list */
	certList = CERT_CreateSubjectCertList(certList, handle, 
					&cert->derSubject, time, validOnly);

	/* drop the extra reference */
	CERT_DestroyCertificate(cert);
	cert = NULL;
    }
	
    if ( certList == NULL ) {
	goto loser;
    }
    
    /* remove certs with incorrect usage */
    rv = CERT_FilterCertListByUsage(certList, usage, PR_FALSE);

    if ( rv != SECSuccess ) {
	goto loser;
    }

    if ( ! CERT_LIST_END(CERT_LIST_HEAD(certList), certList) ) {
	cert = CERT_DupCertificate(CERT_LIST_HEAD(certList)->cert);
    }
    
loser:
    if ( certList != NULL ) {
	CERT_DestroyCertList(certList);
    }

    return(cert);
}

CERTCertList *
CERT_MatchUserCert(CERTCertDBHandle *handle,
		   SECCertUsage usage,
		   int nCANames, char **caNames,
		   void *proto_win)
{
    CERTCertList *certList = NULL;
    SECStatus rv;

    certList = CERT_FindUserCertsByUsage(handle, usage, PR_TRUE, PR_TRUE,
					 proto_win);
    if ( certList == NULL ) {
	goto loser;
    }
    
    rv = CERT_FilterCertListByCANames(certList, nCANames, caNames, usage);
    if ( rv != SECSuccess ) {
	goto loser;
    }
    
    goto done;
    
loser:
    if ( certList != NULL ) {
	CERT_DestroyCertList(certList);
	certList = NULL;
    }

done:

    return(certList);
}


typedef struct stringNode {
    struct stringNode *next;
    char *string;
} stringNode;
    
static SECStatus
CollectNicknames( CERTCertificate *cert, SECItem *k, void *data)
{
    CERTCertNicknames *names;
    PRBool saveit = PR_FALSE;
    CERTCertTrust *trust;
    stringNode *node;
    int len;
    
    names = (CERTCertNicknames *)data;
    
    if ( cert->nickname ) {
	trust = cert->trust;
	
	switch(names->what) {
	  case SEC_CERT_NICKNAMES_ALL:
	    if ( ( trust->sslFlags & (CERTDB_VALID_CA|CERTDB_VALID_PEER) ) ||
	      ( trust->emailFlags & (CERTDB_VALID_CA|CERTDB_VALID_PEER) ) ||
	      ( trust->objectSigningFlags & (CERTDB_VALID_CA|CERTDB_VALID_PEER) ) ) {
		saveit = PR_TRUE;
	    }
	    
	    break;
	  case SEC_CERT_NICKNAMES_USER:
	    if ( ( trust->sslFlags & CERTDB_USER ) ||
		( trust->emailFlags & CERTDB_USER ) ||
		( trust->objectSigningFlags & CERTDB_USER ) ) {
		saveit = PR_TRUE;
	    }
	    
	    break;
	  case SEC_CERT_NICKNAMES_SERVER:
	    if ( trust->sslFlags & CERTDB_VALID_PEER ) {
		saveit = PR_TRUE;
	    }
	    
	    break;
	  case SEC_CERT_NICKNAMES_CA:
	    if ( ( ( trust->sslFlags & CERTDB_VALID_CA ) == CERTDB_VALID_CA ) ||
		( ( trust->emailFlags & CERTDB_VALID_CA ) == CERTDB_VALID_CA ) ||
		( ( trust->objectSigningFlags & CERTDB_VALID_CA ) == CERTDB_VALID_CA ) ) {
		saveit = PR_TRUE;
	    }
	    break;
	}
    }

    /* traverse the list of collected nicknames and make sure we don't make
     * a duplicate
     */
    if ( saveit ) {
	node = (stringNode *)names->head;
	while ( node != NULL ) {
	    if ( PORT_Strcmp(cert->nickname, node->string) == 0 ) { 
		/* if the string matches, then don't save this one */
		saveit = PR_FALSE;
		break;
	    }
	    node = node->next;
	}
    }

    if ( saveit ) {
	
	/* allocate the node */
	node = (stringNode*)PORT_ArenaAlloc(names->arena, sizeof(stringNode));
	if ( node == NULL ) {
	    return(SECFailure);
	}

	/* copy the string */
	len = PORT_Strlen(cert->nickname) + 1;
	node->string = (char*)PORT_ArenaAlloc(names->arena, len);
	if ( node->string == NULL ) {
	    return(SECFailure);
	}
	PORT_Memcpy(node->string, cert->nickname, len);

	/* link it into the list */
	node->next = (stringNode *)names->head;
	names->head = (void *)node;

	/* bump the count */
	names->numnicknames++;
    }
    
    return(SECSuccess);
}

CERTCertNicknames *
CERT_GetCertNicknames(CERTCertDBHandle *handle, int what, void *wincx)
{
    PRArenaPool *arena;
    CERTCertNicknames *names;
    int i;
    SECStatus rv;
    stringNode *node;
    
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if ( arena == NULL ) {
	PORT_SetError(SEC_ERROR_NO_MEMORY);
	return(NULL);
    }
    
    names = (CERTCertNicknames *)PORT_ArenaAlloc(arena, sizeof(CERTCertNicknames));
    if ( names == NULL ) {
	goto loser;
    }

    names->arena = arena;
    names->head = NULL;
    names->numnicknames = 0;
    names->nicknames = NULL;
    names->what = what;
    names->totallen = 0;
    
    rv = SEC_TraversePermCerts(handle, CollectNicknames, (void *)names);
    if ( rv ) {
	goto loser;
    }

    if ( wincx != NULL ) {
	rv = PK11_TraverseSlotCerts(CollectNicknames, (void *)names, wincx);
	if ( rv ) {
	    goto loser;
	}
    }

    if ( names->numnicknames ) {
	names->nicknames = (char**)PORT_ArenaAlloc(arena,
					 names->numnicknames * sizeof(char *));

	if ( names->nicknames == NULL ) {
	    goto loser;
	}
    
	node = (stringNode *)names->head;
	
	for ( i = 0; i < names->numnicknames; i++ ) {
	    PORT_Assert(node != NULL);
	    
	    names->nicknames[i] = node->string;
	    names->totallen += PORT_Strlen(node->string);
	    node = node->next;
	}

	PORT_Assert(node == NULL);
    }

    return(names);
    
loser:
    PORT_FreeArena(arena, PR_FALSE);
    return(NULL);
}

void
CERT_FreeNicknames(CERTCertNicknames *nicknames)
{
    PORT_FreeArena(nicknames->arena, PR_FALSE);
    
    return;
}

/* [ FROM pcertdb.c ] */

typedef struct dnameNode {
    struct dnameNode *next;
    SECItem name;
} dnameNode;

void
CERT_FreeDistNames(CERTDistNames *names)
{
    PORT_FreeArena(names->arena, PR_FALSE);
    
    return;
}

static SECStatus
CollectDistNames( CERTCertificate *cert, SECItem *k, void *data)
{
    CERTDistNames *names;
    PRBool saveit = PR_FALSE;
    CERTCertTrust *trust;
    dnameNode *node;
    int len;
    
    names = (CERTDistNames *)data;
    
    if ( cert->trust ) {
	trust = cert->trust;
	
	/* only collect names of CAs trusted for issuing SSL clients */
	if ( ( trust->sslFlags &
	      ( CERTDB_VALID_CA | CERTDB_TRUSTED_CLIENT_CA ) ) ==
	       ( CERTDB_VALID_CA | CERTDB_TRUSTED_CLIENT_CA ) ) {
	    saveit = PR_TRUE;
	}
    }

    if ( saveit ) {
	/* allocate the node */
	node = (dnameNode*)PORT_ArenaAlloc(names->arena, sizeof(dnameNode));
	if ( node == NULL ) {
	    return(SECFailure);
	}

	/* copy the name */
	node->name.len = len = cert->derSubject.len;
	node->name.type = siBuffer;
	node->name.data = (unsigned char*)PORT_ArenaAlloc(names->arena, len);
	if ( node->name.data == NULL ) {
	    return(SECFailure);
	}
	PORT_Memcpy(node->name.data, cert->derSubject.data, len);

	/* link it into the list */
	node->next = (dnameNode *)names->head;
	names->head = (void *)node;

	/* bump the count */
	names->nnames++;
    }
    
    return(SECSuccess);
}

/*
 * Return all of the CAs that are "trusted" for SSL.
 */
CERTDistNames *
CERT_GetSSLCACerts(CERTCertDBHandle *handle)
{
    PRArenaPool *arena;
    CERTDistNames *names;
    int i;
    SECStatus rv;
    dnameNode *node;
    
    /* allocate an arena to use */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if ( arena == NULL ) {
	PORT_SetError(SEC_ERROR_NO_MEMORY);
	return(NULL);
    }
    
    /* allocate the header structure */
    names = (CERTDistNames *)PORT_ArenaAlloc(arena, sizeof(CERTDistNames));
    if ( names == NULL ) {
	goto loser;
    }

    /* initialize the header struct */
    names->arena = arena;
    names->head = NULL;
    names->nnames = 0;
    names->names = NULL;
    
    /* collect the names from the database */
    rv = SEC_TraversePermCerts(handle, CollectDistNames, (void *)names);
    if ( rv ) {
	goto loser;
    }

    /* construct the array from the list */
    if ( names->nnames ) {
	names->names = (SECItem*)PORT_ArenaAlloc(arena, names->nnames * sizeof(SECItem));

	if ( names->names == NULL ) {
	    goto loser;
	}
    
	node = (dnameNode *)names->head;
	
	for ( i = 0; i < names->nnames; i++ ) {
	    PORT_Assert(node != NULL);
	    
	    names->names[i] = node->name;
	    node = node->next;
	}

	PORT_Assert(node == NULL);
    }

    return(names);
    
loser:
    PORT_FreeArena(arena, PR_FALSE);
    return(NULL);
}

CERTDistNames *
CERT_DistNamesFromNicknames(CERTCertDBHandle *handle, char **nicknames,
			   int nnames)
{
    CERTDistNames *dnames = NULL;
    PRArenaPool *arena;
    int i, rv;
    SECItem *names = NULL;
    CERTCertificate *cert = NULL;
    
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) goto loser;
    dnames = (CERTDistNames*)PORT_Alloc(sizeof(CERTDistNames));
    if (dnames == NULL) goto loser;

    dnames->arena = arena;
    dnames->nnames = nnames;
    dnames->names = names = (SECItem*)PORT_Alloc(nnames * sizeof(SECItem));
    if (names == NULL) goto loser;
    
    for (i = 0; i < nnames; i++) {
	cert = CERT_FindCertByNicknameOrEmailAddr(handle, nicknames[i]);
	if (cert == NULL) goto loser;
	rv = SECITEM_CopyItem(arena, &names[i], &cert->derSubject);
	if (rv == SECFailure) goto loser;
	CERT_DestroyCertificate(cert);
    }
    return dnames;
    
loser:
    if (cert != NULL)
	CERT_DestroyCertificate(cert);
    if (arena != NULL)
	PORT_FreeArena(arena, PR_FALSE);
    return NULL;
}

/* [ from pcertdb.c - calls Ascii to Name ] */
/*
 * Lookup a certificate in the database by name
 */
CERTCertificate *
CERT_FindCertByNameString(CERTCertDBHandle *handle, char *nameStr)
{
    CERTName *name;
    SECItem *nameItem;
    CERTCertificate *cert = NULL;
    PRArenaPool *arena = NULL;
    
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    
    if ( arena == NULL ) {
	goto loser;
    }
    
    name = CERT_AsciiToName(nameStr);
    
    if ( name ) {
	nameItem = SEC_ASN1EncodeItem (arena, NULL, (void *)name,
				       CERT_NameTemplate);
	if ( nameItem == NULL ) {
	    goto loser;
	}

	cert = CERT_FindCertByName(handle, nameItem);
	CERT_DestroyName(name);
    }

loser:
    if ( arena ) {
	PORT_FreeArena(arena, PR_FALSE);
    }
    
    return(cert);
}

/* From certv3.c */

CERTCrlDistributionPoints *
CERT_FindCRLDistributionPoints (CERTCertificate *cert)
{
    SECItem encodedExtenValue;
    SECStatus rv;

    encodedExtenValue.data = NULL;
    encodedExtenValue.len = 0;

    rv = cert_FindExtension(cert->extensions, SEC_OID_X509_CRL_DIST_POINTS,
			    &encodedExtenValue);
    if ( rv != SECSuccess ) {
	return (NULL);
    }

    return (CERT_DecodeCRLDistributionPoints (cert->arena,
					      &encodedExtenValue));
}

/* From crl.c */
CERTSignedCrl * CERT_ImportCRL
   (CERTCertDBHandle *handle, SECItem *derCRL, char *url, int type, void *wincx)
{
    CERTCertificate *caCert;
    CERTSignedCrl *newCrl, *crl;
    SECStatus rv;

    newCrl = crl = NULL;

    PORT_Assert (handle != NULL);
    do {

	newCrl = CERT_DecodeDERCrl(NULL, derCRL, type);
	if (newCrl == NULL) {
	    if (type == SEC_CRL_TYPE) {
		/* only promote error when the error code is too generic */
		if (PORT_GetError () == SEC_ERROR_BAD_DER)
			PORT_SetError(SEC_ERROR_CRL_INVALID);
	    } else {
		PORT_SetError(SEC_ERROR_KRL_INVALID);
	    }
	    break;		
	}
    
	caCert = CERT_FindCertByName (handle, &newCrl->crl.derName);
	if (caCert == NULL) {
	    PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);	    
	    break;
	}

	/* If caCert is a v3 certificate, make sure that it can be used for
	   crl signing purpose */
	rv = CERT_CheckCertUsage (caCert, KU_CRL_SIGN);
	if (rv != SECSuccess) {
	    break;
	}

	rv = CERT_VerifySignedData(&newCrl->signatureWrap, caCert,
				   PR_Now(), wincx);
	if (rv != SECSuccess) {
	    if (type == SEC_CRL_TYPE) {
		PORT_SetError(SEC_ERROR_CRL_BAD_SIGNATURE);
	    } else {
		PORT_SetError(SEC_ERROR_KRL_BAD_SIGNATURE);
	    }
	    break;
	}

	/* Do CRL validation and add to the dbase if this crl is more present then the one
	   in the dbase, if one exists.
	 */
	crl = cert_DBInsertCRL (handle, url, newCrl, derCRL, type);

    } while (0);

    SEC_DestroyCrl (newCrl);
    return (crl);
}

/* From certdb.c */
SECStatus
CERT_ImportCAChain(SECItem *certs, int numcerts, SECCertUsage certUsage)
{
    SECStatus rv;
    SECItem *derCert;
    SECItem certKey;
    PRArenaPool *arena;
    CERTCertificate *cert = NULL;
    CERTCertificate *newcert = NULL;
    CERTCertDBHandle *handle;
    CERTCertTrust trust;
    PRBool isca;
    char *nickname;
    unsigned int certtype;
    
    handle = CERT_GetDefaultCertDB();
    
    arena = NULL;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if ( ! arena ) {
	goto loser;
    }

    while (numcerts--) {
	derCert = certs;
	certs++;
	
	/* get the key (issuer+cn) from the cert */
	rv = CERT_KeyFromDERCert(arena, derCert, &certKey);
	if ( rv != SECSuccess ) {
	    goto loser;
	}

	/* same cert already exists in the database, don't need to do
	 * anything more with it
	 */
	cert = CERT_FindCertByKey(handle, &certKey);
	if ( cert ) {
	    CERT_DestroyCertificate(cert);
	    cert = NULL;
	    continue;
	}

	/* decode my certificate */
	newcert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
	if ( newcert == NULL ) {
	    goto loser;
	}

	/* make sure that cert is valid */
	rv = CERT_CertTimesValid(newcert);
	if ( rv == SECFailure ) {
	    goto endloop;
	}

	/* does it have the CA extension */
	
	/*
	 * Make sure that if this is an intermediate CA in the chain that
	 * it was given permission by its signer to be a CA.
	 */
	isca = CERT_IsCACert(newcert, &certtype);

	if ( !isca ) {
	    goto endloop;
	}

	/* SSL ca's must have the ssl bit set */
	if ( ( certUsage == certUsageSSLCA ) &&
	    ( ( certtype & NS_CERT_TYPE_SSL_CA ) != NS_CERT_TYPE_SSL_CA ) ) {
	    goto endloop;
	}

	/* it passed all of the tests, so lets add it to the database */
	/* mark it as a CA */
	PORT_Memset((void *)&trust, 0, sizeof(trust));
	switch ( certUsage ) {
	  case certUsageSSLCA:
	    trust.sslFlags = CERTDB_VALID_CA;
	    break;
	  case certUsageUserCertImport:
	    if ( ( certtype & NS_CERT_TYPE_SSL_CA ) == NS_CERT_TYPE_SSL_CA ) {
		trust.sslFlags = CERTDB_VALID_CA;
	    }
	    if ( ( certtype & NS_CERT_TYPE_EMAIL_CA ) ==
		NS_CERT_TYPE_EMAIL_CA ) {
		trust.emailFlags = CERTDB_VALID_CA;
	    }
	    if ( ( certtype & NS_CERT_TYPE_OBJECT_SIGNING_CA ) ==
		NS_CERT_TYPE_OBJECT_SIGNING_CA ) {
		trust.objectSigningFlags = CERTDB_VALID_CA;
	    }
	    break;
	  default:
	    PORT_Assert(0);
	    break;
	}
	
	cert = CERT_NewTempCertificate(handle, derCert, NULL, PR_FALSE, PR_TRUE);
	if ( cert == NULL ) {
	    goto loser;
	}
	
	/* get a default nickname for it */
	nickname = CERT_MakeCANickname(cert);

	rv = CERT_AddTempCertToPerm(cert, nickname, &trust);
	/* free the nickname */
	if ( nickname ) {
	    PORT_Free(nickname);
	}

	CERT_DestroyCertificate(cert);
	cert = NULL;
	
	if ( rv != SECSuccess ) {
	    goto loser;
	}

endloop:
	if ( newcert ) {
	    CERT_DestroyCertificate(newcert);
	    newcert = NULL;
	}
	
    }

    rv = SECSuccess;
    goto done;
loser:
    rv = SECFailure;
done:
    
    if ( newcert ) {
	CERT_DestroyCertificate(newcert);
	newcert = NULL;
    }
    
    if ( cert ) {
	CERT_DestroyCertificate(cert);
	cert = NULL;
    }
    
    if ( arena ) {
	PORT_FreeArena(arena, PR_FALSE);
    }

    return(rv);
}

/* Moved from certdb.c */
/*
** CERT_CertChainFromCert
**
** Construct a CERTCertificateList consisting of the given certificate and all
** of the issuer certs until we either get to a self-signed cert or can't find
** an issuer.  Since we don't know how many certs are in the chain we have to
** build a linked list first as we count them.
*/

typedef struct certNode {
    struct certNode *next;
    CERTCertificate *cert;
} certNode;

CERTCertificateList *
CERT_CertChainFromCert(CERTCertificate *cert, SECCertUsage usage,
		       PRBool includeRoot)
{
    CERTCertificateList *chain = NULL;
    CERTCertificate *c;
    SECItem *p;
    int rv, len = 0;
    PRArenaPool *tmpArena, *arena;
    certNode *head, *tail, *node;

    /*
     * Initialize stuff so we can goto loser.
     */
    head = NULL;
    arena = NULL;

    /* arena for linked list */
    tmpArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (tmpArena == NULL) goto no_memory;

    /* arena for SecCertificateList */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) goto no_memory;

    head = tail = (certNode*)PORT_ArenaZAlloc(tmpArena, sizeof(certNode));
    if (head == NULL) goto no_memory;

    /* put primary cert first in the linked list */
    head->cert = c = CERT_DupCertificate(cert);
    if (head->cert == NULL) goto loser;
    len++;

    /* add certs until we come to a self-signed one */
    while(SECITEM_CompareItem(&c->derIssuer, &c->derSubject) != SECEqual) {
	c = CERT_FindCertIssuer(tail->cert, PR_Now(), usage);
	if (c == NULL) {
	    /* no root is found, so make sure we don't attempt to delete one
	     * below
	     */
	    includeRoot = PR_TRUE;
	    break;
	}
	
	tail->next = (certNode*)PORT_ArenaZAlloc(tmpArena, sizeof(certNode));
	tail = tail->next;
	if (tail == NULL) goto no_memory;
	
	tail->cert = c;
	len++;
    }

    /* now build the CERTCertificateList */
    chain = (CERTCertificateList *)PORT_ArenaAlloc(arena, sizeof(CERTCertificateList));
    if (chain == NULL) goto no_memory;
    chain->certs = (SECItem*)PORT_ArenaAlloc(arena, len * sizeof(SECItem));
    if (chain->certs == NULL) goto no_memory;
    
    for(node = head, p = chain->certs; node; node = node->next, p++) {
	rv = SECITEM_CopyItem(arena, p, &node->cert->derCert);
	CERT_DestroyCertificate(node->cert);
	node->cert = NULL;
	if (rv < 0) goto loser;
    }
    if ( !includeRoot && len > 1) {
	chain->len = len - 1;
    } else {
	chain->len = len;
    }
    
    chain->arena = arena;

    PORT_FreeArena(tmpArena, PR_FALSE);
    
    return chain;

no_memory:
    PORT_SetError(SEC_ERROR_NO_MEMORY);
loser:
    if (head != NULL) {
	for (node = head; node; node = node->next) {
	    if (node->cert != NULL)
		CERT_DestroyCertificate(node->cert);
	}
    }

    if (arena != NULL) {
	PORT_FreeArena(arena, PR_FALSE);
    }

    if (tmpArena != NULL) {
	PORT_FreeArena(tmpArena, PR_FALSE);
    }

    return NULL;
}

/* Builds a CERTCertificateList holding just one DER-encoded cert, namely
** the one for the cert passed as an argument.
*/
CERTCertificateList *
CERT_CertListFromCert(CERTCertificate *cert)
{
    CERTCertificateList *chain = NULL;
    int rv;
    PRArenaPool *arena;

    /* arena for SecCertificateList */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) goto no_memory;

    /* build the CERTCertificateList */
    chain = (CERTCertificateList *)PORT_ArenaAlloc(arena, sizeof(CERTCertificateList));
    if (chain == NULL) goto no_memory;
    chain->certs = (SECItem*)PORT_ArenaAlloc(arena, 1 * sizeof(SECItem));
    if (chain->certs == NULL) goto no_memory;
    rv = SECITEM_CopyItem(arena, chain->certs, &(cert->derCert));
    if (rv < 0) goto loser;
    chain->len = 1;
    chain->arena = arena;

    return chain;

no_memory:
    PORT_SetError(SEC_ERROR_NO_MEMORY);
loser:
    if (arena != NULL) {
	PORT_FreeArena(arena, PR_FALSE);
    }
    return NULL;
}

CERTCertificateList *
CERT_DupCertList(CERTCertificateList * oldList)
{
    CERTCertificateList *newList = NULL;
    PRArenaPool         *arena   = NULL;
    SECItem             *newItem;
    SECItem             *oldItem;
    int                 len      = oldList->len;
    int                 rv;

    /* arena for SecCertificateList */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) 
	goto no_memory;

    /* now build the CERTCertificateList */
    newList = PORT_ArenaNew(arena, CERTCertificateList);
    if (newList == NULL) 
	goto no_memory;
    newList->arena = arena;
    newItem = (SECItem*)PORT_ArenaAlloc(arena, len * sizeof(SECItem));
    if (newItem == NULL) 
	goto no_memory;
    newList->certs = newItem;
    newList->len   = len;

    for (oldItem = oldList->certs; len > 0; --len, ++newItem, ++oldItem) {
	rv = SECITEM_CopyItem(arena, newItem, oldItem);
	if (rv < 0) 
	    goto loser;
    }
    return newList;

no_memory:
    PORT_SetError(SEC_ERROR_NO_MEMORY);
loser:
    if (arena != NULL) {
	PORT_FreeArena(arena, PR_FALSE);
    }
    return NULL;
}

void
CERT_DestroyCertificateList(CERTCertificateList *list)
{
    PORT_FreeArena(list->arena, PR_FALSE);
}

