/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nspr.h"
#include "secerr.h"
#include "secport.h"
#include "seccomon.h"
#include "secoid.h"
#include "genname.h"
#include "keyhi.h"
#include "cert.h"
#include "certdb.h"
#include "certi.h"
#include "cryptohi.h"

#ifndef NSS_DISABLE_LIBPKIX
#include "pkix.h"
#include "pkix_pl_cert.h"
#else
#include "nss.h"
#endif /* NSS_DISABLE_LIBPKIX */

#include "nsspki.h"
#include "pkitm.h"
#include "pkim.h"
#include "pki3hack.h"
#include "base.h"
#include "keyi.h"

/*
 * Check the validity times of a certificate
 */
SECStatus
CERT_CertTimesValid(CERTCertificate *c)
{
    SECCertTimeValidity valid = CERT_CheckCertValidTimes(c, PR_Now(), PR_TRUE);
    return (valid == secCertTimeValid) ? SECSuccess : SECFailure;
}

static SECStatus
checkKeyParams(const SECAlgorithmID *sigAlgorithm, const SECKEYPublicKey *key)
{
    SECStatus rv;
    SECOidTag sigAlg;
    SECOidTag curve;
    PRUint32 policyFlags = 0;
    PRInt32 minLen, len, optFlags;

    sigAlg = SECOID_GetAlgorithmTag(sigAlgorithm);

    switch (sigAlg) {
        case SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE:
        case SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE:
        case SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE:
        case SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE:
        case SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE:
            if (key->keyType != ecKey) {
                PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
                return SECFailure;
            }

            curve = SECKEY_GetECCOid(&key->u.ec.DEREncodedParams);
            if (curve != 0) {
                if (NSS_GetAlgorithmPolicy(curve, &policyFlags) == SECFailure ||
                    !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) {
                    PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
                    return SECFailure;
                }
                return SECSuccess;
            }
            PORT_SetError(SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
            return SECFailure;

        case SEC_OID_PKCS1_RSA_PSS_SIGNATURE: {
            PORTCheapArenaPool tmpArena;
            SECOidTag hashAlg;
            SECOidTag maskHashAlg;

            PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE);
            rv = sec_DecodeRSAPSSParams(&tmpArena.arena,
                                        &sigAlgorithm->parameters,
                                        &hashAlg, &maskHashAlg, NULL);
            PORT_DestroyCheapArena(&tmpArena);
            if (rv != SECSuccess) {
                return SECFailure;
            }

            if (NSS_GetAlgorithmPolicy(hashAlg, &policyFlags) == SECSuccess &&
                !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) {
                PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
                return SECFailure;
            }
            if (NSS_GetAlgorithmPolicy(maskHashAlg, &policyFlags) == SECSuccess &&
                !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) {
                PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
                return SECFailure;
            }
        }
        /* fall through to RSA key checking */
        case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION:
        case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION:
        case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION:
        case SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION:
        case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION:
        case SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE:
        case SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE:
            if (key->keyType != rsaKey && key->keyType != rsaPssKey) {
                PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
                return SECFailure;
            }

            if (NSS_OptionGet(NSS_KEY_SIZE_POLICY_FLAGS, &optFlags) == SECFailure) {
                return SECSuccess;
            }
            if ((optFlags & NSS_KEY_SIZE_POLICY_VERIFY_FLAG) == 0) {
                return SECSuccess;
            }

            len = 8 * key->u.rsa.modulus.len;

            rv = NSS_OptionGet(NSS_RSA_MIN_KEY_SIZE, &minLen);
            if (rv != SECSuccess) {
                return SECFailure;
            }

            if (len < minLen) {
                return SECFailure;
            }

            return SECSuccess;
        case SEC_OID_ANSIX9_DSA_SIGNATURE:
        case SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST:
        case SEC_OID_BOGUS_DSA_SIGNATURE_WITH_SHA1_DIGEST:
        case SEC_OID_SDN702_DSA_SIGNATURE:
        case SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST:
        case SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST:
            if (key->keyType != dsaKey) {
                PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
                return SECFailure;
            }
            if (NSS_OptionGet(NSS_KEY_SIZE_POLICY_FLAGS, &optFlags) == SECFailure) {
                return SECSuccess;
            }
            if ((optFlags & NSS_KEY_SIZE_POLICY_VERIFY_FLAG) == 0) {
                return SECSuccess;
            }

            len = 8 * key->u.dsa.params.prime.len;

            rv = NSS_OptionGet(NSS_DSA_MIN_KEY_SIZE, &minLen);
            if (rv != SECSuccess) {
                return SECFailure;
            }

            if (len < minLen) {
                return SECFailure;
            }

            return SECSuccess;
        case SEC_OID_ML_DSA_44:
        case SEC_OID_ML_DSA_65:
        case SEC_OID_ML_DSA_87:
            if (key->keyType != mldsaKey) {
                PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
                return SECFailure;
            }
            if (key->u.mldsa.paramSet != sigAlg) {
                PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
                return SECFailure;
            }
        default:
            return SECSuccess;
    }
}

/*
 * verify the signature of a signed data object with the given DER publickey
 */
SECStatus
CERT_VerifySignedDataWithPublicKey(const CERTSignedData *sd,
                                   SECKEYPublicKey *pubKey,
                                   void *wincx)
{
    SECStatus rv;
    SECItem sig;
    SECOidTag sigAlg;
    SECOidTag encAlg;
    SECOidTag hashAlg;
    CK_MECHANISM_TYPE mech;
    PRUint32 policyFlags;

    if (!pubKey || !sd) {
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
        return SECFailure;
    }

    /* Can we use this algorithm for signature verification?  */
    sigAlg = SECOID_GetAlgorithmTag(&sd->signatureAlgorithm);
    rv = sec_DecodeSigAlg(pubKey, sigAlg,
                          &sd->signatureAlgorithm.parameters,
                          &encAlg, &hashAlg, &mech, NULL);
    if (rv != SECSuccess) {
        return SECFailure; /* error is set */
    }
    rv = NSS_GetAlgorithmPolicy(encAlg, &policyFlags);
    if (rv == SECSuccess &&
        !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) {
        PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
        return SECFailure;
    }
    rv = NSS_GetAlgorithmPolicy(hashAlg, &policyFlags);
    if (rv == SECSuccess &&
        !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) {
        PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
        return SECFailure;
    }
    rv = checkKeyParams(&sd->signatureAlgorithm, pubKey);
    if (rv != SECSuccess) {
        PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
        return SECFailure;
    }

    /* check the signature */
    sig = sd->signature;
    /* convert sig->len from bit counts to byte count. */
    DER_ConvertBitString(&sig);

    rv = VFY_VerifyDataWithAlgorithmID(sd->data.data, sd->data.len, pubKey,
                                       &sig, &sd->signatureAlgorithm,
                                       &hashAlg, wincx);
    if (rv != SECSuccess) {
        return SECFailure; /* error is set */
    }

    /* for some algorithms, hash algorithm is only known after verification */
    rv = NSS_GetAlgorithmPolicy(hashAlg, &policyFlags);
    if (rv == SECSuccess &&
        !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) {
        PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
        return SECFailure;
    }
    return SECSuccess;
}

