/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * 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 the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *    Aaron Spangler <aaron@spangler.ods.org>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/*
 * Certificate handling code
 *
 * $Id: certdb.c,v 1.72.18.1 2006/02/03 22:30:40 wtchang%redhat.com Exp $
 */

#include "nssilock.h"
#include "prmon.h"
#include "prtime.h"
#include "cert.h"
#include "certi.h"
#include "secder.h"
#include "secoid.h"
#include "secasn1.h"
#include "genname.h"
#include "keyhi.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 "nsslocks.h"
#include "pk11func.h"
#include "xconst.h"   /* for  CERT_DecodeAltNameExtension */

#ifndef NSS_3_4_CODE
#define NSS_3_4_CODE
#endif /* NSS_3_4_CODE */
#include "pki.h"
#include "pki3hack.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_CONTEXT_SPECIFIC | 1,
	  offsetof(CERTCertificate,issuerID),
	  SEC_BitStringTemplate },
    { SEC_ASN1_OPTIONAL |  SEC_ASN1_CONTEXT_SPECIFIC | 2,
	  offsetof(CERTCertificate,subjectID),
	  SEC_BitStringTemplate },
    { 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 subjectName in a DER encoded certificate
 */
const SEC_ASN1Template SEC_CertSerialNumberTemplate[] = {
    { 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_ANY, 0, NULL }, /* serial number */
    { 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 }
};

SEC_ASN1_CHOOSER_IMPLEMENT(CERT_CertificateTemplate)
SEC_ASN1_CHOOSER_IMPLEMENT(SEC_SignedCertificateTemplate)
SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SequenceOfCertExtensionTemplate)

SECStatus
CERT_KeyFromIssuerAndSN(PRArenaPool *arena, SECItem *issuer, SECItem *sn,
			SECItem *key)
{
    key->len = sn->len + issuer->len;

    if ((sn->data == NULL) || (issuer->data == NULL)) {
	goto loser;
    }
    
    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_QuickDERDecodeItem(arena, &sd, CERT_SignedDataTemplate, derCert);
    
    if ( rv ) {
	goto loser;
    }
    
    PORT_Memset(derName, 0, sizeof(SECItem));
    rv = SEC_QuickDERDecodeItem(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_QuickDERDecodeItem(arena, &sd, CERT_SignedDataTemplate, derCert);
    
    if ( rv ) {
	goto loser;
    }
    
    PORT_Memset(derName, 0, sizeof(SECItem));
    rv = SEC_QuickDERDecodeItem(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);
}

SECStatus
CERT_SerialNumberFromDERCert(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_QuickDERDecodeItem(arena, &sd, CERT_SignedDataTemplate, derCert);
    
    if ( rv ) {
	goto loser;
    }
    
    PORT_Memset(derName, 0, sizeof(SECItem));
    rv = SEC_QuickDERDecodeItem(arena, derName, SEC_CertSerialNumberTemplate, &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));
    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
 */
SECStatus
cert_GetCertType(CERTCertificate *cert)
{
    SECStatus rv;
    SECItem tmpitem;
    SECItem encodedExtKeyUsage;
    CERTOidSequence *extKeyUsage = NULL;
    PRBool basicConstraintPresent = PR_FALSE;
    CERTBasicConstraints basicConstraint;
    unsigned int nsCertType = 0;

    if (cert->nsCertType) {
        /* once set, no need to recalculate */
        return SECSuccess;
    }

    tmpitem.data = NULL;
    CERT_FindNSCertTypeExtension(cert, &tmpitem);
    encodedExtKeyUsage.data = NULL;
    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) {
	    nsCertType = 0;
	} else {
	    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 ( ( nsCertType & NS_CERT_TYPE_SSL_CLIENT ) &&
	    cert->emailAddr && cert->emailAddr[0]) {
	    nsCertType |= NS_CERT_TYPE_EMAIL;
	}
	/*
	 * for this release, we will allow SSL intermediate CAs to be
	 * email intermediate CAs too.
	 */
	if ( nsCertType & NS_CERT_TYPE_SSL_CA ) {
	    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)) {
		nsCertType |= NS_CERT_TYPE_EMAIL_CA;
	    } else {
		nsCertType |= NS_CERT_TYPE_EMAIL;
	    }
	}
	if (findOIDinOIDSeqByTagNum(extKeyUsage, 
				    SEC_OID_EXT_KEY_USAGE_SERVER_AUTH) ==
	    SECSuccess){
	    if (basicConstraintPresent == PR_TRUE &&
		(basicConstraint.isCA)) {
		nsCertType |= NS_CERT_TYPE_SSL_CA;
	    } else {
		nsCertType |= NS_CERT_TYPE_SSL_SERVER;
	    }
	}
	/* Treat certs with step-up OID as also having SSL server type. */
	if (findOIDinOIDSeqByTagNum(extKeyUsage, 
				    SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) ==
	    SECSuccess){
	    if (basicConstraintPresent == PR_TRUE &&
		(basicConstraint.isCA)) {
		nsCertType |= NS_CERT_TYPE_SSL_CA;
	    } else {
		nsCertType |= NS_CERT_TYPE_SSL_SERVER;
	    }
	}
	if (findOIDinOIDSeqByTagNum(extKeyUsage,
				    SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH) ==
	    SECSuccess){
	    if (basicConstraintPresent == PR_TRUE &&
		(basicConstraint.isCA)) {
		nsCertType |= NS_CERT_TYPE_SSL_CA;
	    } else {
		nsCertType |= NS_CERT_TYPE_SSL_CLIENT;
	    }
	}
	if (findOIDinOIDSeqByTagNum(extKeyUsage,
				    SEC_OID_EXT_KEY_USAGE_CODE_SIGN) ==
	    SECSuccess) {
	    if (basicConstraintPresent == PR_TRUE &&
		(basicConstraint.isCA)) {
		nsCertType |= NS_CERT_TYPE_OBJECT_SIGNING_CA;
	    } else {
		nsCertType |= NS_CERT_TYPE_OBJECT_SIGNING;
	    }
	}
	if (findOIDinOIDSeqByTagNum(extKeyUsage,
				    SEC_OID_EXT_KEY_USAGE_TIME_STAMP) ==
	    SECSuccess) {
	    nsCertType |= EXT_KEY_USAGE_TIME_STAMP;
	}
	if (findOIDinOIDSeqByTagNum(extKeyUsage,
				    SEC_OID_OCSP_RESPONDER) == 
	    SECSuccess) {
	    nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
	}
    } else {
	/* if no extension, then allow any ssl or email (no ca or object
	 * signing)
	 */
	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)) {
		nsCertType |= NS_CERT_TYPE_SSL_CA;
		nsCertType |= NS_CERT_TYPE_EMAIL_CA;
		nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
	} else if (CERT_IsCACert(cert, NULL) == PR_TRUE) {
		nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
	}

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

    if (encodedExtKeyUsage.data != NULL) {
	PORT_Free(encodedExtKeyUsage.data);
    }
    if (extKeyUsage != NULL) {
	CERT_DestroyOidSequence(extKeyUsage);
    }
    /* Assert that it is safe to cast &cert->nsCertType to "PRInt32 *" */
    PORT_Assert(sizeof(cert->nsCertType) == sizeof(PRInt32));
    PR_AtomicSet((PRInt32 *)&cert->nsCertType, nsCertType);
    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_FindSubjectKeyIDExtension(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 = PK11_HashBuf(SEC_OID_SHA1,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);

}

