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

/*
 * Moved from secpkcs7.c
 */

#include "cert.h"
#include "certi.h"
#include "secder.h"
#include "secasn1.h"
#include "secoid.h"
#include "certdb.h"
#include "certxutl.h"
#include "prtime.h"
#include "secerr.h"
#include "pk11func.h"
#include "dev.h"
#include "dev3hack.h"
#include "nssbase.h"
#if defined(DPC_RWLOCK) || defined(GLOBAL_RWLOCK)
#include "nssrwlk.h"
#endif
#include "pk11priv.h"

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

static const SEC_ASN1Template SEC_CERTExtensionsTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF, 0, SEC_CERTExtensionTemplate }
};

/*
 * XXX Also, these templates need to be tested; Lisa did the obvious
 * translation but they still should be verified.
 */

const SEC_ASN1Template CERT_IssuerAndSNTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTIssuerAndSN) },
    { SEC_ASN1_SAVE, offsetof(CERTIssuerAndSN, derIssuer) },
    { SEC_ASN1_INLINE, offsetof(CERTIssuerAndSN, issuer), CERT_NameTemplate },
    { SEC_ASN1_INTEGER, offsetof(CERTIssuerAndSN, serialNumber) },
    { 0 }
};

SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
SEC_ASN1_MKSUB(CERT_TimeChoiceTemplate)

static const SEC_ASN1Template cert_CrlKeyTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrlKey) },
    { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(CERTCrlKey, dummy) },
    { SEC_ASN1_SKIP },
    { SEC_ASN1_ANY, offsetof(CERTCrlKey, derName) },
    { SEC_ASN1_SKIP_REST },
    { 0 }
};

static const SEC_ASN1Template cert_CrlEntryTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrlEntry) },
    { SEC_ASN1_INTEGER, offsetof(CERTCrlEntry, serialNumber) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrlEntry, revocationDate),
      SEC_ASN1_SUB(CERT_TimeChoiceTemplate) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF,
      offsetof(CERTCrlEntry, extensions), SEC_CERTExtensionTemplate },
    { 0 }
};

const SEC_ASN1Template CERT_CrlTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrl) },
    { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(CERTCrl, version) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, signatureAlg),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { SEC_ASN1_SAVE, offsetof(CERTCrl, derName) },
    { SEC_ASN1_INLINE, offsetof(CERTCrl, name), CERT_NameTemplate },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, lastUpdate),
      SEC_ASN1_SUB(CERT_TimeChoiceTemplate) },
    { SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL | SEC_ASN1_XTRN,
      offsetof(CERTCrl, nextUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF, offsetof(CERTCrl, entries),
      cert_CrlEntryTemplate },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC |
          SEC_ASN1_EXPLICIT | 0,
      offsetof(CERTCrl, extensions), SEC_CERTExtensionsTemplate },
    { 0 }
};

const SEC_ASN1Template CERT_CrlTemplateNoEntries[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrl) },
    { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(CERTCrl, version) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, signatureAlg),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { SEC_ASN1_SAVE, offsetof(CERTCrl, derName) },
    { SEC_ASN1_INLINE, offsetof(CERTCrl, name), CERT_NameTemplate },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, lastUpdate),
      SEC_ASN1_SUB(CERT_TimeChoiceTemplate) },
    { SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL | SEC_ASN1_XTRN,
      offsetof(CERTCrl, nextUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF |
      SEC_ASN1_SKIP }, /* skip entries */
    { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC |
          SEC_ASN1_EXPLICIT | 0,
      offsetof(CERTCrl, extensions), SEC_CERTExtensionsTemplate },
    { 0 }
};

const SEC_ASN1Template CERT_CrlTemplateEntriesOnly[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrl) },
    { SEC_ASN1_SKIP | SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL },
    { SEC_ASN1_SKIP },
    { SEC_ASN1_SKIP },
    { SEC_ASN1_SKIP | SEC_ASN1_INLINE | SEC_ASN1_XTRN,
      offsetof(CERTCrl, lastUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) },
    { SEC_ASN1_SKIP | SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL | SEC_ASN1_XTRN,
      offsetof(CERTCrl, nextUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF, offsetof(CERTCrl, entries),
      cert_CrlEntryTemplate }, /* decode entries */
    { SEC_ASN1_SKIP_REST },
    { 0 }
};

const SEC_ASN1Template CERT_SignedCrlTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTSignedCrl) },
    { SEC_ASN1_SAVE, offsetof(CERTSignedCrl, signatureWrap.data) },
    { SEC_ASN1_INLINE, offsetof(CERTSignedCrl, crl), CERT_CrlTemplate },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
      offsetof(CERTSignedCrl, signatureWrap.signatureAlgorithm),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { SEC_ASN1_BIT_STRING, offsetof(CERTSignedCrl, signatureWrap.signature) },
    { 0 }
};

static const SEC_ASN1Template cert_SignedCrlTemplateNoEntries[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTSignedCrl) },
    { SEC_ASN1_SAVE, offsetof(CERTSignedCrl, signatureWrap.data) },
    { SEC_ASN1_INLINE, offsetof(CERTSignedCrl, crl),
      CERT_CrlTemplateNoEntries },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
      offsetof(CERTSignedCrl, signatureWrap.signatureAlgorithm),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { SEC_ASN1_BIT_STRING, offsetof(CERTSignedCrl, signatureWrap.signature) },
    { 0 }
};

const SEC_ASN1Template CERT_SetOfSignedCrlTemplate[] = {
    { SEC_ASN1_SET_OF, 0, CERT_SignedCrlTemplate },
};

/* get CRL version */
int
cert_get_crl_version(CERTCrl* crl)
{
    /* CRL version is defaulted to v1 */
    int version = SEC_CRL_VERSION_1;
    if (crl && crl->version.data != 0) {
        version = (int)DER_GetUInteger(&crl->version);
    }
    return version;
}

/* check the entries in the CRL */
SECStatus
cert_check_crl_entries(CERTCrl* crl)
{
    CERTCrlEntry** entries;
    CERTCrlEntry* entry;
    PRBool hasCriticalExten = PR_FALSE;
    SECStatus rv = SECSuccess;

    if (!crl) {
        return SECFailure;
    }

    if (crl->entries == NULL) {
        /* CRLs with no entries are valid */
        return (SECSuccess);
    }

    /* Look in the crl entry extensions.  If there is a critical extension,
       then the crl version must be v2; otherwise, it should be v1.
     */
    entries = crl->entries;
    while (*entries) {
        entry = *entries;
        if (entry->extensions) {
            /* If there is a critical extension in the entries, then the
               CRL must be of version 2.  If we already saw a critical
               extension,
               there is no need to check the version again.
            */
            if (hasCriticalExten == PR_FALSE) {
                hasCriticalExten = cert_HasCriticalExtension(entry->extensions);
                if (hasCriticalExten) {
                    if (cert_get_crl_version(crl) != SEC_CRL_VERSION_2) {
                        /* only CRL v2 critical extensions are supported */
                        PORT_SetError(SEC_ERROR_CRL_V1_CRITICAL_EXTENSION);
                        rv = SECFailure;
                        break;
                    }
                }
            }

            /* For each entry, make sure that it does not contain an unknown
               critical extension.  If it does, we must reject the CRL since
               we don't know how to process the extension.
            */
            if (cert_HasUnknownCriticalExten(entry->extensions) == PR_TRUE) {
                PORT_SetError(SEC_ERROR_CRL_UNKNOWN_CRITICAL_EXTENSION);
                rv = SECFailure;
                break;
            }
        }
        ++entries;
    }
    return (rv);
}

/* Check the version of the CRL.  If there is a critical extension in the crl
   or crl entry, then the version must be v2. Otherwise, it should be v1. If
   the crl contains critical extension(s), then we must recognized the
   extension's OID.
   */
SECStatus
cert_check_crl_version(CERTCrl* crl)
{
    PRBool hasCriticalExten = PR_FALSE;
    int version = cert_get_crl_version(crl);

    if (version > SEC_CRL_VERSION_2) {
        PORT_SetError(SEC_ERROR_CRL_INVALID_VERSION);
        return (SECFailure);
    }

    /* Check the crl extensions for a critial extension.  If one is found,
       and the version is not v2, then we are done.
     */
    if (crl->extensions) {
        hasCriticalExten = cert_HasCriticalExtension(crl->extensions);
        if (hasCriticalExten) {
            if (version != SEC_CRL_VERSION_2) {
                /* only CRL v2 critical extensions are supported */
                PORT_SetError(SEC_ERROR_CRL_V1_CRITICAL_EXTENSION);
                return (SECFailure);
            }
            /* make sure that there is no unknown critical extension */
            if (cert_HasUnknownCriticalExten(crl->extensions) == PR_TRUE) {
                PORT_SetError(SEC_ERROR_CRL_UNKNOWN_CRITICAL_EXTENSION);
                return (SECFailure);
            }
        }
    }

    return (SECSuccess);
}

/*
 * Generate a database key, based on the issuer name from a
 * DER crl.
 */
SECStatus
CERT_KeyFromDERCrl(PLArenaPool* arena, SECItem* derCrl, SECItem* key)
{
    SECStatus rv;
    CERTSignedData sd;
    CERTCrlKey crlkey;
    PLArenaPool* myArena;

    if (!arena) {
        /* arena needed for QuickDER */
        myArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    } else {
        myArena = arena;
    }
    PORT_Memset(&sd, 0, sizeof(sd));
    rv = SEC_QuickDERDecodeItem(myArena, &sd, CERT_SignedDataTemplate, derCrl);
    if (SECSuccess == rv) {
        PORT_Memset(&crlkey, 0, sizeof(crlkey));
        rv = SEC_QuickDERDecodeItem(myArena, &crlkey, cert_CrlKeyTemplate,
                                    &sd.data);
    }

    /* make a copy so the data doesn't point to memory inside derCrl, which
       may be temporary */
    if (SECSuccess == rv) {
        rv = SECITEM_CopyItem(arena, key, &crlkey.derName);
    }

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

    return rv;
}

#define GetOpaqueCRLFields(x) ((OpaqueCRLFields*)x->opaque)

SECStatus
CERT_CompleteCRLDecodeEntries(CERTSignedCrl* crl)
{
    SECStatus rv = SECSuccess;
    SECItem* crldata = NULL;
    OpaqueCRLFields* extended = NULL;

    if ((!crl) || (!(extended = (OpaqueCRLFields*)crl->opaque)) ||
        (PR_TRUE == extended->decodingError)) {
        rv = SECFailure;
    } else {
        if (PR_FALSE == extended->partial) {
            /* the CRL has already been fully decoded */
            return SECSuccess;
        }
        if (PR_TRUE == extended->badEntries) {
            /* the entries decoding already failed */
            return SECFailure;
        }
        crldata = &crl->signatureWrap.data;
        if (!crldata) {
            rv = SECFailure;
        }
    }

    if (SECSuccess == rv) {
        rv = SEC_QuickDERDecodeItem(crl->arena, &crl->crl,
                                    CERT_CrlTemplateEntriesOnly, crldata);
        if (SECSuccess == rv) {
            extended->partial = PR_FALSE; /* successful decode, avoid
                decoding again */
        } else {
            extended->decodingError = PR_TRUE;
            extended->badEntries = PR_TRUE;
            /* cache the decoding failure. If it fails the first time,
               it will fail again, which will grow the arena and leak
               memory, so we want to avoid it */
        }
        rv = cert_check_crl_entries(&crl->crl);
        if (rv != SECSuccess) {
            extended->badExtensions = PR_TRUE;
        }
    }
    return rv;
}

/*
 * take a DER CRL and decode it into a CRL structure
 * allow reusing the input DER without making a copy
 */