/*
 * verify the signature of a signed data object with the given DER publickey
 */
SECStatus
CERT_VerifySignedDataWithPublicKeyInfo(CERTSignedData *sd,
                                       CERTSubjectPublicKeyInfo *pubKeyInfo,
                                       void *wincx)
{
    SECKEYPublicKey *pubKey;
    SECStatus rv = SECFailure;

    /* get cert's public key */
    pubKey = SECKEY_ExtractPublicKey(pubKeyInfo);
    if (pubKey) {
        rv = CERT_VerifySignedDataWithPublicKey(sd, pubKey, wincx);
        SECKEY_DestroyPublicKey(pubKey);
    }
    return rv;
}

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

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

    /* get cert's public key */
    pubKey = CERT_ExtractPublicKey(cert);
    if (pubKey) {
        rv = CERT_VerifySignedDataWithPublicKey(sd, pubKey, wincx);
        SECKEY_DestroyPublicKey(pubKey);
    }
    return rv;
}

SECStatus
SEC_CheckCRL(CERTCertDBHandle *handle, CERTCertificate *cert,
             CERTCertificate *caCert, PRTime t, void *wincx)
{
    return CERT_CheckCRL(cert, caCert, NULL, t, wincx);
}

/*
 * Find the issuer of a cert.  Use the authorityKeyID if it exists.
 */
CERTCertificate *
CERT_FindCertIssuer(CERTCertificate *cert, PRTime validTime, SECCertUsage usage)
{
    NSSCertificate *me;
    NSSTime *nssTime;
    NSSTrustDomain *td;
    NSSCryptoContext *cc;
    NSSCertificate *chain[3];
    NSSUsage nssUsage;
    PRStatus status;

    me = STAN_GetNSSCertificate(cert);
    if (!me) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }
    nssTime = NSSTime_SetPRTime(NULL, validTime);
    nssUsage.anyUsage = PR_FALSE;
    nssUsage.nss3usage = usage;
    nssUsage.nss3lookingForCA = PR_TRUE;
    memset(chain, 0, 3 * sizeof(NSSCertificate *));
    td = STAN_GetDefaultTrustDomain();
    cc = STAN_GetDefaultCryptoContext();
    (void)NSSCertificate_BuildChain(me, nssTime, &nssUsage, NULL,
                                    chain, 2, NULL, &status, td, cc);
    nss_ZFreeIf(nssTime);
    if (status == PR_SUCCESS) {
        PORT_Assert(me == chain[0]);
        /* if it's a root, the chain will only have one cert */
        if (!chain[1]) {
            /* already has a reference from the call to BuildChain */
            return cert;
        }
        NSSCertificate_Destroy(chain[0]);         /* the first cert in the chain */
        return STAN_GetCERTCertificate(chain[1]); /* return the 2nd */
    }
    if (chain[0]) {
        PORT_Assert(me == chain[0]);
        NSSCertificate_Destroy(chain[0]); /* the first cert in the chain */
    }
    PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
    return NULL;
}

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

void
cert_AddToVerifyLog(CERTVerifyLog *log, CERTCertificate *cert, 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) {                                         \
        cert_AddToVerifyLog(log, cert, PORT_GetError(), depth, \
                            (void *)(PRWord)arg);              \
    } else {                                                   \
        goto loser;                                            \
    }

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

/* /C=CN/O=WoSign CA Limited/CN=CA \xE6\xB2\x83\xE9\x80\x9A\xE6\xA0\xB9\xE8\xAF\x81\xE4\xB9\xA6
 * Using a consistent naming convention, this would actually be called
 * 'CA沃通根证书DN', but since GCC 6.2.1 apparently can't handle UTF-8
 * identifiers, this will have to do.
 */
static const unsigned char CAWoSignRootDN[72] = {
    0x30, 0x46, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
    0x43, 0x4E, 0x31, 0x1A, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x11,
    0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E, 0x20, 0x43, 0x41, 0x20, 0x4C, 0x69, 0x6D,
    0x69, 0x74, 0x65, 0x64, 0x31, 0x1B, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03,
    0x0C, 0x12, 0x43, 0x41, 0x20, 0xE6, 0xB2, 0x83, 0xE9, 0x80, 0x9A, 0xE6, 0xA0,
    0xB9, 0xE8, 0xAF, 0x81, 0xE4, 0xB9, 0xA6
};

/* /C=CN/O=WoSign CA Limited/CN=CA WoSign ECC Root */
static const unsigned char CAWoSignECCRootDN[72] = {
    0x30, 0x46, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
    0x43, 0x4E, 0x31, 0x1A, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x11,
    0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E, 0x20, 0x43, 0x41, 0x20, 0x4C, 0x69, 0x6D,
    0x69, 0x74, 0x65, 0x64, 0x31, 0x1B, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03,
    0x13, 0x12, 0x43, 0x41, 0x20, 0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E, 0x20, 0x45,
    0x43, 0x43, 0x20, 0x52, 0x6F, 0x6F, 0x74
};

/* /C=CN/O=WoSign CA Limited/CN=Certification Authority of WoSign */
static const unsigned char CertificationAuthorityofWoSignDN[87] = {
    0x30, 0x55, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
    0x43, 0x4E, 0x31, 0x1A, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x11,
    0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E, 0x20, 0x43, 0x41, 0x20, 0x4C, 0x69, 0x6D,
    0x69, 0x74, 0x65, 0x64, 0x31, 0x2A, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03,
    0x13, 0x21, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
    0x6F, 0x6E, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79, 0x20,
    0x6F, 0x66, 0x20, 0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E
};