static PRBool
cert_IsRootCert(CERTCertificate *cert)
{
    SECStatus rv;
    SECItem tmpitem;

    /* cache the authKeyID extension, if present */
    cert->authKeyID = CERT_FindAuthKeyIDExten(cert->arena, cert);

    /* it MUST be self-issued to be a root */
    if (cert->derIssuer.len == 0 ||
        !SECITEM_ItemsAreEqual(&cert->derIssuer, &cert->derSubject))
    {
	return PR_FALSE;
    }

    /* check the authKeyID extension */
    if (cert->authKeyID) {
	/* authority key identifier is present */
	if (cert->authKeyID->keyID.len > 0) {
	    /* the keyIdentifier field is set, look for subjectKeyID */
	    rv = CERT_FindSubjectKeyIDExtension(cert, &tmpitem);
	    if (rv == SECSuccess) {
		PRBool match;
		/* also present, they MUST match for it to be a root */
		match = SECITEM_ItemsAreEqual(&cert->authKeyID->keyID,
		                              &tmpitem);
		PORT_Free(tmpitem.data);
		if (!match) return PR_FALSE; /* else fall through */
	    } else {
		/* the subject key ID is required when AKI is present */
		return PR_FALSE;
	    }
	}
	if (cert->authKeyID->authCertIssuer) {
	    SECItem *caName;
	    caName = (SECItem *)CERT_GetGeneralNameByType(
	                                  cert->authKeyID->authCertIssuer,
	                                  certDirectoryName, PR_TRUE);
	    if (caName) {
		if (!SECITEM_ItemsAreEqual(&cert->derIssuer, caName)) {
		    return PR_FALSE;
		} /* else fall through */
	    } /* else ??? could not get general name as directory name? */
	}
	if (cert->authKeyID->authCertSerialNumber.len > 0) {
	    if (!SECITEM_ItemsAreEqual(&cert->serialNumber,
	                         &cert->authKeyID->authCertSerialNumber)) {
		return PR_FALSE;
	    } /* else fall through */
	}
	/* all of the AKI fields that were present passed the test */
	return PR_TRUE;
    }
    /* else the AKI was not present, so this is a root */
    return PR_TRUE;
}

/*
 * 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_QuickDERDecodeItem(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_KeyFromIssuerAndSN(arena, &cert->derIssuer, &cert->serialNumber,
			&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_GetCertificateEmailAddresses(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 = cert_GetCertType(cert);
    if ( rv != SECSuccess ) {
	goto loser;
    }

    /* determine if this is a root cert */
    cert->isRoot = cert_IsRootCert(cert);

    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_HANDLE;
    cert->dbnickname = NULL;
    
    return(cert);
    
