summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/certdb/crl.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/certdb/crl.c')
-rw-r--r--security/nss/lib/certdb/crl.c3045
1 files changed, 3045 insertions, 0 deletions
diff --git a/security/nss/lib/certdb/crl.c b/security/nss/lib/certdb/crl.c
new file mode 100644
index 000000000..87469085e
--- /dev/null
+++ b/security/nss/lib/certdb/crl.c
@@ -0,0 +1,3045 @@
+/* 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* PreAllocator)
+{
+ if (!PreAllocator) {
+ return;
+ }
+ if (PreAllocator->arena) {
+ PORT_FreeArena(PreAllocator->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) {
+ 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_NETSCAPE_CRL != crl_class) {
+ xstatus = PR_FALSE;
+ }
+ } else {
+ xstatus = PR_FALSE;
+ }
+ NSSArena_Destroy(arena);
+ 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;
+}