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

#include "nsspki.h"
#include "pki.h"
#include "pkit.h"
#include "pkitm.h"
#include "pki3hack.h"

PRBool
CERT_MatchNickname(char *name1, char *name2)
{
    char *nickname1 = NULL;
    char *nickname2 = NULL;
    char *token1;
    char *token2;

    /* first deal with the straight comparison */
    if (PORT_Strcmp(name1, name2) == 0) {
        return PR_TRUE;
    }
    /* we need to handle the case where one name has an explicit token and the other
     * doesn't */
    token1 = PORT_Strchr(name1, ':');
    token2 = PORT_Strchr(name2, ':');
    if ((token1 && token2) || (!token1 && !token2)) {
        /* either both token names are specified or neither are, not match */
        return PR_FALSE;
    }
    if (token1) {
        nickname1 = token1;
        nickname2 = name2;
    } else {
        nickname1 = token2;
        nickname2 = name1;
    }
    nickname1++;
    if (PORT_Strcmp(nickname1, nickname2) != 0) {
        return PR_FALSE;
    }
    /* Bug 1192443 - compare the other token with the internal slot here */
    return PR_TRUE;
}

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

    time = PR_Now();

    nicknames = CERT_GetCertNicknames(handle, SEC_CERT_NICKNAMES_USER,
                                      proto_win);

    if ((nicknames == NULL) || (nicknames->numnicknames == 0)) {
        goto loser;
    }

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

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

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

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

            CERT_FilterCertListForUserCerts(certList);

            /* drop the extra reference */
            CERT_DestroyCertificate(cert);
        }

        nnptr++;
        nn--;
    }

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

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

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

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

        if (!certList) {
            goto loser;
        }

        flags = (PRBool *)PORT_ZAlloc(sizeof(PRBool) * nn);
        if (flags == NULL) {
            goto loser;
        }

        node = CERT_LIST_HEAD(certList);

        /* treverse all certs in the list */
        while (!CERT_LIST_END(node, certList)) {

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

    goto done;

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

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

    return (certList);
}

/*
 * Find a user certificate that matchs the given criteria.
 *
 *      "handle" - database to search
 *      "nickname" - nickname to match
 *      "usage" - certificate usage to match
 *      "validOnly" - only return certs that are curently valid
 *      "proto_win" - window handle passed to pkcs11
 */
CERTCertificate *
CERT_FindUserCertByUsage(CERTCertDBHandle *handle,
                         const char *nickname,
                         SECCertUsage usage,
                         PRBool validOnly,
                         void *proto_win)
{
    CERTCertificate *cert = NULL;
    CERTCertList *certList = NULL;
    SECStatus rv;
    PRTime time;

    time = PR_Now();

    /* use the pk11 call so that we pick up any certs on tokens,
     * which may require login
     */
    /* XXX - why is this restricted? */
    if (proto_win != NULL) {
        cert = PK11_FindCertFromNickname(nickname, proto_win);
    }

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

    if (cert != NULL) {
        unsigned int requiredKeyUsage;
        unsigned int requiredCertType;

        rv = CERT_KeyUsageAndTypeForCertUsage(usage, PR_FALSE,
                                              &requiredKeyUsage, &requiredCertType);
        if (rv != SECSuccess) {
            /* drop the extra reference */
            CERT_DestroyCertificate(cert);
            cert = NULL;
            goto loser;
        }
        /* If we already found the right cert, just return it */
        if ((!validOnly || CERT_CheckCertValidTimes(cert, time, PR_FALSE) == secCertTimeValid) &&
            (CERT_CheckKeyUsage(cert, requiredKeyUsage) == SECSuccess) &&
            (cert->nsCertType & requiredCertType) &&
            CERT_IsUserCert(cert)) {
            return (cert);
        }

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

        CERT_FilterCertListForUserCerts(certList);

        /* drop the extra reference */
        CERT_DestroyCertificate(cert);
        cert = NULL;
    }

    if (certList == NULL) {
        goto loser;
    }

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

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

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

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

    return (cert);
}

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

    certList = CERT_FindUserCertsByUsage(handle, usage, PR_TRUE, PR_TRUE,
                                         proto_win);
    if (certList == NULL) {
        goto loser;
    }

    rv = CERT_FilterCertListByCANames(certList, nCANames, caNames, usage);
    if (rv != SECSuccess) {
        goto loser;
    }

    goto done;

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