loser:

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

CERTCertificate *
__CERT_DecodeDERCertificate(SECItem *derSignedCert, PRBool copyDER,
			 char *nickname)
{
    return CERT_DecodeDERCertificate(derSignedCert, copyDER, nickname);
}


/*
** 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)		/* seconds per day */
static PRInt32 pendingSlop = PENDING_SLOP;	/* seconds */

PRInt32
CERT_GetSlopTime(void)
{
    return pendingSlop;			/* seconds */
}

SECStatus
CERT_SetSlopTime(PRInt32 slop)		/* seconds */
{
    if (slop < 0)
	return SECFailure;
    pendingSlop = slop;
    return SECSuccess;
}

SECStatus
CERT_GetCertTimes(CERTCertificate *c, PRTime *notBefore, PRTime *notAfter)
{
    SECStatus rv;

    if (!c || !notBefore || !notAfter) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    
    /* convert DER not-before time */
    rv = DER_DecodeTimeChoice(notBefore, &c->validity.notBefore);
    if (rv) {
	return(SECFailure);
    }
    
    /* convert DER not-after time */
    rv = DER_DecodeTimeChoice(notAfter, &c->validity.notAfter);
    if (rv) {
	return(SECFailure);
    }

    return(SECSuccess);
}

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

    if (!c) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return(secCertTimeUndetermined);
    }
    /* 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(llPendingSlop, pendingSlop);
    /* convert to micro seconds */
    LL_UI2L(tmp1, PR_USEC_PER_SEC);
    LL_MUL(llPendingSlop, llPendingSlop, tmp1);
    LL_SUB(notBefore, notBefore, llPendingSlop);
    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, PRTime *notBefore, PRTime *notAfter)
{
    int rv;
    
    /* convert DER not-before time */
    rv = DER_DecodeTimeChoice(notBefore, &date->lastUpdate);
    if (rv) {
	return(SECFailure);
    }
    
    /* convert DER not-after time */
    if (date->nextUpdate.data) {
	rv = DER_DecodeTimeChoice(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, PRTime t) {
    PRTime notBefore, notAfter, llPendingSlop, tmp1;
    SECStatus rv;

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

    LL_I2L(llPendingSlop, pendingSlop);
    /* convert to micro seconds */
    LL_I2L(tmp1, PR_USEC_PER_SEC);
    LL_MUL(llPendingSlop, llPendingSlop, tmp1);
    LL_SUB(notBefore, notBefore, llPendingSlop);
    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) {
    PRTime newNotBefore, newNotAfter;
    PRTime 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));
}
   
/*
 * 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 certUsageVerifyCA:
	  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);
}

/*
 * check the key usage of a cert against a set of required values
 */
SECStatus
CERT_CheckKeyUsage(CERTCertificate *cert, unsigned int requiredUsage)
{
    unsigned int certKeyUsage;

    if (!cert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
	return SECFailure;
    }
    /* choose between key agreement or key encipherment based on key
     * type in cert
     */
    if ( requiredUsage & KU_KEY_AGREEMENT_OR_ENCIPHERMENT ) {
	KeyType keyType = CERT_GetCertKeyType(&cert->subjectPublicKeyInfo);
	/* turn off the special bit */
	requiredUsage &= (~KU_KEY_AGREEMENT_OR_ENCIPHERMENT);

	switch (keyType) {
	case rsaKey:
	    requiredUsage |= KU_KEY_ENCIPHERMENT;
	    break;
	case dsaKey:
	    requiredUsage |= KU_DIGITAL_SIGNATURE;
	    break;
	case fortezzaKey:
	case keaKey:
	case dhKey:
	    requiredUsage |= KU_KEY_AGREEMENT;
	    break;
	case ecKey:
	    /* Accept either signature or agreement. */
	    if (!(cert->keyUsage & (KU_DIGITAL_SIGNATURE | KU_KEY_AGREEMENT)))
		 goto loser;
	    break;
	default:
	    goto loser;
	}
    }

    certKeyUsage = cert->keyUsage;
    if (certKeyUsage & KU_NON_REPUDIATION)
        certKeyUsage |= KU_DIGITAL_SIGNATURE;
    if ( (certKeyUsage & requiredUsage) == requiredUsage ) 
    	return SECSuccess;

loser:
    PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE);
    return SECFailure;
}


CERTCertificate *
CERT_DupCertificate(CERTCertificate *c)
{
    if (c) {
#ifdef NSS_CLASSIC
	CERT_LockCertRefCount(c);
	++c->referenceCount;
	CERT_UnlockCertRefCount(c);
#else
	NSSCertificate *tmp = STAN_GetNSSCertificate(c);
	nssCertificate_AddRef(tmp);
#endif
    }
    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);
}

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