/* /C=CN/O=WoSign CA Limited/CN=Certification Authority of WoSign G2 */
static const unsigned char CertificationAuthorityofWoSignG2DN[90] = {
    0x30, 0x58, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
    0x43, 0x4E, 0x31, 0x1A, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x11,
    0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E, 0x20, 0x43, 0x41, 0x20, 0x4C, 0x69, 0x6D,
    0x69, 0x74, 0x65, 0x64, 0x31, 0x2D, 0x30, 0x2B, 0x06, 0x03, 0x55, 0x04, 0x03,
    0x13, 0x24, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
    0x6F, 0x6E, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79, 0x20,
    0x6F, 0x66, 0x20, 0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E, 0x20, 0x47, 0x32
};

/* /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority */
static const unsigned char StartComCertificationAuthorityDN[127] = {
    0x30, 0x7D, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
    0x49, 0x4C, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x0D,
    0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6F, 0x6D, 0x20, 0x4C, 0x74, 0x64, 0x2E,
    0x31, 0x2B, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x13, 0x22, 0x53, 0x65,
    0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6C, 0x20,
    0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x53,
    0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55,
    0x04, 0x03, 0x13, 0x20, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6F, 0x6D, 0x20,
    0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E,
    0x20, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79
};

/* /C=IL/O=StartCom Ltd./CN=StartCom Certification Authority G2 */
static const unsigned char StartComCertificationAuthorityG2DN[85] = {
    0x30, 0x53, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
    0x49, 0x4C, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x0D,
    0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6F, 0x6D, 0x20, 0x4C, 0x74, 0x64, 0x2E,
    0x31, 0x2C, 0x30, 0x2A, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x23, 0x53, 0x74,
    0x61, 0x72, 0x74, 0x43, 0x6F, 0x6D, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66,
    0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6F,
    0x72, 0x69, 0x74, 0x79, 0x20, 0x47, 0x32
};

struct DataAndLength {
    const unsigned char *data;
    PRUint32 len;
};

static const struct DataAndLength StartComAndWoSignDNs[] = {
    { CAWoSignRootDN,
      sizeof(CAWoSignRootDN) },
    { CAWoSignECCRootDN,
      sizeof(CAWoSignECCRootDN) },
    { CertificationAuthorityofWoSignDN,
      sizeof(CertificationAuthorityofWoSignDN) },
    { CertificationAuthorityofWoSignG2DN,
      sizeof(CertificationAuthorityofWoSignG2DN) },
    { StartComCertificationAuthorityDN,
      sizeof(StartComCertificationAuthorityDN) },
    { StartComCertificationAuthorityG2DN,
      sizeof(StartComCertificationAuthorityG2DN) },
};

static PRBool
CertIsStartComOrWoSign(const CERTCertificate *cert)
{
    int i;
    const struct DataAndLength *dn = StartComAndWoSignDNs;

    for (i = 0; i < sizeof(StartComAndWoSignDNs) / sizeof(struct DataAndLength); ++i, dn++) {
        if (cert->derSubject.len == dn->len &&
            memcmp(cert->derSubject.data, dn->data, dn->len) == 0) {
            return PR_TRUE;
        }
    }
    return PR_FALSE;
}