done:

    return (certList);
}

typedef struct stringNode {
    struct stringNode *next;
    char *string;
} stringNode;

static PRStatus
CollectNicknames(NSSCertificate *c, void *data)
{
    CERTCertNicknames *names;
    PRBool saveit = PR_FALSE;
    stringNode *node;
    int len;
#ifdef notdef
    NSSTrustDomain *td;
    NSSTrust *trust;
#endif
    char *stanNickname;
    char *nickname = NULL;

    names = (CERTCertNicknames *)data;

    stanNickname = nssCertificate_GetNickname(c, NULL);

    if (stanNickname) {
        nss_ZFreeIf(stanNickname);
        stanNickname = NULL;
        if (names->what == SEC_CERT_NICKNAMES_USER) {
            saveit = NSSCertificate_IsPrivateKeyAvailable(c, NULL, NULL);
        }
#ifdef notdef
        else {
            td = NSSCertificate_GetTrustDomain(c);
            if (!td) {
                return PR_SUCCESS;
            }
            trust = nssTrustDomain_FindTrustForCertificate(td, c);

            switch (names->what) {
                case SEC_CERT_NICKNAMES_ALL:
                    if ((trust->sslFlags & (CERTDB_VALID_CA | CERTDB_VALID_PEER)) ||
                        (trust->emailFlags & (CERTDB_VALID_CA | CERTDB_VALID_PEER)) ||
                        (trust->objectSigningFlags &
                         (CERTDB_VALID_CA | CERTDB_VALID_PEER))) {
                        saveit = PR_TRUE;
                    }

                    break;
                case SEC_CERT_NICKNAMES_SERVER:
                    if (trust->sslFlags & CERTDB_VALID_PEER) {
                        saveit = PR_TRUE;
                    }

                    break;
                case SEC_CERT_NICKNAMES_CA:
                    if (((trust->sslFlags & CERTDB_VALID_CA) == CERTDB_VALID_CA) ||
                        ((trust->emailFlags & CERTDB_VALID_CA) == CERTDB_VALID_CA) ||
                        ((trust->objectSigningFlags & CERTDB_VALID_CA) ==
                         CERTDB_VALID_CA)) {
                        saveit = PR_TRUE;
                    }
                    break;
            }
        }
#endif
    }

    /* traverse the list of collected nicknames and make sure we don't make
     * a duplicate
     */
    if (saveit) {
        nickname = STAN_GetCERTCertificateName(NULL, c);
        /* nickname can only be NULL here if we are having memory
         * alloc problems */
        if (nickname == NULL) {
            return PR_FAILURE;
        }
        node = (stringNode *)names->head;
        while (node != NULL) {
            if (PORT_Strcmp(nickname, node->string) == 0) {
                /* if the string matches, then don't save this one */
                saveit = PR_FALSE;
                break;
            }
            node = node->next;
        }
    }

    if (saveit) {

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

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

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

        /* bump the count */
        names->numnicknames++;
    }

    if (nickname)
        PORT_Free(nickname);
    return (PR_SUCCESS);
}

CERTCertNicknames *
CERT_GetCertNicknames(CERTCertDBHandle *handle, int what, void *wincx)
{
    PLArenaPool *arena;
    CERTCertNicknames *names;
    int i;
    stringNode *node;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return (NULL);
    }

    names = (CERTCertNicknames *)PORT_ArenaAlloc(arena, sizeof(CERTCertNicknames));
    if (names == NULL) {
        goto loser;
    }

    names->arena = arena;
    names->head = NULL;
    names->numnicknames = 0;
    names->nicknames = NULL;
    names->what = what;
    names->totallen = 0;

    /* make sure we are logged in */
    (void)pk11_TraverseAllSlots(NULL, NULL, PR_TRUE, wincx);

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

        if (names->nicknames == NULL) {
            goto loser;
        }

        node = (stringNode *)names->head;

        for (i = 0; i < names->numnicknames; i++) {
            PORT_Assert(node != NULL);

            names->nicknames[i] = node->string;
            names->totallen += PORT_Strlen(node->string);
            node = node->next;
        }

        PORT_Assert(node == NULL);
    }

    return (names);

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

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

    return;
}