CERTSignedCrl*
CERT_DecodeDERCrlWithFlags(PLArenaPool* narena, SECItem* derSignedCrl, int type,
                           PRInt32 options)
{
    PLArenaPool* arena;
    CERTSignedCrl* crl;
    SECStatus rv;
    OpaqueCRLFields* extended = NULL;
    const SEC_ASN1Template* crlTemplate = CERT_SignedCrlTemplate;
    PRInt32 testOptions = options;

    PORT_Assert(derSignedCrl);
    if (!derSignedCrl) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    /* Adopting DER requires not copying it.  Code that sets ADOPT flag
     * but doesn't set DONT_COPY probably doesn't know What it is doing.
     * That condition is a programming error in the caller.
     */
    testOptions &= (CRL_DECODE_ADOPT_HEAP_DER | CRL_DECODE_DONT_COPY_DER);
    PORT_Assert(testOptions != CRL_DECODE_ADOPT_HEAP_DER);
    if (testOptions == CRL_DECODE_ADOPT_HEAP_DER) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    /* make a new arena if needed */
    if (narena == NULL) {
        arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
        if (!arena) {
            return NULL;
        }
    } else {
        arena = narena;
    }

    /* allocate the CRL structure */
    crl = (CERTSignedCrl*)PORT_ArenaZAlloc(arena, sizeof(CERTSignedCrl));
    if (!crl) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    crl->arena = arena;

    /* allocate opaque fields */
    crl->opaque = (void*)PORT_ArenaZAlloc(arena, sizeof(OpaqueCRLFields));
    if (!crl->opaque) {
        goto loser;
    }
    extended = (OpaqueCRLFields*)crl->opaque;
    if (options & CRL_DECODE_ADOPT_HEAP_DER) {
        extended->heapDER = PR_TRUE;
    }
    if (options & CRL_DECODE_DONT_COPY_DER) {
        crl->derCrl = derSignedCrl; /* DER is not copied . The application
                                       must keep derSignedCrl until it
                                       destroys the CRL */
    } else {
        crl->derCrl = (SECItem*)PORT_ArenaZAlloc(arena, sizeof(SECItem));
        if (crl->derCrl == NULL) {
            goto loser;
        }
        rv = SECITEM_CopyItem(arena, crl->derCrl, derSignedCrl);
        if (rv != SECSuccess) {
            goto loser;
        }
    }

    /* Save the arena in the inner crl for CRL extensions support */
    crl->crl.arena = arena;
    if (options & CRL_DECODE_SKIP_ENTRIES) {
        crlTemplate = cert_SignedCrlTemplateNoEntries;
        extended->partial = PR_TRUE;
    }

    /* decode the CRL info */
    switch (type) {
        case SEC_CRL_TYPE:
            rv = SEC_QuickDERDecodeItem(arena, crl, crlTemplate, crl->derCrl);
            if (rv != SECSuccess) {
                extended->badDER = PR_TRUE;
                break;
            }
            /* check for critical extensions */
            rv = cert_check_crl_version(&crl->crl);
            if (rv != SECSuccess) {
                extended->badExtensions = PR_TRUE;
                break;
            }

            if (PR_TRUE == extended->partial) {
                /* partial decoding, don't verify entries */
                break;
            }

            rv = cert_check_crl_entries(&crl->crl);
            if (rv != SECSuccess) {
                extended->badExtensions = PR_TRUE;
            }

            break;

        default:
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
            rv = SECFailure;
            break;
    }

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

    crl->referenceCount = 1;

    return (crl);

loser:
    if (options & CRL_DECODE_KEEP_BAD_CRL) {
        if (extended) {
            extended->decodingError = PR_TRUE;
        }
        if (crl) {
            crl->referenceCount = 1;
            return (crl);
        }
    }

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

    return (0);
}

/*
 * take a DER CRL and decode it into a CRL structure
 */
CERTSignedCrl*
CERT_DecodeDERCrl(PLArenaPool* narena, SECItem* derSignedCrl, int type)
{
    return CERT_DecodeDERCrlWithFlags(narena, derSignedCrl, type,
                                      CRL_DECODE_DEFAULT_OPTIONS);
}

/*
 * Lookup a CRL in the databases. We mirror the same fast caching data base
 *  caching stuff used by certificates....?
 * return values :
 *
 * SECSuccess means we got a valid decodable DER CRL, or no CRL at all.
 * Caller may distinguish those cases by the value returned in "decoded".
 * When DER CRL is not found, error code will be SEC_ERROR_CRL_NOT_FOUND.
 *
 * SECFailure means we got a fatal error - most likely, we found a CRL,
 * and it failed decoding, or there was an out of memory error. Do NOT ignore
 * it and specifically do NOT treat it the same as having no CRL, as this
 * can compromise security !!! Ideally, you should treat this case as if you
 * received a "catch-all" CRL where all certs you were looking up are
 * considered to be revoked
 */
static SECStatus
SEC_FindCrlByKeyOnSlot(PK11SlotInfo* slot, SECItem* crlKey, int type,
                       CERTSignedCrl** decoded, PRInt32 decodeoptions)
{
    SECStatus rv = SECSuccess;
    CERTSignedCrl* crl = NULL;
    SECItem* derCrl = NULL;
    CK_OBJECT_HANDLE crlHandle = 0;
    char* url = NULL;

    PORT_Assert(decoded);
    if (!decoded) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    derCrl = PK11_FindCrlByName(&slot, &crlHandle, crlKey, type, &url);
    if (derCrl == NULL) {
        /* if we had a problem other than the CRL just didn't exist, return
         * a failure to the upper level */
        int nsserror = PORT_GetError();
        if (nsserror != SEC_ERROR_CRL_NOT_FOUND) {
            rv = SECFailure;
        }
        goto loser;
    }
    PORT_Assert(crlHandle != CK_INVALID_HANDLE);
    /* PK11_FindCrlByName obtained a slot reference. */

    /* derCRL is a fresh HEAP copy made for us by PK11_FindCrlByName.
       Force adoption of the DER CRL from the heap - this will cause it
       to be automatically freed when SEC_DestroyCrl is invoked */
    decodeoptions |= (CRL_DECODE_ADOPT_HEAP_DER | CRL_DECODE_DONT_COPY_DER);

    crl = CERT_DecodeDERCrlWithFlags(NULL, derCrl, type, decodeoptions);
    if (crl) {
        crl->slot = slot;
        slot = NULL;   /* adopt it */
        derCrl = NULL; /* adopted by the crl struct */
        crl->pkcs11ID = crlHandle;
        if (url) {
            crl->url = PORT_ArenaStrdup(crl->arena, url);
        }
    } else {
        rv = SECFailure;
    }

    if (url) {
        PORT_Free(url);
    }

    if (slot) {
        PK11_FreeSlot(slot);
    }

loser:
    if (derCrl) {
        SECITEM_FreeItem(derCrl, PR_TRUE);
    }

    *decoded = crl;

    return rv;
}

CERTSignedCrl*
crl_storeCRL(PK11SlotInfo* slot, char* url, CERTSignedCrl* newCrl,
             SECItem* derCrl, int type)
{
    CERTSignedCrl *oldCrl = NULL, *crl = NULL;
    PRBool deleteOldCrl = PR_FALSE;
    CK_OBJECT_HANDLE crlHandle = CK_INVALID_HANDLE;

    PORT_Assert(newCrl);
    PORT_Assert(derCrl);
    PORT_Assert(type == SEC_CRL_TYPE);

    if (type != SEC_CRL_TYPE) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    /* we can't use the cache here because we must look in the same
       token */
    (void)SEC_FindCrlByKeyOnSlot(slot, &newCrl->crl.derName, type, &oldCrl,
                                 CRL_DECODE_SKIP_ENTRIES);
    /* if there is an old crl on the token, make sure the one we are
       installing is newer. If not, exit out, otherwise delete the
       old crl.
     */
    if (oldCrl != NULL) {
        /* if it's already there, quietly continue */
        if (SECITEM_CompareItem(newCrl->derCrl, oldCrl->derCrl) == SECEqual) {
            crl = newCrl;
            crl->slot = PK11_ReferenceSlot(slot);
            crl->pkcs11ID = oldCrl->pkcs11ID;
            if (oldCrl->url && !url)
                url = oldCrl->url;
            if (url)
                crl->url = PORT_ArenaStrdup(crl->arena, url);
            goto done;
        }
        if (!SEC_CrlIsNewer(&newCrl->crl, &oldCrl->crl)) {
            PORT_SetError(SEC_ERROR_OLD_CRL);
            goto done;
        }

        /* if we have a url in the database, use that one */
        if (oldCrl->url && !url) {
            url = oldCrl->url;
        }

        /* really destroy this crl */
        /* first drum it out of the permanment Data base */
        deleteOldCrl = PR_TRUE;
    }

    /* invalidate CRL cache for this issuer */
    CERT_CRLCacheRefreshIssuer(NULL, &newCrl->crl.derName);
    /* Write the new entry into the data base */
    crlHandle = PK11_PutCrl(slot, derCrl, &newCrl->crl.derName, url, type);
    if (crlHandle != CK_INVALID_HANDLE) {
        crl = newCrl;
        crl->slot = PK11_ReferenceSlot(slot);
        crl->pkcs11ID = crlHandle;
        if (url) {
            crl->url = PORT_ArenaStrdup(crl->arena, url);
        }
    }

done:
    if (oldCrl) {
        if (deleteOldCrl && crlHandle != CK_INVALID_HANDLE) {
            SEC_DeletePermCRL(oldCrl);
        }
        SEC_DestroyCrl(oldCrl);
    }

    return crl;
}

/*
 *
 * create a new CRL from DER material.
 *
 * The signature on this CRL must be checked before you
 * load it. ???
 */
CERTSignedCrl*
SEC_NewCrl(CERTCertDBHandle* handle, char* url, SECItem* derCrl, int type)
{
    CERTSignedCrl* retCrl = NULL;
    PK11SlotInfo* slot = PK11_GetInternalKeySlot();
    retCrl =
        PK11_ImportCRL(slot, derCrl, url, type, NULL, CRL_IMPORT_BYPASS_CHECKS,
                       NULL, CRL_DECODE_DEFAULT_OPTIONS);
    PK11_FreeSlot(slot);

    return retCrl;
}

CERTSignedCrl*
SEC_FindCrlByDERCert(CERTCertDBHandle* handle, SECItem* derCrl, int type)
{
    PLArenaPool* arena;
    SECItem crlKey;
    SECStatus rv;
    CERTSignedCrl* crl = NULL;

    /* create a scratch arena */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        return (NULL);
    }

    /* extract the database key from the cert */
    rv = CERT_KeyFromDERCrl(arena, derCrl, &crlKey);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* find the crl */
    crl = SEC_FindCrlByName(handle, &crlKey, type);

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

CERTSignedCrl*
SEC_DupCrl(CERTSignedCrl* acrl)
{
    if (acrl) {
        PR_ATOMIC_INCREMENT(&acrl->referenceCount);
        return acrl;
    }
    return NULL;
}

SECStatus
SEC_DestroyCrl(CERTSignedCrl* crl)
{
    if (crl) {
        if (PR_ATOMIC_DECREMENT(&crl->referenceCount) < 1) {
            if (crl->slot) {
                PK11_FreeSlot(crl->slot);
            }
            if (GetOpaqueCRLFields(crl) &&
                PR_TRUE == GetOpaqueCRLFields(crl)->heapDER) {
                SECITEM_FreeItem(crl->derCrl, PR_TRUE);
            }
            if (crl->arena) {
                PORT_FreeArena(crl->arena, PR_FALSE);
            }
        }
        return SECSuccess;
    } else {
        return SECFailure;
    }
}

SECStatus
SEC_LookupCrls(CERTCertDBHandle* handle, CERTCrlHeadNode** nodes, int type)
{
    CERTCrlHeadNode* head;
    PLArenaPool* arena = NULL;
    SECStatus rv;

    *nodes = NULL;

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

    /* build a head structure */
    head = (CERTCrlHeadNode*)PORT_ArenaAlloc(arena, sizeof(CERTCrlHeadNode));
    head->arena = arena;
    head->first = NULL;
    head->last = NULL;
    head->dbhandle = handle;

    /* Look up the proper crl types */
    *nodes = head;

    rv = PK11_LookupCrls(head, type, NULL);

    if (rv != SECSuccess) {
        if (arena) {
            PORT_FreeArena(arena, PR_FALSE);
            *nodes = NULL;
        }
    }

    return rv;
}

