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

/*
 * Certificate handling code
 *
 * $Id: certdb.c,v 1.152 2000/02/11 04:47:50 relyea Exp $
 */

#include "prtime.h"
#include "cert.h"
#include "secder.h"
#include "secasn1.h"
#include "crypto.h"
#include "genname.h"
#include "key.h"
#include "secitem.h"
#include "mcom_db.h"
#include "certdb.h"
#include "prprf.h"
#include "sechash.h"
#include "prlong.h"
#include "certxutl.h"
#include "portreg.h"
#include "secerr.h"
#include "sslerr.h"
#include "prlock.h"
#include "prmon.h"
#include "nsslocks.h"

/*
 * Certificate database handling code
 */


const SEC_ASN1Template CERT_CertExtensionTemplate[] = {
    { SEC_ASN1_SEQUENCE,
	  0, NULL, sizeof(CERTCertExtension) },
    { SEC_ASN1_OBJECT_ID,
	  offsetof(CERTCertExtension,id) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN,		/* XXX DER_DEFAULT */
	  offsetof(CERTCertExtension,critical) },
    { SEC_ASN1_OCTET_STRING,
	  offsetof(CERTCertExtension,value) },
    { 0, }
};

const SEC_ASN1Template CERT_SequenceOfCertExtensionTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF, 0, CERT_CertExtensionTemplate }
};

const SEC_ASN1Template CERT_CertificateTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(CERTCertificate) },
    { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | 
	  SEC_ASN1_CONTEXT_SPECIFIC | 0, 		/* XXX DER_DEFAULT */ 
	  offsetof(CERTCertificate,version),
	  SEC_IntegerTemplate },
    { SEC_ASN1_INTEGER,
	  offsetof(CERTCertificate,serialNumber) },
    { SEC_ASN1_INLINE,
	  offsetof(CERTCertificate,signature),
	  SECOID_AlgorithmIDTemplate },
    { SEC_ASN1_SAVE, 
	  offsetof(CERTCertificate,derIssuer) },
    { SEC_ASN1_INLINE,
	  offsetof(CERTCertificate,issuer),
	  CERT_NameTemplate },
    { SEC_ASN1_INLINE,
	  offsetof(CERTCertificate,validity),
	  CERT_ValidityTemplate },
    { SEC_ASN1_SAVE,
	  offsetof(CERTCertificate,derSubject) },
    { SEC_ASN1_INLINE,
	  offsetof(CERTCertificate,subject),
	  CERT_NameTemplate },
    { SEC_ASN1_SAVE,
	  offsetof(CERTCertificate,derPublicKey) },
    { SEC_ASN1_INLINE,
	  offsetof(CERTCertificate,subjectPublicKeyInfo),
	  CERT_SubjectPublicKeyInfoTemplate },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1,
	  offsetof(CERTCertificate,issuerID),
	  SEC_ObjectIDTemplate },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 2,
	  offsetof(CERTCertificate,subjectID),
	  SEC_ObjectIDTemplate },
    { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | 
	  SEC_ASN1_CONTEXT_SPECIFIC | 3,
	  offsetof(CERTCertificate,extensions),
	  CERT_SequenceOfCertExtensionTemplate },
    { 0 }
};

const SEC_ASN1Template SEC_SignedCertificateTemplate[] =
{
    { SEC_ASN1_SEQUENCE,
	  0, NULL, sizeof(CERTCertificate) },
    { SEC_ASN1_SAVE, 
	  offsetof(CERTCertificate,signatureWrap.data) },
    { SEC_ASN1_INLINE, 
	  0, CERT_CertificateTemplate },
    { SEC_ASN1_INLINE,
	  offsetof(CERTCertificate,signatureWrap.signatureAlgorithm),
	  SECOID_AlgorithmIDTemplate },
    { SEC_ASN1_BIT_STRING,
	  offsetof(CERTCertificate,signatureWrap.signature) },
    { 0 }
};

/*
 * Find the subjectName in a DER encoded certificate
 */
const SEC_ASN1Template SEC_CertSubjectTemplate[] = {
    { SEC_ASN1_SEQUENCE,
	  0, NULL, sizeof(SECItem) },
    { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | 
	  SEC_ASN1_CONTEXT_SPECIFIC | 0,
	  0, SEC_SkipTemplate },	/* version */
    { SEC_ASN1_SKIP },		/* serial number */
    { SEC_ASN1_SKIP },		/* signature algorithm */
    { SEC_ASN1_SKIP },		/* issuer */
    { SEC_ASN1_SKIP },		/* validity */
    { SEC_ASN1_ANY, 0, NULL },		/* subject */
    { SEC_ASN1_SKIP_REST },
    { 0 }
};

/*
 * Find the issuerName in a DER encoded certificate
 */
const SEC_ASN1Template SEC_CertIssuerTemplate[] = {
    { SEC_ASN1_SEQUENCE,
	  0, NULL, sizeof(SECItem) },
    { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | 
	  SEC_ASN1_CONTEXT_SPECIFIC | 0,
	  0, SEC_SkipTemplate },	/* version */
    { SEC_ASN1_SKIP },		/* serial number */
    { SEC_ASN1_SKIP },		/* signature algorithm */
    { SEC_ASN1_ANY, 0, NULL },		/* issuer */
    { SEC_ASN1_SKIP_REST },
    { 0 }
};

/*
 * Find the issuer and serialNumber in a DER encoded certificate.
 * This data is used as the database lookup key since its the unique
 * identifier of a certificate.
 */
const SEC_ASN1Template CERT_CertKeyTemplate[] = {
    { SEC_ASN1_SEQUENCE,
	  0, NULL, sizeof(CERTCertKey) },
    { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | 
	  SEC_ASN1_CONTEXT_SPECIFIC | 0,
	  0, SEC_SkipTemplate },	/* version */ 
    { SEC_ASN1_INTEGER,
	  offsetof(CERTCertKey,serialNumber) },
    { SEC_ASN1_SKIP },		/* signature algorithm */
    { SEC_ASN1_ANY,
	  offsetof(CERTCertKey,derIssuer) },
    { SEC_ASN1_SKIP_REST },
    { 0 }
};



SECStatus
CERT_KeyFromIssuerAndSN(PRArenaPool *arena, SECItem *issuer, SECItem *sn,
			SECItem *key)
{
    key->len = sn->len + issuer->len;
    
    key->data = (unsigned char*)PORT_ArenaAlloc(arena, key->len);
    if ( !key->data ) {
	goto loser;
    }

    /* copy the serialNumber */
    PORT_Memcpy(key->data, sn->data, sn->len);

    /* copy the issuer */
    PORT_Memcpy(&key->data[sn->len], issuer->data, issuer->len);

    return(SECSuccess);

loser:
    return(SECFailure);
}


/*
 * Extract the subject name from a DER certificate
 */
SECStatus
CERT_NameFromDERCert(SECItem *derCert, SECItem *derName)
{
    int rv;
    PRArenaPool *arena;
    CERTSignedData sd;
    void *tmpptr;
    
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    
    if ( ! arena ) {
	return(SECFailure);
    }
   
    PORT_Memset(&sd, 0, sizeof(CERTSignedData));
    rv = SEC_ASN1DecodeItem(arena, &sd, CERT_SignedDataTemplate, derCert);
    
    if ( rv ) {
	goto loser;
    }
    
    PORT_Memset(derName, 0, sizeof(SECItem));
    rv = SEC_ASN1DecodeItem(arena, derName, SEC_CertSubjectTemplate, &sd.data);

    if ( rv ) {
	goto loser;
    }

    tmpptr = derName->data;
    derName->data = (unsigned char*)PORT_Alloc(derName->len);
    if ( derName->data == NULL ) {
	goto loser;
    }
    
    PORT_Memcpy(derName->data, tmpptr, derName->len);
    
    PORT_FreeArena(arena, PR_FALSE);
    return(SECSuccess);

loser:
    PORT_FreeArena(arena, PR_FALSE);
    return(SECFailure);
}

SECStatus
CERT_IssuerNameFromDERCert(SECItem *derCert, SECItem *derName)
{
    int rv;
    PRArenaPool *arena;
    CERTSignedData sd;
    void *tmpptr;
    
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    
    if ( ! arena ) {
	return(SECFailure);
    }
   
    PORT_Memset(&sd, 0, sizeof(CERTSignedData));
    rv = SEC_ASN1DecodeItem(arena, &sd, CERT_SignedDataTemplate, derCert);
    
    if ( rv ) {
	goto loser;
    }
    
    PORT_Memset(derName, 0, sizeof(SECItem));
    rv = SEC_ASN1DecodeItem(arena, derName, SEC_CertIssuerTemplate, &sd.data);

    if ( rv ) {
	goto loser;
    }

    tmpptr = derName->data;
    derName->data = (unsigned char*)PORT_Alloc(derName->len);
    if ( derName->data == NULL ) {
	goto loser;
    }
    
    PORT_Memcpy(derName->data, tmpptr, derName->len);
    
    PORT_FreeArena(arena, PR_FALSE);
    return(SECSuccess);

loser:
    PORT_FreeArena(arena, PR_FALSE);
    return(SECFailure);
}

/*
 * Generate a database key, based on serial number and issuer, from a
 * DER certificate.
 */
SECStatus
CERT_KeyFromDERCert(PRArenaPool *arena, SECItem *derCert, SECItem *key)
{
    int rv;
    CERTSignedData sd;
    CERTCertKey certkey;

    PORT_Memset(&sd, 0, sizeof(CERTSignedData));    
    PORT_Memset(&certkey, 0, sizeof(CERTCertKey));    

    PORT_Memset(&sd, 0, sizeof(CERTSignedData));
    rv = SEC_ASN1DecodeItem(arena, &sd, CERT_SignedDataTemplate, derCert);
    
    if ( rv ) {
	goto loser;
    }
    
    PORT_Memset(&certkey, 0, sizeof(CERTCertKey));
    rv = SEC_ASN1DecodeItem(arena, &certkey, CERT_CertKeyTemplate, &sd.data);

    if ( rv ) {
	goto loser;
    }

    return(CERT_KeyFromIssuerAndSN(arena, &certkey.derIssuer,
				   &certkey.serialNumber, key));
loser:
    return(SECFailure);
}

/*
 * fill in keyUsage field of the cert based on the cert extension
 * if the extension is not critical, then we allow all uses
 */
static SECStatus
GetKeyUsage(CERTCertificate *cert)
{
    SECStatus rv;
    SECItem tmpitem;
    
    rv = CERT_FindKeyUsageExtension(cert, &tmpitem);
    if ( rv == SECSuccess ) {
	/* remember the actual value of the extension */
	cert->rawKeyUsage = tmpitem.data[0];
	cert->keyUsagePresent = PR_TRUE;
	cert->keyUsage = tmpitem.data[0];

	PORT_Free(tmpitem.data);
	tmpitem.data = NULL;
	
    } else {
	/* if the extension is not present, then we allow all uses */
	cert->keyUsage = KU_ALL;
	cert->rawKeyUsage = KU_ALL;
	cert->keyUsagePresent = PR_FALSE;
    }

    if ( CERT_GovtApprovedBitSet(cert) ) {
	cert->keyUsage |= KU_NS_GOVT_APPROVED;
	cert->rawKeyUsage |= KU_NS_GOVT_APPROVED;
    }
    
    return(SECSuccess);
}


/*
 * determine if a fortezza V1 Cert is a CA or not.
 */
static PRBool
fortezzaIsCA( CERTCertificate *cert) {
    PRBool isCA = PR_FALSE;
    CERTSubjectPublicKeyInfo *spki = &cert->subjectPublicKeyInfo;
    int tag;

    tag = SECOID_GetAlgorithmTag(&spki->algorithm);
    if ((tag == SEC_OID_MISSI_KEA_DSS_OLD) ||
       (tag == SEC_OID_MISSI_KEA_DSS) ||
       (tag == SEC_OID_MISSI_DSS_OLD) ||
       (tag == SEC_OID_MISSI_DSS) ) {
	SECItem rawkey;
	unsigned char *rawptr;
	unsigned char *end;
	int len;

        rawkey = spki->subjectPublicKey;
	DER_ConvertBitString(&rawkey);
	rawptr = rawkey.data;
	end = rawkey.data + rawkey.len;

	/* version */	
	rawptr += sizeof(((SECKEYPublicKey*)0)->u.fortezza.KMID)+2;

	/* clearance (the string up to the first byte with the hi-bit on */
	while ((rawptr < end) && (*rawptr++ & 0x80));
	if (rawptr >= end) { return PR_FALSE; }

	/* KEAPrivilege (the string up to the first byte with the hi-bit on */
	while ((rawptr < end) && (*rawptr++ & 0x80));
	if (rawptr >= end) { return PR_FALSE; }

	/* skip the key */
	len = (*rawptr << 8) | rawptr[1];
	rawptr += 2 + len;

	/* shared key */
	if (rawptr >= end) { return PR_FALSE; }
	/* DSS Version is next */
	rawptr += 2;

	/* DSSPrivilege (the string up to the first byte with the hi-bit on */
	if (*rawptr & 0x30) isCA = PR_TRUE;
        
   }
   return isCA;
}

static SECStatus
findOIDinOIDSeqByTagNum(CERTOidSequence *seq, SECOidTag tagnum)
{
    SECItem **oids;
    SECItem *oid;
    SECStatus rv = SECFailure;
    
    if (seq != NULL) {
	oids = seq->oids;
	while (oids != NULL && *oids != NULL) {
	    oid = *oids;
	    if (SECOID_FindOIDTag(oid) == tagnum) {
		rv = SECSuccess;
		break;
	    }
	    oids++;
	}
    }
    return rv;
}

/*
 * fill in nsCertType field of the cert based on the cert extension
 */