SECStatus
isIssuerCertAllowedAtCertIssuanceTime(CERTCertificate *issuerCert,
                                      CERTCertificate *referenceCert)
{
    if (!issuerCert || !referenceCert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (CertIsStartComOrWoSign(issuerCert)) {
        /* PRTime is microseconds since the epoch, whereas JS time is milliseconds.
         * (new Date("2016-10-21T00:00:00Z")).getTime() * 1000
         */
        static const PRTime OCTOBER_21_2016 = 1477008000000000;

        PRTime notBefore, notAfter;
        SECStatus rv;

        rv = CERT_GetCertTimes(referenceCert, &notBefore, &notAfter);
        if (rv != SECSuccess)
            return rv;

        if (notBefore > OCTOBER_21_2016) {
            return SECFailure;
        }
    }

    return SECSuccess;
}

static SECStatus
cert_VerifyCertChainOld(CERTCertDBHandle *handle, CERTCertificate *cert,
                        PRBool checkSig, PRBool *sigerror,
                        SECCertUsage certUsage, PRTime t, void *wincx,
                        CERTVerifyLog *log, PRBool *revoked)
{
    SECTrustType trustType;
    CERTBasicConstraints basicConstraint;
    CERTCertificate *issuerCert = NULL;
    CERTCertificate *subjectCert = NULL;
    CERTCertificate *badCert = NULL;
    PRBool isca;
    SECStatus rv;
    SECStatus rvFinal = SECSuccess;
    int count;
    int currentPathLen = 0;
    int pathLengthLimit = CERT_UNLIMITED_PATH_CONSTRAINT;
    unsigned int caCertType;
    unsigned int requiredCAKeyUsage;
    unsigned int requiredFlags;
    PLArenaPool *arena = NULL;
    CERTGeneralName *namesList = NULL;
    CERTCertificate **certsList = NULL;
    int certsListLen = 16;
    int namesCount = 0;
    PRBool subjectCertIsSelfIssued;
    CERTCertTrust issuerTrust;

    if (revoked) {
        *revoked = PR_FALSE;
    }

    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 certUsageIPsec:
        case certUsageSSLCA:
        case certUsageSSLServerWithStepUp:
        case certUsageEmailSigner:
        case certUsageEmailRecipient:
        case certUsageObjectSigner:
        case certUsageVerifyCA:
        case certUsageAnyCA:
        case certUsageStatusResponder:
            if (CERT_TrustFlagsForCACertUsage(certUsage, &requiredFlags,
                                              &trustType) != SECSuccess) {
                PORT_Assert(0);
                EXIT_IF_NOT_LOGGING(log);
                /* XXX continuing with requiredFlags = 0 seems wrong.  It'll
                 * cause the following test to be true incorrectly:
                 *   flags = SEC_GET_TRUST_FLAGS(issuerCert->trust, trustType);
                 *   if (( flags & requiredFlags ) == requiredFlags) {
                 *       rv = rvFinal;
                 *       goto done;
                 *   }
                 * There are three other instances of this problem.
                 */
                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;
    }

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

    certsList = PORT_ZNewArray(CERTCertificate *, certsListLen);
    if (certsList == NULL)
        goto loser;

    /* RFC 3280 says that the name constraints will apply to the names
    ** in the leaf (EE) cert, whether it is self issued or not, so
    ** we pretend that it is not.
    */
    subjectCertIsSelfIssued = PR_FALSE;
    for (count = 0; count < CERT_MAX_CERT_CHAIN; count++) {
        PRBool validCAOverride = PR_FALSE;

        /* Construct a list of names for the current and all previous
         * certifcates (except leaf (EE) certs, root CAs, and self-issued
         * intermediate CAs) to be verified against the name constraints
         * extension of the issuer certificate.
         */
        if (subjectCertIsSelfIssued == PR_FALSE) {
            CERTGeneralName *subjectNameList;
            int subjectNameListLen;
            int i;
            PRBool getSubjectCN = (!count &&
                                   (certUsage == certUsageSSLServer || certUsage == certUsageIPsec));
            subjectNameList =
                CERT_GetConstrainedCertificateNames(subjectCert, arena,
                                                    getSubjectCN);
            if (!subjectNameList)
                goto loser;
            subjectNameListLen = CERT_GetNamesLength(subjectNameList);
            if (!subjectNameListLen)
                goto loser;
            if (certsListLen <= namesCount + subjectNameListLen) {
                CERTCertificate **tmpCertsList;
                certsListLen = (namesCount + subjectNameListLen) * 2;
                tmpCertsList =
                    (CERTCertificate **)PORT_Realloc(certsList,
                                                     certsListLen *
                                                         sizeof(CERTCertificate *));
                if (tmpCertsList == NULL) {
                    goto loser;
                }
                certsList = tmpCertsList;
            }
            for (i = 0; i < subjectNameListLen; i++) {
                certsList[namesCount + i] = CERT_DupCertificate(subjectCert);
            }
            namesCount += subjectNameListLen;
            namesList = cert_CombineNamesLists(namesList, subjectNameList);
        }

        /* check if the cert has an unsupported critical extension */
        if (subjectCert->options.bits.hasUnsupportedCriticalExt) {
            PORT_SetError(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION);
            LOG_ERROR_OR_EXIT(log, subjectCert, count, 0);
        }

        /* check that the signatureAlgorithm field of the certificate
         * matches the signature field of the tbsCertificate */
        if (SECOID_CompareAlgorithmID(
                &subjectCert->signatureWrap.signatureAlgorithm,
                &subjectCert->signature)) {
            PORT_SetError(SEC_ERROR_ALGORITHM_MISMATCH);
            LOG_ERROR(log, subjectCert, count, 0);
            goto loser;
        }

        /* 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 (sigerror) {
                    *sigerror = PR_TRUE;
                }
                if (PORT_GetError() == SEC_ERROR_EXPIRED_CERTIFICATE) {
                    PORT_SetError(SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE);
                    LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, 0);
                } else {
                    if (PORT_GetError() !=
                        SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED) {
                        PORT_SetError(SEC_ERROR_BAD_SIGNATURE);
                    }
                    LOG_ERROR_OR_EXIT(log, subjectCert, count, 0);
                }
            }
        }

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

        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);
            }
            pathLengthLimit = CERT_UNLIMITED_PATH_CONSTRAINT;
            /* no basic constraints found, we aren't (yet) a CA. */
            isca = PR_FALSE;
        } else {
            if (basicConstraint.isCA == PR_FALSE) {
                PORT_SetError(SEC_ERROR_CA_CERT_INVALID);
                LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, 0);
            }
            pathLengthLimit = basicConstraint.pathLenConstraint;
            isca = PR_TRUE;
        }
        /* make sure that the path len constraint is properly set.*/
        if (pathLengthLimit >= 0 && currentPathLen > pathLengthLimit) {
            PORT_SetError(SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID);
            LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, pathLengthLimit);
        }

        /* make sure that the entire chain is within the name space of the
         * current issuer certificate.
         */
        rv = CERT_CompareNameSpace(issuerCert, namesList, certsList,
                                   arena, &badCert);
        if (rv != SECSuccess || badCert != NULL) {
            PORT_SetError(SEC_ERROR_CERT_NOT_IN_NAME_SPACE);
            LOG_ERROR_OR_EXIT(log, badCert, count + 1, 0);
            goto loser;
        }

        rv = isIssuerCertAllowedAtCertIssuanceTime(issuerCert, cert);
        if (rv != SECSuccess) {
            PORT_SetError(SEC_ERROR_UNTRUSTED_ISSUER);
            LOG_ERROR(log, issuerCert, count + 1, 0);
            goto loser;
        }

        /* 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) {
            if (revoked) {
                *revoked = PR_TRUE;
            }
            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;
            if (revoked) {
                *revoked = PR_TRUE;
            }
            LOG_ERROR(log, subjectCert, count, 0);
        }

        if (CERT_GetCertTrust(issuerCert, &issuerTrust) == SECSuccess) {
            /* we have some trust info, but this does NOT imply that this
             * cert is actually trusted for any purpose.  The cert may be
             * explicitly UNtrusted.  We won't know until we examine the
             * trust bits.
             */
            unsigned int flags;

            if (certUsage != certUsageAnyCA &&
                certUsage != certUsageStatusResponder) {

                /*
                 * XXX This choice of trustType seems arbitrary.
                 */
                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(&issuerTrust, trustType);
                if ((flags & requiredFlags) == requiredFlags) {
                    /* we found a trusted one, so return */
                    rv = rvFinal;
                    goto done;
                }
                if (flags & CERTDB_VALID_CA) {
                    validCAOverride = PR_TRUE;
                }
                /* is it explicitly distrusted? */
                if ((flags & CERTDB_TERMINAL_RECORD) &&
                    ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0)) {
                    /* untrusted -- the cert is explicitly untrusted, not
                     * just that it doesn't chain to a trusted cert */
                    PORT_SetError(SEC_ERROR_UNTRUSTED_ISSUER);
                    LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, flags);
                }
            } else {
                /* Check if we have any valid trust when cheching for
                 * certUsageAnyCA or certUsageStatusResponder. */
                for (trustType = trustSSL; trustType < trustTypeNone;
                     trustType++) {
                    flags = SEC_GET_TRUST_FLAGS(&issuerTrust, trustType);
                    if ((flags & requiredFlags) == requiredFlags) {
                        rv = rvFinal;
                        goto done;
                    }
                    if (flags & CERTDB_VALID_CA)
                        validCAOverride = PR_TRUE;
                }
                /* We have 2 separate loops because we want any single trust
                 * bit to allow this usage to return trusted. Only if none of
                 * the trust bits are on do we check to see if the cert is
                 * untrusted */
                for (trustType = trustSSL; trustType < trustTypeNone;
                     trustType++) {
                    flags = SEC_GET_TRUST_FLAGS(&issuerTrust, trustType);
                    /* is it explicitly distrusted? */
                    if ((flags & CERTDB_TERMINAL_RECORD) &&
                        ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0)) {
                        /* untrusted -- the cert is explicitly untrusted, not
                         * just that it doesn't chain to a trusted cert */
                        PORT_SetError(SEC_ERROR_UNTRUSTED_ISSUER);
                        LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, flags);
                    }
                }
            }
        }

        if (!validCAOverride) {
            /*
             * 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 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 (!isca || (issuerCert->nsCertType & NS_CERT_TYPE_CA)) {
                isca = (issuerCert->nsCertType & caCertType) ? PR_TRUE : 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 issuer is not self signed.  If it is, then
         * stop here to prevent looping.
         */
        if (issuerCert->isRoot) {
            PORT_SetError(SEC_ERROR_UNTRUSTED_ISSUER);
            LOG_ERROR(log, issuerCert, count + 1, 0);
            goto loser;
        }
        /* The issuer cert will be the subject cert in the next loop.
         * A cert is self-issued if its subject and issuer are equal and
         * both are of non-zero length.
         */
        subjectCertIsSelfIssued = (PRBool)
                                      SECITEM_ItemsAreEqual(&issuerCert->derIssuer,
                                                            &issuerCert->derSubject) &&
                                  issuerCert->derSubject.len >
                                      0;
        if (subjectCertIsSelfIssued == PR_FALSE) {
            /* RFC 3280 says only non-self-issued intermediate CA certs
             * count in path length.
             */
            ++currentPathLen;
        }

        CERT_DestroyCertificate(subjectCert);
        subjectCert = issuerCert;
        issuerCert = NULL;
    }

    PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
    LOG_ERROR(log, subjectCert, count, 0);