/* returns SECSuccess if hn matches pattern cn,
** returns SECFailure with SSL_ERROR_BAD_CERT_DOMAIN if no match,
** returns SECFailure with some other error code if another error occurs.
**
** may modify cn, so caller must pass a modifiable copy.
*/
static SECStatus
cert_TestHostName(char * cn, const char * hn)
{
    int regvalid = PORT_RegExpValid(cn);
    if (regvalid != NON_SXP) {
	SECStatus rv;
	/* cn is a regular expression, try to match the shexp */
	int match = PORT_RegExpCaseSearch(hn, cn);

	if ( match == 0 ) {
	    rv = SECSuccess;
	} else {
	    PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
	    rv = SECFailure;
	}
	return rv;
    } 
    /* cn is not a regular expression */

    /* compare entire hn with cert name */
    if (PORT_Strcasecmp(hn, cn) == 0) {
	return SECSuccess;
    }
	    
    PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
    return SECFailure;
}


SECStatus
cert_VerifySubjectAltName(CERTCertificate *cert, const char *hn)
{
    PRArenaPool *     arena          = NULL;
    CERTGeneralName * nameList       = NULL;
    CERTGeneralName * current;
    char *            cn;
    int               cnBufLen;
    unsigned int      hnLen;
    int               DNSextCount    = 0;
    int               IPextCount     = 0;
    PRBool            isIPaddr;
    SECStatus         rv             = SECFailure;
    SECItem           subAltName;
    PRNetAddr         netAddr;
    char              cnbuf[128];

    subAltName.data = NULL;
    hnLen    = strlen(hn);
    cn       = cnbuf;
    cnBufLen = sizeof cnbuf;

    rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME, 
				&subAltName);
    if (rv != SECSuccess) {
	goto finish;
    }
    isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr));
    rv = SECFailure;
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (!arena) 
	goto finish;

    nameList = current = CERT_DecodeAltNameExtension(arena, &subAltName);
    if (!current)
    	goto finish;

    do {
	switch (current->type) {
	case certDNSName:
	    if (!isIPaddr) {
		/* DNS name current->name.other.data is not null terminated.
		** so must copy it.  
		*/
		int cnLen = current->name.other.len;
		if (cnLen + 1 > cnBufLen) {
		    cnBufLen = cnLen + 1;
		    cn = (char *)PORT_ArenaAlloc(arena, cnBufLen);
		    if (!cn)
			goto finish;
		}
		PORT_Memcpy(cn, current->name.other.data, cnLen);
		cn[cnLen] = 0;
		rv = cert_TestHostName(cn ,hn);
		if (rv == SECSuccess)
		    goto finish;
	    }
	    DNSextCount++;
	    break;
	case certIPAddress:
	    if (isIPaddr) {
		int match = 0;
		PRIPv6Addr v6Addr;
		if (current->name.other.len == 4 &&         /* IP v4 address */
		    netAddr.inet.family == PR_AF_INET) {
		    match = !memcmp(&netAddr.inet.ip, 
		                    current->name.other.data, 4);
		} else if (current->name.other.len == 16 && /* IP v6 address */
		    netAddr.ipv6.family == PR_AF_INET6) {
		    match = !memcmp(&netAddr.ipv6.ip,
		                     current->name.other.data, 16);
		} else if (current->name.other.len == 16 && /* IP v6 address */
		    netAddr.inet.family == PR_AF_INET) {
		    /* convert netAddr to ipv6, then compare. */
		    /* ipv4 must be in Network Byte Order on input. */
		    PR_ConvertIPv4AddrToIPv6(netAddr.inet.ip, &v6Addr);
		    match = !memcmp(&v6Addr, current->name.other.data, 16);
		} else if (current->name.other.len == 4 &&  /* IP v4 address */
		    netAddr.inet.family == PR_AF_INET6) {
		    /* convert netAddr to ipv6, then compare. */
		    PRUint32 ipv4 = (current->name.other.data[0] << 24) |
		                    (current->name.other.data[1] << 16) |
				    (current->name.other.data[2] <<  8) |
				     current->name.other.data[3];
		    /* ipv4 must be in Network Byte Order on input. */
		    PR_ConvertIPv4AddrToIPv6(PR_htonl(ipv4), &v6Addr);
		    match = !memcmp(&netAddr.ipv6.ip, &v6Addr, 16);
		} 
		if (match) {
		    rv = SECSuccess;
		    goto finish;
		}
	    }
	    IPextCount++;
	    break;
	default:
	    break;
	}
	current = CERT_GetNextGeneralName(current);
    } while (current != nameList);

    if ((!isIPaddr && !DNSextCount) || (isIPaddr && !IPextCount)) {
	/* no relevant value in the extension was found. */
	PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND);
    } else {
	PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
    }
    rv = SECFailure;

finish:

    /* Don't free nameList, it's part of the arena. */
    if (arena) {
	PORT_FreeArena(arena, PR_FALSE);
    }

    if (subAltName.data) {
	SECITEM_FreeItem(&subAltName, PR_FALSE);
    }

    return rv;
}