static SECStatus
GetCertType(CERTCertificate *cert)
{
    SECStatus rv;
    SECItem tmpitem;
    SECItem encodedExtKeyUsage;
    CERTOidSequence *extKeyUsage = NULL;
    PRBool basicConstraintPresent = PR_FALSE;
    CERTBasicConstraints basicConstraint;

    tmpitem.data = NULL;
    CERT_FindNSCertTypeExtension(cert, &tmpitem);
    rv = CERT_FindCertExtension(cert, SEC_OID_X509_EXT_KEY_USAGE, 
				&encodedExtKeyUsage);
    if (rv == SECSuccess) {
	extKeyUsage = CERT_DecodeOidSequence(&encodedExtKeyUsage);
    }
    rv = CERT_FindBasicConstraintExten(cert, &basicConstraint);
    if (rv == SECSuccess) {
	basicConstraintPresent = PR_TRUE;
    }
    if (tmpitem.data != NULL || extKeyUsage != NULL) {
	if (tmpitem.data == NULL) {
	    cert->nsCertType = 0;
	} else {
	    cert->nsCertType = tmpitem.data[0];
	}

	/* free tmpitem data pointer to avoid memory leak */
	PORT_Free(tmpitem.data);
	tmpitem.data = NULL;
	
	/*
	 * for this release, we will allow SSL certs with an email address
	 * to be used for email
	 */
	if ( ( cert->nsCertType & NS_CERT_TYPE_SSL_CLIENT ) &&
	    cert->emailAddr ) {
	    cert->nsCertType |= NS_CERT_TYPE_EMAIL;
	}
	/*
	 * for this release, we will allow SSL intermediate CAs to be
	 * email intermediate CAs too.
	 */
	if ( cert->nsCertType & NS_CERT_TYPE_SSL_CA ) {
	    cert->nsCertType |= NS_CERT_TYPE_EMAIL_CA;
	}
	/*
	 * allow a cert with the extended key usage of EMail Protect
	 * to be used for email or as an email CA, if basic constraints
	 * indicates that it is a CA.
	 */
	if (findOIDinOIDSeqByTagNum(extKeyUsage, 
				    SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT) ==
	    SECSuccess) {
	    if (basicConstraintPresent == PR_TRUE &&
		(basicConstraint.isCA)) {
		cert->nsCertType |= NS_CERT_TYPE_EMAIL_CA;
	    } else {
		cert->nsCertType |= NS_CERT_TYPE_EMAIL;
	    }
	}
	if (findOIDinOIDSeqByTagNum(extKeyUsage, 
				    SEC_OID_EXT_KEY_USAGE_SERVER_AUTH) ==
	    SECSuccess){
	    if (basicConstraintPresent == PR_TRUE &&
		(basicConstraint.isCA)) {
		cert->nsCertType |= NS_CERT_TYPE_SSL_CA;
	    } else {
		cert->nsCertType |= NS_CERT_TYPE_SSL_SERVER;
	    }
	}
	if (findOIDinOIDSeqByTagNum(extKeyUsage,
				    SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH) ==
	    SECSuccess){
	    if (basicConstraintPresent == PR_TRUE &&
		(basicConstraint.isCA)) {
		cert->nsCertType |= NS_CERT_TYPE_SSL_CA;
	    } else {
		cert->nsCertType |= NS_CERT_TYPE_SSL_CLIENT;
	    }
	}
	if (findOIDinOIDSeqByTagNum(extKeyUsage,
				    SEC_OID_EXT_KEY_USAGE_CODE_SIGN) ==
	    SECSuccess) {
	    if (basicConstraintPresent == PR_TRUE &&
		(basicConstraint.isCA)) {
		cert->nsCertType |= NS_CERT_TYPE_OBJECT_SIGNING_CA;
	    } else {
		cert->nsCertType |= NS_CERT_TYPE_OBJECT_SIGNING;
	    }
	}
	if (findOIDinOIDSeqByTagNum(extKeyUsage,
				    SEC_OID_EXT_KEY_USAGE_TIME_STAMP) ==
	    SECSuccess) {
	    cert->nsCertType |= EXT_KEY_USAGE_TIME_STAMP;
	}
	if (findOIDinOIDSeqByTagNum(extKeyUsage,
				    SEC_OID_OCSP_RESPONDER) == 
	    SECSuccess) {
	    cert->nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
	}
    } else {
	/* if no extension, then allow any ssl or email (no ca or object
	 * signing)
	 */
	cert->nsCertType = NS_CERT_TYPE_SSL_CLIENT | NS_CERT_TYPE_SSL_SERVER |
	    NS_CERT_TYPE_EMAIL;

        /* if the basic constraint extension says the cert is a CA, then
	   allow SSL CA and EMAIL CA and Status Responder */
	if ((basicConstraintPresent == PR_TRUE)
	    && (basicConstraint.isCA)) {
	        cert->nsCertType |= NS_CERT_TYPE_SSL_CA;
	        cert->nsCertType |= NS_CERT_TYPE_EMAIL_CA;
		cert->nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
	} else if (CERT_IsCACert(cert, NULL) == PR_TRUE) {
		cert->nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
	}

	/* if the cert is a fortezza CA cert, then allow SSL CA and EMAIL CA */
	if (fortezzaIsCA(cert)) {
	        cert->nsCertType |= NS_CERT_TYPE_SSL_CA;
	        cert->nsCertType |= NS_CERT_TYPE_EMAIL_CA;
	}
    }

    if (extKeyUsage != NULL) {
	PORT_Free(encodedExtKeyUsage.data);
	CERT_DestroyOidSequence(extKeyUsage);
    }
    return(SECSuccess);
}

/*
 * cert_GetKeyID() - extract or generate the subjectKeyID from a certificate
 */
SECStatus
cert_GetKeyID(CERTCertificate *cert)
{
    SECItem tmpitem;
    SECStatus rv;
    SECKEYPublicKey *key;
    
    cert->subjectKeyID.len = 0;

    /* see of the cert has a key identifier extension */
    rv = CERT_FindSubjectKeyIDExten(cert, &tmpitem);
    if ( rv == SECSuccess ) {
	cert->subjectKeyID.data = (unsigned char*) PORT_ArenaAlloc(cert->arena, tmpitem.len);
	if ( cert->subjectKeyID.data != NULL ) {
	    PORT_Memcpy(cert->subjectKeyID.data, tmpitem.data, tmpitem.len);
	    cert->subjectKeyID.len = tmpitem.len;
	    cert->keyIDGenerated = PR_FALSE;
	}
	
	PORT_Free(tmpitem.data);
    }
    
    /* if the cert doesn't have a key identifier extension and the cert is
     * a V1 fortezza certificate, use the cert's 8 byte KMID as the
     * key identifier.  */
    key = CERT_KMIDPublicKey(cert);

    if (key != NULL) {
	
	if (key->keyType == fortezzaKey) {

	    cert->subjectKeyID.data = (unsigned char *)PORT_ArenaAlloc(cert->arena, 8);
	    if ( cert->subjectKeyID.data != NULL ) {
	        PORT_Memcpy(cert->subjectKeyID.data, key->u.fortezza.KMID, 8);
	        cert->subjectKeyID.len = 8;
	        cert->keyIDGenerated = PR_FALSE;
	    }
	}
		
	SECKEY_DestroyPublicKey(key);
    }

    /* if the cert doesn't have a key identifier extension, then generate one*/
    if ( cert->subjectKeyID.len == 0 ) {
	/*
	 * pkix says that if the subjectKeyID is not present, then we should
	 * use the SHA-1 hash of the DER-encoded publicKeyInfo from the cert
	 */
	cert->subjectKeyID.data = (unsigned char *)PORT_ArenaAlloc(cert->arena, SHA1_LENGTH);
	if ( cert->subjectKeyID.data != NULL ) {
	    rv = SHA1_HashBuf(cert->subjectKeyID.data,
			      cert->derPublicKey.data,
			      cert->derPublicKey.len);
	    if ( rv == SECSuccess ) {
		cert->subjectKeyID.len = SHA1_LENGTH;
	    }
	}
    }

    if ( cert->subjectKeyID.len == 0 ) {
	return(SECFailure);
    }
    return(SECSuccess);

}

/*
 * take a DER certificate and decode it into a certificate structure
 */
CERTCertificate *
CERT_DecodeDERCertificate(SECItem *derSignedCert, PRBool copyDER,
			 char *nickname)
{
    CERTCertificate *cert;
    PRArenaPool *arena;
    void *data;
    int rv;
    int len;
    char *tmpname;
    
    /* make a new arena */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    
    if ( !arena ) {
	return 0;
    }

    /* allocate the certificate structure */
    cert = (CERTCertificate *)PORT_ArenaZAlloc(arena, sizeof(CERTCertificate));
    
    if ( !cert ) {
	goto loser;
    }
    
    cert->arena = arena;
    
    if ( copyDER ) {
	/* copy the DER data for the cert into this arena */
	data = (void *)PORT_ArenaAlloc(arena, derSignedCert->len);
	if ( !data ) {
	    goto loser;
	}
	cert->derCert.data = (unsigned char *)data;
	cert->derCert.len = derSignedCert->len;
	PORT_Memcpy(data, derSignedCert->data, derSignedCert->len);
    } else {
	/* point to passed in DER data */
	cert->derCert = *derSignedCert;
    }

    /* decode the certificate info */
    rv = SEC_ASN1DecodeItem(arena, cert, SEC_SignedCertificateTemplate,
		    &cert->derCert);

    if ( rv ) {
	goto loser;
    }

    if (cert_HasUnknownCriticalExten (cert->extensions) == PR_TRUE) {
	PORT_SetError(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION);
	goto loser;
    }

    /* generate and save the database key for the cert */
    rv = CERT_KeyFromDERCert(arena, &cert->derCert, &cert->certKey);
    if ( rv ) {
	goto loser;
    }

    /* set the nickname */
    if ( nickname == NULL ) {
	cert->nickname = NULL;
    } else {
	/* copy and install the nickname */
	len = PORT_Strlen(nickname) + 1;
	cert->nickname = (char*)PORT_ArenaAlloc(arena, len);
	if ( cert->nickname == NULL ) {
	    goto loser;
	}

	PORT_Memcpy(cert->nickname, nickname, len);
    }

    /* set the email address */
    cert->emailAddr = CERT_GetCertificateEmailAddress(cert);
    
    /* initialize the subjectKeyID */
    rv = cert_GetKeyID(cert);
    if ( rv != SECSuccess ) {
	goto loser;
    }

    /* initialize keyUsage */
    rv = GetKeyUsage(cert);
    if ( rv != SECSuccess ) {
	goto loser;
    }

    /* initialize the certType */
    rv = GetCertType(cert);
    if ( rv != SECSuccess ) {
	goto loser;
    }

    tmpname = CERT_NameToAscii(&cert->subject);
    if ( tmpname != NULL ) {
	cert->subjectName = PORT_ArenaStrdup(cert->arena, tmpname);
	PORT_Free(tmpname);
    }
    
    tmpname = CERT_NameToAscii(&cert->issuer);
    if ( tmpname != NULL ) {
	cert->issuerName = PORT_ArenaStrdup(cert->arena, tmpname);
	PORT_Free(tmpname);
    }
    
    cert->referenceCount = 1;
    cert->slot = NULL;
    cert->pkcs11ID = CK_INVALID_KEY;
    cert->dbnickname = NULL;
    
    return(cert);
    
loser:

    if ( arena ) {
	PORT_FreeArena(arena, PR_FALSE);
    }
    
    return(0);
}

/*
** Amount of time that a certifiate is allowed good before it is actually
** good. This is used for pending certificates, ones that are about to be
** valid. The slop is designed to allow for some variance in the clocks
** of the machine checking the certificate.
*/
#define PENDING_SLOP (24L*60L*60L)

SECStatus
CERT_GetCertTimes(CERTCertificate *c, int64 *notBefore, int64 *notAfter)
{
    int rv;
    
    /* convert DER not-before time */
    rv = DER_UTCTimeToTime(notBefore, &c->validity.notBefore);
    if (rv) {
	return(SECFailure);
    }
    
    /* convert DER not-after time */
    rv = DER_UTCTimeToTime(notAfter, &c->validity.notAfter);
    if (rv) {
	return(SECFailure);
    }

    return(SECSuccess);
}

/*
 * Check the validity times of a certificate
 */
SECCertTimeValidity
CERT_CheckCertValidTimes(CERTCertificate *c, int64 t, PRBool allowOverride)
{
    int64 notBefore, notAfter, pendingSlop;
    SECStatus rv;

    /* if cert is already marked OK, then don't bother to check */
    if ( allowOverride && c->timeOK ) {
        return(secCertTimeValid);
    }

    rv = CERT_GetCertTimes(c, &notBefore, &notAfter);
    
    if (rv) {
	return(secCertTimeExpired); /*XXX is this the right thing to do here?*/
    }
    
    LL_I2L(pendingSlop, PENDING_SLOP);
    LL_SUB(notBefore, notBefore, pendingSlop);
    if ( LL_CMP( t, <, notBefore ) ) {
	PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE);
	return(secCertTimeNotValidYet);
    }
    if ( LL_CMP( t, >, notAfter) ) {
	PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE);
	return(secCertTimeExpired);
    }

    return(secCertTimeValid);
}

SECStatus
SEC_GetCrlTimes(CERTCrl *date, int64 *notBefore, int64 *notAfter)
{
    int rv;
    
    /* convert DER not-before time */
    rv = DER_UTCTimeToTime(notBefore, &date->lastUpdate);
    if (rv) {
	return(SECFailure);
    }
    
    /* convert DER not-after time */
    if (date->nextUpdate.data) {
	rv = DER_UTCTimeToTime(notAfter, &date->nextUpdate);
	if (rv) {
	    return(SECFailure);
	}
    }
    else {
	LL_I2L(*notAfter, 0L);
    }
    return(SECSuccess);
}

/* These routines should probably be combined with the cert
 * routines using an common extraction routine.
 */