loser:
    rv = SECFailure;
done:
    if (certsList != NULL) {
        for (int i = 0; i < namesCount; i++) {
            if (certsList[i]) {
                CERT_DestroyCertificate(certsList[i]);
            }
        }
        PORT_Free(certsList);
    }
    if (issuerCert) {
        CERT_DestroyCertificate(issuerCert);
    }

    if (subjectCert) {
        CERT_DestroyCertificate(subjectCert);
    }

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

SECStatus
cert_VerifyCertChain(CERTCertDBHandle *handle, CERTCertificate *cert,
                     PRBool checkSig, PRBool *sigerror,
                     SECCertUsage certUsage, PRTime t, void *wincx,
                     CERTVerifyLog *log, PRBool *revoked)
{
    if (CERT_GetUsePKIXForValidation()) {
        return cert_VerifyCertChainPkix(cert, checkSig, certUsage, t,
                                        wincx, log, sigerror, revoked);
    }
    return cert_VerifyCertChainOld(handle, cert, checkSig, sigerror,
                                   certUsage, t, wincx, log, revoked);
}

SECStatus
CERT_VerifyCertChain(CERTCertDBHandle *handle, CERTCertificate *cert,
                     PRBool checkSig, SECCertUsage certUsage, PRTime t,
                     void *wincx, CERTVerifyLog *log)
{
    return cert_VerifyCertChain(handle, cert, checkSig, NULL, certUsage, t,
                                wincx, log, NULL);
}

/*
 * verify that a CA can sign a certificate with the requested usage.
 */
SECStatus
CERT_VerifyCACertForUsage(CERTCertDBHandle *handle, CERTCertificate *cert,
                          PRBool checkSig, SECCertUsage certUsage, PRTime t,
                          void *wincx, CERTVerifyLog *log)
{
    SECTrustType trustType;
    CERTBasicConstraints basicConstraint;
    PRBool isca;
    PRBool validCAOverride = PR_FALSE;
    SECStatus rv;
    SECStatus rvFinal = SECSuccess;
    unsigned int flags;
    unsigned int caCertType;
    unsigned int requiredCAKeyUsage;
    unsigned int requiredFlags;
    CERTCertificate *issuerCert;
    CERTCertTrust certTrust;

    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 certUsageIPsec:
        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;
    }

    /* If the basicConstraint extension is included in an intermmediate 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).
     */

    rv = CERT_FindBasicConstraintExten(cert, &basicConstraint);
    if (rv != SECSuccess) {
        if (PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) {
            LOG_ERROR_OR_EXIT(log, cert, 0, 0);
        }
        /* no basic constraints found, we aren't (yet) a CA. */
        isca = PR_FALSE;
    } else {
        if (basicConstraint.isCA == PR_FALSE) {
            PORT_SetError(SEC_ERROR_CA_CERT_INVALID);
            LOG_ERROR_OR_EXIT(log, cert, 0, 0);
        }

        /* can't check path length if we don't know the previous path */
        isca = PR_TRUE;
    }

    if (CERT_GetCertTrust(cert, &certTrust) == SECSuccess) {
        /* we have some trust info, but this does NOT imply that this
         * cert is actually trusted for any purpose.  The cert may be
         * explicitly UNtrusted.  We won't know until we examine the
         * trust bits.
         */
        if (certUsage == certUsageStatusResponder) {
            /* Check the special case of certUsageStatusResponder */
            issuerCert = CERT_FindCertIssuer(cert, t, certUsage);
            if (issuerCert) {
                if (SEC_CheckCRL(handle, cert, issuerCert, t, wincx) !=
                    SECSuccess) {
                    PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE);
                    CERT_DestroyCertificate(issuerCert);
                    goto loser;
                }
                CERT_DestroyCertificate(issuerCert);
            }
            /* XXX We have NOT determined that this cert is trusted.
             * For years, NSS has treated this as trusted,
             * but it seems incorrect.
             */
            rv = rvFinal;
            goto done;
        }

        /*
         * check the trust params of the issuer
         */
        flags = SEC_GET_TRUST_FLAGS(&certTrust, trustType);
        if ((flags & requiredFlags) == requiredFlags) {
            /* we found a trusted one, so return */
            rv = rvFinal;
            goto done;
        }
        if (flags & CERTDB_VALID_CA) {
            validCAOverride = PR_TRUE;
        }
        /* is it explicitly distrusted? */
        if ((flags & CERTDB_TERMINAL_RECORD) &&
            ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0)) {
            /* untrusted -- the cert is explicitly untrusted, not
             * just that it doesn't chain to a trusted cert */
            PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
            LOG_ERROR_OR_EXIT(log, cert, 0, flags);
        }
    }
    if (!validCAOverride) {
        /*
         * 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 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 (!isca || (cert->nsCertType & NS_CERT_TYPE_CA)) {
            isca = (cert->nsCertType & caCertType) ? PR_TRUE : PR_FALSE;
        }

        if (!isca) {
            PORT_SetError(SEC_ERROR_CA_CERT_INVALID);
            LOG_ERROR_OR_EXIT(log, cert, 0, 0);
        }

        /* make sure key usage allows cert signing */
        if (CERT_CheckKeyUsage(cert, requiredCAKeyUsage) != SECSuccess) {
            PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE);
            LOG_ERROR_OR_EXIT(log, cert, 0, requiredCAKeyUsage);
        }
    }
    /* make sure that the issuer is not self signed.  If it is, then
     * stop here to prevent looping.
     */
    if (cert->isRoot) {
        PORT_SetError(SEC_ERROR_UNTRUSTED_ISSUER);
        LOG_ERROR(log, cert, 0, 0);
        goto loser;
    }

    return CERT_VerifyCertChain(handle, cert, checkSig, certUsage, t,
                                wincx, log);