/* 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;
    SECStatus rv;
    CERTOKDomainName *domainOK;

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

    /* 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_Strcasecmp(hn, domainOK->name)) {
	    return SECSuccess;
    	}
    }

    /* Per RFC 2818, if the SubjectAltName extension is present, it must
    ** be used as the cert's identity.
    */
    rv = cert_VerifySubjectAltName(cert, hn);
    if (rv == SECSuccess || PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND)
    	return rv;

    /* try the cert extension first, then the common name */
    cn = CERT_FindNSStringExtension(cert, SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME);
    if ( !cn ) {
	cn = CERT_GetCommonName(&cert->subject);
    }
    if ( cn ) {
	rv = cert_TestHostName(cn, hn);
	PORT_Free(cn);
    } else 
	PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
    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 and CERT_DestroyCertificateList moved
   to certhigh.c */


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) {
	    org = CERT_GetDomainComponentName(&cert->issuer);
	    if (org == NULL) {
		if (firstname) {
		    org = firstname;
		    firstname = NULL;
		} else {
		    org = PORT_Strdup("Unknown CA");
		}
	    }
	}

	/* can only fail if PORT_Strdup fails, in which case
	 * we're having memory problems. */
	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);
}

/* CERT_Import_CAChain moved to certhigh.c */

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->trust && (cert->trust->sslFlags|cert->trust->emailFlags|
				cert->trust->objectSigningFlags)) {
	trust = cert->trust;
	if ( ( ( trust->sslFlags & CERTDB_VALID_CA ) == CERTDB_VALID_CA ) ||
	   ( ( trust->sslFlags & CERTDB_TRUSTED_CA ) == CERTDB_TRUSTED_CA ) ) {
	    ret = PR_TRUE;
	    type |= NS_CERT_TYPE_SSL_CA;
	}
	
	if ( ( ( trust->emailFlags & CERTDB_VALID_CA ) == CERTDB_VALID_CA ) ||
	  ( ( trust->emailFlags & CERTDB_TRUSTED_CA ) == CERTDB_TRUSTED_CA ) ) {
	    ret = PR_TRUE;
	    type |= NS_CERT_TYPE_EMAIL_CA;
	}
	
	if ( ( ( trust->objectSigningFlags & CERTDB_VALID_CA ) 
						== CERTDB_VALID_CA ) ||
          ( ( trust->objectSigningFlags & CERTDB_TRUSTED_CA ) 
						== CERTDB_TRUSTED_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);
	    }
	}
    }

    /* the isRoot flag trumps all */
    if (cert->isRoot) {
	ret = PR_TRUE;
	/* set only these by default, same as above */
	type = (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA);
    }

    if ( rettype != NULL ) {
	*rettype = type;
    }
    
    return(ret);
}

PRBool
CERT_IsCADERCert(SECItem *derCert, unsigned int *type) {
    CERTCertificate *cert;
    PRBool isCA;

    /* This is okay -- only looks at extensions */
    cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
    if (cert == NULL) return PR_FALSE;

    isCA = CERT_IsCACert(cert,type);
    CERT_DestroyCertificate (cert);
    return isCA;
}

PRBool
CERT_IsRootDERCert(SECItem *derCert)
{
    CERTCertificate *cert;
    PRBool isRoot;

    /* This is okay -- only looks at extensions */
    cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
    if (cert == NULL) return PR_FALSE;

    isRoot = cert->isRoot;
    CERT_DestroyCertificate (cert);
    return isRoot;
}

CERTCompareValidityStatus
CERT_CompareValidityTimes(CERTValidity* val_a, CERTValidity* val_b)
{
    PRTime notBeforeA, notBeforeB, notAfterA, notAfterB;

    if (!val_a || !val_b)
    {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return certValidityUndetermined;
    }

    if ( SECSuccess != DER_DecodeTimeChoice(&notBeforeA, &val_a->notBefore) ||
         SECSuccess != DER_DecodeTimeChoice(&notBeforeB, &val_b->notBefore) ||
         SECSuccess != DER_DecodeTimeChoice(&notAfterA, &val_a->notAfter) ||
         SECSuccess != DER_DecodeTimeChoice(&notAfterB, &val_b->notAfter) ) {
        return certValidityUndetermined;
    }

    /* sanity check */
    if (LL_CMP(notBeforeA,>,notAfterA) || LL_CMP(notBeforeB,>,notAfterB)) {
        PORT_SetError(SEC_ERROR_INVALID_TIME);
        return certValidityUndetermined;
    }

    if (LL_CMP(notAfterA,!=,notAfterB)) {
        /* one cert validity goes farther into the future, select it */
        return LL_CMP(notAfterA,<,notAfterB) ?
            certValidityChooseB : certValidityChooseA;
    }
    /* the two certs have the same expiration date */
    PORT_Assert(LL_CMP(notAfterA, == , notAfterB));
    /* do they also have the same start date ? */
    if (LL_CMP(notBeforeA,==,notBeforeB)) {
	return certValidityEqual;
    }
    /* choose cert with the later start date */
    return LL_CMP(notBeforeA,<,notBeforeB) ?
        certValidityChooseB : certValidityChooseA;
}