SECCertTimeValidity
SEC_CheckCrlTimes(CERTCrl *crl, int64 t) {
    int64 notBefore, notAfter, pendingSlop;
    SECStatus rv;

    rv = SEC_GetCrlTimes(crl, &notBefore, &notAfter);
    
    if (rv) {
	return(secCertTimeExpired); 
    }

    LL_I2L(pendingSlop, PENDING_SLOP);
    LL_SUB(notBefore, notBefore, pendingSlop);
    if ( LL_CMP( t, <, notBefore ) ) {
	return(secCertTimeNotValidYet);
    }

    /* If next update is omitted and the test for notBefore passes, then
       we assume that the crl is up to date.
     */
    if ( LL_IS_ZERO(notAfter) ) {
	return(secCertTimeValid);
    }

    if ( LL_CMP( t, >, notAfter) ) {
	return(secCertTimeExpired);
    }

    return(secCertTimeValid);
}

PRBool
SEC_CrlIsNewer(CERTCrl *inNew, CERTCrl *old) {
    int64 newNotBefore, newNotAfter;
    int64 oldNotBefore, oldNotAfter;
    SECStatus rv;

    /* problems with the new CRL? reject it */
    rv = SEC_GetCrlTimes(inNew, &newNotBefore, &newNotAfter);
    if (rv) return PR_FALSE;

    /* problems with the old CRL? replace it */
    rv = SEC_GetCrlTimes(old, &oldNotBefore, &oldNotAfter);
    if (rv) return PR_TRUE;

    /* Question: what about the notAfter's? */
    return ((PRBool)LL_CMP(oldNotBefore, <, newNotBefore));
}
   

/*
 * WARNING - this function is depricated, and will go away in the near future.
 *	It has been superseded by CERT_CheckCertValidTimes().
 *
 * Check the validity times of a certificate
 */
SECStatus
CERT_CertTimesValid(CERTCertificate *c)
{
    int64 now, notBefore, notAfter, pendingSlop;
    SECStatus rv;
    
    /* if cert is already marked OK, then don't bother to check */
    if ( c->timeOK ) {
	return(SECSuccess);
    }
    
    /* get current UTC time */
    now = PR_Now();
    rv = CERT_GetCertTimes(c, &notBefore, &notAfter);
    
    if (rv) {
	return(SECFailure);
    }
    
    LL_I2L(pendingSlop, PENDING_SLOP);
    LL_SUB(notBefore, notBefore, pendingSlop);

    if (LL_CMP(now, <, notBefore) || LL_CMP(now, >, notAfter)) {
	PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE);
	return(SECFailure);
    }

    return(SECSuccess);
}

/*
 * verify the signature of a signed data object with the given certificate
 */
SECStatus
CERT_VerifySignedData(CERTSignedData *sd, CERTCertificate *cert,
		      int64 t, void *wincx)
{
    SECItem sig;
    SECKEYPublicKey *pubKey = 0;
    SECStatus rv;
    SECCertTimeValidity validity;
    SECOidTag algid;

    /* check the certificate's validity */
    validity = CERT_CheckCertValidTimes(cert, t, PR_FALSE);
    if ( validity != secCertTimeValid ) {
	return(SECFailure);
    }

    /* get cert's public key */
    pubKey = CERT_ExtractPublicKey(cert);
    if ( !pubKey ) {
	return(SECFailure);
    }
    
    /* check the signature */
    sig = sd->signature;
    DER_ConvertBitString(&sig);

    algid = SECOID_GetAlgorithmTag(&sd->signatureAlgorithm);
    rv = VFY_VerifyData(sd->data.data, sd->data.len, pubKey, &sig,
			algid, wincx);

    SECKEY_DestroyPublicKey(pubKey);

    if ( rv ) {
	return(SECFailure);
    }

    return(SECSuccess);
}


/*
 * This must only be called on a cert that is known to have an issuer
 * with an invalid time
 */
CERTCertificate *
CERT_FindExpiredIssuer(CERTCertDBHandle *handle, CERTCertificate *cert)
{
    CERTCertificate *issuerCert = NULL;
    CERTCertificate *subjectCert;
    int              count;
    SECStatus        rv;
    SECComparison    rvCompare;
    
    subjectCert = CERT_DupCertificate(cert);
    if ( subjectCert == NULL ) {
	goto loser;
    }
    
    for ( count = 0; count < CERT_MAX_CERT_CHAIN; count++ ) {
	/* find the certificate of the issuer */
	issuerCert = CERT_FindCertByName(handle, &subjectCert->derIssuer);
    
	if ( ! issuerCert ) {
	    goto loser;
	}

	rv = CERT_CertTimesValid(issuerCert);
	if ( rv == SECFailure ) {
	    /* this is the invalid issuer */
	    CERT_DestroyCertificate(subjectCert);
	    return(issuerCert);
	}

	/* make sure that the issuer is not self signed.  If it is, then
	 * stop here to prevent looping.
	 */
	rvCompare = SECITEM_CompareItem(&issuerCert->derSubject,
				 &issuerCert->derIssuer);
	if (rvCompare == SECEqual) {
	    PORT_Assert(0);		/* No expired issuer! */
	    goto loser;
	}
	CERT_DestroyCertificate(subjectCert);
	subjectCert = issuerCert;
    }

loser:
    if ( issuerCert ) {
	CERT_DestroyCertificate(issuerCert);
    }
    
    if ( subjectCert ) {
	CERT_DestroyCertificate(subjectCert);
    }
    
    return(NULL);
}

/* Software FORTEZZA installation hack. The software fortezza installer does
 * not have access to the krl and cert.db file. Accept FORTEZZA Certs without
 * KRL's in this case. 
 */
static int dont_use_krl = 0;
/* not a public exposed function... */
void sec_SetCheckKRLState(int value) { dont_use_krl = value; }

SECStatus
SEC_CheckKRL(CERTCertDBHandle *handle,SECKEYPublicKey *key,
	     CERTCertificate *rootCert, int64 t, void * wincx)
{
    CERTSignedCrl *crl = NULL;
    SECStatus rv = SECFailure;
    SECStatus rv2;
    CERTCrlEntry **crlEntry;
    SECCertTimeValidity validity;
    CERTCertificate *issuerCert = NULL;

    if (dont_use_krl) return SECSuccess;

    /* first look up the KRL */
    crl = SEC_FindCrlByName(handle,&rootCert->derSubject, SEC_KRL_TYPE);
    if (crl == NULL) {
	PORT_SetError(SEC_ERROR_NO_KRL);
	goto done;
    }

    /* get the issuing certificate */
    issuerCert = CERT_FindCertByName(handle, &crl->crl.derName);
    if (issuerCert == NULL) {
        PORT_SetError(SEC_ERROR_KRL_BAD_SIGNATURE);
        goto done;
    }


    /* now verify the KRL signature */
    rv2 = CERT_VerifySignedData(&crl->signatureWrap, issuerCert, t, wincx);
    if (rv2 != SECSuccess) {
	PORT_SetError(SEC_ERROR_KRL_BAD_SIGNATURE);
    	goto done;
    }

    /* Verify the date validity of the KRL */
    validity = SEC_CheckCrlTimes(&crl->crl, t);
    if (validity == secCertTimeExpired) {
	PORT_SetError(SEC_ERROR_KRL_EXPIRED);
	goto done;
    }

    /* now make sure the key in this cert is still valid */
    if (key->keyType != fortezzaKey) {
	PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
	goto done; /* This should be an assert? */
    }

    /* now make sure the key is not on the revocation list */
    for (crlEntry = crl->crl.entries; crlEntry && *crlEntry; crlEntry++) {
	if (PORT_Memcmp((*crlEntry)->serialNumber.data,
				key->u.fortezza.KMID,
				    (*crlEntry)->serialNumber.len) == 0) {
	    PORT_SetError(SEC_ERROR_REVOKED_KEY);
	    goto done;
	}
    }
    rv = SECSuccess;

done:
    if (issuerCert) CERT_DestroyCertificate(issuerCert);
    if (crl) SEC_DestroyCrl(crl);
    return rv;
}

SECStatus
SEC_CheckCRL(CERTCertDBHandle *handle,CERTCertificate *cert,
	     CERTCertificate *caCert, int64 t, void * wincx)
{
    CERTSignedCrl *crl = NULL;
    SECStatus rv = SECSuccess;
    CERTCrlEntry **crlEntry;
    SECCertTimeValidity validity;

    /* first look up the CRL */
    crl = SEC_FindCrlByName(handle,&caCert->derSubject, SEC_CRL_TYPE);
    if (crl == NULL) {
	/* XXX for now no CRL is ok */
	goto done;
    }

    /* now verify the CRL signature */
    rv = CERT_VerifySignedData(&crl->signatureWrap, caCert, t, wincx);
    if (rv != SECSuccess) {
	PORT_SetError(SEC_ERROR_CRL_BAD_SIGNATURE);
        rv = SECWouldBlock; /* Soft error, ask the user */
    	goto done;
    }

    /* Verify the date validity of the KRL */
    validity = SEC_CheckCrlTimes(&crl->crl,t);
    if (validity == secCertTimeExpired) {
	PORT_SetError(SEC_ERROR_CRL_EXPIRED);
        rv = SECWouldBlock; /* Soft error, ask the user */
    } else if (validity == secCertTimeNotValidYet) {
	PORT_SetError(SEC_ERROR_CRL_NOT_YET_VALID);
	rv = SECWouldBlock; /* Soft error, ask the user */
    }

    /* now make sure the key is not on the revocation list */
    for (crlEntry = crl->crl.entries; crlEntry && *crlEntry; crlEntry++) {
	if (SECITEM_CompareItem(&(*crlEntry)->serialNumber,&cert->serialNumber) == SECEqual) {
	    PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE);
	    rv = SECFailure; /* cert is revoked */
	    goto done;
	}
    }

done:
    if (crl) SEC_DestroyCrl(crl);
    return rv;
}

/*
 * Find the issuer of a cert.  Use the authorityKeyID if it exists.
 */
CERTCertificate *
CERT_FindCertIssuer(CERTCertificate *cert, int64 validTime, SECCertUsage usage)
{
    CERTAuthKeyID *   authorityKeyID = NULL;  
    CERTCertificate * issuerCert     = NULL;
    SECItem *         caName;
    PRArenaPool       *tmpArena = NULL;
    SECItem           issuerCertKey;
    SECStatus         rv;

    tmpArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    
    if ( !tmpArena ) {
	goto loser;
    }
    authorityKeyID = CERT_FindAuthKeyIDExten(tmpArena,cert);

    if ( authorityKeyID != NULL ) {
	/* has the authority key ID extension */
	if ( authorityKeyID->keyID.data != NULL ) {
	    /* extension contains a key ID, so lookup based on it */
	    issuerCert = CERT_FindCertByKeyID(cert->dbhandle, &cert->derIssuer,
					      &authorityKeyID->keyID);
	    if ( issuerCert == NULL ) {
		PORT_SetError (SEC_ERROR_UNKNOWN_ISSUER);
		goto loser;
	    }
	    
	} else if ( authorityKeyID->authCertIssuer != NULL ) {
	    /* no key ID, so try issuer and serial number */
	    caName = (SECItem*)CERT_GetGeneralNameByType(authorityKeyID->authCertIssuer,
					       		 certDirectoryName, PR_TRUE);

	    /*
	     * caName is NULL when the authCertIssuer field is not
	     * being used, or other name form is used instead.
	     * If the directoryName format and serialNumber fields are
	     * used, we use them to find the CA cert.
	     * Note:
	     *	By the time it gets here, we known for sure that if the
	     *	authCertIssuer exists, then the authCertSerialNumber
	     *	must also exists (CERT_DecodeAuthKeyID() ensures this).
	     *	We don't need to check again. 
	     */

	    if (caName != NULL) {
		rv = CERT_KeyFromIssuerAndSN(tmpArena, caName,
					     &authorityKeyID->authCertSerialNumber,
					     &issuerCertKey);
		if ( rv == SECSuccess ) {
		    issuerCert = CERT_FindCertByKey(cert->dbhandle,
						    &issuerCertKey);
		}
		
		if ( issuerCert == NULL ) {
		    PORT_SetError (SEC_ERROR_UNKNOWN_ISSUER);
		    goto loser;
		}
	    }
	}
    }
    if ( issuerCert == NULL ) {
	/* if there is not authorityKeyID, then try to find the issuer */
	/* find a valid CA cert with correct usage */
	issuerCert = CERT_FindMatchingCert(cert->dbhandle,
					   &cert->derIssuer,
					   certOwnerCA, usage, PR_TRUE,
					   validTime, PR_TRUE);

	/* if that fails, then fall back to grabbing any cert with right name*/
	if ( issuerCert == NULL ) {
	    issuerCert = CERT_FindCertByName(cert->dbhandle, &cert->derIssuer);
	    if ( issuerCert == NULL ) {
		PORT_SetError (SEC_ERROR_UNKNOWN_ISSUER);
	    }
	}
    }

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

    return(issuerCert);
}

/*
 * return required key usage and cert type based on cert usage 
 */