loser:
    rv = SECFailure;
done:
    return rv;
}

#define NEXT_USAGE() \
    {                \
        i *= 2;      \
        certUsage++; \
        continue;    \
    }

#define VALID_USAGE() \
    {                 \
        NEXT_USAGE(); \
    }

#define INVALID_USAGE()                 \
    {                                   \
        if (returnedUsages) {           \
            *returnedUsages &= (~i);    \
        }                               \
        if (PR_TRUE == requiredUsage) { \
            valid = SECFailure;         \
        }                               \
        NEXT_USAGE();                   \
    }

/*
 * check the leaf cert against trust and usage.
 *   returns success if the cert is not distrusted. If the cert is
 *       trusted, then the trusted bool will be true.
 *   returns failure if the cert is distrusted. If failure, flags
 *       will return the flag bits that indicated distrust.
 */
SECStatus
cert_CheckLeafTrust(CERTCertificate *cert, SECCertUsage certUsage,
                    unsigned int *failedFlags, PRBool *trusted)
{
    unsigned int flags;
    CERTCertTrust trust;

    *failedFlags = 0;
    *trusted = PR_FALSE;

    /* check trust flags to see if this cert is directly trusted */
    if (CERT_GetCertTrust(cert, &trust) == SECSuccess) {
        switch (certUsage) {
            case certUsageSSLClient:
            case certUsageSSLServer:
            case certUsageIPsec:
                flags = trust.sslFlags;

                /* is the cert directly trusted or not trusted ? */
                if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is
                                                       * authoritative */
                    if (flags & CERTDB_TRUSTED) {     /* trust this cert */
                        *trusted = PR_TRUE;
                        return SECSuccess;
                    } else { /* don't trust this cert */
                        *failedFlags = flags;
                        return SECFailure;
                    }
                }
                break;
            case certUsageSSLServerWithStepUp:
                /* XXX - step up certs can't be directly trusted, only distrust */
                flags = trust.sslFlags;
                if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is
                                                       * authoritative */
                    if ((flags & CERTDB_TRUSTED) == 0) {
                        /* don't trust this cert */
                        *failedFlags = flags;
                        return SECFailure;
                    }
                }
                break;
            case certUsageSSLCA:
                flags = trust.sslFlags;
                if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is
                                                       * authoritative */
                    if ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0) {
                        /* don't trust this cert */
                        *failedFlags = flags;
                        return SECFailure;
                    }
                }
                break;
            case certUsageEmailSigner:
            case certUsageEmailRecipient:
                flags = trust.emailFlags;
                if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is
                                                       * authoritative */
                    if (flags & CERTDB_TRUSTED) {     /* trust this cert */
                        *trusted = PR_TRUE;
                        return SECSuccess;
                    } else { /* don't trust this cert */
                        *failedFlags = flags;
                        return SECFailure;
                    }
                }

                break;
            case certUsageObjectSigner:
                flags = trust.objectSigningFlags;

                if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is
                                                       * authoritative */
                    if (flags & CERTDB_TRUSTED) {     /* trust this cert */
                        *trusted = PR_TRUE;
                        return SECSuccess;
                    } else { /* don't trust this cert */
                        *failedFlags = flags;
                        return SECFailure;
                    }
                }
                break;
            case certUsageVerifyCA:
            case certUsageStatusResponder:
                flags = trust.sslFlags;
                /* is the cert directly trusted or not trusted ? */
                if ((flags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) ==
                    (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) {
                    *trusted = PR_TRUE;
                    return SECSuccess;
                }
                flags = trust.emailFlags;
                /* is the cert directly trusted or not trusted ? */
                if ((flags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) ==
                    (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) {
                    *trusted = PR_TRUE;
                    return SECSuccess;
                }
                flags = trust.objectSigningFlags;
                /* is the cert directly trusted or not trusted ? */
                if ((flags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) ==
                    (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) {
                    *trusted = PR_TRUE;
                    return SECSuccess;
                }
            /* fall through to test distrust */
            case certUsageAnyCA:
            case certUsageUserCertImport:
                /* do we distrust these certs explicitly */
                flags = trust.sslFlags;
                if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is
                                                       * authoritative */
                    if ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0) {
                        *failedFlags = flags;
                        return SECFailure;
                    }
                }
                flags = trust.emailFlags;
                if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is
                                                       * authoritative */
                    if ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0) {
                        *failedFlags = flags;
                        return SECFailure;
                    }
                }
            /* fall through */
            case certUsageProtectedObjectSigner:
                flags = trust.objectSigningFlags;
                if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is
                                                       * authoritative */
                    if ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0) {
                        *failedFlags = flags;
                        return SECFailure;
                    }
                }
                break;
        }
    }
    return SECSuccess;
}

/*
 * verify a certificate by checking if it's valid and that we
 * trust the issuer.
 *
 * certificateUsage contains a bitfield of all cert usages that are
 * required for verification to succeed
 *
 * a bitfield of cert usages is returned in *returnedUsages
 * if requiredUsages is non-zero, the returned bitmap is only
 * for those required usages, otherwise it is for all usages
 *
 */