/*
 * is certa newer than certb?  If one is expired, pick the other one.
 */
PRBool
CERT_IsNewer(CERTCertificate *certa, CERTCertificate *certb)
{
    PRTime 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 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)
{
    unsigned int i;
    unsigned int *pflags;
    
    if (!trust) {
	PORT_SetError(SEC_ERROR_INVALID_ARGS);
	return SECFailure;
    }
    trust->sslFlags = 0;
    trust->emailFlags = 0;
    trust->objectSigningFlags = 0;
    if (!trusts) {
	PORT_SetError(SEC_ERROR_INVALID_ARGS);
	return SECFailure;
    }

    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;

	  case 'i':
	      *pflags = *pflags | CERTDB_INVISIBLE_CA;
	      break;
	  case 'g':
	      *pflags = *pflags | CERTDB_GOVT_APPROVED_CA;
	      break;

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

/* in 3.4, this will only set trust */
SECStatus
CERT_SaveImportedCert(CERTCertificate *cert, SECCertUsage usage,
		      PRBool caOnly, char *nickname)
{
    SECStatus rv;
    PRBool saveit;
    CERTCertTrust trust;
    PRBool isCA;
    unsigned int certtype;
    
    isCA = CERT_IsCACert(cert, NULL);
    if ( caOnly && ( !isCA ) ) {
	return(SECSuccess);
    }
    /* In NSS 3.4, certs are given zero trust upon import.  However, this
    * function needs to set up default CA trust (CERTDB_VALID_CA), or
    * PKCS#12 imported certs will not show up correctly.  In the case of a
    * CA cert with zero trust, continue with this function.  But if the cert
    * does already have some trust bits, exit and do not change them.
    */
    if (isCA && cert->trust && 
        (cert->trust->sslFlags |
         cert->trust->emailFlags |
         cert->trust->objectSigningFlags)) {
	return(SECSuccess);
    }

    saveit = PR_TRUE;
    
    PORT_Memset((void *)&trust, 0, sizeof(trust));

    certtype = cert->nsCertType;

    /* if no CA bits in cert type, then set all CA bits */
    if ( isCA && ( ! ( certtype & NS_CERT_TYPE_CA ) ) ) {
	certtype |= NS_CERT_TYPE_CA;
    }

    /* if no app bits in cert type, then set all app bits */
    if ( ( !isCA ) && ( ! ( certtype & NS_CERT_TYPE_APP ) ) ) {
	certtype |= NS_CERT_TYPE_APP;
    }

    switch ( usage ) {
      case certUsageEmailSigner:
      case certUsageEmailRecipient:
	if ( isCA ) {
	    if ( certtype & NS_CERT_TYPE_EMAIL_CA ) {
		trust.emailFlags = CERTDB_VALID_CA;
	    }
	} else {
	    if ( !cert->emailAddr || !cert->emailAddr[0] ) {
		saveit = PR_FALSE;
	    }
	    
	    if ( certtype & NS_CERT_TYPE_EMAIL ) {
		trust.emailFlags = CERTDB_VALID_PEER;
		if ( ! ( cert->rawKeyUsage & KU_KEY_ENCIPHERMENT ) ) {
		    /* don't save it if KeyEncipherment is not allowed */
		    saveit = PR_FALSE;
		}
	    }
	}
	break;
      case certUsageUserCertImport:
	if ( isCA ) {
	    if ( certtype & NS_CERT_TYPE_SSL_CA ) {
		trust.sslFlags = CERTDB_VALID_CA;
	    }
	    
	    if ( certtype & NS_CERT_TYPE_EMAIL_CA ) {
		trust.emailFlags = CERTDB_VALID_CA;
	    }
	    
	    if ( certtype & NS_CERT_TYPE_OBJECT_SIGNING_CA ) {
		trust.objectSigningFlags = CERTDB_VALID_CA;
	    }
	    
	} else {
	    if ( certtype & NS_CERT_TYPE_SSL_CLIENT ) {
		trust.sslFlags = CERTDB_VALID_PEER;
	    }
	    
	    if ( certtype & NS_CERT_TYPE_EMAIL ) {
		trust.emailFlags = CERTDB_VALID_PEER;
	    }
	    
	    if ( certtype & NS_CERT_TYPE_OBJECT_SIGNING ) {
		trust.objectSigningFlags = CERTDB_VALID_PEER;
	    }
	}
	break;
      case certUsageAnyCA:
	trust.sslFlags = CERTDB_VALID_CA;
	break;
      case certUsageSSLCA:
	trust.sslFlags = CERTDB_VALID_CA | 
			CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA;
	break;
      default:	/* XXX added to quiet warnings; no other cases needed? */
	break;
    }

    if ( saveit ) {
	rv = CERT_ChangeCertTrust(cert->dbhandle, cert, &trust);
	if ( rv != SECSuccess ) {
	    goto loser;
	}
    }

    rv = SECSuccess;
    goto done;

loser:
    rv = SECFailure;
done:

    return(rv);
}

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

    if ( ncerts ) {
	certs = PORT_ZNewArray(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++ ) {
                char* canickname = NULL;
                PRBool freeNickname = PR_FALSE;

		SECKEY_UpdateCertPQG(certs[i]);
                
                if ( CERT_IsCACert(certs[i], NULL) ) {
                    canickname = CERT_MakeCANickname(certs[i]);
                    if ( canickname != NULL ) {
                        freeNickname = PR_TRUE;
                    }
                }

		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. But we still may try
                     * the individual canickname from the cert itself.
		     */
		    rv = CERT_AddTempCertToPerm(certs[i], canickname, NULL);
		} else {
		    rv = CERT_AddTempCertToPerm(certs[i],
                                                nickname?nickname:canickname, NULL);
		}
		if (rv == SECSuccess) {
		    CERT_SaveImportedCert(certs[i], usage, caOnly, NULL);
		}

                if (PR_TRUE == freeNickname) {
                    PORT_Free(canickname);
                }
		/* don't care if it fails - keep going */
	    }
	}
    }

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

    return ((fcerts || !ncerts) ? SECSuccess : SECFailure);
}