SECStatus
CERT_KeyUsageAndTypeForCertUsage(SECCertUsage usage,
				 PRBool ca,
				 unsigned int *retKeyUsage,
				 unsigned int *retCertType)
{
    unsigned int requiredKeyUsage = 0;
    unsigned int requiredCertType = 0;
    
    if ( ca ) {
	switch ( usage ) {
	  case certUsageSSLServerWithStepUp:
	    requiredKeyUsage = KU_NS_GOVT_APPROVED | KU_KEY_CERT_SIGN;
	    requiredCertType = NS_CERT_TYPE_SSL_CA;
	    break;
	  case certUsageSSLClient:
	    requiredKeyUsage = KU_KEY_CERT_SIGN;
	    requiredCertType = NS_CERT_TYPE_SSL_CA;
	    break;
	  case certUsageSSLServer:
	    requiredKeyUsage = KU_KEY_CERT_SIGN;
	    requiredCertType = NS_CERT_TYPE_SSL_CA;
	    break;
	  case certUsageSSLCA:
	    requiredKeyUsage = KU_KEY_CERT_SIGN;
	    requiredCertType = NS_CERT_TYPE_SSL_CA;
	    break;
	  case certUsageEmailSigner:
	    requiredKeyUsage = KU_KEY_CERT_SIGN;
	    requiredCertType = NS_CERT_TYPE_EMAIL_CA;
	    break;
	  case certUsageEmailRecipient:
	    requiredKeyUsage = KU_KEY_CERT_SIGN;
	    requiredCertType = NS_CERT_TYPE_EMAIL_CA;
	    break;
	  case certUsageObjectSigner:
	    requiredKeyUsage = KU_KEY_CERT_SIGN;
	    requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING_CA;
	    break;
	  case certUsageAnyCA:
	  case certUsageStatusResponder:
	    requiredKeyUsage = KU_KEY_CERT_SIGN;
	    requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING_CA |
		NS_CERT_TYPE_EMAIL_CA |
		    NS_CERT_TYPE_SSL_CA;
	    break;
	  default:
	    PORT_Assert(0);
	    goto loser;
	}
    } else {
	switch ( usage ) {
	  case certUsageSSLClient:
	    requiredKeyUsage = KU_DIGITAL_SIGNATURE;
	    requiredCertType = NS_CERT_TYPE_SSL_CLIENT;
	    break;
	  case certUsageSSLServer:
	    requiredKeyUsage = KU_KEY_AGREEMENT_OR_ENCIPHERMENT;
	    requiredCertType = NS_CERT_TYPE_SSL_SERVER;
	    break;
	  case certUsageSSLServerWithStepUp:
	    requiredKeyUsage = KU_KEY_AGREEMENT_OR_ENCIPHERMENT |
		KU_NS_GOVT_APPROVED;
	    requiredCertType = NS_CERT_TYPE_SSL_SERVER;
	    break;
	  case certUsageSSLCA:
	    requiredKeyUsage = KU_KEY_CERT_SIGN;
	    requiredCertType = NS_CERT_TYPE_SSL_CA;
	    break;
	  case certUsageEmailSigner:
	    requiredKeyUsage = KU_DIGITAL_SIGNATURE;
	    requiredCertType = NS_CERT_TYPE_EMAIL;
	    break;
	  case certUsageEmailRecipient:
	    requiredKeyUsage = KU_KEY_AGREEMENT_OR_ENCIPHERMENT;
	    requiredCertType = NS_CERT_TYPE_EMAIL;
	    break;
	  case certUsageObjectSigner:
	    requiredKeyUsage = KU_DIGITAL_SIGNATURE;
	    requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING;
	    break;
	  case certUsageStatusResponder:
	    requiredKeyUsage = KU_DIGITAL_SIGNATURE;
	    requiredCertType = EXT_KEY_USAGE_STATUS_RESPONDER;
	    break;
	  default:
	    PORT_Assert(0);
	    goto loser;
	}
    }

    if ( retKeyUsage != NULL ) {
	*retKeyUsage = requiredKeyUsage;
    }
    if ( retCertType != NULL ) {
	*retCertType = requiredCertType;
    }

    return(SECSuccess);
loser:
    return(SECFailure);
}

/*
 * return required trust flags for various cert usages for CAs
 */
SECStatus
CERT_TrustFlagsForCACertUsage(SECCertUsage usage,
			      unsigned int *retFlags,
			      SECTrustType *retTrustType)
{
    unsigned int requiredFlags;
    SECTrustType trustType;

    switch ( usage ) {
      case certUsageSSLClient:
	requiredFlags = CERTDB_TRUSTED_CLIENT_CA;
	trustType = trustSSL;
        break;
      case certUsageSSLServer:
      case certUsageSSLCA:
	requiredFlags = CERTDB_TRUSTED_CA;
	trustType = trustSSL;
        break;
      case certUsageSSLServerWithStepUp:
	requiredFlags = CERTDB_TRUSTED_CA | CERTDB_GOVT_APPROVED_CA;
	trustType = trustSSL;
        break;
      case certUsageEmailSigner:
      case certUsageEmailRecipient:
	requiredFlags = CERTDB_TRUSTED_CA;
	trustType = trustEmail;
	break;
      case certUsageObjectSigner:
	requiredFlags = CERTDB_TRUSTED_CA;
	trustType = trustObjectSigning;
	break;
      case certUsageVerifyCA:
      case certUsageAnyCA:
      case certUsageStatusResponder:
	requiredFlags = CERTDB_TRUSTED_CA;
	trustType = trustTypeNone;
	break;
      default:
	PORT_Assert(0);
	goto loser;
    }
    if ( retFlags != NULL ) {
	*retFlags = requiredFlags;
    }
    if ( retTrustType != NULL ) {
	*retTrustType = trustType;
    }
    
    return(SECSuccess);
loser:
    return(SECFailure);
}



/*
 * check the key usage of a cert against a set of required values
 */
SECStatus
CERT_CheckKeyUsage(CERTCertificate *cert, unsigned int requiredUsage)
{
    SECKEYPublicKey *key;
    
    /* choose between key agreement or key encipherment based on key
     * type in cert
     */
    if ( requiredUsage & KU_KEY_AGREEMENT_OR_ENCIPHERMENT ) {
	key = CERT_ExtractPublicKey(cert);
	if ( ( key->keyType == keaKey ) || ( key->keyType == fortezzaKey ) ) {
	    requiredUsage |= KU_KEY_AGREEMENT;
	} else {
	    requiredUsage |= KU_KEY_ENCIPHERMENT;
	} 

	/* now turn off the special bit */
	requiredUsage &= (~KU_KEY_AGREEMENT_OR_ENCIPHERMENT);
	
	SECKEY_DestroyPublicKey(key);
    }

    if ( ( cert->keyUsage & requiredUsage ) != requiredUsage ) {
	return(SECFailure);
    }
    return(SECSuccess);
}




static void
AddToVerifyLog(CERTVerifyLog *log, CERTCertificate *cert, unsigned long error,
	       unsigned int depth, void *arg)
{
    CERTVerifyLogNode *node, *tnode;

    PORT_Assert(log != NULL);
    
    node = (CERTVerifyLogNode *)PORT_ArenaAlloc(log->arena,
						sizeof(CERTVerifyLogNode));
    if ( node != NULL ) {
	node->cert = CERT_DupCertificate(cert);
	node->error = error;
	node->depth = depth;
	node->arg = arg;
	
	if ( log->tail == NULL ) {
	    /* empty list */
	    log->head = log->tail = node;
	    node->prev = NULL;
	    node->next = NULL;
	} else if ( depth >= log->tail->depth ) {
	    /* add to tail */
	    node->prev = log->tail;
	    log->tail->next = node;
	    log->tail = node;
	    node->next = NULL;
	} else if ( depth < log->head->depth ) {
	    /* add at head */
	    node->prev = NULL;
	    node->next = log->head;
	    log->head->prev = node;
	    log->head = node;
	} else {
	    /* add in middle */
	    tnode = log->tail;
	    while ( tnode != NULL ) {
		if ( depth >= tnode->depth ) {
		    /* insert after tnode */
		    node->prev = tnode;
		    node->next = tnode->next;
		    tnode->next->prev = node;
		    tnode->next = node;
		    break;
		}

		tnode = tnode->prev;
	    }
	}

	log->count++;
    }
    return;
}

#define EXIT_IF_NOT_LOGGING(log) \
    if ( log == NULL ) { \
	goto loser; \
    }

#define LOG_ERROR_OR_EXIT(log,cert,depth,arg) \
    if ( log != NULL ) { \
	AddToVerifyLog(log, cert, PORT_GetError(), depth, (void *)arg); \
    } else { \
	goto loser; \
    }

#define LOG_ERROR(log,cert,depth,arg) \
    if ( log != NULL ) { \
	AddToVerifyLog(log, cert, PORT_GetError(), depth, (void *)arg); \
    }