SECStatus
CERT_VerifyCertificate(CERTCertDBHandle *handle, CERTCertificate *cert,
                       PRBool checkSig, SECCertificateUsage requiredUsages, PRTime t,
                       void *wincx, CERTVerifyLog *log, SECCertificateUsage *returnedUsages)
{
    SECStatus rv;
    SECStatus valid;
    unsigned int requiredKeyUsage;
    unsigned int requiredCertType;
    unsigned int flags;
    unsigned int certType;
    PRBool allowOverride;
    SECCertTimeValidity validity;
    CERTStatusConfig *statusConfig;
    PRInt32 i;
    SECCertUsage certUsage = 0;
    PRBool checkedOCSP = PR_FALSE;
    PRBool checkAllUsages = PR_FALSE;
    PRBool revoked = PR_FALSE;
    PRBool sigerror = PR_FALSE;
    PRBool trusted = PR_FALSE;

    if (!requiredUsages) {
        /* there are no required usages, so the user probably wants to
           get status for all usages */
        checkAllUsages = PR_TRUE;
    }

    if (returnedUsages) {
        *returnedUsages = 0;
    } else {
        /* we don't have a place to return status for all usages,
           so we can skip checks for usages that aren't required */
        checkAllUsages = PR_FALSE;
    }
    valid = SECSuccess; /* start off assuming cert is valid */

    /* make sure that the cert is valid at time t */
    allowOverride = (PRBool)((requiredUsages & certificateUsageSSLServer) ||
                             (requiredUsages & certificateUsageSSLServerWithStepUp) ||
                             (requiredUsages & certificateUsageIPsec));
    validity = CERT_CheckCertValidTimes(cert, t, allowOverride);
    if (validity != secCertTimeValid) {
        valid = SECFailure;
        LOG_ERROR_OR_EXIT(log, cert, 0, validity);
    }

    /* check key usage and netscape cert type */
    cert_GetCertType(cert);
    certType = cert->nsCertType;

    for (i = 1; i <= certificateUsageHighest &&
                (SECSuccess == valid || returnedUsages || log);) {
        PRBool requiredUsage = (i & requiredUsages) ? PR_TRUE : PR_FALSE;
        if (PR_FALSE == requiredUsage && PR_FALSE == checkAllUsages) {
            NEXT_USAGE();
        }
        if (returnedUsages) {
            *returnedUsages |= i; /* start off assuming this usage is valid */
        }
        switch (certUsage) {
            case certUsageSSLClient:
            case certUsageSSLServer:
            case certUsageSSLServerWithStepUp:
            case certUsageSSLCA:
            case certUsageEmailSigner:
            case certUsageEmailRecipient:
            case certUsageObjectSigner:
            case certUsageStatusResponder:
            case certUsageIPsec:
                rv = CERT_KeyUsageAndTypeForCertUsage(certUsage, PR_FALSE,
                                                      &requiredKeyUsage,
                                                      &requiredCertType);
                if (rv != SECSuccess) {
                    PORT_Assert(0);
                    /* EXIT_IF_NOT_LOGGING(log); XXX ??? */
                    requiredKeyUsage = 0;
                    requiredCertType = 0;
                    INVALID_USAGE();
                }
                break;

            case certUsageAnyCA:
            case certUsageProtectedObjectSigner:
            case certUsageUserCertImport:
            case certUsageVerifyCA:
                /* these usages cannot be verified */
                NEXT_USAGE();

            default:
                PORT_Assert(0);
                requiredKeyUsage = 0;
                requiredCertType = 0;
                INVALID_USAGE();
        }
        if (CERT_CheckKeyUsage(cert, requiredKeyUsage) != SECSuccess) {
            if (PR_TRUE == requiredUsage) {
                PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE);
            }
            LOG_ERROR(log, cert, 0, requiredKeyUsage);
            INVALID_USAGE();
        }
        if (!(certType & requiredCertType)) {
            if (PR_TRUE == requiredUsage) {
                PORT_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE);
            }
            LOG_ERROR(log, cert, 0, requiredCertType);
            INVALID_USAGE();
        }

        rv = cert_CheckLeafTrust(cert, certUsage, &flags, &trusted);
        if (rv == SECFailure) {
            if (PR_TRUE == requiredUsage) {
                PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
            }
            LOG_ERROR(log, cert, 0, flags);
            INVALID_USAGE();
        } else if (trusted) {
            VALID_USAGE();
        }

        if (PR_TRUE == revoked || PR_TRUE == sigerror) {
            INVALID_USAGE();
        }

        rv = cert_VerifyCertChain(handle, cert,
                                  checkSig, &sigerror,
                                  certUsage, t, wincx, log,
                                  &revoked);

        if (rv != SECSuccess) {
            /* EXIT_IF_NOT_LOGGING(log); XXX ???? */
            INVALID_USAGE();
        }

        /*
         * Check OCSP revocation status, but only if the cert we are checking
         * is not a status responder 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) by
         * bypassing this code.
         */

        if (PR_FALSE == checkedOCSP) {
            checkedOCSP = PR_TRUE; /* only check OCSP once */
            statusConfig = CERT_GetStatusConfig(handle);
            if (requiredUsages != certificateUsageStatusResponder &&
                statusConfig != NULL) {
                if (statusConfig->statusChecker != NULL) {
                    rv = (*statusConfig->statusChecker)(handle, cert,
                                                        t, wincx);
                    if (rv != SECSuccess) {
                        LOG_ERROR(log, cert, 0, 0);
                        revoked = PR_TRUE;
                        INVALID_USAGE();
                    }
                }
            }
        }

        NEXT_USAGE();
    }

loser:
    return (valid);
}

SECStatus
CERT_VerifyCert(CERTCertDBHandle *handle, CERTCertificate *cert,
                PRBool checkSig, SECCertUsage certUsage, PRTime t,
                void *wincx, CERTVerifyLog *log)
{
    return cert_VerifyCertWithFlags(handle, cert, checkSig, certUsage, t,
                                    CERT_VERIFYCERT_USE_DEFAULTS, wincx, log);
}

SECStatus
cert_VerifyCertWithFlags(CERTCertDBHandle *handle, CERTCertificate *cert,
                         PRBool checkSig, SECCertUsage certUsage, PRTime t,
                         PRUint32 flags, void *wincx, CERTVerifyLog *log)
{
    SECStatus rv;
    unsigned int requiredKeyUsage;
    unsigned int requiredCertType;
    unsigned int failedFlags;
    unsigned int certType;
    PRBool trusted;
    PRBool allowOverride;
    SECCertTimeValidity validity;
    CERTStatusConfig *statusConfig;

#ifdef notdef
    /* 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);
    }
#endif

    /* make sure that the cert is valid at time t */
    allowOverride = (PRBool)((certUsage == certUsageSSLServer) ||
                             (certUsage == certUsageSSLServerWithStepUp) ||
                             (certUsage == certUsageIPsec));
    validity = CERT_CheckCertValidTimes(cert, t, allowOverride);
    if (validity != secCertTimeValid) {
        LOG_ERROR_OR_EXIT(log, cert, 0, validity);
    }

    /* check key usage and netscape cert type */
    cert_GetCertType(cert);
    certType = cert->nsCertType;
    switch (certUsage) {
        case certUsageSSLClient:
        case certUsageSSLServer:
        case certUsageSSLServerWithStepUp:
        case certUsageIPsec:
        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:
        case certUsageAnyCA:
            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);
    }

    rv = cert_CheckLeafTrust(cert, certUsage, &failedFlags, &trusted);
    if (rv == SECFailure) {
        PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
        LOG_ERROR_OR_EXIT(log, cert, 0, failedFlags);
    } else if (trusted) {
        goto done;
    }

    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 responder itself and the caller did not ask us to skip the check.
     * 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 (!(flags & CERT_VERIFYCERT_SKIP_OCSP) &&
        certUsage != certUsageStatusResponder) {
        statusConfig = CERT_GetStatusConfig(handle);
        if (statusConfig && statusConfig->statusChecker) {
            rv = (*statusConfig->statusChecker)(handle, cert,
                                                t, wincx);
            if (rv != SECSuccess) {
                LOG_ERROR_OR_EXIT(log, cert, 0, 0);
            }
        }
    }