/*
 * 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_AddCertToListTailWithData(CERTCertList *certs, 
				CERTCertificate *cert, void *appData)
{
    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;
    node->appData = appData;
    return(SECSuccess);
    
loser:
    return(SECFailure);
}

SECStatus
CERT_AddCertToListTail(CERTCertList *certs, CERTCertificate *cert)
{
    return CERT_AddCertToListTailWithData(certs, cert, NULL);
}

SECStatus
CERT_AddCertToListHeadWithData(CERTCertList *certs, 
					CERTCertificate *cert, void *appData)
{
    CERTCertListNode *node;
    CERTCertListNode *head;
    
    head = CERT_LIST_HEAD(certs);

    if (head == NULL) return CERT_AddCertToListTail(certs,cert);

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

SECStatus
CERT_AddCertToListHead(CERTCertList *certs, CERTCertificate *cert)
{
    return CERT_AddCertToListHeadWithData(certs, cert, NULL);
}

/*
 * 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)
{
    PRTime sorttime;
    PRTime notBeforeA, notAfterA, notBeforeB, notAfterB;
    SECStatus rv;
    PRBool newerbefore, newerafter;
    PRBool aNotValid = PR_FALSE, bNotValid = PR_FALSE;

    sorttime = *(PRTime *)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);
}

/* This routine is here because pcertdb.c still has a call to it.
 * The SMIME profile code in pcertdb.c should be split into high (find
 * the email cert) and low (store the profile) code.  At that point, we
 * can move this to certhigh.c where it belongs.
 *
 * 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;
    SECStatus rv;
    
    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) ) {

	PRBool bad = (PRBool)(!node->cert);

	/* bad key usage ? */
	if ( !bad && 
	     CERT_CheckKeyUsage(node->cert, requiredKeyUsage) != SECSuccess ) {
	    bad = PR_TRUE;
	}
	/* bad cert type ? */
	if ( !bad ) {
	    unsigned int certType = 0;
	    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.
		 */
		(void)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);
}

PRBool CERT_IsUserCert(CERTCertificate* cert)
{
    if ( cert->trust &&
        ((cert->trust->sslFlags & CERTDB_USER ) ||
         (cert->trust->emailFlags & CERTDB_USER ) ||
         (cert->trust->objectSigningFlags & CERTDB_USER )) ) {
        return PR_TRUE;
    } else {
        return PR_FALSE;
    }
}

SECStatus
CERT_FilterCertListForUserCerts(CERTCertList *certList)
{
    CERTCertListNode *node, *freenode;
    CERTCertificate *cert;

    if (!certList) {
        return SECFailure;
    }

    node = CERT_LIST_HEAD(certList);
    
    while ( ! CERT_LIST_END(node, certList) ) {
	cert = node->cert;
	if ( PR_TRUE != CERT_IsUserCert(cert) ) {
	    /* Not a User Cert, so remove this cert from the list */
	    freenode = node;
	    node = CERT_LIST_NEXT(node);
	    CERT_RemoveCertListNode(freenode);
	} else {
	    /* Is a User cert, so leave it in the list */
	    node = CERT_LIST_NEXT(node);
	}
    }

    return(SECSuccess);
}

static PZLock *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, nssILockRefLock);
	PORT_Assert(certRefCountLock != NULL);
    }
    
    PZ_Lock(certRefCountLock);
    return;
}

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

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

    return;
}

static PZLock *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, nssILockCertDB);
	PORT_Assert(certTrustLock != NULL);
    }
    
    PZ_Lock(certTrustLock);
    return;
}

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

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

    return;
}


/*
 * Get the StatusConfig data for this handle
 */
CERTStatusConfig *
CERT_GetStatusConfig(CERTCertDBHandle *handle)
{
  return handle->statusConfig;
}

/*
 * Set the StatusConfig data for this handle.  There
 * should not be another configuration set.
 */