SECStatus
CERT_VerifyCertChain(CERTCertDBHandle *handle, CERTCertificate *cert,
		     PRBool checkSig, SECCertUsage certUsage, int64 t,
		     void *wincx, CERTVerifyLog *log)
{
    SECTrustType trustType;
    CERTBasicConstraints basicConstraint;
    CERTCertificate *issuerCert = NULL;
    CERTCertificate *subjectCert = NULL;
    CERTCertificate *badCert = NULL;
    PRBool isca;
    PRBool isFortezzaV1 = PR_FALSE;
    SECStatus rv;
    SECComparison rvCompare;
    SECStatus rvFinal = SECSuccess;
    int count;
    int currentPathLen = -1;
    int flags;
    unsigned int caCertType;
    unsigned int requiredCAKeyUsage;
    unsigned int requiredFlags;
    PRArenaPool *arena = NULL;
    CERTGeneralName *namesList = NULL;
    CERTGeneralName *subjectNameList = NULL;
    SECItem *namesIndex = NULL;
    int namesIndexLen = 10;
    int namesCount = 0;

    enum { cbd_None, cbd_User, cbd_CA } last_type = cbd_None;
    SECKEYPublicKey *key;

    if (CERT_KeyUsageAndTypeForCertUsage(certUsage, PR_TRUE,
					 &requiredCAKeyUsage,
					 &caCertType)
	!= SECSuccess ) {
	PORT_Assert(0);
	EXIT_IF_NOT_LOGGING(log);
	requiredCAKeyUsage = 0;
	caCertType = 0;
    }

    switch ( certUsage ) {
      case certUsageSSLClient:
      case certUsageSSLServer:
      case certUsageSSLCA:
      case certUsageSSLServerWithStepUp:
      case certUsageEmailSigner:
      case certUsageEmailRecipient:
      case certUsageObjectSigner:
      case certUsageVerifyCA:
      case certUsageStatusResponder:
	if ( CERT_TrustFlagsForCACertUsage(certUsage, &requiredFlags,
					   &trustType) != SECSuccess ) {
	    PORT_Assert(0);
	    EXIT_IF_NOT_LOGGING(log);
	    requiredFlags = 0;
	    trustType = trustSSL;
	}
	break;
      default:
	PORT_Assert(0);
	EXIT_IF_NOT_LOGGING(log);
	requiredFlags = 0;
	trustType = trustSSL;/* This used to be 0, but we need something
			      * that matches the enumeration type.
			      */
	caCertType = 0;
    }
    
    subjectCert = CERT_DupCertificate(cert);
    if ( subjectCert == NULL ) {
	goto loser;
    }

    /* determine if the cert is fortezza. Getting the key is an easy
     * way to determine it, especially since we need to get the privillege
     * from the key anyway.
     */
    key = CERT_ExtractPublicKey(cert);

    if (key != NULL) {
	isFortezzaV1 = (PRBool)(key->keyType == fortezzaKey);

	/* find out what type of cert we are starting with */
	if (isFortezzaV1) {
	    unsigned char priv = 0;;

	    rv = SEC_CheckKRL(handle, key, NULL, t, wincx);
	    if (rv == SECFailure) {
		/**** PORT_SetError is already set by SEC_CheckKRL **/
		SECKEY_DestroyPublicKey(key);
		/**** Bob - should we log and continue when logging? **/
		LOG_ERROR(log,subjectCert,0,0);
		goto loser;
	    }                

	    if (key->u.fortezza.DSSpriviledge.len > 0) {
		priv = key->u.fortezza.DSSpriviledge.data[0];
	    }

	    last_type = (priv & 0x30) ? cbd_CA : cbd_User;
	}
		
	SECKEY_DestroyPublicKey(key);
    }

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

    namesIndex = (SECItem *) PORT_ZAlloc(sizeof(SECItem) * namesIndexLen);
    if (namesIndex == NULL) {
	goto loser;
    }

    for ( count = 0; count < CERT_MAX_CERT_CHAIN; count++ ) {
	int subjectNameListLen;
	int i;

	/* Construct a list of names for the current and all previous certifcates 
	   to be verified against the name constraints extension of the issuer
	   certificate. */
	subjectNameList = CERT_GetCertificateNames(subjectCert, arena);
	subjectNameListLen = CERT_GetNamesLength(subjectNameList);
	for (i = 0; i < subjectNameListLen; i++) {
	    if (namesIndexLen < namesCount + i) {
		namesIndexLen = namesIndexLen * 2;
		namesIndex = (SECItem *) PORT_Realloc(namesIndex, namesIndexLen * 
						       sizeof(SECItem));
		if (namesIndex == NULL) {
		    goto loser;
		}
	    }
	    rv = SECITEM_CopyItem(arena, &(namesIndex[namesCount + i]), &(subjectCert->derSubject));
	}
	namesCount += subjectNameListLen;
	namesList = cert_CombineNamesLists(namesList, subjectNameList);

	/* find the certificate of the issuer */
	issuerCert = CERT_FindCertIssuer(subjectCert, t, certUsage);
	if ( ! issuerCert ) {
	    PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
	    LOG_ERROR(log,subjectCert,count,0);
	    goto loser;
	}

	/* verify the signature on the cert */
	if ( checkSig ) {
	    rv = CERT_VerifySignedData(&subjectCert->signatureWrap,
				       issuerCert, t, wincx);
    
	    if ( rv != SECSuccess ) {
		if ( PORT_GetError() == SEC_ERROR_EXPIRED_CERTIFICATE ) {
		    PORT_SetError(SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE);
		    LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
		} else {
		    PORT_SetError(SEC_ERROR_BAD_SIGNATURE);
		    LOG_ERROR_OR_EXIT(log,subjectCert,count,0);
		}
	    }
	}

	/*
	 * XXX - fortezza may need error logging stuff added
	 */
	if (isFortezzaV1) {
	    unsigned char priv = 0;

	    /* read the key */
	    key = CERT_ExtractPublicKey(issuerCert);

	    /* Cant' get Key? fail. */
	    if (key == NULL) {
	    	PORT_SetError(SEC_ERROR_BAD_KEY);
		LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
		goto fortezzaDone;
	    }


	    /* if the issuer is not an old fortezza cert, we bail */
	    if (key->keyType != fortezzaKey) {
	    	SECKEY_DestroyPublicKey(key);
		/* CA Cert not fortezza */
	    	PORT_SetError(SEC_ERROR_NOT_FORTEZZA_ISSUER);
		LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
		goto fortezzaDone;
	    }

	    /* get the privilege mask */
	    if (key->u.fortezza.DSSpriviledge.len > 0) {
		priv = key->u.fortezza.DSSpriviledge.data[0];
	    }

	    /*
	     * make sure the CA's keys are OK
	     */
            
	    rv = SEC_CheckKRL(handle, key, NULL, t, wincx);
	    if (rv != SECSuccess) {
	    	SECKEY_DestroyPublicKey(key);
		LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
		goto fortezzaDone;
		/** fall through looking for more stuff **/
	    } else {
	        SECKEY_DestroyPublicKey(key);
	    }

	    switch (last_type) {
	      case cbd_User:
		/* first check for subordination */
		/*rv = FortezzaSubordinateCheck(cert,issuerCert);*/
		rv = SECSuccess;

		/* now check for issuer privilege */
		if ((rv != SECSuccess) || ((priv & 0x10) == 0)) {
		    /* bail */
		    PORT_SetError (SEC_ERROR_CA_CERT_INVALID);
		    LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
		}
		break;
	      case cbd_CA:
	      case cbd_None:
		if ((priv & 0x20) == 0) {
		    /* bail */
		    PORT_SetError (SEC_ERROR_CA_CERT_INVALID);
		    LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
		}
		break;
	      default:
		/* bail */ /* shouldn't ever happen */
	    	PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
		LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
	    }

fortezzaDone:
	    last_type =  cbd_CA;
	}

	/* If the basicConstraint extension is included in an immediate CA
	 * certificate, make sure that the isCA flag is on.  If the
	 * pathLenConstraint component exists, it must be greater than the
	 * number of CA certificates we have seen so far.  If the extension
	 * is omitted, we will assume that this is a CA certificate with
	 * an unlimited pathLenConstraint (since it already passes the
	 * netscape-cert-type extension checking).
	 *
	 * In the fortezza (V1) case, we've already checked the CA bits
	 * in the key, so we're presumed to be a CA; however we really don't
	 * want to bypass Basic constraint or netscape extension parsing.
         * 
         * In Fortezza V2, basicConstraint will be set for every CA,PCA,PAA
	 */

	rv = CERT_FindBasicConstraintExten(issuerCert, &basicConstraint);
	if ( rv != SECSuccess ) {
	    if (PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) {
		LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
	    } else {
		currentPathLen = CERT_UNLIMITED_PATH_CONSTRAINT;
	    }
	    /* no basic constraints found, if we're fortezza, CA bit is already
	     * verified (isca = PR_TRUE). otherwise, we aren't (yet) a ca
	     * isca = PR_FALSE */
	    isca = isFortezzaV1;
	} else  {
	    if ( basicConstraint.isCA == PR_FALSE ) {
		PORT_SetError (SEC_ERROR_CA_CERT_INVALID);
		LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
	    }
	    
	    /* make sure that the path len constraint is properly set.
	     */
	    if ( basicConstraint.pathLenConstraint ==
		CERT_UNLIMITED_PATH_CONSTRAINT ) {
		currentPathLen = CERT_UNLIMITED_PATH_CONSTRAINT;
	    } else if ( currentPathLen == CERT_UNLIMITED_PATH_CONSTRAINT ) {
		/* error if the previous CA's path length constraint is
		 * unlimited but its CA's path is not.
		 */
		PORT_SetError (SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID);
		LOG_ERROR_OR_EXIT(log,issuerCert,count+1,basicConstraint.pathLenConstraint);
	    } else if (basicConstraint.pathLenConstraint > currentPathLen) {
		currentPathLen = basicConstraint.pathLenConstraint;
	    } else {
		PORT_SetError (SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID);
		LOG_ERROR_OR_EXIT(log,issuerCert,count+1,basicConstraint.pathLenConstraint);
	    }

	    isca = PR_TRUE;
	}
	
	/* XXX - the error logging may need to go down into CRL stuff at some
	 * point
	 */
	/* check revoked list (issuer) */
	rv = SEC_CheckCRL(handle, subjectCert, issuerCert, t, wincx);
	if (rv == SECFailure) {
	    LOG_ERROR_OR_EXIT(log,subjectCert,count,0);
	} else if (rv == SECWouldBlock) {
	    /* We found something fishy, so we intend to issue an
	     * error to the user, but the user may wish to continue
	     * processing, in which case we better make sure nothing
	     * worse has happened... so keep cranking the loop */
	    rvFinal = SECFailure;
	    LOG_ERROR(log,subjectCert,count,0);
	}


	if ( issuerCert->trust ) {
	    /*
	     * check the trust parms of the issuer
	     */
	    if ( certUsage == certUsageVerifyCA ) {
		if ( subjectCert->nsCertType & NS_CERT_TYPE_EMAIL_CA ) {
		    trustType = trustEmail;
		} else if ( subjectCert->nsCertType & NS_CERT_TYPE_SSL_CA ) {
		    trustType = trustSSL;
		} else {
		    trustType = trustObjectSigning;
		}
	    }
	    
	    flags = SEC_GET_TRUST_FLAGS(issuerCert->trust, trustType);
	    
	    if ( (flags & CERTDB_VALID_CA) ||
		 (certUsage == certUsageStatusResponder)) {
		if ( ( flags & requiredFlags ) == requiredFlags ||
		     certUsage == certUsageStatusResponder ) {
		    /* we found a trusted one, so return */
		    rv = rvFinal; 
		    goto done;
		}
	    }
	}

	/*
	 * Make sure that if this is an intermediate CA in the chain that
	 * it was given permission by its signer to be a CA.
	 */
	if ( isca ) {
	    /*
	     * if basicConstraints says it is a ca, then we check the
	     * nsCertType.  If the nsCertType has any CA bits set, then
	     * it must have the right one.
	     */
	    if ( issuerCert->nsCertType & NS_CERT_TYPE_CA ) {
		if ( issuerCert->nsCertType & caCertType ) {
		    isca = PR_TRUE;
		} else {
		    isca = PR_FALSE;
		}
	    }
	} else {
	    if ( issuerCert->nsCertType & caCertType ) {
		isca = PR_TRUE;
	    } else {
		isca = PR_FALSE;
	    }
	}
	
	if (  !isca  ) {
	    PORT_SetError(SEC_ERROR_CA_CERT_INVALID);
	    LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
	}
	    
	/* make sure key usage allows cert signing */
	if (CERT_CheckKeyUsage(issuerCert, requiredCAKeyUsage) != SECSuccess) {
	    PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE);
	    LOG_ERROR_OR_EXIT(log,issuerCert,count+1,requiredCAKeyUsage);
	}
	/* make sure that the entire chain is within the name space of the current issuer
	 * certificate.
	 */

	badCert = CERT_CompareNameSpace(issuerCert, namesList, namesIndex, arena, handle);
	if (badCert != NULL) {
	    PORT_SetError(SEC_ERROR_CERT_NOT_IN_NAME_SPACE);
            LOG_ERROR_OR_EXIT(log, badCert, count + 1, 0);
	    goto loser;
	}
	/* make sure that the issuer is not self signed.  If it is, then
	 * stop here to prevent looping.
	 */
	rvCompare = SECITEM_CompareItem(&issuerCert->derSubject,
				 &issuerCert->derIssuer);
	if (rvCompare == SECEqual) {
	    PORT_SetError(SEC_ERROR_UNTRUSTED_ISSUER);
	    LOG_ERROR(log, issuerCert, count+1, 0);
	    goto loser;
	}

	CERT_DestroyCertificate(subjectCert);
	subjectCert = issuerCert;
    }

    subjectCert = NULL;
    PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
    LOG_ERROR(log,issuerCert,count,0);
loser:
    rv = SECFailure;
done:
    if (namesIndex != NULL) {
	PORT_Free(namesIndex);
    }
    if ( issuerCert ) {
	CERT_DestroyCertificate(issuerCert);
    }
    
    if ( subjectCert ) {
	CERT_DestroyCertificate(subjectCert);
    }

    if ( arena != NULL ) {
	PORT_FreeArena(arena, PR_FALSE);
    }
    return rv;
}
			
/*
 * verify a certificate by checking if its valid and that we
 * trust the issuer.
 * Note that this routine does not verify the signature of the certificate.
 */
