Mercurial > trustbridge > nss-cmake-static
diff nss/lib/certdb/crl.c @ 0:1e5118fa0cb1
This is NSS with a Cmake Buildsyste
To compile a static NSS library for Windows we've used the
Chromium-NSS fork and added a Cmake buildsystem to compile
it statically for Windows. See README.chromium for chromium
changes and README.trustbridge for our modifications.
author | Andre Heinecke <andre.heinecke@intevation.de> |
---|---|
date | Mon, 28 Jul 2014 10:47:06 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nss/lib/certdb/crl.c Mon Jul 28 10:47:06 2014 +0200 @@ -0,0 +1,3371 @@ +/* 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; + SECStatus rv; + + 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 */ + rv = 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->issuer) + { + CERT_DestroyCertificate(cache->issuer); + } + /* 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->issuer) + { + signstatus = CERT_VerifyCRL(crlobject->crl, cache->issuer, vfdate, + wincx); + } + if (SECSuccess != signstatus) + { + if (!cache->issuer) + { + /* 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->issuer) && + (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->issuer) + { + dirty = PR_TRUE; + cache->issuer = CERT_DupCertificate(issuer); + } + 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->issuer && 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; + } + /* all CRLs are good, sort them by thisUpdate */ + qsort(cache->crls, cache->ncrls, sizeof(CachedCrl*), + SortCRLsByThisUpdate); + + if (cache->ncrls) + { + /* 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->issuer = CERT_DupCertificate(issuer); + } + 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) + { + SECStatus rv2 = 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, rv2; + + 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) + { + rv = 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); + rv2 = NamedCRLCacheEntry_Destroy(newEntry); + PORT_Assert(SECSuccess == rv2); + 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 + { + rv2 = NamedCRLCacheEntry_Destroy(oldEntry); + PORT_Assert(SECSuccess == rv2); + } + 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 + { + rv2 = NamedCRLCacheEntry_Destroy(oldEntry); + PORT_Assert(SECSuccess == rv2); + } + if (NULL == PL_HashTableAdd(namedCRLCache.entries, + (void*) newEntry->canonicalizedName, + (void*) newEntry)) + { + PORT_Assert(0); + rv = SECFailure; + } + } + } + } + rv2 = cert_ReleaseNamedCRLCache(ncc); + PORT_Assert(SECSuccess == rv2); + + 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; +}