/* [ FROM pcertdb.c ] */

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

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

    return;
}

static SECStatus
CollectDistNames(CERTCertificate *cert, SECItem *k, void *data)
{
    CERTDistNames *names;
    PRBool saveit = PR_FALSE;
    CERTCertTrust trust;
    dnameNode *node;
    int len;

    names = (CERTDistNames *)data;

    if (CERT_GetCertTrust(cert, &trust) == SECSuccess) {
        /* only collect names of CAs trusted for issuing SSL clients */
        if (trust.sslFlags & CERTDB_TRUSTED_CLIENT_CA) {
            saveit = PR_TRUE;
        }
    }

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

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

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

        /* bump the count */
        names->nnames++;
    }

    return (SECSuccess);
}

/*
 * Return all of the CAs that are "trusted" for SSL.
 */
CERTDistNames *
CERT_DupDistNames(CERTDistNames *orig)
{
    PLArenaPool *arena;
    CERTDistNames *names;
    int i;
    SECStatus rv;

    /* allocate an arena to use */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return (NULL);
    }

    /* allocate the header structure */
    names = (CERTDistNames *)PORT_ArenaAlloc(arena, sizeof(CERTDistNames));
    if (names == NULL) {
        goto loser;
    }

    /* initialize the header struct */
    names->arena = arena;
    names->head = NULL;
    names->nnames = orig->nnames;
    names->names = NULL;

    /* construct the array from the list */
    if (orig->nnames) {
        names->names = (SECItem *)PORT_ArenaNewArray(arena, SECItem,
                                                     orig->nnames);
        if (names->names == NULL) {
            goto loser;
        }
        for (i = 0; i < orig->nnames; i++) {
            rv = SECITEM_CopyItem(arena, &names->names[i], &orig->names[i]);
            if (rv != SECSuccess) {
                goto loser;
            }
        }
    }
    return (names);

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

CERTDistNames *
CERT_GetSSLCACerts(CERTCertDBHandle *handle)
{
    PLArenaPool *arena;
    CERTDistNames *names;
    int i;
    SECStatus rv;
    dnameNode *node;

    /* allocate an arena to use */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return (NULL);
    }

    /* allocate the header structure */
    names = (CERTDistNames *)PORT_ArenaAlloc(arena, sizeof(CERTDistNames));
    if (names == NULL) {
        goto loser;
    }

    /* initialize the header struct */
    names->arena = arena;
    names->head = NULL;
    names->nnames = 0;
    names->names = NULL;

    /* collect the names from the database */
    rv = PK11_TraverseSlotCerts(CollectDistNames, (void *)names, NULL);
    if (rv) {
        goto loser;
    }

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

        if (names->names == NULL) {
            goto loser;
        }

        node = (dnameNode *)names->head;

        for (i = 0; i < names->nnames; i++) {
            PORT_Assert(node != NULL);

            names->names[i] = node->name;
            node = node->next;
        }

        PORT_Assert(node == NULL);
    }

    return (names);

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

CERTDistNames *
CERT_DistNamesFromCertList(CERTCertList *certList)
{
    CERTDistNames *dnames = NULL;
    PLArenaPool *arena;
    CERTCertListNode *node = NULL;
    SECItem *names = NULL;
    int listLen = 0, i = 0;

    if (certList == NULL) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    node = CERT_LIST_HEAD(certList);
    while (!CERT_LIST_END(node, certList)) {
        listLen += 1;
        node = CERT_LIST_NEXT(node);
    }

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL)
        goto loser;
    dnames = PORT_ArenaZNew(arena, CERTDistNames);
    if (dnames == NULL)
        goto loser;

    dnames->arena = arena;
    dnames->nnames = listLen;
    dnames->names = names = PORT_ArenaZNewArray(arena, SECItem, listLen);
    if (names == NULL)
        goto loser;

    node = CERT_LIST_HEAD(certList);
    while (!CERT_LIST_END(node, certList)) {
        CERTCertificate *cert = node->cert;
        SECStatus rv = SECITEM_CopyItem(arena, &names[i++], &cert->derSubject);
        if (rv == SECFailure) {
            goto loser;
        }
        node = CERT_LIST_NEXT(node);
    }
    return dnames;
loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return NULL;
}

CERTDistNames *
CERT_DistNamesFromNicknames(CERTCertDBHandle *handle, char **nicknames,
                            int nnames)
{
    CERTDistNames *dnames = NULL;
    PLArenaPool *arena;
    int i, rv;
    SECItem *names = NULL;
    CERTCertificate *cert = NULL;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL)
        goto loser;
    dnames = PORT_ArenaZNew(arena, CERTDistNames);
    if (dnames == NULL)
        goto loser;

    dnames->arena = arena;
    dnames->nnames = nnames;
    dnames->names = names = PORT_ArenaZNewArray(arena, SECItem, nnames);
    if (names == NULL)
        goto loser;

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

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

/* [ from pcertdb.c - calls Ascii to Name ] */
/*
 * Lookup a certificate in the database by name
 */
CERTCertificate *
CERT_FindCertByNameString(CERTCertDBHandle *handle, char *nameStr)
{
    CERTName *name;
    SECItem *nameItem;
    CERTCertificate *cert = NULL;
    PLArenaPool *arena = NULL;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

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

    name = CERT_AsciiToName(nameStr);

    if (name) {
        nameItem = SEC_ASN1EncodeItem(arena, NULL, (void *)name,
                                      CERT_NameTemplate);
        if (nameItem != NULL) {
            cert = CERT_FindCertByName(handle, nameItem);
        }
        CERT_DestroyName(name);
    }

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

    return (cert);
}

/* From certv3.c */

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

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

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

    dps = CERT_DecodeCRLDistributionPoints(cert->arena, &encodedExtenValue);

    PORT_Free(encodedExtenValue.data);

    return dps;
}

/* From crl.c */
CERTSignedCrl *
CERT_ImportCRL(CERTCertDBHandle *handle, SECItem *derCRL, char *url, int type, void *wincx)
{
    CERTSignedCrl *retCrl = NULL;
    PK11SlotInfo *slot = PK11_GetInternalKeySlot();
    retCrl = PK11_ImportCRL(slot, derCRL, url, type, wincx,
                            CRL_IMPORT_DEFAULT_OPTIONS, NULL, CRL_DECODE_DEFAULT_OPTIONS);
    PK11_FreeSlot(slot);

    return retCrl;
}

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

    handle = CERT_GetDefaultCertDB();

    while (numcerts--) {
        derCert = certs;
        certs++;

        /* decode my certificate */
        /* This use is ok -- only looks at decoded parts, calls NewTemp later */
        newcert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
        if (newcert == NULL) {
            goto loser;
        }

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

        /* does it have the CA extension */

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

        if (!isca) {
            if (!trusted) {
                goto endloop;
            }
            trust.sslFlags = CERTDB_VALID_CA;
            trust.emailFlags = CERTDB_VALID_CA;
            trust.objectSigningFlags = CERTDB_VALID_CA;
        } else {
            /* SSL ca's must have the ssl bit set */
            if ((certUsage == certUsageSSLCA) &&
                ((certtype & NS_CERT_TYPE_SSL_CA) != NS_CERT_TYPE_SSL_CA)) {
                goto endloop;
            }

            /* it passed all of the tests, so lets add it to the database */
            /* mark it as a CA */
            PORT_Memset((void *)&trust, 0, sizeof(trust));
            switch (certUsage) {
                case certUsageSSLCA:
                    trust.sslFlags = CERTDB_VALID_CA;
                    break;
                case certUsageUserCertImport:
                    if ((certtype & NS_CERT_TYPE_SSL_CA) == NS_CERT_TYPE_SSL_CA) {
                        trust.sslFlags = CERTDB_VALID_CA;
                    }
                    if ((certtype & NS_CERT_TYPE_EMAIL_CA) ==
                        NS_CERT_TYPE_EMAIL_CA) {
                        trust.emailFlags = CERTDB_VALID_CA;
                    }
                    if ((certtype & NS_CERT_TYPE_OBJECT_SIGNING_CA) ==
                        NS_CERT_TYPE_OBJECT_SIGNING_CA) {
                        trust.objectSigningFlags = CERTDB_VALID_CA;
                    }
                    break;
                default:
                    PORT_Assert(0);
                    break;
            }
        }

        cert = CERT_NewTempCertificate(handle, derCert, NULL,
                                       PR_FALSE, PR_FALSE);
        if (cert == NULL) {
            goto loser;
        }

        /* if the cert is temp, make it perm; otherwise we're done */
        if (cert->istemp) {
            /* get a default nickname for it */
            nickname = CERT_MakeCANickname(cert);

            rv = CERT_AddTempCertToPerm(cert, nickname, &trust);

            /* free the nickname */
            if (nickname) {
                PORT_Free(nickname);
            }
        } else {
            rv = SECSuccess;
        }

        CERT_DestroyCertificate(cert);
        cert = NULL;

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

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

    rv = SECSuccess;
    goto done;
loser:
    rv = SECFailure;
done:

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

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

    return (rv);
}