/* These functions simply return the address of the above-declared templates.
** This is necessary for Windows DLLs.  Sigh.
*/
SEC_ASN1_CHOOSER_IMPLEMENT(CERT_IssuerAndSNTemplate)
SEC_ASN1_CHOOSER_IMPLEMENT(CERT_CrlTemplate)
SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SignedCrlTemplate)
SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SetOfSignedCrlTemplate)

/* CRL cache code starts here */

/* constructor */
static SECStatus CachedCrl_Create(CachedCrl** returned, CERTSignedCrl* crl,
                                  CRLOrigin origin);
/* destructor */
static SECStatus CachedCrl_Destroy(CachedCrl* crl);

/* create hash table of CRL entries */
static SECStatus CachedCrl_Populate(CachedCrl* crlobject);

/* empty the cache content */
static SECStatus CachedCrl_Depopulate(CachedCrl* crl);

/* are these CRLs the same, as far as the cache is concerned ?
   Or are they the same token object, but with different DER ? */

static SECStatus CachedCrl_Compare(CachedCrl* a, CachedCrl* b, PRBool* isDupe,
                                   PRBool* isUpdated);

/* create a DPCache object */
static SECStatus DPCache_Create(CRLDPCache** returned, CERTCertificate* issuer,
                                const SECItem* subject, SECItem* dp);

/* destructor for CRL DPCache object */
static SECStatus DPCache_Destroy(CRLDPCache* cache);

/* add a new CRL object to the dynamic array of CRLs of the DPCache, and
   returns the cached CRL object . Needs write access to DPCache. */
static SECStatus DPCache_AddCRL(CRLDPCache* cache, CachedCrl* crl,
                                PRBool* added);

/* fetch the CRL for this DP from the PKCS#11 tokens */
static SECStatus DPCache_FetchFromTokens(CRLDPCache* cache, PRTime vfdate,
                                         void* wincx);

/* update the content of the CRL cache, including fetching of CRLs, and
   reprocessing with specified issuer and date */
static SECStatus DPCache_GetUpToDate(CRLDPCache* cache, CERTCertificate* issuer,
                                     PRBool readlocked, PRTime vfdate,
                                     void* wincx);

/* returns true if there are CRLs from PKCS#11 slots */
static PRBool DPCache_HasTokenCRLs(CRLDPCache* cache);

/* remove CRL at offset specified */
static SECStatus DPCache_RemoveCRL(CRLDPCache* cache, PRUint32 offset);

/* Pick best CRL to use . needs write access */
static SECStatus DPCache_SelectCRL(CRLDPCache* cache);

/* create an issuer cache object (per CA subject ) */
static SECStatus IssuerCache_Create(CRLIssuerCache** returned,
                                    CERTCertificate* issuer,
                                    const SECItem* subject, const SECItem* dp);

/* destructor for CRL IssuerCache object */
SECStatus IssuerCache_Destroy(CRLIssuerCache* cache);

/* add a DPCache to the issuer cache */
static SECStatus IssuerCache_AddDP(CRLIssuerCache* cache,
                                   CERTCertificate* issuer,
                                   const SECItem* subject, const SECItem* dp,
                                   CRLDPCache** newdpc);

/* get a particular DPCache object from an IssuerCache */
static CRLDPCache* IssuerCache_GetDPCache(CRLIssuerCache* cache,
                                          const SECItem* dp);

/*
** Pre-allocator hash allocator ops.
*/

/* allocate memory for hash table */
static void* PR_CALLBACK
PreAllocTable(void* pool, PRSize size)
{
    PreAllocator* alloc = (PreAllocator*)pool;
    PORT_Assert(alloc);
    if (!alloc) {
        /* no allocator, or buffer full */
        return NULL;
    }
    if (size > (alloc->len - alloc->used)) {
        /* initial buffer full, let's use the arena */
        alloc->extra += size;
        return PORT_ArenaAlloc(alloc->arena, size);
    }
    /* use the initial buffer */
    alloc->used += size;
    return (char*)alloc->data + alloc->used - size;
}

/* free hash table memory.
   Individual PreAllocator elements cannot be freed, so this is a no-op. */
static void PR_CALLBACK
PreFreeTable(void* pool, void* item)
{
}

/* allocate memory for hash table */
static PLHashEntry* PR_CALLBACK
PreAllocEntry(void* pool, const void* key)
{
    return PreAllocTable(pool, sizeof(PLHashEntry));
}

/* free hash table entry.
   Individual PreAllocator elements cannot be freed, so this is a no-op. */
static void PR_CALLBACK
PreFreeEntry(void* pool, PLHashEntry* he, PRUintn flag)
{
}

/* methods required for PL hash table functions */
static PLHashAllocOps preAllocOps = { PreAllocTable, PreFreeTable,
                                      PreAllocEntry, PreFreeEntry };

/* destructor for PreAllocator object */
void
PreAllocator_Destroy(PreAllocator* allocator)
{
    if (!allocator) {
        return;
    }
    if (allocator->arena) {
        PORT_FreeArena(allocator->arena, PR_TRUE);
    }
}

/* constructor for PreAllocator object */
PreAllocator*
PreAllocator_Create(PRSize size)
{
    PLArenaPool* arena = NULL;
    PreAllocator* prebuffer = NULL;
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (!arena) {
        return NULL;
    }
    prebuffer = (PreAllocator*)PORT_ArenaZAlloc(arena, sizeof(PreAllocator));
    if (!prebuffer) {
        PORT_FreeArena(arena, PR_TRUE);
        return NULL;
    }
    prebuffer->arena = arena;

    if (size) {
        prebuffer->len = size;
        prebuffer->data = PORT_ArenaAlloc(arena, size);
        if (!prebuffer->data) {
            PORT_FreeArena(arena, PR_TRUE);
            return NULL;
        }
    }
    return prebuffer;
}

/* global Named CRL cache object */
static NamedCRLCache namedCRLCache = { NULL, NULL };

/* global CRL cache object */
static CRLCache crlcache = { NULL, NULL };

/* initial state is off */
static PRBool crlcache_initialized = PR_FALSE;

PRTime CRLCache_Empty_TokenFetch_Interval = 60 * 1000000; /* how often
    to query the tokens for CRL objects, in order to discover new objects, if
    the cache does not contain any token CRLs . In microseconds */

PRTime CRLCache_TokenRefetch_Interval = 600 * 1000000; /* how often
   to query the tokens for CRL objects, in order to discover new objects, if
   the cache already contains token CRLs In microseconds */

PRTime CRLCache_ExistenceCheck_Interval = 60 * 1000000; /* how often to check
    if a token CRL object still exists. In microseconds */

/* this function is called at NSS initialization time */
SECStatus
InitCRLCache(void)
{
    if (PR_FALSE == crlcache_initialized) {
        PORT_Assert(NULL == crlcache.lock);
        PORT_Assert(NULL == crlcache.issuers);
        PORT_Assert(NULL == namedCRLCache.lock);
        PORT_Assert(NULL == namedCRLCache.entries);
        if (crlcache.lock || crlcache.issuers || namedCRLCache.lock ||
            namedCRLCache.entries) {
            /* CRL cache already partially initialized */
            PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
            return SECFailure;
        }
#ifdef GLOBAL_RWLOCK
        crlcache.lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL);
#else
        crlcache.lock = PR_NewLock();
#endif
        namedCRLCache.lock = PR_NewLock();
        crlcache.issuers = PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare,
                                           PL_CompareValues, NULL, NULL);
        namedCRLCache.entries = PL_NewHashTable(
            0, SECITEM_Hash, SECITEM_HashCompare, PL_CompareValues, NULL, NULL);
        if (!crlcache.lock || !namedCRLCache.lock || !crlcache.issuers ||
            !namedCRLCache.entries) {
            if (crlcache.lock) {
#ifdef GLOBAL_RWLOCK
                NSSRWLock_Destroy(crlcache.lock);
#else
                PR_DestroyLock(crlcache.lock);
#endif
                crlcache.lock = NULL;
            }
            if (namedCRLCache.lock) {
                PR_DestroyLock(namedCRLCache.lock);
                namedCRLCache.lock = NULL;
            }
            if (crlcache.issuers) {
                PL_HashTableDestroy(crlcache.issuers);
                crlcache.issuers = NULL;
            }
            if (namedCRLCache.entries) {
                PL_HashTableDestroy(namedCRLCache.entries);
                namedCRLCache.entries = NULL;
            }

            return SECFailure;
        }
        crlcache_initialized = PR_TRUE;
        return SECSuccess;
    } else {
        PORT_Assert(crlcache.lock);
        PORT_Assert(crlcache.issuers);
        if ((NULL == crlcache.lock) || (NULL == crlcache.issuers)) {
            /* CRL cache not fully initialized */
            return SECFailure;
        } else {
            /* CRL cache already initialized */
            return SECSuccess;
        }
    }
}