SECStatus
CERT_VerifyCert(CERTCertDBHandle *handle, CERTCertificate *cert,
		PRBool checkSig, SECCertUsage certUsage, int64 t,
		void *wincx, CERTVerifyLog *log)
{
    SECStatus rv;
    unsigned int requiredKeyUsage;
    unsigned int requiredCertType;
    unsigned int flags;
    unsigned int certType;
    PRBool       allowOverride;
    SECCertTimeValidity validity;
    
    /* check if this cert is in the Evil list */
    rv = CERT_CheckForEvilCert(cert);
    if ( rv != SECSuccess ) {
	PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE);
	LOG_ERROR_OR_EXIT(log,cert,0,0);
    }
    
    /* make sure that the cert is valid at time t */
    allowOverride = (PRBool)((certUsage == certUsageSSLServer) ||
                             (certUsage == certUsageSSLServerWithStepUp));
    validity = CERT_CheckCertValidTimes(cert, t, allowOverride);
    if ( validity != secCertTimeValid ) {
	LOG_ERROR_OR_EXIT(log,cert,0,validity);
    }

    /* check key usage and netscape cert type */
    GetCertType(cert);
    certType = cert->nsCertType;
    switch ( certUsage ) {
      case certUsageSSLClient:
      case certUsageSSLServer:
      case certUsageSSLServerWithStepUp:
      case certUsageSSLCA:
      case certUsageEmailSigner:
      case certUsageEmailRecipient:
      case certUsageObjectSigner:
      case certUsageStatusResponder:
	rv = CERT_KeyUsageAndTypeForCertUsage(certUsage, PR_FALSE,
					      &requiredKeyUsage,
					      &requiredCertType);
	if ( rv != SECSuccess ) {
	    PORT_Assert(0);
	    EXIT_IF_NOT_LOGGING(log);
	    requiredKeyUsage = 0;
	    requiredCertType = 0;
	}
	break;
      case certUsageVerifyCA:
	requiredKeyUsage = KU_KEY_CERT_SIGN;
	requiredCertType = NS_CERT_TYPE_CA;
	if ( ! ( certType & NS_CERT_TYPE_CA ) ) {
	    certType |= NS_CERT_TYPE_CA;
	}
	break;
      default:
	PORT_Assert(0);
	EXIT_IF_NOT_LOGGING(log);
	requiredKeyUsage = 0;
	requiredCertType = 0;
    }
    if ( CERT_CheckKeyUsage(cert, requiredKeyUsage) != SECSuccess ) {
	PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE);
	LOG_ERROR_OR_EXIT(log,cert,0,requiredKeyUsage);
    }
    if ( !( certType & requiredCertType ) ) {
	PORT_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE);
	LOG_ERROR_OR_EXIT(log,cert,0,requiredCertType);
    }

    /* check trust flags to see if this cert is directly trusted */
    if ( cert->trust ) { /* the cert is in the DB */
	switch ( certUsage ) {
	  case certUsageSSLClient:
	  case certUsageSSLServer:
	    flags = cert->trust->sslFlags;
	    
	    /* is the cert directly trusted or not trusted ? */
	    if ( flags & CERTDB_VALID_PEER ) {/*the trust record is valid*/
		if ( flags & CERTDB_TRUSTED ) {	/* trust this cert */
		    goto winner;
		} else { /* don't trust this cert */
		    PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
		    LOG_ERROR_OR_EXIT(log,cert,0,flags);
		}
	    }
	    break;
	  case certUsageSSLServerWithStepUp:
	    /* XXX - step up certs can't be directly trusted */
	    break;
	  case certUsageSSLCA:
	    break;
	  case certUsageEmailSigner:
	  case certUsageEmailRecipient:
	    flags = cert->trust->emailFlags;
	    
	    /* is the cert directly trusted or not trusted ? */
	    if ( ( flags & ( CERTDB_VALID_PEER | CERTDB_TRUSTED ) ) ==
		( CERTDB_VALID_PEER | CERTDB_TRUSTED ) ) {
		goto winner;
	    }
	    break;
	  case certUsageObjectSigner:
	    flags = cert->trust->objectSigningFlags;

	    /* is the cert directly trusted or not trusted ? */
	    if ( flags & CERTDB_VALID_PEER ) {/*the trust record is valid*/
		if ( flags & CERTDB_TRUSTED ) {	/* trust this cert */
		    goto winner;
		} else { /* don't trust this cert */
		    PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
		    LOG_ERROR_OR_EXIT(log,cert,0,flags);
		}
	    }
	    break;
	  case certUsageVerifyCA:
	  case certUsageStatusResponder:
	    flags = cert->trust->sslFlags;
	    /* is the cert directly trusted or not trusted ? */
	    if ( ( flags & ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) ==
		( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) {
		goto winner;
	    }
	    flags = cert->trust->emailFlags;
	    /* is the cert directly trusted or not trusted ? */
	    if ( ( flags & ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) ==
		( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) {
		goto winner;
	    }
	    flags = cert->trust->objectSigningFlags;
	    /* is the cert directly trusted or not trusted ? */
	    if ( ( flags & ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) ==
		( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) {
		goto winner;
	    }
	    break;
	  case certUsageAnyCA:
	  case certUsageProtectedObjectSigner:
	  case certUsageUserCertImport:
	    /* XXX to make the compiler happy.  Should these be
	     * explicitly handled?
	     */
	    break;
	}
    }

    rv = CERT_VerifyCertChain(handle, cert, checkSig, certUsage,
			      t, wincx, log);
    if (rv != SECSuccess) {
	EXIT_IF_NOT_LOGGING(log);
    }

    /*
     * Check revocation status, but only if the cert we are checking
     * is not a status reponder itself.  We only do this in the case
     * where we checked the cert chain (above); explicit trust "wins"
     * (avoids status checking, just as it avoids CRL checking, which
     * is all done inside VerifyCertChain) by bypassing this code.
     */
    if (certUsage != certUsageStatusResponder && handle->statusConfig != NULL) {
	if (handle->statusConfig->statusChecker != NULL) {
	    rv = (* handle->statusConfig->statusChecker)(handle, cert,
							 t, wincx);
	    if (rv != SECSuccess) {
		LOG_ERROR_OR_EXIT(log,cert,0,0);
	    }
	}
    }

winner:
    return(SECSuccess);

loser:
    rv = SECFailure;
    
    return(rv);
}

/*
 * verify a certificate by checking if its valid and that we
 * trust the issuer.  Verify time against now.
 */
SECStatus
CERT_VerifyCertNow(CERTCertDBHandle *handle, CERTCertificate *cert,
		   PRBool checkSig, SECCertUsage certUsage, void *wincx)
{
    return(CERT_VerifyCert(handle, cert, checkSig, 
		   certUsage, PR_Now(), wincx, NULL));
}

CERTCertificate *
CERT_DupCertificate(CERTCertificate *c)
{
    if (c) {
	CERT_LockCertRefCount(c);
	++c->referenceCount;
	CERT_UnlockCertRefCount(c);
    }
    return c;
}

/*
 * Allow use of default cert database, so that apps(such as mozilla) don't
 * have to pass the handle all over the place.
 */
static CERTCertDBHandle *default_cert_db_handle = 0;

void
CERT_SetDefaultCertDB(CERTCertDBHandle *handle)
{
    default_cert_db_handle = handle;
    
    return;
}

CERTCertDBHandle *
CERT_GetDefaultCertDB(void)
{
    return(default_cert_db_handle);
}

/*
 * Open volatile certificate database and index databases.  This is a
 * fallback if the real databases can't be opened or created.  It is only
 * resident in memory, so it will not be persistent.  We do this so that
 * we don't crash if the databases can't be created.
 */
SECStatus
CERT_OpenVolatileCertDB(CERTCertDBHandle *handle)
{
    /*
     * Open the memory resident perm cert database.
     */
    handle->permCertDB = dbopen( 0, O_RDWR | O_CREAT, 0600, DB_HASH, 0 );
    if ( !handle->permCertDB ) {
	goto loser;
    }

    /*
     * Open the memory resident decoded cert database.
     */
    handle->tempCertDB = dbopen( 0, O_RDWR | O_CREAT, 0600, DB_HASH, 0 );
    if ( !handle->tempCertDB ) {
	goto loser;
    }

    handle->dbMon = PR_NewMonitor();
    PORT_Assert(handle->dbMon != NULL);

    handle->spkDigestInfo = NULL;
    handle->statusConfig = NULL;

    /* initialize the cert database */
    (void) CERT_InitCertDB(handle);

    return (SECSuccess);
    
loser:

    PORT_SetError(SEC_ERROR_BAD_DATABASE);

    if ( handle->permCertDB ) {
	(* handle->permCertDB->close)(handle->permCertDB);
	handle->permCertDB = 0;
    }

    if ( handle->tempCertDB ) {
	(* handle->tempCertDB->close)(handle->tempCertDB);
	handle->tempCertDB = 0;
    }

    return(SECFailure);
}

/* XXX this would probably be okay/better as an xp routine? */
static void
sec_lower_string(char *s)
{
    if ( s == NULL ) {
	return;
    }
    
    while ( *s ) {
	*s = PORT_Tolower(*s);
	s++;
    }
    
    return;
}

/*
** Add a domain name to the list of names that the user has explicitly
** allowed (despite cert name mismatches) for use with a server cert.
*/
SECStatus
CERT_AddOKDomainName(CERTCertificate *cert, const char *hn)
{
    CERTOKDomainName *domainOK;
    int               newNameLen;

    if (!hn || !(newNameLen = strlen(hn))) {
    	PORT_SetError(SEC_ERROR_INVALID_ARGS);
	return SECFailure;
    }
    domainOK = (CERTOKDomainName *)PORT_ArenaZAlloc(cert->arena, 
                                  (sizeof *domainOK) + newNameLen);
    if (!domainOK) 
    	return SECFailure;	/* error code is already set. */

    PORT_Strcpy(domainOK->name, hn);
    sec_lower_string(domainOK->name);

    /* put at head of list. */
    domainOK->next = cert->domainOK;
    cert->domainOK = domainOK;
    return SECSuccess;
}

/* Make sure that the name of the host we are connecting to matches the
 * name that is incoded in the common-name component of the certificate
 * that they are using.
 */
SECStatus
CERT_VerifyCertName(CERTCertificate *cert, const char *hn)
{
    char *    cn;
    char *    domain;
    char *    hndomain;
    char *    hostname;
    int       regvalid;
    int       match;
    SECStatus rv;
    CERTOKDomainName *domainOK;

    if (!hn || !strlen(hn)) {
    	PORT_SetError(SEC_ERROR_INVALID_ARGS);
	return SECFailure;
    }

    hostname = PORT_Strdup(hn);
    if ( hostname == NULL ) {
	return(SECFailure);
    }
    sec_lower_string(hostname);

    /* if the name is one that the user has already approved, it's OK. */
    for (domainOK = cert->domainOK; domainOK; domainOK = domainOK->next) {
	if (0 == PORT_Strcmp(hostname, domainOK->name)) {
	    PORT_Free(hostname);
	    return SECSuccess;
    	}
    }

    /* try the cert extension first, then the common name */
    cn = CERT_FindNSStringExtension(cert, SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME);
    if ( cn == NULL ) {
	cn = CERT_GetCommonName(&cert->subject);
    }
    
    sec_lower_string(cn);

    if ( cn ) {
	if ( ( hndomain = PORT_Strchr(hostname, '.') ) == NULL ) {
	    /* No domain in server name */
	    if ( ( domain = PORT_Strchr(cn, '.') ) != NULL ) {
		/* there is a domain in the cn string, so chop it off */
		*domain = '\0';
	    }
	}

	regvalid = PORT_RegExpValid(cn);
	
	if ( regvalid == NON_SXP ) {
	    /* compare entire hostname with cert name */
	    if ( PORT_Strcmp(hostname, cn) == 0 ) {
		rv = SECSuccess;
		goto done;
	    }
	    
	    if ( hndomain ) {
		/* compare just domain name with cert name */
		if ( PORT_Strcmp(hndomain+1, cn) == 0 ) {
		    rv = SECSuccess;
		    goto done;
		}
	    }

	    PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
	    rv = SECFailure;
	    goto done;
	    
	} else {
	    /* try to match the shexp */
	    match = PORT_RegExpCaseSearch(hostname, cn);

	    if ( match == 0 ) {
		rv = SECSuccess;
	    } else {
		PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
		rv = SECFailure;
	    }
	    goto done;
	}
    }

    PORT_SetError(SEC_ERROR_NO_MEMORY);
    rv = SECFailure;

done:
    /* free the common name */
    if ( cn ) {
	PORT_Free(cn);
    }
    
    if ( hostname ) {
	PORT_Free(hostname);
    }
    
    return(rv);
}

PRBool
CERT_CompareCerts(CERTCertificate *c1, CERTCertificate *c2)
{
    SECComparison comp;
    
    comp = SECITEM_CompareItem(&c1->derCert, &c2->derCert);
    if ( comp == SECEqual ) { /* certs are the same */
	return(PR_TRUE);
    } else {
	return(PR_FALSE);
    }
}

static SECStatus
StringsEqual(char *s1, char *s2) {
    if ( ( s1 == NULL ) || ( s2 == NULL ) ) {
	if ( s1 != s2 ) { /* only one is null */
	    return(SECFailure);
	}
	return(SECSuccess); /* both are null */
    }
	
    if ( PORT_Strcmp( s1, s2 ) != 0 ) {
	return(SECFailure); /* not equal */
    }

    return(SECSuccess); /* strings are equal */
}


PRBool
CERT_CompareCertsForRedirection(CERTCertificate *c1, CERTCertificate *c2)
{
    SECComparison comp;
    char *c1str, *c2str;
    SECStatus eq;
    
    comp = SECITEM_CompareItem(&c1->derCert, &c2->derCert);
    if ( comp == SECEqual ) { /* certs are the same */
	return(PR_TRUE);
    }
	
    /* check if they are issued by the same CA */
    comp = SECITEM_CompareItem(&c1->derIssuer, &c2->derIssuer);
    if ( comp != SECEqual ) { /* different issuer */
	return(PR_FALSE);
    }

    /* check country name */
    c1str = CERT_GetCountryName(&c1->subject);
    c2str = CERT_GetCountryName(&c2->subject);
    eq = StringsEqual(c1str, c2str);
    PORT_Free(c1str);
    PORT_Free(c2str);
    if ( eq != SECSuccess ) {
	return(PR_FALSE);
    }

    /* check locality name */
    c1str = CERT_GetLocalityName(&c1->subject);
    c2str = CERT_GetLocalityName(&c2->subject);
    eq = StringsEqual(c1str, c2str);
    PORT_Free(c1str);
    PORT_Free(c2str);
    if ( eq != SECSuccess ) {
	return(PR_FALSE);
    }
	
    /* check state name */
    c1str = CERT_GetStateName(&c1->subject);
    c2str = CERT_GetStateName(&c2->subject);
    eq = StringsEqual(c1str, c2str);
    PORT_Free(c1str);
    PORT_Free(c2str);
    if ( eq != SECSuccess ) {
	return(PR_FALSE);
    }

    /* check org name */
    c1str = CERT_GetOrgName(&c1->subject);
    c2str = CERT_GetOrgName(&c2->subject);
    eq = StringsEqual(c1str, c2str);
    PORT_Free(c1str);
    PORT_Free(c2str);
    if ( eq != SECSuccess ) {
	return(PR_FALSE);
    }

#ifdef NOTDEF	
    /* check orgUnit name */
    /*
     * We need to revisit this and decide which fields should be allowed to be
     * different
     */
    c1str = CERT_GetOrgUnitName(&c1->subject);
    c2str = CERT_GetOrgUnitName(&c2->subject);
    eq = StringsEqual(c1str, c2str);
    PORT_Free(c1str);
    PORT_Free(c2str);
    if ( eq != SECSuccess ) {
	return(PR_FALSE);
    }
#endif

    return(PR_TRUE); /* all fields but common name are the same */
}


/*
** 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 ) {
	chain->len = len;
    } else {
	chain->len = len - 1;
    }
    
    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;
}

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


CERTIssuerAndSN *
CERT_GetCertIssuerAndSN(PRArenaPool *arena, CERTCertificate *cert)
{
    CERTIssuerAndSN *result;
    SECStatus rv;

    if ( arena == NULL ) {
	arena = cert->arena;
    }
    
    result = (CERTIssuerAndSN*)PORT_ArenaZAlloc(arena, sizeof(*result));
    if (result == NULL) {
	PORT_SetError (SEC_ERROR_NO_MEMORY);
	return NULL;
    }

    rv = SECITEM_CopyItem(arena, &result->derIssuer, &cert->derIssuer);
    if (rv != SECSuccess)
	return NULL;

    rv = CERT_CopyName(arena, &result->issuer, &cert->issuer);
    if (rv != SECSuccess)
	return NULL;

    rv = SECITEM_CopyItem(arena, &result->serialNumber, &cert->serialNumber);
    if (rv != SECSuccess)
	return NULL;

    return result;
}

char *
CERT_MakeCANickname(CERTCertificate *cert)
{
    char *firstname = NULL;
    char *org = NULL;
    char *nickname = NULL;
    int count;
    CERTCertificate *dummycert;
    CERTCertDBHandle *handle;
    
    handle = cert->dbhandle;
    
    nickname = CERT_GetNickName(cert, handle, cert->arena);
    if (nickname == NULL) {
	firstname = CERT_GetCommonName(&cert->subject);
	if ( firstname == NULL ) {
	    firstname = CERT_GetOrgUnitName(&cert->subject);
	}

	org = CERT_GetOrgName(&cert->issuer);
	if ( org == NULL ) {
	    goto loser;
	}
    
	count = 1;
	while ( 1 ) {

	    if ( firstname ) {
		if ( count == 1 ) {
		    nickname = PR_smprintf("%s - %s", firstname, org);
		} else {
		    nickname = PR_smprintf("%s - %s #%d", firstname, org, count);
		}
	    } else {
		if ( count == 1 ) {
		    nickname = PR_smprintf("%s", org);
		} else {
		    nickname = PR_smprintf("%s #%d", org, count);
		}
	    }
	    if ( nickname == NULL ) {
		goto loser;
	    }

	    /* look up the nickname to make sure it isn't in use already */
	    dummycert = CERT_FindCertByNickname(handle, nickname);

	    if ( dummycert == NULL ) {
		goto done;
	    }
	
	    /* found a cert, destroy it and loop */
	    CERT_DestroyCertificate(dummycert);

	    /* free the nickname */
	    PORT_Free(nickname);

	    count++;
	}
    }
loser:
    if ( nickname ) {
	PORT_Free(nickname);
    }

    nickname = "";
    
done:
    if ( firstname ) {
	PORT_Free(firstname);
    }
    if ( org ) {
	PORT_Free(org);
    }
    
    return(nickname);
}

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);
}

void
CERT_DestroyCrl (CERTSignedCrl *crl)
{
    SEC_DestroyCrl (crl);
}



/*
 * Does a cert belong to a CA?  We decide based on perm database trust
 * flags, Netscape Cert Type Extension, and KeyUsage Extension.
 */