SECStatus
CERT_ImportCAChain(SECItem *certs, int numcerts, SECCertUsage certUsage)
{
    return cert_ImportCAChain(certs, numcerts, certUsage, PR_FALSE);
}

SECStatus
CERT_ImportCAChainTrusted(SECItem *certs, int numcerts, SECCertUsage certUsage)
{
    return cert_ImportCAChain(certs, numcerts, certUsage, PR_TRUE);
}

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

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

CERTCertificateList *
CERT_CertChainFromCert(CERTCertificate *cert, SECCertUsage usage,
                       PRBool includeRoot)
{
    CERTCertificateList *chain = NULL;
    NSSCertificate **stanChain;
    NSSCertificate *stanCert;
    PLArenaPool *arena;
    NSSUsage nssUsage;
    int i, len;
    NSSTrustDomain *td = STAN_GetDefaultTrustDomain();
    NSSCryptoContext *cc = STAN_GetDefaultCryptoContext();

    stanCert = STAN_GetNSSCertificate(cert);
    if (!stanCert) {
        /* error code is set */
        return NULL;
    }
    nssUsage.anyUsage = PR_FALSE;
    nssUsage.nss3usage = usage;
    nssUsage.nss3lookingForCA = PR_FALSE;
    stanChain = NSSCertificate_BuildChain(stanCert, NULL, &nssUsage, NULL, NULL,
                                          CERT_MAX_CERT_CHAIN, NULL, NULL, td, cc);
    if (!stanChain) {
        PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
        return NULL;
    }

    len = 0;
    stanCert = stanChain[0];
    while (stanCert) {
        stanCert = stanChain[++len];
    }

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

    chain = (CERTCertificateList *)PORT_ArenaAlloc(arena,
                                                   sizeof(CERTCertificateList));
    if (!chain)
        goto loser;
    chain->certs = (SECItem *)PORT_ArenaAlloc(arena, len * sizeof(SECItem));
    if (!chain->certs)
        goto loser;
    i = 0;
    stanCert = stanChain[i];
    while (stanCert) {
        SECItem derCert;
        CERTCertificate *cCert = STAN_GetCERTCertificate(stanCert);
        if (!cCert) {
            goto loser;
        }
        derCert.len = (unsigned int)stanCert->encoding.size;
        derCert.data = (unsigned char *)stanCert->encoding.data;
        derCert.type = siBuffer;
        SECITEM_CopyItem(arena, &chain->certs[i], &derCert);
        stanCert = stanChain[++i];
        if (!stanCert && !cCert->isRoot) {
            /* reached the end of the chain, but the final cert is
             * not a root.  Don't discard it.
             */
            includeRoot = PR_TRUE;
        }
        CERT_DestroyCertificate(cCert);
    }
    if (!includeRoot && len > 1) {
        chain->len = len - 1;
    } else {
        chain->len = len;
    }

    chain->arena = arena;
    nss_ZFreeIf(stanChain);
    return chain;
loser:
    i = 0;
    stanCert = stanChain[i];
    while (stanCert) {
        CERTCertificate *cCert = STAN_GetCERTCertificate(stanCert);
        if (cCert) {
            CERT_DestroyCertificate(cCert);
        }
        stanCert = stanChain[++i];
    }
    nss_ZFreeIf(stanChain);
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return NULL;
}

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

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

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

    return chain;

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

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

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

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

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

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

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