done:
    if (log && log->head) {
        return SECFailure;
    }
    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_VerifyCertificateNow(CERTCertDBHandle *handle, CERTCertificate *cert,
                          PRBool checkSig, SECCertificateUsage requiredUsages,
                          void *wincx, SECCertificateUsage *returnedUsages)
{
    return (CERT_VerifyCertificate(handle, cert, checkSig,
                                   requiredUsages, PR_Now(), wincx, NULL, returnedUsages));
}

/* obsolete, do not use for new code */
SECStatus
CERT_VerifyCertNow(CERTCertDBHandle *handle, CERTCertificate *cert,
                   PRBool checkSig, SECCertUsage certUsage, void *wincx)
{
    return (CERT_VerifyCert(handle, cert, checkSig,
                            certUsage, PR_Now(), wincx, NULL));
}

/* [ FROM pcertdb.c ] */
/*
 * Supported usage values and types:
 *  certUsageSSLClient
 *  certUsageSSLServer
 *  certUsageSSLServerWithStepUp
 *  certUsageIPsec
 *  certUsageEmailSigner
 *  certUsageEmailRecipient
 *  certUsageObjectSigner
 */

CERTCertificate *
CERT_FindMatchingCert(CERTCertDBHandle *handle, SECItem *derName,
                      CERTCertOwner owner, SECCertUsage usage,
                      PRBool preferTrusted, PRTime validTime, PRBool validOnly)
{
    CERTCertList *certList = NULL;
    CERTCertificate *cert = NULL;
    CERTCertTrust certTrust;
    unsigned int requiredTrustFlags;
    SECTrustType requiredTrustType;
    unsigned int flags;

    PRBool lookingForCA = PR_FALSE;
    SECStatus rv;
    CERTCertListNode *node;
    CERTCertificate *saveUntrustedCA = NULL;

    /* if preferTrusted is set, must be a CA cert */
    PORT_Assert(!(preferTrusted && (owner != certOwnerCA)));

    if (owner == certOwnerCA) {
        lookingForCA = PR_TRUE;
        if (preferTrusted) {
            rv = CERT_TrustFlagsForCACertUsage(usage, &requiredTrustFlags,
                                               &requiredTrustType);
            if (rv != SECSuccess) {
                goto loser;
            }
            requiredTrustFlags |= CERTDB_VALID_CA;
        }
    }

    certList = CERT_CreateSubjectCertList(NULL, handle, derName, validTime,
                                          validOnly);
    if (certList != NULL) {
        rv = CERT_FilterCertListByUsage(certList, usage, lookingForCA);
        if (rv != SECSuccess) {
            goto loser;
        }

        node = CERT_LIST_HEAD(certList);

        while (!CERT_LIST_END(node, certList)) {
            cert = node->cert;

            /* looking for a trusted CA cert */
            if ((owner == certOwnerCA) && preferTrusted &&
                (requiredTrustType != trustTypeNone)) {

                if (CERT_GetCertTrust(cert, &certTrust) != SECSuccess) {
                    flags = 0;
                } else {
                    flags = SEC_GET_TRUST_FLAGS(&certTrust, requiredTrustType);
                }

                if ((flags & requiredTrustFlags) != requiredTrustFlags) {
                    /* cert is not trusted */
                    /* if this is the first cert to get this far, then save
                     * it, so we can use it if we can't find a trusted one
                     */
                    if (saveUntrustedCA == NULL) {
                        saveUntrustedCA = cert;
                    }
                    goto endloop;
                }
            }
            /* if we got this far, then this cert meets all criteria */
            break;

        endloop:
            node = CERT_LIST_NEXT(node);
            cert = NULL;
        }

        /* use the saved one if we have it */
        if (cert == NULL) {
            cert = saveUntrustedCA;
        }

        /* if we found one then bump the ref count before freeing the list */
        if (cert != NULL) {
            /* bump the ref count */
            cert = CERT_DupCertificate(cert);
        }

        CERT_DestroyCertList(certList);
    }

    return (cert);

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

    return (NULL);
}

/* [ From certdb.c ] */
/*
 * 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;
    PRTime 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;

            if (subjectCert->issuerName != NULL) {
                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(PLArenaPool *arena, CERTCertificate *cert,
                                 char *expiredString, char *notYetGoodString)
{
    SECCertTimeValidity validity;
    char *nickname = NULL, *tmpstr = NULL;
    const char *srcNickname = cert->nickname;
    if (!srcNickname) {
        srcNickname = "{???}";
    }

    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(srcNickname);
        } else {
            nickname = PORT_ArenaStrdup(arena, srcNickname);
        }

        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", srcNickname,
                                 expiredString);
        } else if (validity == secCertTimeNotValidYet) {
            /* not yet valid */
            tmpstr = PR_smprintf("%s%s", srcNickname,
                                 notYetGoodString);
        } else {
            /* undetermined */
            tmpstr = PR_smprintf("%s",
                                 "(NULL) (Validity Unknown)");
        }

        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;
    PLArenaPool *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, PRTime time, SECCertUsage usage)
{
    CERTCertList *chain = NULL;
    int count = 0;

    if (NULL == cert) {
        return NULL;
    }

    cert = CERT_DupCertificate(cert);
    if (NULL == cert) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    chain = CERT_NewCertList();
    if (NULL == chain) {
        CERT_DestroyCertificate(cert);
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    while (cert != NULL && ++count <= CERT_MAX_CERT_CHAIN) {
        if (SECSuccess != CERT_AddCertToListTail(chain, cert)) {
            /* return partial chain */
            CERT_DestroyCertificate(cert);
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            return chain;
        }

        if (cert->isRoot) {
            /* return complete chain */
            return chain;
        }

        cert = CERT_FindCertIssuer(cert, time, usage);
    }

    /* return partial chain */
    CERT_DestroyCertificate(cert);
    PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
    return chain;
}