PRBool
CERT_IsCACert(CERTCertificate *cert, unsigned int *rettype)
{
    CERTCertTrust *trust;
    SECStatus rv;
    unsigned int type;
    PRBool ret;

    ret = PR_FALSE;
    type = 0;
    
    if ( cert->isperm ) {
	trust = cert->trust;
	if ( ( trust->sslFlags & CERTDB_VALID_CA ) == CERTDB_VALID_CA ) {
	    ret = PR_TRUE;
	    type |= NS_CERT_TYPE_SSL_CA;
	}
	
	if ( ( trust->emailFlags & CERTDB_VALID_CA ) == CERTDB_VALID_CA ) {
	    ret = PR_TRUE;
	    type |= NS_CERT_TYPE_EMAIL_CA;
	}
	
	if ( ( trust->objectSigningFlags & CERTDB_VALID_CA ) ==
	    CERTDB_VALID_CA ) {
	    ret = PR_TRUE;
	    type |= NS_CERT_TYPE_OBJECT_SIGNING_CA;
	}
    } else {
	if ( cert->nsCertType &
	    ( NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA |
	     NS_CERT_TYPE_OBJECT_SIGNING_CA ) ) {
	    ret = PR_TRUE;
	    type = (cert->nsCertType & NS_CERT_TYPE_CA);
	} else {
	    CERTBasicConstraints constraints;
	    rv = CERT_FindBasicConstraintExten(cert, &constraints);
	    if ( rv == SECSuccess ) {
		if ( constraints.isCA ) {
		    ret = PR_TRUE;
		    type = (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA);
		}
	    } 
	} 

	/* finally check if it's a FORTEZZA V1 CA */
	if (ret == PR_FALSE) {
	    if (fortezzaIsCA(cert)) {
		ret = PR_TRUE;
		type = (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA);
	    }
	}
    }
    if ( rettype != NULL ) {
	*rettype = type;
    }
    
    return(ret);
}

/*
 * is certa newer than certb?  If one is expired, pick the other one.
 */
PRBool
CERT_IsNewer(CERTCertificate *certa, CERTCertificate *certb)
{
    int64 notBeforeA, notAfterA, notBeforeB, notAfterB, now;
    SECStatus rv;
    PRBool newerbefore, newerafter;
    
    rv = CERT_GetCertTimes(certa, &notBeforeA, &notAfterA);
    if ( rv != SECSuccess ) {
	return(PR_FALSE);
    }
    
    rv = CERT_GetCertTimes(certb, &notBeforeB, &notAfterB);
    if ( rv != SECSuccess ) {
	return(PR_TRUE);
    }

    newerbefore = PR_FALSE;
    if ( LL_CMP(notBeforeA, >, notBeforeB) ) {
	newerbefore = PR_TRUE;
    }

    newerafter = PR_FALSE;
    if ( LL_CMP(notAfterA, >, notAfterB) ) {
	newerafter = PR_TRUE;
    }
    
    if ( newerbefore && newerafter ) {
	return(PR_TRUE);
    }
    
    if ( ( !newerbefore ) && ( !newerafter ) ) {
	return(PR_FALSE);
    }

    /* get current UTC time */
    now = PR_Now();

    if ( newerbefore ) {
	/* cert A was issued after cert B, but expires sooner */
	/* if A is expired, then pick B */
	if ( LL_CMP(notAfterA, <, now ) ) {
	    return(PR_FALSE);
	}
	return(PR_TRUE);
    } else {
	/* cert B was issued after cert A, but expires sooner */
	/* if B is expired, then pick A */
	if ( LL_CMP(notAfterB, <, now ) ) {
	    return(PR_TRUE);
	}
	return(PR_FALSE);
    }
}

void
CERT_DestroyCertArray(CERTCertificate **certs, unsigned int ncerts)
{
    unsigned int i;
    
    if ( certs ) {
	for ( i = 0; i < ncerts; i++ ) {
	    if ( certs[i] ) {
		CERT_DestroyCertificate(certs[i]);
	    }
	}

	PORT_Free(certs);
    }
    
    return;
}

char *
CERT_FixupEmailAddr(char *emailAddr)
{
    char *retaddr;
    char *str;

    if ( emailAddr == NULL ) {
	return(NULL);
    }
    
    /* copy the string */
    str = retaddr = PORT_Strdup(emailAddr);
    if ( str == NULL ) {
	return(NULL);
    }
    
    /* make it lower case */
    while ( *str ) {
	*str = tolower( *str );
	str++;
    }
    
    return(retaddr);
}

/*
 * NOTE - don't allow encode of govt-approved or invisible bits
 */
SECStatus
CERT_DecodeTrustString(CERTCertTrust *trust, char *trusts)
{
    int i;
    unsigned int *pflags;
    
    trust->sslFlags = 0;
    trust->emailFlags = 0;
    trust->objectSigningFlags = 0;

    pflags = &trust->sslFlags;
    
    for (i=0; i < PORT_Strlen(trusts); i++) {
	switch (trusts[i]) {
	  case 'p':
	      *pflags = *pflags | CERTDB_VALID_PEER;
	      break;

	  case 'P':
	      *pflags = *pflags | CERTDB_TRUSTED | CERTDB_VALID_PEER;
	      break;

	  case 'w':
	      *pflags = *pflags | CERTDB_SEND_WARN;
	      break;

	  case 'c':
	      *pflags = *pflags | CERTDB_VALID_CA;
	      break;

	  case 'T':
	      *pflags = *pflags | CERTDB_TRUSTED_CLIENT_CA | CERTDB_VALID_CA;
	      break;

	  case 'C' :
	      *pflags = *pflags | CERTDB_TRUSTED_CA | CERTDB_VALID_CA;
	      break;

	  case 'u':
	      *pflags = *pflags | CERTDB_USER;
	      break;

#ifdef DEBUG_NSSTEAM_ONLY
	  case 'i':
	      *pflags = *pflags | CERTDB_INVISIBLE_CA;
	      break;
	  case 'g':
	      *pflags = *pflags | CERTDB_GOVT_APPROVED_CA;
	      break;
#endif /* DEBUG_NSSTEAM_ONLY */

	  case ',':
	      if ( pflags == &trust->sslFlags ) {
		  pflags = &trust->emailFlags;
	      } else {
		  pflags = &trust->objectSigningFlags;
	      }
	      break;
	  default:
	      return SECFailure;
	}
    }

    return SECSuccess;
}

static void
EncodeFlags(char *trusts, unsigned int flags)
{
    if (flags & CERTDB_VALID_CA)
	if (!(flags & CERTDB_TRUSTED_CA) &&
	    !(flags & CERTDB_TRUSTED_CLIENT_CA))
	    PORT_Strcat(trusts, "c");
    if (flags & CERTDB_VALID_PEER)
	if (!(flags & CERTDB_TRUSTED))
	    PORT_Strcat(trusts, "p");
    if (flags & CERTDB_TRUSTED_CA)
	PORT_Strcat(trusts, "C");
    if (flags & CERTDB_TRUSTED_CLIENT_CA)
	PORT_Strcat(trusts, "T");
    if (flags & CERTDB_TRUSTED)
	PORT_Strcat(trusts, "P");
    if (flags & CERTDB_USER)
	PORT_Strcat(trusts, "u");
    if (flags & CERTDB_SEND_WARN)
	PORT_Strcat(trusts, "w");
    if (flags & CERTDB_INVISIBLE_CA)
	PORT_Strcat(trusts, "I");
    if (flags & CERTDB_GOVT_APPROVED_CA)
	PORT_Strcat(trusts, "G");
    return;
}

char *
CERT_EncodeTrustString(CERTCertTrust *trust)
{
    char tmpTrustSSL[32];
    char tmpTrustEmail[32];
    char tmpTrustSigning[32];
    char *retstr = NULL;

    if ( trust ) {
	tmpTrustSSL[0] = '\0';
	tmpTrustEmail[0] = '\0';
	tmpTrustSigning[0] = '\0';
    
	EncodeFlags(tmpTrustSSL, trust->sslFlags);
	EncodeFlags(tmpTrustEmail, trust->emailFlags);
	EncodeFlags(tmpTrustSigning, trust->objectSigningFlags);
    
	retstr = PR_smprintf("%s,%s,%s", tmpTrustSSL, tmpTrustEmail,
			     tmpTrustSigning);
    }
    
    return(retstr);
}

SECStatus
CERT_ImportCerts(CERTCertDBHandle *certdb, SECCertUsage usage,
		 unsigned int ncerts, SECItem **derCerts,
		 CERTCertificate ***retCerts, PRBool keepCerts,
		 PRBool caOnly, char *nickname)
{
    int i;
    CERTCertificate **certs = NULL;
    SECStatus rv;
    int fcerts;

    if ( ncerts ) {
	certs = (CERTCertificate**)PORT_ZAlloc(sizeof(CERTCertificate *) * ncerts );
	if ( certs == NULL ) {
	    return(SECFailure);
	}
    
	/* decode all of the certs into the temporary DB */
	for ( i = 0, fcerts= 0; i < ncerts; i++) {
	    certs[fcerts] = CERT_NewTempCertificate(certdb, derCerts[i], NULL,
					       PR_FALSE, PR_TRUE);
	    if (certs[fcerts]) fcerts++;
	}

	if ( keepCerts ) {
	    for ( i = 0; i < fcerts; i++ ) {
		SECKEY_UpdateCertPQG(certs[i]);
		if(CERT_IsCACert(certs[i], NULL) && (fcerts > 1)) {
		    /* if we are importing only a single cert and specifying
		     * a nickname, we want to use that nickname if it a CA,
		     * otherwise if there are more than one cert, we don't
		     * know which cert it belongs to.
		     */
		    rv = CERT_SaveImportedCert(certs[i], usage, caOnly, NULL);
		} else {
		    rv = CERT_SaveImportedCert(certs[i], usage, caOnly, 
                			       nickname);
		}
		/* don't care if it fails - keep going */
	    }
	}
    }

    if ( retCerts ) {
	*retCerts = certs;
    } else {
	CERT_DestroyCertArray(certs, fcerts);
    }

    return(SECSuccess);
    
#if 0	/* dead code here - why ?? XXX */
loser:
    if ( retCerts ) {
	*retCerts = NULL;
    }
    if ( certs ) {
	CERT_DestroyCertArray(certs, ncerts);
    }    
    return(SECFailure);
#endif
}

/*
 * a real list of certificates - need to convert CERTCertificateList
 * stuff and ASN 1 encoder/decoder over to using this...
 */
CERTCertList *
CERT_NewCertList(void)
{
    PRArenaPool *arena = NULL;
    CERTCertList *ret = NULL;
    
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if ( arena == NULL ) {
	goto loser;
    }
    
    ret = (CERTCertList *)PORT_ArenaZAlloc(arena, sizeof(CERTCertList));
    if ( ret == NULL ) {
	goto loser;
    }
    
    ret->arena = arena;
    
    PR_INIT_CLIST(&ret->list);
    
    return(ret);

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

void
CERT_DestroyCertList(CERTCertList *certs)
{
    PRCList *node;

    while( !PR_CLIST_IS_EMPTY(&certs->list) ) {
	node = PR_LIST_HEAD(&certs->list);
	CERT_DestroyCertificate(((CERTCertListNode *)node)->cert);
	PR_REMOVE_LINK(node);
    }
    
    PORT_FreeArena(certs->arena, PR_FALSE);
    
    return;
}

void
CERT_RemoveCertListNode(CERTCertListNode *node)
{
    CERT_DestroyCertificate(node->cert);
    PR_REMOVE_LINK(&node->links);
    return;
}

SECStatus
CERT_AddCertToListTail(CERTCertList *certs, CERTCertificate *cert)
{
    CERTCertListNode *node;
    
    node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena,
						sizeof(CERTCertListNode));
    if ( node == NULL ) {
	goto loser;
    }
    
    PR_INSERT_BEFORE(&node->links, &certs->list);
    /* certs->count++; */
    node->cert = cert;
    return(SECSuccess);
    
loser:
    return(SECFailure);
}


/*
 * Sort callback function to determine if cert a is newer than cert b.
 * Not valid certs are considered older than valid certs.
 */
PRBool
CERT_SortCBValidity(CERTCertificate *certa,
		    CERTCertificate *certb,
		    void *arg)
{
    int64 sorttime;
    int64 notBeforeA, notAfterA, notBeforeB, notAfterB;
    SECStatus rv;
    PRBool newerbefore, newerafter;
    PRBool aNotValid = PR_FALSE, bNotValid = PR_FALSE;

    sorttime = *(int64 *)arg;
    
    rv = CERT_GetCertTimes(certa, &notBeforeA, &notAfterA);
    if ( rv != SECSuccess ) {
	return(PR_FALSE);
    }
    
    rv = CERT_GetCertTimes(certb, &notBeforeB, &notAfterB);
    if ( rv != SECSuccess ) {
	return(PR_TRUE);
    }
    newerbefore = PR_FALSE;
    if ( LL_CMP(notBeforeA, >, notBeforeB) ) {
	newerbefore = PR_TRUE;
    }
    newerafter = PR_FALSE;
    if ( LL_CMP(notAfterA, >, notAfterB) ) {
	newerafter = PR_TRUE;
    }

    /* check if A is valid at sorttime */
    if ( CERT_CheckCertValidTimes(certa, sorttime, PR_FALSE)
	!= secCertTimeValid ) {
	aNotValid = PR_TRUE;
    }

    /* check if B is valid at sorttime */
    if ( CERT_CheckCertValidTimes(certb, sorttime, PR_FALSE)
	!= secCertTimeValid ) {
	bNotValid = PR_TRUE;
    }

    /* a is valid, b is not */
    if ( bNotValid && ( ! aNotValid ) ) {
	return(PR_TRUE);
    }

    /* b is valid, a is not */
    if ( aNotValid && ( ! bNotValid ) ) {
	return(PR_FALSE);
    }
    
    /* a and b are either valid or not valid */
    if ( newerbefore && newerafter ) {
	return(PR_TRUE);
    }
    
    if ( ( !newerbefore ) && ( !newerafter ) ) {
	return(PR_FALSE);
    }

    if ( newerbefore ) {
	/* cert A was issued after cert B, but expires sooner */
	return(PR_TRUE);
    } else {
	/* cert B was issued after cert A, but expires sooner */
	return(PR_FALSE);
    }
}