void
CERT_SetStatusConfig(CERTCertDBHandle *handle, CERTStatusConfig *statusConfig)
{
  PORT_Assert(handle->statusConfig == NULL);
  handle->statusConfig = statusConfig;
}

/*
 * Code for dealing with subjKeyID to cert mappings.
 */

static PLHashTable *gSubjKeyIDHash = NULL;
static PRLock      *gSubjKeyIDLock = NULL;

static void *cert_AllocTable(void *pool, PRSize size)
{
    return PORT_Alloc(size);
}

static void cert_FreeTable(void *pool, void *item)
{
    PORT_Free(item);
}

static PLHashEntry* cert_AllocEntry(void *pool, const void *key)
{
    return PORT_New(PLHashEntry);
}

static void cert_FreeEntry(void *pool, PLHashEntry *he, PRUintn flag)
{
    SECITEM_FreeItem((SECItem*)(he->value), PR_TRUE);
    if (flag == HT_FREE_ENTRY) {
        SECITEM_FreeItem((SECItem*)(he->key), PR_TRUE);
        PORT_Free(he);
    }
}

static PLHashAllocOps cert_AllocOps = {
    cert_AllocTable, cert_FreeTable, cert_AllocEntry, cert_FreeEntry
};

SECStatus
cert_CreateSubjectKeyIDHashTable(void)
{
    gSubjKeyIDHash = PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare,
                                    SECITEM_HashCompare,
                                    &cert_AllocOps, NULL);
    if (!gSubjKeyIDHash) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }
    gSubjKeyIDLock = PR_NewLock();
    if (!gSubjKeyIDLock) {
        PL_HashTableDestroy(gSubjKeyIDHash);
        gSubjKeyIDHash = NULL;
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }
    return SECSuccess;

}

SECStatus
cert_AddSubjectKeyIDMapping(SECItem *subjKeyID, CERTCertificate *cert)
{
    SECItem *newKeyID, *oldVal, *newVal;
    SECStatus rv = SECFailure;

    if (!gSubjKeyIDLock) {
	/* If one is created, then both are there.  So only check for one. */
	return SECFailure;
    }

    newVal = SECITEM_DupItem(&cert->derCert);
    if (!newVal) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto done;
    }
    newKeyID = SECITEM_DupItem(subjKeyID);
    if (!newKeyID) {
        SECITEM_FreeItem(newVal, PR_TRUE);
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto done;
    }

    PR_Lock(gSubjKeyIDLock);
    /* The hash table implementation does not free up the memory 
     * associated with the key of an already existing entry if we add a 
     * duplicate, so we would wind up leaking the previously allocated 
     * key if we don't remove before adding.
     */
    oldVal = (SECItem*)PL_HashTableLookup(gSubjKeyIDHash, subjKeyID);
    if (oldVal) {
        PL_HashTableRemove(gSubjKeyIDHash, subjKeyID);
    }

    rv = (PL_HashTableAdd(gSubjKeyIDHash, newKeyID, newVal)) ? SECSuccess :
                                                               SECFailure;
    PR_Unlock(gSubjKeyIDLock);
done:
    return rv;
}

SECStatus
cert_RemoveSubjectKeyIDMapping(SECItem *subjKeyID)
{
    SECStatus rv;
    if (!gSubjKeyIDLock)
        return SECFailure;

    PR_Lock(gSubjKeyIDLock);
    rv = (PL_HashTableRemove(gSubjKeyIDHash, subjKeyID)) ? SECSuccess :
                                                           SECFailure;
    PR_Unlock(gSubjKeyIDLock);
    return rv;
}

SECStatus
cert_DestroySubjectKeyIDHashTable(void)
{
    if (gSubjKeyIDHash) {
        PR_Lock(gSubjKeyIDLock);
        PL_HashTableDestroy(gSubjKeyIDHash);
        gSubjKeyIDHash = NULL;
        PR_Unlock(gSubjKeyIDLock);
        PR_DestroyLock(gSubjKeyIDLock);
        gSubjKeyIDLock = NULL;
    }
    return SECSuccess;
}

SECItem*
cert_FindDERCertBySubjectKeyID(SECItem *subjKeyID)
{
    SECItem   *val;
 
    if (!gSubjKeyIDLock)
        return NULL;

    PR_Lock(gSubjKeyIDLock);
    val = (SECItem*)PL_HashTableLookup(gSubjKeyIDHash, subjKeyID);
    if (val) {
        val = SECITEM_DupItem(val);
    }
    PR_Unlock(gSubjKeyIDLock);
    return val;
}

CERTCertificate*
CERT_FindCertBySubjectKeyID(CERTCertDBHandle *handle, SECItem *subjKeyID)
{
    CERTCertificate *cert = NULL;
    SECItem *derCert;

    derCert = cert_FindDERCertBySubjectKeyID(subjKeyID);
    if (derCert) {
        cert = CERT_FindCertByDERCert(handle, derCert);
        SECITEM_FreeItem(derCert, PR_TRUE);
    }
    return cert;
}