/* destructor for CRL DPCache object */
static SECStatus
DPCache_Destroy(CRLDPCache* cache)
{
    PRUint32 i = 0;
    PORT_Assert(cache);
    if (!cache) {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    if (cache->lock) {
#ifdef DPC_RWLOCK
        NSSRWLock_Destroy(cache->lock);
#else
        PR_DestroyLock(cache->lock);
#endif
    } else {
        PORT_Assert(0);
        return SECFailure;
    }
    /* destroy all our CRL objects */
    for (i = 0; i < cache->ncrls; i++) {
        if (!cache->crls || !cache->crls[i] ||
            SECSuccess != CachedCrl_Destroy(cache->crls[i])) {
            return SECFailure;
        }
    }
    /* free the array of CRLs */
    if (cache->crls) {
        PORT_Free(cache->crls);
    }
    /* destroy the cert */
    if (cache->issuerDERCert) {
        SECITEM_FreeItem(cache->issuerDERCert, PR_TRUE);
    }
    /* free the subject */
    if (cache->subject) {
        SECITEM_FreeItem(cache->subject, PR_TRUE);
    }
    /* free the distribution points */
    if (cache->distributionPoint) {
        SECITEM_FreeItem(cache->distributionPoint, PR_TRUE);
    }
    PORT_Free(cache);
    return SECSuccess;
}

/* destructor for CRL IssuerCache object */
SECStatus
IssuerCache_Destroy(CRLIssuerCache* cache)
{
    PORT_Assert(cache);
    if (!cache) {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
#ifdef XCRL
    if (cache->lock) {
        NSSRWLock_Destroy(cache->lock);
    } else {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    if (cache->issuer) {
        CERT_DestroyCertificate(cache->issuer);
    }
#endif
    /* free the subject */
    if (cache->subject) {
        SECITEM_FreeItem(cache->subject, PR_TRUE);
    }
    if (SECSuccess != DPCache_Destroy(cache->dpp)) {
        PORT_Assert(0);
        return SECFailure;
    }
    PORT_Free(cache);
    return SECSuccess;
}

/* create a named CRL entry object */
static SECStatus
NamedCRLCacheEntry_Create(NamedCRLCacheEntry** returned)
{
    NamedCRLCacheEntry* entry = NULL;
    if (!returned) {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    *returned = NULL;
    entry = (NamedCRLCacheEntry*)PORT_ZAlloc(sizeof(NamedCRLCacheEntry));
    if (!entry) {
        return SECFailure;
    }
    *returned = entry;
    return SECSuccess;
}

/* destroy a named CRL entry object */
static SECStatus
NamedCRLCacheEntry_Destroy(NamedCRLCacheEntry* entry)
{
    if (!entry) {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    if (entry->crl) {
        /* named CRL cache owns DER memory */
        SECITEM_ZfreeItem(entry->crl, PR_TRUE);
    }
    if (entry->canonicalizedName) {
        SECITEM_FreeItem(entry->canonicalizedName, PR_TRUE);
    }
    PORT_Free(entry);
    return SECSuccess;
}

/* callback function used in hash table destructor */
static PRIntn PR_CALLBACK
FreeIssuer(PLHashEntry* he, PRIntn i, void* arg)
{
    CRLIssuerCache* issuer = NULL;
    SECStatus* rv = (SECStatus*)arg;

    PORT_Assert(he);
    if (!he) {
        return HT_ENUMERATE_NEXT;
    }
    issuer = (CRLIssuerCache*)he->value;
    PORT_Assert(issuer);
    if (issuer) {
        if (SECSuccess != IssuerCache_Destroy(issuer)) {
            PORT_Assert(rv);
            if (rv) {
                *rv = SECFailure;
            }
            return HT_ENUMERATE_NEXT;
        }
    }
    return HT_ENUMERATE_NEXT;
}

/* callback function used in hash table destructor */
static PRIntn PR_CALLBACK
FreeNamedEntries(PLHashEntry* he, PRIntn i, void* arg)
{
    NamedCRLCacheEntry* entry = NULL;
    SECStatus* rv = (SECStatus*)arg;

    PORT_Assert(he);
    if (!he) {
        return HT_ENUMERATE_NEXT;
    }
    entry = (NamedCRLCacheEntry*)he->value;
    PORT_Assert(entry);
    if (entry) {
        if (SECSuccess != NamedCRLCacheEntry_Destroy(entry)) {
            PORT_Assert(rv);
            if (rv) {
                *rv = SECFailure;
            }
            return HT_ENUMERATE_NEXT;
        }
    }
    return HT_ENUMERATE_NEXT;
}

/* needs to be called at NSS shutdown time
   This will destroy the global CRL cache, including
   - the hash table of issuer cache objects
   - the issuer cache objects
   - DPCache objects in issuer cache objects */
SECStatus
ShutdownCRLCache(void)
{
    SECStatus rv = SECSuccess;
    if (PR_FALSE == crlcache_initialized && !crlcache.lock &&
        !crlcache.issuers) {
        /* CRL cache has already been shut down */
        return SECSuccess;
    }
    if (PR_TRUE == crlcache_initialized &&
        (!crlcache.lock || !crlcache.issuers || !namedCRLCache.lock ||
         !namedCRLCache.entries)) {
        /* CRL cache has partially been shut down */
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    /* empty the CRL cache */
    /* free the issuers */
    PL_HashTableEnumerateEntries(crlcache.issuers, &FreeIssuer, &rv);
    /* free the hash table of issuers */
    PL_HashTableDestroy(crlcache.issuers);
    crlcache.issuers = NULL;
/* free the global lock */
#ifdef GLOBAL_RWLOCK
    NSSRWLock_Destroy(crlcache.lock);
#else
    PR_DestroyLock(crlcache.lock);
#endif
    crlcache.lock = NULL;

    /* empty the named CRL cache. This must be done after freeing the CRL
     * cache, since some CRLs in this cache are in the memory for the other  */
    /* free the entries */
    PL_HashTableEnumerateEntries(namedCRLCache.entries, &FreeNamedEntries, &rv);
    /* free the hash table of issuers */
    PL_HashTableDestroy(namedCRLCache.entries);
    namedCRLCache.entries = NULL;
    /* free the global lock */
    PR_DestroyLock(namedCRLCache.lock);
    namedCRLCache.lock = NULL;

    crlcache_initialized = PR_FALSE;
    return rv;
}

/* add a new CRL object to the dynamic array of CRLs of the DPCache, and
   returns the cached CRL object . Needs write access to DPCache. */
static SECStatus
DPCache_AddCRL(CRLDPCache* cache, CachedCrl* newcrl, PRBool* added)
{
    CachedCrl** newcrls = NULL;
    PRUint32 i = 0;
    PORT_Assert(cache);
    PORT_Assert(newcrl);
    PORT_Assert(added);
    if (!cache || !newcrl || !added) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }

    *added = PR_FALSE;
    /* before adding a new CRL, check if it is a duplicate */
    for (i = 0; i < cache->ncrls; i++) {
        CachedCrl* existing = NULL;
        SECStatus rv = SECSuccess;
        PRBool dupe = PR_FALSE, updated = PR_FALSE;
        if (!cache->crls) {
            PORT_Assert(0);
            return SECFailure;
        }
        existing = cache->crls[i];
        if (!existing) {
            PORT_Assert(0);
            return SECFailure;
        }
        rv = CachedCrl_Compare(existing, newcrl, &dupe, &updated);
        if (SECSuccess != rv) {
            PORT_Assert(0);
            PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
            return SECFailure;
        }
        if (PR_TRUE == dupe) {
            /* dupe */
            PORT_SetError(SEC_ERROR_CRL_ALREADY_EXISTS);
            return SECSuccess;
        }
        if (PR_TRUE == updated) {
            /* this token CRL is in the same slot and has the same object ID,
               but different content. We need to remove the old object */
            if (SECSuccess != DPCache_RemoveCRL(cache, i)) {
                PORT_Assert(0);
                PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
                return PR_FALSE;
            }
        }
    }

    newcrls = (CachedCrl**)PORT_Realloc(cache->crls, (cache->ncrls + 1) * sizeof(CachedCrl*));
    if (!newcrls) {
        return SECFailure;
    }
    cache->crls = newcrls;
    cache->ncrls++;
    cache->crls[cache->ncrls - 1] = newcrl;
    *added = PR_TRUE;
    return SECSuccess;
}

/* remove CRL at offset specified */
static SECStatus
DPCache_RemoveCRL(CRLDPCache* cache, PRUint32 offset)
{
    CachedCrl* acrl = NULL;
    PORT_Assert(cache);
    if (!cache || (!cache->crls) || (!(offset < cache->ncrls))) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    acrl = cache->crls[offset];
    PORT_Assert(acrl);
    if (!acrl) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    cache->crls[offset] = cache->crls[cache->ncrls - 1];
    cache->crls[cache->ncrls - 1] = NULL;
    cache->ncrls--;
    if (cache->selected == acrl) {
        cache->selected = NULL;
    }
    if (SECSuccess != CachedCrl_Destroy(acrl)) {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    return SECSuccess;
}

/* check whether a CRL object stored in a PKCS#11 token still exists in
   that token . This has to be efficient (the entire CRL value cannot be
   transferred accross the token boundaries), so this is accomplished by
   simply fetching the subject attribute and making sure it hasn't changed .
   Note that technically, the CRL object could have been replaced with a new
   PKCS#11 object of the same ID and subject (which actually happens in
   softoken), but this function has no way of knowing that the object
   value changed, since CKA_VALUE isn't checked. */
static PRBool
TokenCRLStillExists(CERTSignedCrl* crl)
{
    NSSItem newsubject;
    SECItem subject;
    CK_ULONG crl_class;
    PRStatus status;
    PK11SlotInfo* slot = NULL;
    nssCryptokiObject instance;
    NSSArena* arena;
    PRBool xstatus = PR_TRUE;
    SECItem* oldSubject = NULL;

    PORT_Assert(crl);
    if (!crl) {
        return PR_FALSE;
    }
    slot = crl->slot;
    PORT_Assert(crl->slot);
    if (!slot) {
        return PR_FALSE;
    }
    oldSubject = &crl->crl.derName;
    PORT_Assert(oldSubject);
    if (!oldSubject) {
        return PR_FALSE;
    }

    /* query subject and type attributes in order to determine if the
       object has been deleted */

    /* first, make an nssCryptokiObject */
    instance.handle = crl->pkcs11ID;
    PORT_Assert(instance.handle);
    if (!instance.handle) {
        return PR_FALSE;
    }
    instance.token = PK11Slot_GetNSSToken(slot);
    PORT_Assert(instance.token);
    if (!instance.token) {
        return PR_FALSE;
    }
    instance.isTokenObject = PR_TRUE;
    instance.label = NULL;

    arena = NSSArena_Create();
    PORT_Assert(arena);
    if (!arena) {
        (void)nssToken_Destroy(instance.token);
        return PR_FALSE;
    }

    status =
        nssCryptokiCRL_GetAttributes(&instance, NULL,          /* XXX sessionOpt */
                                     arena, NULL, &newsubject, /* subject */
                                     &crl_class,               /* class */
                                     NULL, NULL);
    if (PR_SUCCESS == status) {
        subject.data = newsubject.data;
        subject.len = newsubject.size;
        if (SECITEM_CompareItem(oldSubject, &subject) != SECEqual) {
            xstatus = PR_FALSE;
        }
        if (CKO_NSS_CRL != crl_class) {
            xstatus = PR_FALSE;
        }
    } else {
        xstatus = PR_FALSE;
    }
    NSSArena_Destroy(arena);
    (void)nssToken_Destroy(instance.token);
    return xstatus;
}

/* verify the signature of a CRL against its issuer at a given date */
static SECStatus
CERT_VerifyCRL(CERTSignedCrl* crlobject, CERTCertificate* issuer, PRTime vfdate,
               void* wincx)
{
    return CERT_VerifySignedData(&crlobject->signatureWrap, issuer, vfdate,
                                 wincx);
}

/* verify a CRL and update cache state */
static SECStatus
CachedCrl_Verify(CRLDPCache* cache, CachedCrl* crlobject, PRTime vfdate,
                 void* wincx)
{
    /*  Check if it is an invalid CRL
        if we got a bad CRL, we want to cache it in order to avoid
        subsequent fetches of this same identical bad CRL. We set
        the cache to the invalid state to ensure that all certs on this
        DP are considered to have unknown status from now on. The cache
        object will remain in this state until the bad CRL object
        is removed from the token it was fetched from. If the cause
        of the failure is that we didn't have the issuer cert to
        verify the signature, this state can be cleared when
        the issuer certificate becomes available if that causes the
        signature to verify */

    if (!cache || !crlobject) {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    if (PR_TRUE == GetOpaqueCRLFields(crlobject->crl)->decodingError) {
        crlobject->sigChecked = PR_TRUE; /* we can never verify a CRL
            with bogus DER. Mark it checked so we won't try again */
        PORT_SetError(SEC_ERROR_BAD_DER);
        return SECSuccess;
    } else {
        SECStatus signstatus = SECFailure;
        if (cache->issuerDERCert) {
            CERTCertificate* issuer = CERT_NewTempCertificate(
                cache->dbHandle, cache->issuerDERCert, NULL, PR_FALSE, PR_TRUE);

            if (issuer) {
                signstatus =
                    CERT_VerifyCRL(crlobject->crl, issuer, vfdate, wincx);
                CERT_DestroyCertificate(issuer);
            }
        }
        if (SECSuccess != signstatus) {
            if (!cache->issuerDERCert) {
                /* we tried to verify without an issuer cert . This is
                   because this CRL came through a call to SEC_FindCrlByName.
                   So, we don't cache this verification failure. We'll try
                   to verify the CRL again when a certificate from that issuer
                   becomes available */
            } else {
                crlobject->sigChecked = PR_TRUE;
            }
            PORT_SetError(SEC_ERROR_CRL_BAD_SIGNATURE);
            return SECSuccess;
        } else {
            crlobject->sigChecked = PR_TRUE;
            crlobject->sigValid = PR_TRUE;
        }
    }

    return SECSuccess;
}

/* fetch the CRLs for this DP from the PKCS#11 tokens */
static SECStatus
DPCache_FetchFromTokens(CRLDPCache* cache, PRTime vfdate, void* wincx)
{
    SECStatus rv = SECSuccess;
    CERTCrlHeadNode head;
    if (!cache) {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    /* first, initialize list */
    memset(&head, 0, sizeof(head));
    head.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    rv = pk11_RetrieveCrls(&head, cache->subject, wincx);

    /* if this function fails, something very wrong happened, such as an out
       of memory error during CRL decoding. We don't want to proceed and must
       mark the cache object invalid */
    if (SECFailure == rv) {
        /* fetch failed, add error bit */
        cache->invalid |= CRL_CACHE_LAST_FETCH_FAILED;
    } else {
        /* fetch was successful, clear this error bit */
        cache->invalid &= (~CRL_CACHE_LAST_FETCH_FAILED);
    }

    /* add any CRLs found to our array */
    if (SECSuccess == rv) {
        CERTCrlNode* crlNode = NULL;

        for (crlNode = head.first; crlNode; crlNode = crlNode->next) {
            CachedCrl* returned = NULL;
            CERTSignedCrl* crlobject = crlNode->crl;
            if (!crlobject) {
                PORT_Assert(0);
                continue;
            }
            rv = CachedCrl_Create(&returned, crlobject, CRL_OriginToken);
            if (SECSuccess == rv) {
                PRBool added = PR_FALSE;
                rv = DPCache_AddCRL(cache, returned, &added);
                if (PR_TRUE != added) {
                    rv = CachedCrl_Destroy(returned);
                    returned = NULL;
                } else if (vfdate) {
                    rv = CachedCrl_Verify(cache, returned, vfdate, wincx);
                }
            } else {
                /* not enough memory to add the CRL to the cache. mark it
                   invalid so we will try again . */
                cache->invalid |= CRL_CACHE_LAST_FETCH_FAILED;
            }
            if (SECFailure == rv) {
                break;
            }
        }
    }

    if (head.arena) {
        CERTCrlNode* crlNode = NULL;
        /* clean up the CRL list in case we got a partial one
           during a failed fetch */
        for (crlNode = head.first; crlNode; crlNode = crlNode->next) {
            if (crlNode->crl) {
                SEC_DestroyCrl(crlNode->crl); /* free the CRL. Either it got
                   added to the cache and the refcount got bumped, or not, and
                   thus we need to free its RAM */
            }
        }
        PORT_FreeArena(head.arena, PR_FALSE); /* destroy CRL list */
    }

    return rv;
}

static SECStatus
CachedCrl_GetEntry(CachedCrl* crl, const SECItem* sn, CERTCrlEntry** returned)
{
    CERTCrlEntry* acrlEntry;

    PORT_Assert(crl);
    PORT_Assert(crl->entries);
    PORT_Assert(sn);
    PORT_Assert(returned);
    if (!crl || !sn || !returned || !crl->entries) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    acrlEntry = PL_HashTableLookup(crl->entries, (void*)sn);
    if (acrlEntry) {
        *returned = acrlEntry;
    } else {
        *returned = NULL;
    }
    return SECSuccess;
}

/* check if a particular SN is in the CRL cache and return its entry */
dpcacheStatus
DPCache_Lookup(CRLDPCache* cache, const SECItem* sn, CERTCrlEntry** returned)
{
    SECStatus rv;
    if (!cache || !sn || !returned) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        /* no cache or SN to look up, or no way to return entry */
        return dpcacheCallerError;
    }
    *returned = NULL;
    if (0 != cache->invalid) {
        /* the cache contains a bad CRL, or there was a CRL fetching error. */
        PORT_SetError(SEC_ERROR_CRL_INVALID);
        return dpcacheInvalidCacheError;
    }
    if (!cache->selected) {
        /* no CRL means no entry to return. This is OK, except for
         * NIST policy */
        return dpcacheEmpty;
    }
    rv = CachedCrl_GetEntry(cache->selected, sn, returned);
    if (SECSuccess != rv) {
        return dpcacheLookupError;
    } else {
        if (*returned) {
            return dpcacheFoundEntry;
        } else {
            return dpcacheNoEntry;
        }
    }
}

#if defined(DPC_RWLOCK)

#define DPCache_LockWrite()                    \
    {                                          \
        if (readlocked) {                      \
            NSSRWLock_UnlockRead(cache->lock); \
        }                                      \
        NSSRWLock_LockWrite(cache->lock);      \
    }

#define DPCache_UnlockWrite()                \
    {                                        \
        if (readlocked) {                    \
            NSSRWLock_LockRead(cache->lock); \
        }                                    \
        NSSRWLock_UnlockWrite(cache->lock);  \
    }

#else

/* with a global lock, we are always locked for read before we need write
   access, so do nothing */

#define DPCache_LockWrite() \
    {                       \
    }

#define DPCache_UnlockWrite() \
    {                         \
    }

#endif

/* update the content of the CRL cache, including fetching of CRLs, and
   reprocessing with specified issuer and date . We are always holding
   either the read or write lock on DPCache upon entry. */
static SECStatus
DPCache_GetUpToDate(CRLDPCache* cache, CERTCertificate* issuer,
                    PRBool readlocked, PRTime vfdate, void* wincx)
{
    /* Update the CRLDPCache now. We don't cache token CRL lookup misses
       yet, as we have no way of getting notified of new PKCS#11 object
       creation that happens in a token  */
    SECStatus rv = SECSuccess;
    PRUint32 i = 0;
    PRBool forcedrefresh = PR_FALSE;
    PRBool dirty = PR_FALSE; /* whether something was changed in the
                                cache state during this update cycle */
    PRBool hastokenCRLs = PR_FALSE;
    PRTime now = 0;
    PRTime lastfetch = 0;
    PRBool mustunlock = PR_FALSE;

    if (!cache) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }

    /* first, make sure we have obtained all the CRLs we need.
       We do an expensive token fetch in the following cases :
       1) cache is empty because no fetch was ever performed yet
       2) cache is explicitly set to refresh state
       3) cache is in invalid state because last fetch failed
       4) cache contains no token CRLs, and it's been more than one minute
          since the last fetch
       5) cache contains token CRLs, and it's been more than 10 minutes since
          the last fetch
    */
    forcedrefresh = cache->refresh;
    lastfetch = cache->lastfetch;
    if (PR_TRUE != forcedrefresh &&
        (!(cache->invalid & CRL_CACHE_LAST_FETCH_FAILED))) {
        now = PR_Now();
        hastokenCRLs = DPCache_HasTokenCRLs(cache);
    }
    if ((0 == lastfetch) ||

        (PR_TRUE == forcedrefresh) ||

        (cache->invalid & CRL_CACHE_LAST_FETCH_FAILED) ||

        ((PR_FALSE == hastokenCRLs) &&
         ((now - cache->lastfetch > CRLCache_Empty_TokenFetch_Interval) ||
          (now < cache->lastfetch))) ||

        ((PR_TRUE == hastokenCRLs) &&
         ((now - cache->lastfetch > CRLCache_TokenRefetch_Interval) ||
          (now < cache->lastfetch)))) {
        /* the cache needs to be refreshed, and/or we had zero CRL for this
           DP. Try to get one from PKCS#11 tokens */
        DPCache_LockWrite();
        /* check if another thread updated before us, and skip update if so */
        if (lastfetch == cache->lastfetch) {
            /* we are the first */
            rv = DPCache_FetchFromTokens(cache, vfdate, wincx);
            if (PR_TRUE == cache->refresh) {
                cache->refresh = PR_FALSE; /* clear refresh state */
            }
            dirty = PR_TRUE;
            cache->lastfetch = PR_Now();
        }
        DPCache_UnlockWrite();
    }

    /* now, make sure we have no extraneous CRLs (deleted token objects)
       we'll do this inexpensive existence check either
       1) if there was a token object fetch
       2) every minute */
    if ((PR_TRUE != dirty) && (!now)) {
        now = PR_Now();
    }
    if ((PR_TRUE == dirty) ||
        ((now - cache->lastcheck > CRLCache_ExistenceCheck_Interval) ||
         (now < cache->lastcheck))) {
        PRTime lastcheck = cache->lastcheck;
        mustunlock = PR_FALSE;
        /* check if all CRLs still exist */
        for (i = 0; (i < cache->ncrls); i++) {
            CachedCrl* savcrl = cache->crls[i];
            if ((!savcrl) || (savcrl && CRL_OriginToken != savcrl->origin)) {
                /* we only want to check token CRLs */
                continue;
            }
            if ((PR_TRUE != TokenCRLStillExists(savcrl->crl))) {

                /* this CRL is gone */
                if (PR_TRUE != mustunlock) {
                    DPCache_LockWrite();
                    mustunlock = PR_TRUE;
                }
                /* first, we need to check if another thread did an update
                   before we did */
                if (lastcheck == cache->lastcheck) {
                    /* the CRL is gone. And we are the one to do the update */
                    DPCache_RemoveCRL(cache, i);
                    dirty = PR_TRUE;
                }
                /* stay locked here intentionally so we do all the other
                   updates in this thread for the remaining CRLs */
            }
        }
        if (PR_TRUE == mustunlock) {
            cache->lastcheck = PR_Now();
            DPCache_UnlockWrite();
            mustunlock = PR_FALSE;
        }
    }

    /* add issuer certificate if it was previously unavailable */
    if (issuer && (NULL == cache->issuerDERCert) &&
        (SECSuccess == CERT_CheckCertUsage(issuer, KU_CRL_SIGN))) {
        /* if we didn't have a valid issuer cert yet, but we do now. add it */
        DPCache_LockWrite();
        if (!cache->issuerDERCert) {
            dirty = PR_TRUE;
            cache->dbHandle = issuer->dbhandle;
            cache->issuerDERCert = SECITEM_DupItem(&issuer->derCert);
        }
        DPCache_UnlockWrite();
    }

    /* verify CRLs that couldn't be checked when inserted into the cache
       because the issuer cert or a verification date was unavailable.
       These are CRLs that were inserted into the cache through
       SEC_FindCrlByName, or through manual insertion, rather than through a
       certificate verification (CERT_CheckCRL) */

    if (cache->issuerDERCert && vfdate) {
        mustunlock = PR_FALSE;
        /* re-process all unverified CRLs */
        for (i = 0; i < cache->ncrls; i++) {
            CachedCrl* savcrl = cache->crls[i];
            if (!savcrl) {
                continue;
            }
            if (PR_TRUE != savcrl->sigChecked) {
                if (!mustunlock) {
                    DPCache_LockWrite();
                    mustunlock = PR_TRUE;
                }
                /* first, we need to check if another thread updated
                   it before we did, and abort if it has been modified since
                   we acquired the lock. Make sure first that the CRL is still
                   in the array at the same position */
                if ((i < cache->ncrls) && (savcrl == cache->crls[i]) &&
                    (PR_TRUE != savcrl->sigChecked)) {
                    /* the CRL is still there, unverified. Do it */
                    CachedCrl_Verify(cache, savcrl, vfdate, wincx);
                    dirty = PR_TRUE;
                }
                /* stay locked here intentionally so we do all the other
                   updates in this thread for the remaining CRLs */
            }
            if (mustunlock && !dirty) {
                DPCache_UnlockWrite();
                mustunlock = PR_FALSE;
            }
        }
    }

    if (dirty || cache->mustchoose) {
        /* changes to the content of the CRL cache necessitate examining all
           CRLs for selection of the most appropriate one to cache */
        if (!mustunlock) {
            DPCache_LockWrite();
            mustunlock = PR_TRUE;
        }
        DPCache_SelectCRL(cache);
        cache->mustchoose = PR_FALSE;
    }
    if (mustunlock)
        DPCache_UnlockWrite();

    return rv;
}

/* callback for qsort to sort by thisUpdate */
static int
SortCRLsByThisUpdate(const void* arg1, const void* arg2)
{
    PRTime timea, timeb;
    SECStatus rv = SECSuccess;
    CachedCrl *a, *b;

    a = *(CachedCrl**)arg1;
    b = *(CachedCrl**)arg2;

    if (!a || !b) {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        rv = SECFailure;
    }

    if (SECSuccess == rv) {
        rv = DER_DecodeTimeChoice(&timea, &a->crl->crl.lastUpdate);
    }
    if (SECSuccess == rv) {
        rv = DER_DecodeTimeChoice(&timeb, &b->crl->crl.lastUpdate);
    }
    if (SECSuccess == rv) {
        if (timea > timeb) {
            return 1; /* a is better than b */
        }
        if (timea < timeb) {
            return -1; /* a is not as good as b */
        }
    }

    /* if they are equal, or if all else fails, use pointer differences */
    PORT_Assert(a != b); /* they should never be equal */
    return a > b ? 1 : -1;
}

/* callback for qsort to sort a set of disparate CRLs, some of which are
   invalid DER or failed signature check.

   Validated CRLs are differentiated by thisUpdate .
   Validated CRLs are preferred over non-validated CRLs .
   Proper DER CRLs are preferred over non-DER data .
*/
static int
SortImperfectCRLs(const void* arg1, const void* arg2)
{
    CachedCrl *a, *b;

    a = *(CachedCrl**)arg1;
    b = *(CachedCrl**)arg2;

    if (!a || !b) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        PORT_Assert(0);
    } else {
        PRBool aDecoded = PR_FALSE, bDecoded = PR_FALSE;
        if ((PR_TRUE == a->sigValid) && (PR_TRUE == b->sigValid)) {
            /* both CRLs have been validated, choose the latest one */
            return SortCRLsByThisUpdate(arg1, arg2);
        }
        if (PR_TRUE == a->sigValid) {
            return 1; /* a is greater than b */
        }
        if (PR_TRUE == b->sigValid) {
            return -1; /* a is not as good as b */
        }
        aDecoded = GetOpaqueCRLFields(a->crl)->decodingError;
        bDecoded = GetOpaqueCRLFields(b->crl)->decodingError;
        /* neither CRL had its signature check pass */
        if ((PR_FALSE == aDecoded) && (PR_FALSE == bDecoded)) {
            /* both CRLs are proper DER, choose the latest one */
            return SortCRLsByThisUpdate(arg1, arg2);
        }
        if (PR_FALSE == aDecoded) {
            return 1; /* a is better than b */
        }
        if (PR_FALSE == bDecoded) {
            return -1; /* a is not as good as b */
        }
        /* both are invalid DER. sigh. */
    }
    /* if they are equal, or if all else fails, use pointer differences */
    PORT_Assert(a != b); /* they should never be equal */
    return a > b ? 1 : -1;
}

/* Pick best CRL to use . needs write access */
static SECStatus
DPCache_SelectCRL(CRLDPCache* cache)
{
    PRUint32 i;
    PRBool valid = PR_TRUE;
    CachedCrl* selected = NULL;

    PORT_Assert(cache);
    if (!cache) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    /* if any invalid CRL is present, then the CRL cache is
       considered invalid, for security reasons */
    for (i = 0; i < cache->ncrls; i++) {
        if (!cache->crls[i] || !cache->crls[i]->sigChecked ||
            !cache->crls[i]->sigValid) {
            valid = PR_FALSE;
            break;
        }
    }
    if (PR_TRUE == valid) {
        /* all CRLs are valid, clear this error */
        cache->invalid &= (~CRL_CACHE_INVALID_CRLS);
    } else {
        /* some CRLs are invalid, set this error */
        cache->invalid |= CRL_CACHE_INVALID_CRLS;
    }

    if (cache->invalid) {
        /* cache is in an invalid state, so reset it */
        if (cache->selected) {
            cache->selected = NULL;
        }
        /* also sort the CRLs imperfectly */
        qsort(cache->crls, cache->ncrls, sizeof(CachedCrl*), SortImperfectCRLs);
        return SECSuccess;
    }

    if (cache->ncrls) {
        /* all CRLs are good, sort them by thisUpdate */
        qsort(cache->crls, cache->ncrls, sizeof(CachedCrl*), SortCRLsByThisUpdate);

        /* pick the newest CRL */
        selected = cache->crls[cache->ncrls - 1];

        /* and populate the cache */
        if (SECSuccess != CachedCrl_Populate(selected)) {
            return SECFailure;
        }
    }

    cache->selected = selected;

    return SECSuccess;
}

/* initialize a DPCache object */
static SECStatus
DPCache_Create(CRLDPCache** returned, CERTCertificate* issuer,
               const SECItem* subject, SECItem* dp)
{
    CRLDPCache* cache = NULL;
    PORT_Assert(returned);
    /* issuer and dp are allowed to be NULL */
    if (!returned || !subject) {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    *returned = NULL;
    cache = PORT_ZAlloc(sizeof(CRLDPCache));
    if (!cache) {
        return SECFailure;
    }
#ifdef DPC_RWLOCK
    cache->lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL);
#else
    cache->lock = PR_NewLock();
#endif
    if (!cache->lock) {
        PORT_Free(cache);
        return SECFailure;
    }
    if (issuer) {
        cache->dbHandle = issuer->dbhandle;
        cache->issuerDERCert = SECITEM_DupItem(&issuer->derCert);
    }
    cache->distributionPoint = SECITEM_DupItem(dp);
    cache->subject = SECITEM_DupItem(subject);
    cache->lastfetch = 0;
    cache->lastcheck = 0;
    *returned = cache;
    return SECSuccess;
}

/* create an issuer cache object (per CA subject ) */
static SECStatus
IssuerCache_Create(CRLIssuerCache** returned, CERTCertificate* issuer,
                   const SECItem* subject, const SECItem* dp)
{
    SECStatus rv = SECSuccess;
    CRLIssuerCache* cache = NULL;
    PORT_Assert(returned);
    PORT_Assert(subject);
    /* issuer and dp are allowed to be NULL */
    if (!returned || !subject) {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    *returned = NULL;
    cache = (CRLIssuerCache*)PORT_ZAlloc(sizeof(CRLIssuerCache));
    if (!cache) {
        return SECFailure;
    }
    cache->subject = SECITEM_DupItem(subject);
#ifdef XCRL
    cache->lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL);
    if (!cache->lock) {
        rv = SECFailure;
    }
    if (SECSuccess == rv && issuer) {
        cache->issuer = CERT_DupCertificate(issuer);
        if (!cache->issuer) {
            rv = SECFailure;
        }
    }
#endif
    if (SECSuccess != rv) {
        PORT_Assert(SECSuccess == IssuerCache_Destroy(cache));
        return SECFailure;
    }
    *returned = cache;
    return SECSuccess;
}

/* add a DPCache to the issuer cache */
static SECStatus
IssuerCache_AddDP(CRLIssuerCache* cache, CERTCertificate* issuer,
                  const SECItem* subject, const SECItem* dp,
                  CRLDPCache** newdpc)
{
    /* now create the required DP cache object */
    if (!cache || !subject || !newdpc) {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    if (!dp) {
        /* default distribution point */
        SECStatus rv = DPCache_Create(&cache->dpp, issuer, subject, NULL);
        if (SECSuccess == rv) {
            *newdpc = cache->dpp;
            return SECSuccess;
        }
    } else {
        /* we should never hit this until we support multiple DPs */
        PORT_Assert(dp);
        /* XCRL allocate a new distribution point cache object, initialize it,
           and add it to the hash table of DPs */
    }
    return SECFailure;
}

/* add an IssuerCache to the global hash table of issuers */
static SECStatus
CRLCache_AddIssuer(CRLIssuerCache* issuer)
{
    PORT_Assert(issuer);
    PORT_Assert(crlcache.issuers);
    if (!issuer || !crlcache.issuers) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    if (NULL == PL_HashTableAdd(crlcache.issuers, (void*)issuer->subject,
                                (void*)issuer)) {
        return SECFailure;
    }
    return SECSuccess;
}

/* retrieve the issuer cache object for a given issuer subject */
static SECStatus
CRLCache_GetIssuerCache(CRLCache* cache, const SECItem* subject,
                        CRLIssuerCache** returned)
{
    /* we need to look up the issuer in the hash table */
    SECStatus rv = SECSuccess;
    PORT_Assert(cache);
    PORT_Assert(subject);
    PORT_Assert(returned);
    PORT_Assert(crlcache.issuers);
    if (!cache || !subject || !returned || !crlcache.issuers) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        rv = SECFailure;
    }

    if (SECSuccess == rv) {
        *returned = (CRLIssuerCache*)PL_HashTableLookup(crlcache.issuers,
                                                        (void*)subject);
    }

    return rv;
}

/* retrieve the full CRL object that best matches the content of a DPCache */
static CERTSignedCrl*
GetBestCRL(CRLDPCache* cache, PRBool entries)
{
    CachedCrl* acrl = NULL;

    PORT_Assert(cache);
    if (!cache) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return NULL;
    }

    if (0 == cache->ncrls) {
        /* empty cache*/
        PORT_SetError(SEC_ERROR_CRL_NOT_FOUND);
        return NULL;
    }

    /* if we have a valid full CRL selected, return it */
    if (cache->selected) {
        return SEC_DupCrl(cache->selected->crl);
    }

    /* otherwise, use latest valid DER CRL */
    acrl = cache->crls[cache->ncrls - 1];

    if (acrl && (PR_FALSE == GetOpaqueCRLFields(acrl->crl)->decodingError)) {
        SECStatus rv = SECSuccess;
        if (PR_TRUE == entries) {
            rv = CERT_CompleteCRLDecodeEntries(acrl->crl);
        }
        if (SECSuccess == rv) {
            return SEC_DupCrl(acrl->crl);
        }
    }

    PORT_SetError(SEC_ERROR_CRL_NOT_FOUND);
    return NULL;
}

/* get a particular DPCache object from an IssuerCache */
static CRLDPCache*
IssuerCache_GetDPCache(CRLIssuerCache* cache, const SECItem* dp)
{
    CRLDPCache* dpp = NULL;
    PORT_Assert(cache);
    /* XCRL for now we only support the "default" DP, ie. the
       full CRL. So we can return the global one without locking. In
       the future we will have a lock */
    PORT_Assert(NULL == dp);
    if (!cache || dp) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return NULL;
    }
#ifdef XCRL
    NSSRWLock_LockRead(cache->lock);
#endif
    dpp = cache->dpp;
#ifdef XCRL
    NSSRWLock_UnlockRead(cache->lock);
#endif
    return dpp;
}

/* get a DPCache object for the given issuer subject and dp
   Automatically creates the cache object if it doesn't exist yet.
   */
SECStatus
AcquireDPCache(CERTCertificate* issuer, const SECItem* subject,
               const SECItem* dp, PRTime t, void* wincx, CRLDPCache** dpcache,
               PRBool* writeLocked)
{
    SECStatus rv = SECSuccess;
    CRLIssuerCache* issuercache = NULL;
#ifdef GLOBAL_RWLOCK
    PRBool globalwrite = PR_FALSE;
#endif
    PORT_Assert(crlcache.lock);
    if (!crlcache.lock) {
        /* CRL cache is not initialized */
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
#ifdef GLOBAL_RWLOCK
    NSSRWLock_LockRead(crlcache.lock);
#else
    PR_Lock(crlcache.lock);
#endif
    rv = CRLCache_GetIssuerCache(&crlcache, subject, &issuercache);
    if (SECSuccess != rv) {
#ifdef GLOBAL_RWLOCK
        NSSRWLock_UnlockRead(crlcache.lock);
#else
        PR_Unlock(crlcache.lock);
#endif
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    if (!issuercache) {
        /* there is no cache for this issuer yet. This means this is the
           first time we look up a cert from that issuer, and we need to
           create the cache. */

        rv = IssuerCache_Create(&issuercache, issuer, subject, dp);
        if (SECSuccess == rv && !issuercache) {
            PORT_Assert(issuercache);
            rv = SECFailure;
        }

        if (SECSuccess == rv) {
            /* This is the first time we look up a cert of this issuer.
               Create the DPCache for this DP . */
            rv = IssuerCache_AddDP(issuercache, issuer, subject, dp, dpcache);
        }

        if (SECSuccess == rv) {
            /* lock the DPCache for write to ensure the update happens in this
               thread */
            *writeLocked = PR_TRUE;
#ifdef DPC_RWLOCK
            NSSRWLock_LockWrite((*dpcache)->lock);
#else
            PR_Lock((*dpcache)->lock);
#endif
        }

        if (SECSuccess == rv) {
/* now add the new issuer cache to the global hash table of
   issuers */
#ifdef GLOBAL_RWLOCK
            CRLIssuerCache* existing = NULL;
            NSSRWLock_UnlockRead(crlcache.lock);
            /* when using a r/w lock for the global cache, check if the issuer
               already exists before adding to the hash table */
            NSSRWLock_LockWrite(crlcache.lock);
            globalwrite = PR_TRUE;
            rv = CRLCache_GetIssuerCache(&crlcache, subject, &existing);
            if (!existing) {
#endif
                rv = CRLCache_AddIssuer(issuercache);
                if (SECSuccess != rv) {
                    /* failure */
                    rv = SECFailure;
                }
#ifdef GLOBAL_RWLOCK
            } else {
                /* somebody else updated before we did */
                IssuerCache_Destroy(issuercache); /* destroy the new object */
                issuercache = existing;           /* use the existing one */
                *dpcache = IssuerCache_GetDPCache(issuercache, dp);
            }
#endif
        }

/* now unlock the global cache. We only want to lock the issuer hash
   table addition. Holding it longer would hurt scalability */
#ifdef GLOBAL_RWLOCK
        if (PR_TRUE == globalwrite) {
            NSSRWLock_UnlockWrite(crlcache.lock);
            globalwrite = PR_FALSE;
        } else {
            NSSRWLock_UnlockRead(crlcache.lock);
        }
#else
        PR_Unlock(crlcache.lock);
#endif

        /* if there was a failure adding an issuer cache object, destroy it */
        if (SECSuccess != rv && issuercache) {
            if (PR_TRUE == *writeLocked) {
#ifdef DPC_RWLOCK
                NSSRWLock_UnlockWrite((*dpcache)->lock);
#else
                PR_Unlock((*dpcache)->lock);
#endif
            }
            IssuerCache_Destroy(issuercache);
            issuercache = NULL;
        }

        if (SECSuccess != rv) {
            return SECFailure;
        }
    } else {
#ifdef GLOBAL_RWLOCK
        NSSRWLock_UnlockRead(crlcache.lock);
#else
        PR_Unlock(crlcache.lock);
#endif
        *dpcache = IssuerCache_GetDPCache(issuercache, dp);
    }
    /* we now have a DPCache that we can use for lookups */
    /* lock it for read, unless we already locked for write */
    if (PR_FALSE == *writeLocked) {
#ifdef DPC_RWLOCK
        NSSRWLock_LockRead((*dpcache)->lock);
#else
        PR_Lock((*dpcache)->lock);
#endif
    }

    if (SECSuccess == rv) {
        /* currently there is always one and only one DPCache per issuer */
        PORT_Assert(*dpcache);
        if (*dpcache) {
            /* make sure the DP cache is up to date before using it */
            rv = DPCache_GetUpToDate(*dpcache, issuer, PR_FALSE == *writeLocked,
                                     t, wincx);
        } else {
            rv = SECFailure;
        }
    }
    return rv;
}

/* unlock access to the DPCache */
void
ReleaseDPCache(CRLDPCache* dpcache, PRBool writeLocked)
{
    if (!dpcache) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return;
    }
#ifdef DPC_RWLOCK
    if (PR_TRUE == writeLocked) {
        NSSRWLock_UnlockWrite(dpcache->lock);
    } else {
        NSSRWLock_UnlockRead(dpcache->lock);
    }
#else
    PR_Unlock(dpcache->lock);
#endif
}

SECStatus
cert_CheckCertRevocationStatus(CERTCertificate* cert, CERTCertificate* issuer,
                               const SECItem* dp, PRTime t, void* wincx,
                               CERTRevocationStatus* revStatus,
                               CERTCRLEntryReasonCode* revReason)
{
    PRBool lockedwrite = PR_FALSE;
    SECStatus rv = SECSuccess;
    CRLDPCache* dpcache = NULL;
    CERTRevocationStatus status = certRevocationStatusRevoked;
    CERTCRLEntryReasonCode reason = crlEntryReasonUnspecified;
    CERTCrlEntry* entry = NULL;
    dpcacheStatus ds;

    if (!cert || !issuer) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }

    if (revStatus) {
        *revStatus = status;
    }
    if (revReason) {
        *revReason = reason;
    }

    if (t &&
        secCertTimeValid != CERT_CheckCertValidTimes(issuer, t, PR_FALSE)) {
        /* we won't be able to check the CRL's signature if the issuer cert
           is expired as of the time we are verifying. This may cause a valid
           CRL to be cached as bad. short-circuit to avoid this case. */
        PORT_SetError(SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE);
        return SECFailure;
    }

    rv = AcquireDPCache(issuer, &issuer->derSubject, dp, t, wincx, &dpcache,
                        &lockedwrite);
    PORT_Assert(SECSuccess == rv);
    if (SECSuccess != rv) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    /* now look up the certificate SN in the DP cache's CRL */
    ds = DPCache_Lookup(dpcache, &cert->serialNumber, &entry);
    switch (ds) {
        case dpcacheFoundEntry:
            PORT_Assert(entry);
            /* check the time if we have one */
            if (entry->revocationDate.data && entry->revocationDate.len) {
                PRTime revocationDate = 0;
                if (SECSuccess ==
                    DER_DecodeTimeChoice(&revocationDate,
                                         &entry->revocationDate)) {
                    /* we got a good revocation date, only consider the
                       certificate revoked if the time we are inquiring about
                       is past the revocation date */
                    if (t >= revocationDate) {
                        rv = SECFailure;
                    } else {
                        status = certRevocationStatusValid;
                    }
                } else {
                    /* invalid revocation date, consider the certificate
                       permanently revoked */
                    rv = SECFailure;
                }
            } else {
                /* no revocation date, certificate is permanently revoked */
                rv = SECFailure;
            }
            if (SECFailure == rv) {
                (void)CERT_FindCRLEntryReasonExten(entry, &reason);
                PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE);
            }
            break;

        case dpcacheEmpty:
            /* useful for NIST policy */
            status = certRevocationStatusUnknown;
            break;

        case dpcacheNoEntry:
            status = certRevocationStatusValid;
            break;

        case dpcacheInvalidCacheError:
            /* treat it as unknown and let the caller decide based on
               the policy */
            status = certRevocationStatusUnknown;
            break;

        default:
            /* leave status as revoked */
            break;
    }

    ReleaseDPCache(dpcache, lockedwrite);
    if (revStatus) {
        *revStatus = status;
    }
    if (revReason) {
        *revReason = reason;
    }
    return rv;
}

/* check CRL revocation status of given certificate and issuer */
SECStatus
CERT_CheckCRL(CERTCertificate* cert, CERTCertificate* issuer, const SECItem* dp,
              PRTime t, void* wincx)
{
    return cert_CheckCertRevocationStatus(cert, issuer, dp, t, wincx, NULL,
                                          NULL);
}

/* retrieve full CRL object that best matches the cache status */
CERTSignedCrl*
SEC_FindCrlByName(CERTCertDBHandle* handle, SECItem* crlKey, int type)
{
    CERTSignedCrl* acrl = NULL;
    CRLDPCache* dpcache = NULL;
    SECStatus rv = SECSuccess;
    PRBool writeLocked = PR_FALSE;

    if (!crlKey) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    rv = AcquireDPCache(NULL, crlKey, NULL, 0, NULL, &dpcache, &writeLocked);
    if (SECSuccess == rv) {
        acrl = GetBestCRL(dpcache, PR_TRUE); /* decode entries, because
        SEC_FindCrlByName always returned fully decoded CRLs in the past */
        ReleaseDPCache(dpcache, writeLocked);
    }
    return acrl;
}

/* invalidate the CRL cache for a given issuer, which forces a refetch of
   CRL objects from PKCS#11 tokens */
void
CERT_CRLCacheRefreshIssuer(CERTCertDBHandle* dbhandle, SECItem* crlKey)
{
    CRLDPCache* cache = NULL;
    SECStatus rv = SECSuccess;
    PRBool writeLocked = PR_FALSE;
    PRBool readlocked;

    (void)dbhandle; /* silence compiler warnings */

    /* XCRL we will need to refresh all the DPs of the issuer in the future,
            not just the default one */
    rv = AcquireDPCache(NULL, crlKey, NULL, 0, NULL, &cache, &writeLocked);
    if (SECSuccess != rv) {
        return;
    }
    /* we need to invalidate the DPCache here */
    readlocked = (writeLocked == PR_TRUE ? PR_FALSE : PR_TRUE);
    DPCache_LockWrite();
    cache->refresh = PR_TRUE;
    DPCache_UnlockWrite();
    ReleaseDPCache(cache, writeLocked);
    return;
}

/* add the specified RAM CRL object to the cache */
SECStatus
CERT_CacheCRL(CERTCertDBHandle* dbhandle, SECItem* newdercrl)
{
    CRLDPCache* cache = NULL;
    SECStatus rv = SECSuccess;
    PRBool writeLocked = PR_FALSE;
    PRBool readlocked;
    CachedCrl* returned = NULL;
    PRBool added = PR_FALSE;
    CERTSignedCrl* newcrl = NULL;
    int realerror = 0;

    if (!dbhandle || !newdercrl) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    /* first decode the DER CRL to make sure it's OK */
    newcrl = CERT_DecodeDERCrlWithFlags(NULL, newdercrl, SEC_CRL_TYPE,
                                        CRL_DECODE_DONT_COPY_DER |
                                            CRL_DECODE_SKIP_ENTRIES);

    if (!newcrl) {
        return SECFailure;
    }

    /* XXX check if it has IDP extension. If so, do not proceed and set error */

    rv = AcquireDPCache(NULL, &newcrl->crl.derName, NULL, 0, NULL, &cache,
                        &writeLocked);
    if (SECSuccess == rv) {
        readlocked = (writeLocked == PR_TRUE ? PR_FALSE : PR_TRUE);

        rv = CachedCrl_Create(&returned, newcrl, CRL_OriginExplicit);
        if (SECSuccess == rv && returned) {
            DPCache_LockWrite();
            rv = DPCache_AddCRL(cache, returned, &added);
            if (PR_TRUE != added) {
                realerror = PORT_GetError();
                CachedCrl_Destroy(returned);
                returned = NULL;
            }
            DPCache_UnlockWrite();
        }

        ReleaseDPCache(cache, writeLocked);

        if (!added) {
            rv = SECFailure;
        }
    }
    SEC_DestroyCrl(newcrl); /* free the CRL. Either it got added to the cache
        and the refcount got bumped, or not, and thus we need to free its
        RAM */
    if (realerror) {
        PORT_SetError(realerror);
    }
    return rv;
}

/* remove the specified RAM CRL object from the cache */
SECStatus
CERT_UncacheCRL(CERTCertDBHandle* dbhandle, SECItem* olddercrl)
{
    CRLDPCache* cache = NULL;
    SECStatus rv = SECSuccess;
    PRBool writeLocked = PR_FALSE;
    PRBool readlocked;
    PRBool removed = PR_FALSE;
    PRUint32 i;
    CERTSignedCrl* oldcrl = NULL;

    if (!dbhandle || !olddercrl) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    /* first decode the DER CRL to make sure it's OK */
    oldcrl = CERT_DecodeDERCrlWithFlags(NULL, olddercrl, SEC_CRL_TYPE,
                                        CRL_DECODE_DONT_COPY_DER |
                                            CRL_DECODE_SKIP_ENTRIES);

    if (!oldcrl) {
        /* if this DER CRL can't decode, it can't be in the cache */
        return SECFailure;
    }

    rv = AcquireDPCache(NULL, &oldcrl->crl.derName, NULL, 0, NULL, &cache,
                        &writeLocked);
    if (SECSuccess == rv) {
        CachedCrl* returned = NULL;

        readlocked = (writeLocked == PR_TRUE ? PR_FALSE : PR_TRUE);

        rv = CachedCrl_Create(&returned, oldcrl, CRL_OriginExplicit);
        if (SECSuccess == rv && returned) {
            DPCache_LockWrite();
            for (i = 0; i < cache->ncrls; i++) {
                PRBool dupe = PR_FALSE, updated = PR_FALSE;
                rv = CachedCrl_Compare(returned, cache->crls[i], &dupe,
                                       &updated);
                if (SECSuccess != rv) {
                    PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
                    break;
                }
                if (PR_TRUE == dupe) {
                    rv = DPCache_RemoveCRL(cache, i); /* got a match */
                    if (SECSuccess == rv) {
                        cache->mustchoose = PR_TRUE;
                        removed = PR_TRUE;
                    }
                    break;
                }
            }

            DPCache_UnlockWrite();

            if (SECSuccess != CachedCrl_Destroy(returned)) {
                rv = SECFailure;
            }
        }

        ReleaseDPCache(cache, writeLocked);
    }
    if (SECSuccess != SEC_DestroyCrl(oldcrl)) {
        /* need to do this because object is refcounted */
        rv = SECFailure;
    }
    if (SECSuccess == rv && PR_TRUE != removed) {
        PORT_SetError(SEC_ERROR_CRL_NOT_FOUND);
    }
    return rv;
}

SECStatus
cert_AcquireNamedCRLCache(NamedCRLCache** returned)
{
    PORT_Assert(returned);
    if (!namedCRLCache.lock) {
        PORT_Assert(0);
        return SECFailure;
    }
    PR_Lock(namedCRLCache.lock);
    *returned = &namedCRLCache;
    return SECSuccess;
}

/* This must be called only while cache is acquired, and the entry is only
 * valid until cache is released.
 */
SECStatus
cert_FindCRLByGeneralName(NamedCRLCache* ncc, const SECItem* canonicalizedName,
                          NamedCRLCacheEntry** retEntry)
{
    if (!ncc || !canonicalizedName || !retEntry) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    *retEntry = (NamedCRLCacheEntry*)PL_HashTableLookup(
        namedCRLCache.entries, (void*)canonicalizedName);
    return SECSuccess;
}

SECStatus
cert_ReleaseNamedCRLCache(NamedCRLCache* ncc)
{
    if (!ncc) {
        return SECFailure;
    }
    if (!ncc->lock) {
        PORT_Assert(0);
        return SECFailure;
    }
    PR_Unlock(namedCRLCache.lock);
    return SECSuccess;
}

/* creates new named cache entry from CRL, and tries to add it to CRL cache */
static SECStatus
addCRLToCache(CERTCertDBHandle* dbhandle, SECItem* crl,
              const SECItem* canonicalizedName, NamedCRLCacheEntry** newEntry)
{
    SECStatus rv = SECSuccess;
    NamedCRLCacheEntry* entry = NULL;

    /* create new named entry */
    if (SECSuccess != NamedCRLCacheEntry_Create(newEntry) || !*newEntry) {
        /* no need to keep unused CRL around */
        SECITEM_ZfreeItem(crl, PR_TRUE);
        return SECFailure;
    }
    entry = *newEntry;
    entry->crl = crl; /* named CRL cache owns DER */
    entry->lastAttemptTime = PR_Now();
    entry->canonicalizedName = SECITEM_DupItem(canonicalizedName);
    if (!entry->canonicalizedName) {
        rv = NamedCRLCacheEntry_Destroy(entry); /* destroys CRL too */
        PORT_Assert(SECSuccess == rv);
        return SECFailure;
    }
    /* now, attempt to insert CRL into CRL cache */
    if (SECSuccess == CERT_CacheCRL(dbhandle, entry->crl)) {
        entry->inCRLCache = PR_TRUE;
        entry->successfulInsertionTime = entry->lastAttemptTime;
    } else {
        switch (PR_GetError()) {
            case SEC_ERROR_CRL_ALREADY_EXISTS:
                entry->dupe = PR_TRUE;
                break;

            case SEC_ERROR_BAD_DER:
                entry->badDER = PR_TRUE;
                break;

            /* all other reasons */
            default:
                entry->unsupported = PR_TRUE;
                break;
        }
        rv = SECFailure;
        /* no need to keep unused CRL around */
        SECITEM_ZfreeItem(entry->crl, PR_TRUE);
        entry->crl = NULL;
    }
    return rv;
}

/* take ownership of CRL, and insert it into the named CRL cache
 * and indexed CRL cache
 */
SECStatus
cert_CacheCRLByGeneralName(CERTCertDBHandle* dbhandle, SECItem* crl,
                           const SECItem* canonicalizedName)
{
    NamedCRLCacheEntry *oldEntry, *newEntry = NULL;
    NamedCRLCache* ncc = NULL;
    SECStatus rv = SECSuccess;

    PORT_Assert(namedCRLCache.lock);
    PORT_Assert(namedCRLCache.entries);

    if (!crl || !canonicalizedName) {
        PORT_Assert(0);
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    rv = cert_AcquireNamedCRLCache(&ncc);
    PORT_Assert(SECSuccess == rv);
    if (SECSuccess != rv) {
        SECITEM_ZfreeItem(crl, PR_TRUE);
        return SECFailure;
    }
    rv = cert_FindCRLByGeneralName(ncc, canonicalizedName, &oldEntry);
    PORT_Assert(SECSuccess == rv);
    if (SECSuccess != rv) {
        (void)cert_ReleaseNamedCRLCache(ncc);
        SECITEM_ZfreeItem(crl, PR_TRUE);
        return SECFailure;
    }
    if (SECSuccess ==
        addCRLToCache(dbhandle, crl, canonicalizedName, &newEntry)) {
        if (!oldEntry) {
            /* add new good entry to the hash table */
            if (NULL == PL_HashTableAdd(namedCRLCache.entries,
                                        (void*)newEntry->canonicalizedName,
                                        (void*)newEntry)) {
                PORT_Assert(0);
                NamedCRLCacheEntry_Destroy(newEntry);
                rv = SECFailure;
            }
        } else {
            PRBool removed;
            /* remove the old CRL from the cache if needed */
            if (oldEntry->inCRLCache) {
                rv = CERT_UncacheCRL(dbhandle, oldEntry->crl);
                PORT_Assert(SECSuccess == rv);
            }
            removed = PL_HashTableRemove(namedCRLCache.entries,
                                         (void*)oldEntry->canonicalizedName);
            PORT_Assert(removed);
            if (!removed) {
                rv = SECFailure;
                /* leak old entry since we couldn't remove it from the hash
                 * table */
            } else {
                PORT_CheckSuccess(NamedCRLCacheEntry_Destroy(oldEntry));
            }
            if (NULL == PL_HashTableAdd(namedCRLCache.entries,
                                        (void*)newEntry->canonicalizedName,
                                        (void*)newEntry)) {
                PORT_Assert(0);
                rv = SECFailure;
            }
        }
    } else {
        /* error adding new CRL to cache */
        if (!oldEntry) {
            /* no old cache entry, use the new one even though it's bad */
            if (NULL == PL_HashTableAdd(namedCRLCache.entries,
                                        (void*)newEntry->canonicalizedName,
                                        (void*)newEntry)) {
                PORT_Assert(0);
                rv = SECFailure;
            }
        } else {
            if (oldEntry->inCRLCache) {
                /* previous cache entry was good, keep it and update time */
                oldEntry->lastAttemptTime = newEntry->lastAttemptTime;
                /* throw away new bad entry */
                rv = NamedCRLCacheEntry_Destroy(newEntry);
                PORT_Assert(SECSuccess == rv);
            } else {
                /* previous cache entry was bad, just replace it */
                PRBool removed = PL_HashTableRemove(
                    namedCRLCache.entries, (void*)oldEntry->canonicalizedName);
                PORT_Assert(removed);
                if (!removed) {
                    /* leak old entry since we couldn't remove it from the hash
                     * table */
                    rv = SECFailure;
                } else {
                    PORT_CheckSuccess(NamedCRLCacheEntry_Destroy(oldEntry));
                }
                if (NULL == PL_HashTableAdd(namedCRLCache.entries,
                                            (void*)newEntry->canonicalizedName,
                                            (void*)newEntry)) {
                    PORT_Assert(0);
                    rv = SECFailure;
                }
            }
        }
    }
    PORT_CheckSuccess(cert_ReleaseNamedCRLCache(ncc));

    return rv;
}

static SECStatus
CachedCrl_Create(CachedCrl** returned, CERTSignedCrl* crl, CRLOrigin origin)
{
    CachedCrl* newcrl = NULL;
    if (!returned) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    newcrl = PORT_ZAlloc(sizeof(CachedCrl));
    if (!newcrl) {
        return SECFailure;
    }
    newcrl->crl = SEC_DupCrl(crl);
    newcrl->origin = origin;
    *returned = newcrl;
    return SECSuccess;
}

/* empty the cache content */
static SECStatus
CachedCrl_Depopulate(CachedCrl* crl)
{
    if (!crl) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    /* destroy the hash table */
    if (crl->entries) {
        PL_HashTableDestroy(crl->entries);
        crl->entries = NULL;
    }

    /* free the pre buffer */
    if (crl->prebuffer) {
        PreAllocator_Destroy(crl->prebuffer);
        crl->prebuffer = NULL;
    }
    return SECSuccess;
}

static SECStatus
CachedCrl_Destroy(CachedCrl* crl)
{
    if (!crl) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    CachedCrl_Depopulate(crl);
    SEC_DestroyCrl(crl->crl);
    PORT_Free(crl);
    return SECSuccess;
}

/* create hash table of CRL entries */
static SECStatus
CachedCrl_Populate(CachedCrl* crlobject)
{
    SECStatus rv = SECFailure;
    CERTCrlEntry** crlEntry = NULL;
    PRUint32 numEntries = 0;

    if (!crlobject) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    /* complete the entry decoding . XXX thread-safety of CRL object */
    rv = CERT_CompleteCRLDecodeEntries(crlobject->crl);
    if (SECSuccess != rv) {
        crlobject->unbuildable = PR_TRUE; /* don't try to build this again */
        return SECFailure;
    }

    if (crlobject->entries && crlobject->prebuffer) {
        /* cache is already built */
        return SECSuccess;
    }

    /* build the hash table from the full CRL */
    /* count CRL entries so we can pre-allocate space for hash table entries */
    for (crlEntry = crlobject->crl->crl.entries; crlEntry && *crlEntry;
         crlEntry++) {
        numEntries++;
    }
    crlobject->prebuffer =
        PreAllocator_Create(numEntries * sizeof(PLHashEntry));
    PORT_Assert(crlobject->prebuffer);
    if (!crlobject->prebuffer) {
        return SECFailure;
    }
    /* create a new hash table */
    crlobject->entries =
        PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare, PL_CompareValues,
                        &preAllocOps, crlobject->prebuffer);
    PORT_Assert(crlobject->entries);
    if (!crlobject->entries) {
        return SECFailure;
    }
    /* add all serial numbers to the hash table */
    for (crlEntry = crlobject->crl->crl.entries; crlEntry && *crlEntry;
         crlEntry++) {
        PL_HashTableAdd(crlobject->entries, &(*crlEntry)->serialNumber,
                        *crlEntry);
    }

    return SECSuccess;
}

/* returns true if there are CRLs from PKCS#11 slots */
static PRBool
DPCache_HasTokenCRLs(CRLDPCache* cache)
{
    PRBool answer = PR_FALSE;
    PRUint32 i;
    for (i = 0; i < cache->ncrls; i++) {
        if (cache->crls[i] && (CRL_OriginToken == cache->crls[i]->origin)) {
            answer = PR_TRUE;
            break;
        }
    }
    return answer;
}

/* are these CRLs the same, as far as the cache is concerned ? */
/* are these CRLs the same token object but with different DER ?
   This can happen if the DER CRL got updated in the token, but the PKCS#11
   object ID did not change. NSS softoken has the unfortunate property to
   never change the object ID for CRL objects. */
static SECStatus
CachedCrl_Compare(CachedCrl* a, CachedCrl* b, PRBool* isDupe, PRBool* isUpdated)
{
    PORT_Assert(a);
    PORT_Assert(b);
    PORT_Assert(isDupe);
    PORT_Assert(isUpdated);
    if (!a || !b || !isDupe || !isUpdated || !a->crl || !b->crl) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }

    *isDupe = *isUpdated = PR_FALSE;

    if (a == b) {
        /* dupe */
        *isDupe = PR_TRUE;
        *isUpdated = PR_FALSE;
        return SECSuccess;
    }
    if (b->origin != a->origin) {
        /* CRLs of different origins are not considered dupes,
           and can't be updated either */
        return SECSuccess;
    }
    if (CRL_OriginToken == b->origin) {
        /* for token CRLs, slot and PKCS#11 object handle must match for CRL
           to truly be a dupe */
        if ((b->crl->slot == a->crl->slot) &&
            (b->crl->pkcs11ID == a->crl->pkcs11ID)) {
            /* ASN.1 DER needs to match for dupe check */
            /* could optimize by just checking a few fields like thisUpdate */
            if (SECEqual ==
                SECITEM_CompareItem(b->crl->derCrl, a->crl->derCrl)) {
                *isDupe = PR_TRUE;
            } else {
                *isUpdated = PR_TRUE;
            }
        }
        return SECSuccess;
    }
    if (CRL_OriginExplicit == b->origin) {
        /* We need to make sure this is the same object that the user provided
           to CERT_CacheCRL previously. That API takes a SECItem*, thus, we
           just do a pointer comparison here.
        */
        if (b->crl->derCrl == a->crl->derCrl) {
            *isDupe = PR_TRUE;
        }
    }
    return SECSuccess;
}