SECStatus
CERT_AddCertToListSorted(CERTCertList *certs,
			 CERTCertificate *cert,
			 CERTSortCallback f,
			 void *arg)
{
    CERTCertListNode *node;
    CERTCertListNode *head;
    PRBool ret;
    
    node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena,
						sizeof(CERTCertListNode));
    if ( node == NULL ) {
	goto loser;
    }
    
    head = CERT_LIST_HEAD(certs);
    
    while ( !CERT_LIST_END(head, certs) ) {

	/* if cert is already in the list, then don't add it again */
	if ( cert == head->cert ) {
	    /*XXX*/
	    /* don't keep a reference */
	    CERT_DestroyCertificate(cert);
	    goto done;
	}
	
	ret = (* f)(cert, head->cert, arg);
	/* if sort function succeeds, then insert before current node */
	if ( ret ) {
	    PR_INSERT_BEFORE(&node->links, &head->links);
	    goto done;
	}

	head = CERT_LIST_NEXT(head);
    }
    /* if we get to the end, then just insert it at the tail */
    PR_INSERT_BEFORE(&node->links, &certs->list);

done:    
    /* certs->count++; */
    node->cert = cert;
    return(SECSuccess);
    
loser:
    return(SECFailure);
}


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);
}

/*
 * remove certs from a list that don't have keyUsage and certType
 * that match the given usage.
 */
SECStatus
CERT_FilterCertListByUsage(CERTCertList *certList, SECCertUsage usage,
			   PRBool ca)
{
    unsigned int requiredKeyUsage;
    unsigned int requiredCertType;
    CERTCertListNode *node, *savenode;
    PRBool bad;
    SECStatus rv;
    unsigned int certType;
    PRBool dummyret;
    
    if (certList == NULL) goto loser;

    rv = CERT_KeyUsageAndTypeForCertUsage(usage, ca, &requiredKeyUsage,
					  &requiredCertType);
    if ( rv != SECSuccess ) {
	goto loser;
    }

    node = CERT_LIST_HEAD(certList);
	
    while ( !CERT_LIST_END(node, certList) ) {

	bad = PR_FALSE;

	/* bad key usage */
	if ( CERT_CheckKeyUsage(node->cert, requiredKeyUsage )
	    != SECSuccess ) {
	    bad = PR_TRUE;
	}
	/* bad cert type */
	if ( ca ) {
	    /* This function returns a more comprehensive cert type that
	     * takes trust flags into consideration.  Should probably
	     * fix the cert decoding code to do this.
	     */
	    dummyret = CERT_IsCACert(node->cert, &certType);
	} else {
	    certType = node->cert->nsCertType;
	}
	
	if ( ! ( certType & requiredCertType ) ) {
	    bad = PR_TRUE;
	}

	if ( bad ) {
	    /* remove the node if it is bad */
	    savenode = CERT_LIST_NEXT(node);
	    CERT_RemoveCertListNode(node);
	    node = savenode;
	} else {
	    node = CERT_LIST_NEXT(node);
	}
    }
    return(SECSuccess);
    
loser:
    return(SECFailure);
}


/*
 * 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
     */
    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);
}

/*
 * Filter a list of certificates, removing those certs that do not have
 * one of the named CA certs somewhere in their cert chain.
 *
 *	"certList" - the list of certificates to filter
 *	"nCANames" - number of CA names
 *	"caNames" - array of CA names in string(rfc 1485) form
 *	"usage" - what use the certs are for, this is used when
 *		selecting CA certs
 */
SECStatus
CERT_FilterCertListByCANames(CERTCertList *certList, int nCANames,
			     char **caNames, SECCertUsage usage)
{
    CERTCertificate *issuerCert = NULL;
    CERTCertificate *subjectCert;
    CERTCertListNode *node, *freenode;
    CERTCertificate *cert;
    int n;
    char **names;
    PRBool found;
    int64 time;
    
    if ( nCANames <= 0 ) {
	return(SECSuccess);
    }

    time = PR_Now();
    
    node = CERT_LIST_HEAD(certList);
    
    while ( ! CERT_LIST_END(node, certList) ) {
	cert = node->cert;
	
	subjectCert = CERT_DupCertificate(cert);

	/* traverse the CA certs for this cert */
	found = PR_FALSE;
	while ( subjectCert != NULL ) {
	    n = nCANames;
	    names = caNames;
	    
	    while ( n > 0 ) {
		if ( PORT_Strcmp(*names, subjectCert->issuerName) == 0 ) {
		    found = PR_TRUE;
		    break;
		}

		n--;
		names++;
	    }

	    if ( found ) {
		break;
	    }
	    
	    issuerCert = CERT_FindCertIssuer(subjectCert, time, usage);
	    if ( issuerCert == subjectCert ) {
		CERT_DestroyCertificate(issuerCert);
		issuerCert = NULL;
		break;
	    }
	    CERT_DestroyCertificate(subjectCert);
	    subjectCert = issuerCert;

	}
	CERT_DestroyCertificate(subjectCert);
	if ( !found ) {
	    /* CA was not found, so remove this cert from the list */
	    freenode = node;
	    node = CERT_LIST_NEXT(node);
	    CERT_RemoveCertListNode(freenode);
	} else {
	    /* CA was found, so leave it in the list */
	    node = CERT_LIST_NEXT(node);
	}
    }
    
    return(SECSuccess);
}

/*
 * Given a certificate, return a string containing the nickname, and possibly
 * one of the validity strings, based on the current validity state of the
 * certificate.
 *
 * "arena" - arena to allocate returned string from.  If NULL, then heap
 *	is used.
 * "cert" - the cert to get nickname from
 * "expiredString" - the string to append to the nickname if the cert is
 *		expired.
 * "notYetGoodString" - the string to append to the nickname if the cert is
 *		not yet good.
 */
char *
CERT_GetCertNicknameWithValidity(PRArenaPool *arena, CERTCertificate *cert,
				 char *expiredString, char *notYetGoodString)
{
    SECCertTimeValidity validity;
    char *nickname, *tmpstr;
    
    validity = CERT_CheckCertValidTimes(cert, PR_Now(), PR_FALSE);

    /* if the cert is good, then just use the nickname directly */
    if ( validity == secCertTimeValid ) {
	if ( arena == NULL ) {
	    nickname = PORT_Strdup(cert->nickname);
	} else {
	    nickname = PORT_ArenaStrdup(arena, cert->nickname);
	}
	
	if ( nickname == NULL ) {
	    goto loser;
	}
    } else {
	    
	/* if the cert is not valid, then tack one of the strings on the
	 * end
	 */
	if ( validity == secCertTimeExpired ) {
	    tmpstr = PR_smprintf("%s%s", cert->nickname,
				 expiredString);
	} else {
	    /* not yet valid */
	    tmpstr = PR_smprintf("%s%s", cert->nickname,
				 notYetGoodString);
	}
	if ( tmpstr == NULL ) {
	    goto loser;
	}

	if ( arena ) {
	    /* copy the string into the arena and free the malloc'd one */
	    nickname = PORT_ArenaStrdup(arena, tmpstr);
	    PORT_Free(tmpstr);
	} else {
	    nickname = tmpstr;
	}
	if ( nickname == NULL ) {
	    goto loser;
	}
    }    
    return(nickname);

loser:
    return(NULL);
}

/*
 * Collect the nicknames from all certs in a CertList.  If the cert is not
 * valid, append a string to that nickname.
 *
 * "certList" - the list of certificates
 * "expiredString" - the string to append to the nickname of any expired cert
 * "notYetGoodString" - the string to append to the nickname of any cert
 *		that is not yet valid
 */
CERTCertNicknames *
CERT_NicknameStringsFromCertList(CERTCertList *certList, char *expiredString,
				 char *notYetGoodString)
{
    CERTCertNicknames *names;
    PRArenaPool *arena;
    CERTCertListNode *node;
    char **nn;
    
    /* allocate an arena */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if ( arena == NULL ) {
	return(NULL);
    }
    
    /* allocate the structure */
    names = PORT_ArenaAlloc(arena, sizeof(CERTCertNicknames));
    if ( names == NULL ) {
	goto loser;
    }

    /* init the structure */
    names->arena = arena;
    names->head = NULL;
    names->numnicknames = 0;
    names->nicknames = NULL;
    names->totallen = 0;

    /* count the certs in the list */
    node = CERT_LIST_HEAD(certList);
    while ( ! CERT_LIST_END(node, certList) ) {
	names->numnicknames++;
	node = CERT_LIST_NEXT(node);
    }
    
    /* allocate nicknames array */
    names->nicknames = PORT_ArenaAlloc(arena,
				       sizeof(char *) * names->numnicknames);
    if ( names->nicknames == NULL ) {
	goto loser;
    }

    /* just in case printf can't deal with null strings */
    if (expiredString == NULL ) {
	expiredString = "";
    }

    if ( notYetGoodString == NULL ) {
	notYetGoodString = "";
    }
    
    /* traverse the list of certs and collect the nicknames */
    nn = names->nicknames;
    node = CERT_LIST_HEAD(certList);
    while ( ! CERT_LIST_END(node, certList) ) {
	*nn = CERT_GetCertNicknameWithValidity(arena, node->cert,
					       expiredString,
					       notYetGoodString);
	if ( *nn == NULL ) {
	    goto loser;
	}

	names->totallen += PORT_Strlen(*nn);
	
	nn++;
	node = CERT_LIST_NEXT(node);
    }

    return(names);

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

/*
 * Extract the nickname from a nickmake string that may have either
 * expiredString or notYetGoodString appended.
 *
 * Args:
 *	"namestring" - the string containing the nickname, and possibly
 *		one of the validity label strings
 *	"expiredString" - the expired validity label string
 *	"notYetGoodString" - the not yet good validity label string
 *
 * Returns the raw nickname
 */
char *
CERT_ExtractNicknameString(char *namestring, char *expiredString,
			   char *notYetGoodString)
{
    int explen, nyglen, namelen;
    int retlen;
    char *retstr;
    
    namelen = PORT_Strlen(namestring);
    explen = PORT_Strlen(expiredString);
    nyglen = PORT_Strlen(notYetGoodString);
    
    if ( namelen > explen ) {
	if ( PORT_Strcmp(expiredString, &namestring[namelen-explen]) == 0 ) {
	    retlen = namelen - explen;
	    retstr = (char *)PORT_Alloc(retlen+1);
	    if ( retstr == NULL ) {
		goto loser;
	    }
	    
	    PORT_Memcpy(retstr, namestring, retlen);
	    retstr[retlen] = '\0';
	    goto done;
	}
    }

    if ( namelen > nyglen ) {
	if ( PORT_Strcmp(notYetGoodString, &namestring[namelen-nyglen]) == 0) {
	    retlen = namelen - nyglen;
	    retstr = (char *)PORT_Alloc(retlen+1);
	    if ( retstr == NULL ) {
		goto loser;
	    }
	    
	    PORT_Memcpy(retstr, namestring, retlen);
	    retstr[retlen] = '\0';
	    goto done;
	}
    }

    /* if name string is shorter than either invalid string, then it must
     * be a raw nickname
     */
    retstr = PORT_Strdup(namestring);
    
done:
    return(retstr);

loser:
    return(NULL);
}

CERTCertList *
CERT_GetCertChainFromCert(CERTCertificate *cert, int64 time, SECCertUsage usage)
{
    CERTCertList *chain;

    if (cert != NULL) {
	chain = CERT_NewCertList();
	cert = CERT_DupCertificate(cert);
	while (SECITEM_CompareItem(&cert->derIssuer, &cert->derSubject) 
	       != SECEqual) {
	    CERT_AddCertToListTail(chain, cert);
	    cert = CERT_FindCertIssuer(cert, time, usage);
	}
	CERT_AddCertToListTail(chain, cert);
	return chain;
    }
    return NULL;
}


/*
 * Acquire the global lock on the cert database.
 * This lock is currently used for the following operations:
 *	adding or deleting a cert to either the temp or perm databases
 *	converting a temp to perm or perm to temp
 *	changing(maybe just adding????) the trust of a cert
 *      chaning the DB status checking Configuration
 */
void
CERT_LockDB(CERTCertDBHandle *handle)
{
    PR_EnterMonitor(handle->dbMon);
    return;
}

/*
 * Free the global cert database lock.
 */
void
CERT_UnlockDB(CERTCertDBHandle *handle)
{
    PRStatus prstat;
    
    prstat = PR_ExitMonitor(handle->dbMon);
    
    PORT_Assert(prstat == PR_SUCCESS);
    
    return;
}

static PRLock *certRefCountLock = NULL;

/*
 * Acquire the cert reference count lock
 * There is currently one global lock for all certs, but I'm putting a cert
 * arg here so that it will be easy to make it per-cert in the future if
 * that turns out to be necessary.
 */
void
CERT_LockCertRefCount(CERTCertificate *cert)
{
    if ( certRefCountLock == NULL ) {
	nss_InitLock(&certRefCountLock);
	PORT_Assert(certRefCountLock != NULL);
    }
    
    PR_Lock(certRefCountLock);
    return;
}

/*
 * Free the cert reference count lock
 */
void
CERT_UnlockCertRefCount(CERTCertificate *cert)
{
    PRStatus prstat;

    PORT_Assert(certRefCountLock != NULL);
    
    prstat = PR_Unlock(certRefCountLock);
    
    PORT_Assert(prstat == PR_SUCCESS);

    return;
}

static PRLock *certTrustLock = NULL;

/*
 * Acquire the cert trust lock
 * There is currently one global lock for all certs, but I'm putting a cert
 * arg here so that it will be easy to make it per-cert in the future if
 * that turns out to be necessary.
 */
void
CERT_LockCertTrust(CERTCertificate *cert)
{
    if ( certTrustLock == NULL ) {
	nss_InitLock(&certTrustLock);
	PORT_Assert(certTrustLock != NULL);
    }
    
    PR_Lock(certTrustLock);
    return;
}

/*
 * Free the cert trust lock
 */
void
CERT_UnlockCertTrust(CERTCertificate *cert)
{
    PRStatus prstat;

    PORT_Assert(certTrustLock != NULL);
    
    prstat = PR_Unlock(certTrustLock);
    
    PORT_Assert(prstat == PR_SUCCESS);

    return;
}
