diff --git a/security/nss/lib/certdb/cert.h b/security/nss/lib/certdb/cert.h index db4ba75f03b5..0893b65a93eb 100644 --- a/security/nss/lib/certdb/cert.h +++ b/security/nss/lib/certdb/cert.h @@ -37,7 +37,7 @@ /* * cert.h - public data structures and prototypes for the certificate library * - * $Id: cert.h,v 1.48 2004/11/09 06:13:32 wchang0222%aol.com Exp $ + * $Id: cert.h,v 1.49 2005/02/15 06:26:42 julien.pierre.bugs%sun.com Exp $ */ #ifndef _CERT_H_ @@ -428,6 +428,7 @@ CERT_DecodeDERCrlWithFlags(PRArenaPool *narena, SECItem *derSignedCrl, #define CRL_DECODE_DONT_COPY_DER 0x00000001 #define CRL_DECODE_SKIP_ENTRIES 0x00000002 #define CRL_DECODE_KEEP_BAD_CRL 0x00000004 +#define CRL_DECODE_ADOPT_HEAP_DER 0x00000008 /* complete the decoding of a partially decoded CRL, ie. decode the entries. Note that entries is an optional field in a CRL, so the @@ -452,6 +453,18 @@ extern void CERT_DestroyCrl (CERTSignedCrl *crl); the issuer (CA). */ void CERT_CRLCacheRefreshIssuer(CERTCertDBHandle* dbhandle, SECItem* crlKey); +/* add the specified RAM CRL object to the cache. + Once a CRL is added to the cache, the application must hold on to the + memory, because the cache will reference it directly. It can only + free it after it calls CERT_UncacheCRL . +*/ +SECStatus CERT_CacheCRL(CERTCertDBHandle* dbhandle, SECItem* newcrl); + +/* remove a previously added CRL object from the CRL cache. It is OK + for the application to free the memory after a successful removal +*/ +SECStatus CERT_UncacheCRL(CERTCertDBHandle* dbhandle, SECItem* oldcrl); + /* ** Decode a certificate and put it into the temporary certificate database */ diff --git a/security/nss/lib/certdb/certi.h b/security/nss/lib/certdb/certi.h index f2de7fca5daa..6ceb372a354c 100644 --- a/security/nss/lib/certdb/certi.h +++ b/security/nss/lib/certdb/certi.h @@ -36,7 +36,7 @@ /* * certi.h - private data structures for the certificate library * - * $Id: certi.h,v 1.11 2004/04/25 15:03:03 gerv%gerv.net Exp $ + * $Id: certi.h,v 1.12 2005/02/15 06:26:42 julien.pierre.bugs%sun.com Exp $ */ #ifndef _CERTI_H_ #define _CERTI_H_ @@ -44,7 +44,11 @@ #include "certt.h" #include "nssrwlkt.h" -#define USE_RWLOCK 1 +/* +#define GLOBAL_RWLOCK 1 +*/ + +#define DPC_RWLOCK 1 /* all definitions in this file are subject to change */ @@ -53,16 +57,15 @@ typedef struct CRLEntryCacheStr CRLEntryCache; typedef struct CRLDPCacheStr CRLDPCache; typedef struct CRLIssuerCacheStr CRLIssuerCache; typedef struct CRLCacheStr CRLCache; +typedef struct CachedCrlStr CachedCrl; struct OpaqueCRLFieldsStr { PRBool partial; + PRBool decodingError; PRBool badEntries; - PRBool bad; PRBool badDER; PRBool badExtensions; - PRBool deleted; PRBool heapDER; - PRBool unverified; }; typedef struct PreAllocatorStr PreAllocator; @@ -95,23 +98,14 @@ struct CRLEntryCacheStr { #define CRL_CACHE_OUT_OF_MEMORY 0x0004 /* this state will be set if we don't have enough memory to build the hash table of entries */ -/* CRL distribution point cache object - This is a cache of CRL entries for a given distribution point of an issuer - It is built from a collection of one full and 0 or more delta CRLs. -*/ - -struct CRLDPCacheStr { -#ifdef USE_RWLOCK - NSSRWLock* lock; -#else - PRLock* lock; -#endif - CERTCertificate* issuer; /* cert issuer */ - SECItem* subject; /* DER of issuer subject */ - SECItem* distributionPoint; /* DER of distribution point. This may be - NULL when distribution points aren't - in use (ie. the CA has a single CRL) */ +typedef enum { + CRL_OriginToken = 0, /* CRL came from PKCS#11 token */ + CRL_OriginExplicit = 1, /* CRL was explicitly added to the cache, from RAM */ +} CRLOrigin; +struct CachedCrlStr { + CERTSignedCrl* crl; + CRLOrigin origin; /* hash table of entries. We use a PLHashTable and pre-allocate the required amount of memory in one shot, so that our allocator can simply pass offsets into it when hashing. @@ -125,30 +119,110 @@ struct CRLDPCacheStr { */ PLHashTable* entries; PreAllocator* prebuffer; /* big pre-allocated buffer mentioned above */ + PRBool sigChecked; /* this CRL signature has already been checked */ + PRBool sigValid; /* signature verification status . + Only meaningful if checked is PR_TRUE . */ +}; - /* array of CRLs matching this distribution point */ +/* constructor */ +SECStatus CachedCrl_Create(CachedCrl** returned, CERTSignedCrl* crl, + CRLOrigin origin); +/* destructor */ +SECStatus CachedCrl_Destroy(CachedCrl* crl); + +/* create hash table of CRL entries */ +SECStatus CachedCrl_Populate(CachedCrl* crlobject); + +/* empty the cache content */ +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); + +/* CRL distribution point cache object + This is a cache of CRL entries for a given distribution point of an issuer + It is built from a collection of one full and 0 or more delta CRLs. +*/ + +struct CRLDPCacheStr { +#ifdef DPC_RWLOCK + NSSRWLock* lock; +#else + PRLock* lock; +#endif + CERTCertificate* issuer; /* cert issuer + XXX there may be multiple issuer certs, + with different validity dates. Also + need to deal with SKID/AKID . See + bugzilla 217387, 233118 */ + SECItem* subject; /* DER of issuer subject */ + SECItem* distributionPoint; /* DER of distribution point. This may be + NULL when distribution points aren't + in use (ie. the CA has a single CRL). + Currently not used. */ + + /* array of full CRLs matching this distribution point */ PRUint32 ncrls; /* total number of CRLs in crls */ - CERTSignedCrl** crls; /* array of all matching DER CRLs - from all tokens */ + CachedCrl** crls; /* array of all matching CRLs */ /* XCRL With iCRLs and multiple DPs, the CRL can be shared accross several issuers. In the future, we'll need to globally recycle the CRL in a separate list in order to avoid extra lookups, decodes, and copies */ /* pointers to good decoded CRLs used to build the cache */ - CERTSignedCrl* full; /* full CRL used for the cache */ + CachedCrl* selected; /* full CRL selected for use in the cache */ #if 0 /* for future use */ PRInt32 numdeltas; /* number of delta CRLs used for the cache */ - CERTSignedCrl** deltas; /* delta CRLs used for the cache */ + CachedCrl** deltas; /* delta CRLs used for the cache */ #endif - /* invalidity bitflag */ + /* cache invalidity bitflag */ PRUint16 invalid; /* this state will be set if either CRL_CACHE_INVALID_CRLS or CRL_CACHE_LAST_FETCH_FAILED is set. In those cases, all certs are considered revoked as a security precaution. The invalid state can only be cleared during an update if all error states are cleared */ + PRBool refresh; /* manual refresh from tokens has been forced */ + PRBool mustchoose; /* trigger reselection algorithm, for case when + RAM CRL objects are dropped from the cache */ + PRIntervalTime lastfetch; /* time a CRL token fetch was last performed */ + PRIntervalTime lastcheck; /* time CRL token objects were last checked for + existence */ }; +/* create a DPCache object */ +SECStatus DPCache_Create(CRLDPCache** returned, CERTCertificate* issuer, + SECItem* subject, SECItem* dp); + +/* destructor for CRL DPCache object */ +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. */ +SECStatus DPCache_AddCRL(CRLDPCache* cache, CachedCrl* crl, PRBool* added); + +/* fetch the CRL for this DP from the PKCS#11 tokens */ +SECStatus DPCache_FetchFromTokens(CRLDPCache* cache, PRTime vfdate, void* wincx); + +/* check if a particular SN is in the CRL cache and return its entry */ +SECStatus DPCache_Lookup(CRLDPCache* cache, SECItem* sn, CERTCrlEntry** returned); + +/* update the content of the CRL cache, including fetching of CRLs, and + reprocessing with specified issuer and date */ +SECStatus DPCache_GetUpToDate(CRLDPCache* cache, CERTCertificate* issuer, + PRBool readlocked, PRTime vfdate, void* wincx); + +/* returns true if there are CRLs from PKCS#11 slots */ +PRBool DPCache_HasTokenCRLs(CRLDPCache* cache); + +/* remove CRL at offset specified */ +SECStatus DPCache_RemoveCRL(CRLDPCache* cache, PRUint32 offset); + +/* Pick best CRL to use . needs write access */ +SECStatus DPCache_SelectCRL(CRLDPCache* cache); + /* CRL issuer cache object This object tracks all the distribution point caches for a given issuer. XCRL once we support multiple issuing distribution points, this object @@ -158,7 +232,6 @@ struct CRLDPCacheStr { struct CRLIssuerCacheStr { SECItem* subject; /* DER of issuer subject */ - CRLDPCache dp; /* DER of distribution point */ CRLDPCache* dpp; #if 0 /* XCRL for future use. @@ -171,12 +244,32 @@ struct CRLIssuerCacheStr { #endif }; +/* create an issuer cache object (per CA subject ) */ +SECStatus IssuerCache_Create(CRLIssuerCache** returned, + CERTCertificate* issuer, + SECItem* subject, SECItem* dp); + +/* destructor for CRL IssuerCache object */ +SECStatus IssuerCache_Destroy(CRLIssuerCache* cache); + +/* add a DPCache to the issuer cache */ +SECStatus IssuerCache_AddDP(CRLIssuerCache* cache, CERTCertificate* issuer, + SECItem* subject, SECItem* dp, CRLDPCache** newdpc); + +/* get a particular DPCache object from an IssuerCache */ +CRLDPCache* IssuerCache_GetDPCache(CRLIssuerCache* cache, SECItem* dp); + + /* CRL revocation cache object This object tracks all the issuer caches */ struct CRLCacheStr { +#ifdef GLOBAL_RWLOCK + NSSRWLock* lock; +#else PRLock* lock; +#endif /* hash table of issuer to CRLIssuerCacheStr, indexed by issuer DER subject */ PLHashTable* issuers; diff --git a/security/nss/lib/certdb/crl.c b/security/nss/lib/certdb/crl.c index 6e781c6f58d1..93feb9632583 100644 --- a/security/nss/lib/certdb/crl.c +++ b/security/nss/lib/certdb/crl.c @@ -37,7 +37,7 @@ /* * Moved from secpkcs7.c * - * $Id: crl.c,v 1.42 2004/04/25 15:03:03 gerv%gerv.net Exp $ + * $Id: crl.c,v 1.43 2005/02/15 06:26:42 julien.pierre.bugs%sun.com Exp $ */ #include "cert.h" @@ -53,9 +53,10 @@ #include "dev.h" #include "dev3hack.h" #include "nssbase.h" -#ifdef USE_RWLOCK +#if defined(DPC_RWLOCK) || defined(GLOBAL_RWLOCK) #include "nssrwlk.h" #endif +#include "pk11priv.h" const SEC_ASN1Template SEC_CERTExtensionTemplate[] = { { SEC_ASN1_SEQUENCE, @@ -334,9 +335,9 @@ SECStatus cert_check_crl_entries (CERTCrl *crl) } /* 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. + 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) { @@ -408,7 +409,8 @@ SECStatus CERT_CompleteCRLDecodeEntries(CERTSignedCrl* crl) OpaqueCRLFields* extended = NULL; if ( (!crl) || - (!(extended = (OpaqueCRLFields*) crl->opaque)) ) { + (!(extended = (OpaqueCRLFields*) crl->opaque)) || + (PR_TRUE == extended->decodingError) ) { rv = SECFailure; } else { if (PR_FALSE == extended->partial) { @@ -434,7 +436,7 @@ SECStatus CERT_CompleteCRLDecodeEntries(CERTSignedCrl* crl) extended->partial = PR_FALSE; /* successful decode, avoid decoding again */ } else { - extended->bad = PR_TRUE; + 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 @@ -462,7 +464,17 @@ CERT_DecodeDERCrlWithFlags(PRArenaPool *narena, SECItem *derSignedCrl, OpaqueCRLFields* extended = NULL; const SEC_ASN1Template* crlTemplate = cert_SignedCrlTemplate; - /* make a new arena */ + if (!derSignedCrl || + ( (options & CRL_DECODE_ADOPT_HEAP_DER) && /* adopting DER requires + not copying it */ + (!(options & CRL_DECODE_DONT_COPY_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 ) { @@ -487,7 +499,9 @@ CERT_DecodeDERCrlWithFlags(PRArenaPool *narena, SECItem *derSignedCrl, 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 @@ -556,7 +570,7 @@ CERT_DecodeDERCrlWithFlags(PRArenaPool *narena, SECItem *derSignedCrl, loser: if (options & CRL_DECODE_KEEP_BAD_CRL) { - extended->bad = PR_TRUE; + extended->decodingError = PR_TRUE; crl->referenceCount = 1; return(crl); } @@ -574,7 +588,8 @@ loser: CERTSignedCrl * CERT_DecodeDERCrl(PRArenaPool *narena, SECItem *derSignedCrl, int type) { - return CERT_DecodeDERCrlWithFlags(narena, derSignedCrl, type, CRL_DECODE_DEFAULT_OPTIONS); + return CERT_DecodeDERCrlWithFlags(narena, derSignedCrl, type, + CRL_DECODE_DEFAULT_OPTIONS); } /* @@ -582,7 +597,8 @@ CERT_DecodeDERCrl(PRArenaPool *narena, SECItem *derSignedCrl, int type) * caching stuff used by certificates....? * return values : * - * SECSuccess means we got a valid DER CRL (passed in "decoded"), or no CRL at all + * SECSuccess means we got a valid DER CRL (passed in "decoded"), or no CRL at + * all * * 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 @@ -608,8 +624,8 @@ SEC_FindCrlByKeyOnSlot(PK11SlotInfo *slot, SECItem *crlKey, int type, return SECFailure; } - /* XXX it would be really useful to be able to fetch the CRL directly into an - arena. This would avoid a copy later on in the decode step */ + /* XXX it would be really useful to be able to fetch the CRL directly into + an arena. This would avoid a copy later on in the decode step */ PORT_SetError(0); derCrl = PK11_FindCrlByName(&slot, &crlHandle, crlKey, type, &url); if (derCrl == NULL) { @@ -624,6 +640,11 @@ SEC_FindCrlByKeyOnSlot(PK11SlotInfo *slot, SECItem *crlKey, int type, PORT_Assert(crlHandle != CK_INVALID_HANDLE); /* PK11_FindCrlByName obtained a slot reference. */ + if (!(decodeoptions & CRL_DECODE_DONT_COPY_DER) ) { + /* force adoption of the DER from the heap - this will cause it to be + automatically freed when SEC_DestroyCrl is invoked */ + decodeoptions |= CRL_DECODE_ADOPT_HEAP_DER; + } crl = CERT_DecodeDERCrlWithFlags(NULL, derCrl, type, decodeoptions); if (crl) { crl->slot = slot; @@ -646,13 +667,8 @@ SEC_FindCrlByKeyOnSlot(PK11SlotInfo *slot, SECItem *crlKey, int type, loser: if (derCrl) { - /* destroy the DER, unless a decoded CRL was returned with DER - allocated on the heap. This is solely for cache purposes */ - if (crl && (decodeoptions & CRL_DECODE_DONT_COPY_DER)) { - /* mark the DER as having come from the heap instead of the - arena, so it can be destroyed */ - GetOpaqueCRLFields(crl)->heapDER = PR_TRUE; - } else { + /* destroy the DER if it was copied to the CRL */ + if (crl && (!(decodeoptions & CRL_DECODE_DONT_COPY_DER)) ) { SECITEM_FreeItem(derCrl, PR_TRUE); } } @@ -816,8 +832,10 @@ SEC_DestroyCrl(CERTSignedCrl *crl) } PORT_FreeArena(crl->arena, PR_FALSE); } + return SECSuccess; + } else { + return SECFailure; } - return SECSuccess; } SECStatus @@ -866,11 +884,13 @@ SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SetOfSignedCrlTemplate) /* ** Pre-allocator hash allocator ops. */ + +/* allocate memory for hash table */ static void * PR_CALLBACK PreAllocTable(void *pool, PRSize size) { PreAllocator* alloc = (PreAllocator*)pool; - PR_ASSERT(alloc); + PORT_Assert(alloc); if (!alloc) { /* no allocator, or buffer full */ @@ -878,34 +898,44 @@ PreAllocTable(void *pool, PRSize size) } 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) { } -static PLHashAllocOps preAllocOps = { +/* 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) @@ -923,6 +953,7 @@ void PreAllocator_Destroy(PreAllocator* PreAllocator) PORT_Free(PreAllocator); } +/* constructor for PreAllocator object */ PreAllocator* PreAllocator_Create(PRSize size) { PreAllocator prebuffer; @@ -930,22 +961,28 @@ PreAllocator* PreAllocator_Create(PRSize size) memset(&prebuffer, 0, sizeof(PreAllocator)); prebuffer.len = size; prebuffer.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); - PR_ASSERT(prebuffer.arena); - if (!prebuffer.arena) { + PORT_Assert(prebuffer.arena); + if (!prebuffer.arena) + { PreAllocator_Destroy(&prebuffer); return NULL; } - if (prebuffer.len) { - prebuffer.data = PR_Malloc(prebuffer.len); - if (!prebuffer.data) { + if (prebuffer.len) + { + prebuffer.data = PORT_Alloc(prebuffer.len); + if (!prebuffer.data) + { PreAllocator_Destroy(&prebuffer); return NULL; } - } else { + } + else + { prebuffer.data = NULL; } - prepointer = (PreAllocator*)PR_Malloc(sizeof(PreAllocator)); - if (!prepointer) { + prepointer = (PreAllocator*)PORT_Alloc(sizeof(PreAllocator)); + if (!prepointer) + { PreAllocator_Destroy(&prebuffer); return NULL; } @@ -953,83 +990,120 @@ PreAllocator* PreAllocator_Create(PRSize size) return prepointer; } +/* global CRL cache object */ static CRLCache crlcache = { NULL, NULL }; +/* initial state is off */ static PRBool crlcache_initialized = PR_FALSE; -/* this needs to be called at NSS initialization time */ +PRIntervalTime oneminute = 0; +PRIntervalTime tenminutes = 0; +PRIntervalTime CRLCache_Empty_TokenFetch_Interval; /* how often + to query the tokens for CRL objects, in order to discover new objects, if + the cache does not contain any token CRLs */ + +PRIntervalTime CRLCache_TokenRefetch_Interval; /* how often + to query the tokens for CRL objects, in order to discover new objects, if + the cache already contains token CRLs */ + +PRIntervalTime CRLCache_ExistenceCheck_Interval; /* how often to check if + a token CRL object still exists */ + +/* this function is called at NSS initialization time */ SECStatus InitCRLCache(void) { if (PR_FALSE == crlcache_initialized) { - PR_ASSERT(NULL == crlcache.lock); + PORT_Assert(NULL == crlcache.lock); + PORT_Assert(NULL == crlcache.issuers); + if (crlcache.lock || crlcache.issuers) + { + /* 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 if (!crlcache.lock) { return SECFailure; } - PR_ASSERT(NULL == crlcache.issuers); crlcache.issuers = PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare, PL_CompareValues, NULL, NULL); if (!crlcache.issuers) { +#ifdef GLOBAL_RWLOCK + NSSRWLock_Destroy(crlcache.lock); +#else PR_DestroyLock(crlcache.lock); +#endif crlcache.lock = NULL; return SECFailure; } + CRLCache_Empty_TokenFetch_Interval = CRLCache_ExistenceCheck_Interval + = oneminute = PR_SecondsToInterval(60); + CRLCache_TokenRefetch_Interval = tenminutes = PR_SecondsToInterval(600); crlcache_initialized = PR_TRUE; return SECSuccess; } else { - PR_ASSERT(crlcache.lock); - PR_ASSERT(crlcache.issuers); + 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; } } } -SECStatus DPCache_Destroy(CRLDPCache* cache) +/* destructor for CRL DPCache object */ +static SECStatus DPCache_Destroy(CRLDPCache* cache) { PRUint32 i = 0; - PR_ASSERT(cache); - if (!cache) { + PORT_Assert(cache); + if (!cache) + { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } if (cache->lock) { -#ifdef USE_RWLOCK +#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;incrls;i++) { - SEC_DestroyCrl(cache->crls[i]); + if (!cache->crls || !cache->crls[i] || + SECSuccess != CachedCrl_Destroy(cache->crls[i])) + { + return SECFailure; + } } /* free the array of CRLs */ if (cache->crls) { - PR_Free(cache->crls); - } - /* destroy the hash table */ - if (cache->entries) - { - PL_HashTableDestroy(cache->entries); - } - /* free the pre buffer */ - if (cache->prebuffer) - { - PreAllocator_Destroy(cache->prebuffer); + PORT_Free(cache->crls); } /* destroy the cert */ if (cache->issuer) @@ -1046,22 +1120,31 @@ SECStatus DPCache_Destroy(CRLDPCache* cache) { 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; } -#if 0 - /* XCRL */ +#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); @@ -1072,82 +1155,195 @@ SECStatus IssuerCache_Destroy(CRLIssuerCache* cache) { SECITEM_FreeItem(cache->subject, PR_TRUE); } - DPCache_Destroy(&cache->dp); - PR_Free(cache); + if (SECSuccess != DPCache_Destroy(cache->dpp)) + { + PORT_Assert(0); + return SECFailure; + } + PORT_Free(cache); return SECSuccess; } -PRIntn PR_CALLBACK FreeIssuer(PLHashEntry *he, PRIntn i, void *arg) +/* callback function used in hash table destructor */ +static PRIntn PR_CALLBACK FreeIssuer(PLHashEntry *he, PRIntn i, void *arg) { CRLIssuerCache* issuer = NULL; - PR_ASSERT(he); - if (!he) { + SECStatus* rv = (SECStatus*) arg; + + PORT_Assert(he); + if (!he) + { return HT_ENUMERATE_NEXT; } issuer = (CRLIssuerCache*) he->value; - PR_ASSERT(issuer); - if (issuer) { - IssuerCache_Destroy(issuer); + 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; } +/* 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) { - if (!crlcache.lock || !crlcache.issuers) + 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)) + { + /* CRL cache has partially been shut down */ + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } /* empty the cache */ - PL_HashTableEnumerateEntries(crlcache.issuers, &FreeIssuer, NULL); + /* 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; crlcache_initialized = PR_FALSE; - return SECSuccess; + return rv; } -SECStatus DPCache_AddCRL(CRLDPCache* cache, CERTSignedCrl* crl) +/* 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) { - CERTSignedCrl** newcrls = NULL; + CachedCrl** newcrls = NULL; + PRUint32 i = 0; PORT_Assert(cache); - PORT_Assert(crl); - if (!cache || !crl) { + PORT_Assert(newcrl); + PORT_Assert(added); + if (!cache || !newcrl || !added) + { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } - newcrls = (CERTSignedCrl**)PORT_Realloc(cache->crls, - (cache->ncrls+1)*sizeof(CERTSignedCrl*)); - if (!newcrls) { + *added = PR_FALSE; + /* before adding a new CRL, check if it is a duplicate */ + for (i=0;incrls;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] = crl; + cache->crls[cache->ncrls-1] = newcrl; + *added = PR_TRUE; return SECSuccess; } -SECStatus DPCache_Cleanup(CRLDPCache* cache) +/* remove CRL at offset specified */ +static SECStatus DPCache_RemoveCRL(CRLDPCache* cache, PRUint32 offset) { - /* remove deleted CRLs from memory */ - PRUint32 i = 0; + CachedCrl* acrl = NULL; PORT_Assert(cache); - if (!cache) { + if (!cache || (!cache->crls) || (!(offsetncrls)) ) + { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } - for (i=0;incrls;i++) { - CERTSignedCrl* acrl = cache->crls[i]; - if (acrl && (PR_TRUE == GetOpaqueCRLFields(acrl)->deleted)) { - cache->crls[i] = cache->crls[cache->ncrls-1]; - cache->crls[cache->ncrls-1] = NULL; - cache->ncrls--; - } + 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; } -PRBool CRLStillExists(CERTSignedCrl* crl) +/* 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; @@ -1160,17 +1356,20 @@ PRBool CRLStillExists(CERTSignedCrl* crl) SECItem* oldSubject = NULL; PORT_Assert(crl); - if (!crl) { + if (!crl) + { return PR_FALSE; } slot = crl->slot; - PORT_Assert(slot); - if (!slot) { + PORT_Assert(crl->slot); + if (!slot) + { return PR_FALSE; } oldSubject = &crl->crl.derName; - PR_ASSERT(oldSubject); - if (!oldSubject) { + PORT_Assert(oldSubject); + if (!oldSubject) + { return PR_FALSE; } @@ -1180,12 +1379,14 @@ PRBool CRLStillExists(CERTSignedCrl* crl) /* first, make an nssCryptokiObject */ instance.handle = crl->pkcs11ID; PORT_Assert(instance.handle); - if (!instance.handle) { + if (!instance.handle) + { return PR_FALSE; } instance.token = PK11Slot_GetNSSToken(slot); PORT_Assert(instance.token); - if (!instance.token) { + if (!instance.token) + { return PR_FALSE; } instance.isTokenObject = PR_TRUE; @@ -1193,7 +1394,8 @@ PRBool CRLStillExists(CERTSignedCrl* crl) arena = NSSArena_Create(); PORT_Assert(arena); - if (!arena) { + if (!arena) + { return PR_FALSE; } @@ -1205,26 +1407,42 @@ PRBool CRLStillExists(CERTSignedCrl* crl) &crl_class, /* class */ NULL, NULL); - if (PR_SUCCESS == status) { + if (PR_SUCCESS == status) + { subject.data = newsubject.data; subject.len = newsubject.size; - if (SECITEM_CompareItem(oldSubject, &subject) != SECEqual) { + if (SECITEM_CompareItem(oldSubject, &subject) != SECEqual) + { xstatus = PR_FALSE; } - if (CKO_NETSCAPE_CRL != crl_class) { + if (CKO_NETSCAPE_CRL != crl_class) + { xstatus = PR_FALSE; } - } else { + } + else + { xstatus = PR_FALSE; } NSSArena_Destroy(arena); return xstatus; } -SECStatus DPCache_Refresh(CRLDPCache* cache, CERTSignedCrl* crlobject, +/* 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) { - SECStatus rv = SECSuccess; /* 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 @@ -1237,210 +1455,175 @@ SECStatus DPCache_Refresh(CRLDPCache* cache, CERTSignedCrl* crlobject, the issuer certificate becomes available if that causes the signature to verify */ - if (PR_TRUE == GetOpaqueCRLFields(crlobject)->bad) { + 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); - cache->invalid |= CRL_CACHE_INVALID_CRLS; return SECSuccess; - } else { + } + else + { SECStatus signstatus = SECFailure; - if (cache->issuer) { - signstatus = CERT_VerifySignedData(&crlobject->signatureWrap, - cache->issuer, vfdate, wincx); + if (cache->issuer) + { + signstatus = CERT_VerifyCRL(crlobject->crl, cache->issuer, vfdate, + wincx); } - if (SECSuccess != signstatus) { - if (!cache->issuer) { + 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 + So, we don't cache this verification failure. We'll try to verify the CRL again when a certificate from that issuer becomes available */ - GetOpaqueCRLFields(crlobject)->unverified = PR_TRUE; - } else { - GetOpaqueCRLFields(crlobject)->unverified = PR_FALSE; + } else + { + crlobject->sigChecked = PR_TRUE; } PORT_SetError(SEC_ERROR_CRL_BAD_SIGNATURE); - cache->invalid |= CRL_CACHE_INVALID_CRLS; return SECSuccess; - } else { - GetOpaqueCRLFields(crlobject)->unverified = PR_FALSE; + } else + { + crlobject->sigChecked = PR_TRUE; + crlobject->sigValid = PR_TRUE; } } - /* complete the entry decoding */ - rv = CERT_CompleteCRLDecodeEntries(crlobject); - if (SECSuccess == rv) { - /* XCRL : if this is a delta, add it to the hash table */ - /* for now, always build the hash table from the full CRL */ - CERTCrlEntry** crlEntry = NULL; - PRUint32 numEntries = 0; - if (cache->entries) { - /* we already have a hash table, destroy it */ - PL_HashTableDestroy(cache->entries); - cache->entries = NULL; - } - /* also destroy the PreAllocator */ - if (cache->prebuffer) - { - PreAllocator_Destroy(cache->prebuffer); - cache->prebuffer = NULL; - } - /* count CRL entries so we can pre-allocate space for hash table entries */ - for (crlEntry = crlobject->crl.entries; crlEntry && *crlEntry; crlEntry++) { - numEntries++; - } - cache->prebuffer = PreAllocator_Create(numEntries*sizeof(PLHashEntry)); - PR_ASSERT(cache->prebuffer); - if (cache->prebuffer) { - /* create a new hash table */ - cache->entries = PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare, - PL_CompareValues, &preAllocOps, cache->prebuffer); - } - PR_ASSERT(cache->entries); - if (!cache->entries) { - rv = SECFailure; - } - if (SECSuccess == rv) { - /* add all serial numbers to the hash table */ - for (crlEntry = crlobject->crl.entries; crlEntry && *crlEntry; crlEntry++) { - PL_HashTableAdd(cache->entries, &(*crlEntry)->serialNumber, *crlEntry); - } - cache->full = crlobject; - cache->invalid = 0; /* valid cache */ - } else { - cache->invalid |= CRL_CACHE_OUT_OF_MEMORY; - } - } else { - cache->invalid |= CRL_CACHE_INVALID_CRLS; - } - return rv; + return SECSuccess; } -void DPCache_Empty(CRLDPCache* cache) -{ - PRUint32 i; - PR_ASSERT(cache); - if (!cache) - { - return; - } - cache->full = NULL; - - cache->invalid = 0; - - if (cache->entries) { - /* we already have a hash table, destroy it */ - PL_HashTableDestroy(cache->entries); - cache->entries = NULL; - } - /* also destroy the PreAllocator */ - if (cache->prebuffer) - { - PreAllocator_Destroy(cache->prebuffer); - cache->prebuffer = NULL; - } - - for (i=0;incrls;i++) - { - CERTSignedCrl* crl = cache->crls[i]; - if (crl) - { - GetOpaqueCRLFields(crl)->deleted = PR_TRUE; - } - } -} - -SECStatus DPCache_Fetch(CRLDPCache* cache, PRTime vfdate, void* wincx) +/* fetch the CRLs for this DP from the PKCS#11 tokens */ +static SECStatus DPCache_FetchFromTokens(CRLDPCache* cache, PRTime vfdate, + void* wincx) { SECStatus rv = SECSuccess; - CERTSignedCrl* crlobject = NULL; - PRUint32 i=0; - /* XCRL For now, we can only get one full CRL. In the future, we'll be able to - find more than one object, because of multiple tokens and deltas */ - rv = SEC_FindCrlByKeyOnSlot(NULL, cache->subject, SEC_CRL_TYPE, - &crlobject, CRL_DECODE_DONT_COPY_DER | - CRL_DECODE_SKIP_ENTRIES | - CRL_DECODE_KEEP_BAD_CRL); + 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) { + if (SECFailure == rv) + { + /* fetch failed, add error bit */ cache->invalid |= CRL_CACHE_LAST_FETCH_FAILED; - } else { + } else + { + /* fetch was successful, clear this error bit */ cache->invalid &= (~CRL_CACHE_LAST_FETCH_FAILED); } - if ((SECSuccess == rv) && (!crlobject)) { - /* no CRL was found. This is OK */ - DPCache_Empty(cache); - return SECSuccess; - } + /* add any CRLs found to our array */ + if (SECSuccess == rv) + { + CERTCrlNode* crlNode = NULL; - /* now check if we already have a binary equivalent DER CRL */ - for (i=0;incrls;i++) { - CERTSignedCrl* existing = cache->crls[i]; - if (existing && (SECEqual == SECITEM_CompareItem(existing->derCrl, crlobject->derCrl))) { - /* yes. Has the matching CRL been marked deleted ? */ - if (PR_TRUE == GetOpaqueCRLFields(crlobject)->deleted) { - /* Yes. Just replace the CK object ID and slot in the existing object. - This avoids an unnecessary signature verification & entry decode */ - /* XCRL we'll need to lock the CRL here in the future for iCRLs that are - shared between multiple CAs */ - existing->pkcs11ID = crlobject->pkcs11ID; - PK11_FreeSlot(existing->slot); /* release reference to old - CRL slot */ - existing->slot = crlobject->slot; /* adopt new CRL slot */ - crlobject->slot = NULL; /* clear slot to avoid double-freeing it - during CRL destroy */ - rv = SEC_DestroyCrl(crlobject); - PORT_Assert(SECSuccess == rv); - return rv; - } else { - /* We got an identical CRL from a different token. - Throw it away. */ - return SEC_DestroyCrl(crlobject); + 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 + { + 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; } } } - /* add the CRL to our array */ - if (SECSuccess == rv) { - rv = DPCache_AddCRL(cache, crlobject); + 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 */ } - /* update the cache with this new CRL */ - if (SECSuccess == rv) { - rv = DPCache_Refresh(cache, crlobject, vfdate, wincx); - } return rv; } -SECStatus DPCache_Lookup(CRLDPCache* cache, SECItem* sn, CERTCrlEntry** returned) +/* check if a particular SN is in the CRL cache and return its entry */ +static SECStatus DPCache_Lookup(CRLDPCache* cache, SECItem* sn, + CERTCrlEntry** returned) { - CERTSignedCrl* crl = NULL; CERTCrlEntry* acrlEntry = NULL; - if (!cache || !sn) { - /* no cache or SN to look up, this is bad */ + if (!cache || !sn || !returned) + { PORT_SetError(SEC_ERROR_INVALID_ARGS); + /* no cache or SN to look up, or no way to return entry */ return SECFailure; } - if (0 != cache->invalid) { - /* the cache contains a bad CRL, consider all certs revoked - as a security measure */ + if (0 != cache->invalid) + { + /* the cache contains a bad CRL, or there was a CRL fetching error. + consider all certs revoked as a security measure */ PORT_SetError(SEC_ERROR_CRL_INVALID); return SECFailure; } - if (!cache->full) { + if (!cache->selected) + { /* no CRL means no entry to return, but this is OK */ *returned = NULL; return SECSuccess; } - crl = cache->full; - PR_ASSERT(cache->entries); - if (!cache->entries) + PORT_Assert(cache->selected->entries); + if (!cache->selected->entries) { return SECFailure; } - acrlEntry = PL_HashTableLookup(cache->entries, (void*)sn); + /* XXX should probably use CachedCrl accessor function here */ + acrlEntry = PL_HashTableLookup(cache->selected->entries, (void*)sn); if (acrlEntry) { *returned = acrlEntry; @@ -1448,138 +1631,443 @@ SECStatus DPCache_Lookup(CRLDPCache* cache, SECItem* sn, CERTCrlEntry** returned return SECSuccess; } -#ifdef USE_RWLOCK +#if defined(DPC_RWLOCK) -#define DPCache_LockWrite() { \ - if (readlocked){ \ - NSSRWLock_UnlockRead(cache->lock); \ - } \ - NSSRWLock_LockWrite(cache->lock); \ +#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); \ +#define DPCache_UnlockWrite() \ +{ \ + if (readlocked) \ + { \ + NSSRWLock_LockRead(cache->lock); \ + } \ + NSSRWLock_UnlockWrite(cache->lock); \ } #else -#define DPCache_LockWrite() {} +/* with a global lock, we are always locked for read before we need write + access, so do nothing */ -#define DPCache_UnlockWrite() {} +#define DPCache_LockWrite() \ +{ \ +} + +#define DPCache_UnlockWrite() \ +{ \ +} #endif -SECStatus DPCache_Update(CRLDPCache* cache, CERTCertificate* issuer, - PRBool readlocked, PRTime vfdate, void* wincx) +/* 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 updated = PR_FALSE; + PRBool forcedrefresh = PR_FALSE; + PRBool dirty = PR_FALSE; /* whether something was changed in the + cache state during this update cycle */ + PRBool hastokenCRLs = PR_FALSE; + PRIntervalTime now = 0; + PRIntervalTime lastfetch = 0; - if (!cache) { + if (!cache) + { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } - /* verify CRLs that couldn't be checked when inserted into the cache - because the issuer cert was unavailable. These are CRLs that were - inserted into the cache through SEC_FindCrlByName, rather than - through a certificate verification (CERT_CheckCRL) */ - if (issuer) { - /* if we didn't have a valid issuer cert yet, but we do now. add it */ - if ( (NULL == cache->issuer) && (SECSuccess == CERT_CheckCertUsage(issuer, KU_CRL_SIGN))) { - /* save the issuer cert */ - cache->issuer = CERT_DupCertificate(issuer); - } + /* first, make sure we have obtained all the CRLs we need. + We do an expensive token fetch in the following cases : + 1) cache is explicitly set to refresh state + 2) cache is in invalid state because last fetch failed + 3) cache contains no token CRLs, and it's been more than one minute + since the last fetch + 4) 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_IntervalNow(); + hastokenCRLs = DPCache_HasTokenCRLs(cache); + } + if ( (PR_TRUE == forcedrefresh) || - /* re-process all unverified CRLs */ - if (cache->issuer) { - for (i = 0; i < cache->ncrls ; i++) { - CERTSignedCrl* acrl = cache->crls[i]; - if (PR_TRUE == GetOpaqueCRLFields(acrl)->unverified) { + (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_IntervalNow(); + } + 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_IntervalNow(); + } + if ( (PR_TRUE == dirty) || + ( (now - cache->lastcheck > CRLCache_ExistenceCheck_Interval) || + (now < cache->lastcheck)) ) + { + PRBool mustunlock = PR_FALSE; + PRIntervalTime lastcheck = cache->lastcheck; + /* 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(); - /* check that we are the first thread to update */ - if (PR_TRUE == GetOpaqueCRLFields(acrl)->unverified) { - DPCache_Refresh(cache, acrl, vfdate, wincx); - /* also check all the other CRLs */ - for (i = i+1 ; i < cache->ncrls ; i++) { - acrl = cache->crls[i]; - if (acrl && (PR_TRUE == GetOpaqueCRLFields(acrl)->unverified)) { - DPCache_Refresh(cache, acrl, vfdate, wincx); - } - } - } - DPCache_UnlockWrite(); - break; + 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_IntervalNow(); + 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 ) + { + PRBool 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 (PR_TRUE != 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 ( (incrls) && (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 (PR_TRUE == mustunlock) + { + DPCache_UnlockWrite(); + mustunlock = PR_FALSE; } } } - if (cache->ncrls) { - /* check if all CRLs still exist */ - for (i = 0; (i < cache->ncrls) && (PR_FALSE == updated); i++) - { - CERTSignedCrl* savcrl = cache->crls[i]; - if (savcrl && (PR_TRUE != CRLStillExists(savcrl))) { - - /* this CRL is gone */ - DPCache_LockWrite(); - /* 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 */ - if ((savcrl == cache->crls[i]) && - PR_TRUE != CRLStillExists(savcrl)) { - /* the CRL is gone. And we are the one to do the update */ - /* Mark the CRL deleted */ - GetOpaqueCRLFields(savcrl)->deleted = PR_TRUE; - /* also check all the other CRLs */ - for (i = i+1 ; i < cache->ncrls ; i++) { - CERTSignedCrl* acrl = cache->crls[i]; - if (acrl && (PR_TRUE != CRLStillExists(acrl))) { - GetOpaqueCRLFields(acrl)->deleted = PR_TRUE; - } - } - /* and try to fetch a new one */ - rv = DPCache_Fetch(cache, vfdate, wincx); - updated = PR_TRUE; - if (SECSuccess == rv) { - rv = DPCache_Cleanup(cache); /* clean up deleted CRLs - from the cache*/ - } - } - DPCache_UnlockWrite(); - } - } - } else { - /* we had zero CRL for this DP, try to get one from tokens */ + 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 */ DPCache_LockWrite(); - /* check if another thread updated before us, and skip update if so */ - if (0 == cache->ncrls) - { - /* we are the first */ - rv = DPCache_Fetch(cache, vfdate, wincx); - } + DPCache_SelectCRL(cache); + cache->mustchoose = PR_FALSE; DPCache_UnlockWrite(); } return rv; } -SECStatus DPCache_Initialize(CRLDPCache* cache, CERTCertificate* issuer, - SECItem* subject, SECItem* dp) +/* 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; + PRBool agood = PR_FALSE, bgood = PR_FALSE; + + 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; + PRBool agood = PR_FALSE, bgood = PR_FALSE; + + 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) { + if (!cache) + { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } - memset(cache, 0, sizeof(CRLDPCache)); -#ifdef USE_RWLOCK + /* if any invalid CRL is present, then the CRL cache is + considered invalid, for security reasons */ + for (i = 0 ; incrls; 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 destroy it */ + if (cache->selected) + { + if (SECSuccess != CachedCrl_Depopulate(cache->selected)) + { + PORT_Assert(0); + return SECFailure; + } + 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; + } + } + + /* free the old CRL cache, if it's for a different CRL */ + if (cache->selected && cache->selected != selected) + { + if (SECSuccess != CachedCrl_Depopulate(cache->selected)) + { + return SECFailure; + } + } + + cache->selected = selected; + + return SECSuccess; +} + +/* initialize a DPCache object */ +static SECStatus DPCache_Create(CRLDPCache** returned, CERTCertificate* issuer, + 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)); + PORT_Assert(cache); + if (!cache) + { + return SECFailure; + } +#ifdef DPC_RWLOCK cache->lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL); #else cache->lock = PR_NewLock(); @@ -1594,10 +2082,14 @@ SECStatus DPCache_Initialize(CRLDPCache* cache, CERTCertificate* issuer, } cache->distributionPoint = SECITEM_DupItem(dp); cache->subject = SECITEM_DupItem(subject); + cache->lastfetch = 0; + cache->lastcheck = 0; + *returned = cache; return SECSuccess; } -SECStatus IssuerCache_Create(CRLIssuerCache** returned, +/* create an issuer cache object (per CA subject ) */ +static SECStatus IssuerCache_Create(CRLIssuerCache** returned, CERTCertificate* issuer, SECItem* subject, SECItem* dp) { @@ -1605,19 +2097,21 @@ SECStatus IssuerCache_Create(CRLIssuerCache** returned, 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; } - cache = (CRLIssuerCache*) PR_Malloc(sizeof(CRLIssuerCache)); + *returned = NULL; + cache = (CRLIssuerCache*) PORT_ZAlloc(sizeof(CRLIssuerCache)); if (!cache) { return SECFailure; } - memset(cache, 0, sizeof(CRLIssuerCache)); cache->subject = SECITEM_DupItem(subject); -#if 0 - /* XCRL */ +#ifdef XCRL cache->lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL); if (!cache->lock) { @@ -1634,51 +2128,67 @@ SECStatus IssuerCache_Create(CRLIssuerCache** returned, #endif if (SECSuccess != rv) { - return IssuerCache_Destroy(cache); + PORT_Assert(SECSuccess == IssuerCache_Destroy(cache)); + return SECFailure; } *returned = cache; return SECSuccess; } -SECStatus IssuerCache_AddDP(CRLIssuerCache* cache, CERTCertificate* issuer, - SECItem* subject, SECItem* dp, CRLDPCache** newdpc) +/* add a DPCache to the issuer cache */ +static SECStatus IssuerCache_AddDP(CRLIssuerCache* cache, + CERTCertificate* issuer, + SECItem* subject, SECItem* dp, + CRLDPCache** newdpc) { - SECStatus rv = SECSuccess; /* now create the required DP cache object */ - if (!dp) { + if (!cache || !subject || !newdpc) + { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + if (!dp) + { /* default distribution point */ - rv = DPCache_Initialize(&cache->dp, issuer, subject, NULL); - if (SECSuccess == rv) { - cache->dpp = &cache->dp; - if (newdpc) { - *newdpc = cache->dpp; - } + SECStatus rv = DPCache_Create(&cache->dpp, issuer, subject, NULL); + if (SECSuccess == rv) + { + *newdpc = cache->dpp; + return SECSuccess; } - } else { + } + else + { /* we should never hit this until we support multiple DPs */ PORT_Assert(dp); - rv = SECFailure; /* XCRL allocate a new distribution point cache object, initialize it, and add it to the hash table of DPs */ } - return rv; + return SECFailure; } -SECStatus CRLCache_AddIssuer(CRLIssuerCache* issuer) +/* 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) { + if (!issuer || !crlcache.issuers) + { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } if (NULL == PL_HashTableAdd(crlcache.issuers, (void*) issuer->subject, - (void*) issuer)) { + (void*) issuer)) + { return SECFailure; } return SECSuccess; } -SECStatus GetIssuerCache(CRLCache* cache, SECItem* subject, CRLIssuerCache** returned) +/* retrieve the issuer cache object for a given issuer subject */ +static SECStatus CRLCache_GetIssuerCache(CRLCache* cache, SECItem* subject, + CRLIssuerCache** returned) { /* we need to look up the issuer in the hash table */ SECStatus rv = SECSuccess; @@ -1686,11 +2196,14 @@ SECStatus GetIssuerCache(CRLCache* cache, SECItem* subject, CRLIssuerCache** ret PORT_Assert(subject); PORT_Assert(returned); PORT_Assert(crlcache.issuers); - if (!cache || !subject || !returned || !crlcache.issuers) { + if (!cache || !subject || !returned || !crlcache.issuers) + { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); rv = SECFailure; } - if (SECSuccess == rv){ + if (SECSuccess == rv) + { *returned = (CRLIssuerCache*) PL_HashTableLookup(crlcache.issuers, (void*) subject); } @@ -1698,35 +2211,52 @@ SECStatus GetIssuerCache(CRLCache* cache, SECItem* subject, CRLIssuerCache** ret return rv; } -CERTSignedCrl* GetBestCRL(CRLDPCache* cache) +/* retrieve the full CRL object that best matches the content of a DPCache */ +static CERTSignedCrl* GetBestCRL(CRLDPCache* cache, PRBool entries) { - PRUint32 i = 0; - PR_ASSERT(cache); - if (!cache) { + PRInt32 i = 0; + CachedCrl* acrl = NULL; + + PORT_Assert(cache); + if (!cache) + { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return NULL; } - if (0 == cache->ncrls) { - /* no CRLs in the cache */ + + if (0 == cache->ncrls) + { + /* empty cache*/ return NULL; + } + + /* if we have a valid full CRL selected, return it */ + if (cache->selected) + { + return SEC_DupCrl(cache->selected->crl); } - /* first, check if we have a valid full CRL, and use that */ - if (cache->full) { - return SEC_DupCrl(cache->full); - } - /* otherwise, check all the fetched CRLs for one with valid DER */ - for (i = 0; i < cache->ncrls ; i++) { - CERTSignedCrl* acrl = cache->crls[i]; - if (PR_FALSE == GetOpaqueCRLFields(acrl)->bad) { - SECStatus rv = CERT_CompleteCRLDecodeEntries(acrl); - if (SECSuccess == rv) { - return SEC_DupCrl(acrl); - } + + /* 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); } } + return NULL; } -CRLDPCache* GetDPCache(CRLIssuerCache* cache, SECItem* dp) +/* get a particular DPCache object from an IssuerCache */ +static CRLDPCache* IssuerCache_GetDPCache(CRLIssuerCache* cache, SECItem* dp) { CRLDPCache* dpp = NULL; PORT_Assert(cache); @@ -1734,86 +2264,143 @@ CRLDPCache* GetDPCache(CRLIssuerCache* cache, SECItem* dp) 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) { + if (!cache || dp) + { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return NULL; } -#if 0 - /* XCRL */ +#ifdef XCRL NSSRWLock_LockRead(cache->lock); #endif dpp = cache->dpp; -#if 0 - /* XCRL */ +#ifdef XCRL NSSRWLock_UnlockRead(cache->lock); #endif return dpp; } -SECStatus AcquireDPCache(CERTCertificate* issuer, SECItem* subject, SECItem* dp, - int64 t, void* wincx, CRLDPCache** dpcache, - PRBool* writeLocked) +/* get a DPCache object for the given issuer subject and dp + Automatically creates the cache object if it doesn't exist yet. + */ +static SECStatus AcquireDPCache(CERTCertificate* issuer, SECItem* subject, + SECItem* dp, int64 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) { + 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); - rv = GetIssuerCache(&crlcache, subject, &issuercache); - if (SECSuccess != rv) { +#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) { + 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. Do it within the global cache lock to ensure - no two threads will simultaneously try to create the same issuer - cache. XXX this could be optimized with a r/w lock at this level - too. But the code would have to check if it already exists when - adding to the hash table */ + create the cache. */ rv = IssuerCache_Create(&issuercache, issuer, subject, dp); - if (SECSuccess == rv && !issuercache) { + if (SECSuccess == rv && !issuercache) + { PORT_Assert(issuercache); rv = SECFailure; } - if (SECSuccess == rv) { + 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 */ + if (SECSuccess == rv) + { + /* lock the DPCache for write to ensure the update happens in this + thread */ *writeLocked = PR_TRUE; -#ifdef USE_RWLOCK +#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 */ - rv = CRLCache_AddIssuer(issuercache); - if (SECSuccess != rv) { - /* failure */ - rv = SECFailure; + 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 hash table - addition. Holding it longer would hurt scalability */ + /* 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 (SECSuccess != rv && issuercache) { - if (PR_TRUE == *writeLocked) { -#ifdef USE_RWLOCK + /* 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); @@ -1823,31 +2410,39 @@ SECStatus AcquireDPCache(CERTCertificate* issuer, SECItem* subject, SECItem* dp, issuercache = NULL; } - if (SECSuccess != rv) { + if (SECSuccess != rv) + { return SECFailure; } - } else { + } else + { +#ifdef GLOBAL_RWLOCK + NSSRWLock_UnlockRead(crlcache.lock); +#else PR_Unlock(crlcache.lock); - *dpcache = GetDPCache(issuercache, dp); +#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 USE_RWLOCK +#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 */ + 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_Update(*dpcache, issuer, PR_FALSE == *writeLocked, t, wincx); + rv = DPCache_GetUpToDate(*dpcache, issuer, PR_FALSE == *writeLocked, + t, wincx); } else { @@ -1857,26 +2452,29 @@ SECStatus AcquireDPCache(CERTCertificate* issuer, SECItem* subject, SECItem* dp, return rv; } -void ReleaseDPCache(CRLDPCache* dpcache, PRBool writeLocked) +/* unlock access to the DPCache */ +static void ReleaseDPCache(CRLDPCache* dpcache, PRBool writeLocked) { - if (!dpcache) { + if (!dpcache) + { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return; } - if (PR_TRUE == writeLocked) { -#ifdef USE_RWLOCK +#ifdef DPC_RWLOCK + if (PR_TRUE == writeLocked) + { NSSRWLock_UnlockWrite(dpcache->lock); -#else - PR_Unlock(dpcache->lock); -#endif - } else { -#ifdef USE_RWLOCK - NSSRWLock_UnlockRead(dpcache->lock); -#else - PR_Unlock(dpcache->lock); -#endif } + else + { + NSSRWLock_UnlockRead(dpcache->lock); + } +#else + PR_Unlock(dpcache->lock); +#endif } +/* check CRL revocation status of given certificate and issuer */ SECStatus CERT_CheckCRL(CERTCertificate* cert, CERTCertificate* issuer, SECItem* dp, int64 t, void* wincx) @@ -1884,11 +2482,14 @@ CERT_CheckCRL(CERTCertificate* cert, CERTCertificate* issuer, SECItem* dp, PRBool lockedwrite = PR_FALSE; SECStatus rv = SECSuccess; CRLDPCache* dpcache = NULL; - if (!cert || !issuer) { + if (!cert || !issuer) + { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } - if (SECSuccess != CERT_CheckCertValidTimes(issuer, t, PR_FALSE)) { + if (SECSuccess != 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. */ @@ -1896,22 +2497,28 @@ CERT_CheckCRL(CERTCertificate* cert, CERTCertificate* issuer, SECItem* dp, return SECFailure; } - rv = AcquireDPCache(issuer, &issuer->derSubject, dp, t, wincx, &dpcache, &lockedwrite); + rv = AcquireDPCache(issuer, &issuer->derSubject, dp, t, wincx, &dpcache, + &lockedwrite); - if (SECSuccess == rv) { + if (SECSuccess == rv) + { /* now look up the certificate SN in the DP cache's CRL */ CERTCrlEntry* entry = NULL; rv = DPCache_Lookup(dpcache, &cert->serialNumber, &entry); - if (SECSuccess == rv && entry) { + if (SECSuccess == rv && entry) + { /* check the time if we have one */ - if (entry->revocationDate.data && entry->revocationDate.len) { + if (entry->revocationDate.data && entry->revocationDate.len) + { int64 revocationDate = 0; if (SECSuccess == DER_DecodeTimeChoice(&revocationDate, - &entry->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) { + if (t>=revocationDate) + { rv = SECFailure; } } else { @@ -1923,7 +2530,8 @@ CERT_CheckCRL(CERTCertificate* cert, CERTCertificate* issuer, SECItem* dp, /* no revocation date, certificate is permanently revoked */ rv = SECFailure; } - if (SECFailure == rv) { + if (SECFailure == rv) + { PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE); } } @@ -1933,6 +2541,7 @@ CERT_CheckCRL(CERTCertificate* cert, CERTCertificate* issuer, SECItem* dp, return rv; } +/* retrieve full CRL object that best matches the cache status */ CERTSignedCrl * SEC_FindCrlByName(CERTCertDBHandle *handle, SECItem *crlKey, int type) { @@ -1941,43 +2550,388 @@ SEC_FindCrlByName(CERTCertDBHandle *handle, SECItem *crlKey, int type) 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); + 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; } - if (PR_TRUE == writeLocked) - { - /* the DPCache is write-locked. This means that the issuer was just - added to the CRL cache. There is no need to do anything */ - } - else - { - PRBool readlocked = PR_TRUE; - /* we need to invalidate the DPCache here */ - DPCache_LockWrite(); - DPCache_Empty(cache); - DPCache_Cleanup(cache); - DPCache_UnlockWrite(); - } + /* 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; + } + + 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;incrls;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) + { + DPCache_RemoveCRL(cache, i); /* got a match */ + cache->mustchoose = PR_TRUE; + removed = PR_TRUE; + break; + } + } + + DPCache_UnlockWrite(); + } + + ReleaseDPCache(cache, writeLocked); + + if (PR_TRUE != removed) + { + rv = SECFailure; + } + } + SEC_DestroyCrl(oldcrl); /* need to do this because object is refcounted */ + if (PR_TRUE != removed) + { + PORT_SetError(SEC_ERROR_CRL_NOT_FOUND); + } + 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) + { + 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;incrls;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; +} + diff --git a/security/nss/lib/nss/nss.def b/security/nss/lib/nss/nss.def index e406183131a4..7025b3afe5c1 100644 --- a/security/nss/lib/nss/nss.def +++ b/security/nss/lib/nss/nss.def @@ -813,6 +813,7 @@ SECMOD_WaitForAnyTokenEvent; ;+}; ;+NSS_3.10 { # NSS 3.10 release ;+ global: +CERT_CacheCRL; CERT_DecodeAltNameExtension; CERT_DecodeAuthInfoAccessExtension; CERT_DecodeAuthKeyID; @@ -824,6 +825,7 @@ CERT_GetNextGeneralName; CERT_GetNextNameConstraint; CERT_GetPrevGeneralName; CERT_GetPrevNameConstraint; +CERT_UncacheCRL; HASH_Clone; HASH_HashBuf; HASH_ResultLenByOidTag; diff --git a/security/nss/lib/pk11wrap/pk11nobj.c b/security/nss/lib/pk11wrap/pk11nobj.c index e3c0e6b4efcc..4a4f1147aae0 100644 --- a/security/nss/lib/pk11wrap/pk11nobj.c +++ b/security/nss/lib/pk11wrap/pk11nobj.c @@ -247,7 +247,8 @@ loser: } /* - * Return a list of all the CRLs + * Return a list of all the CRLs . + * CRLs are allocated in the list's arena. */ SECStatus PK11_LookupCrls(CERTCrlHeadNode *nodes, int type, void *wincx) { @@ -271,6 +272,157 @@ PK11_LookupCrls(CERTCrlHeadNode *nodes, int type, void *wincx) { return pk11_TraverseAllSlots(PK11_TraverseSlot, &creater, wincx); } +struct crlOptionsStr { + CERTCrlHeadNode* head; + PRInt32 decodeOptions; +}; + +typedef struct crlOptionsStr crlOptions; + +static SECStatus +pk11_RetrieveCrlsCallback(PK11SlotInfo *slot, CK_OBJECT_HANDLE crlID, + void *arg) +{ + SECItem* derCrl = NULL; + crlOptions* options = (crlOptions*) arg; + CERTCrlHeadNode *head = options->head; + CERTCrlNode *new_node = NULL; + CK_ATTRIBUTE fetchCrl[3] = { + { CKA_VALUE, NULL, 0}, + { CKA_NETSCAPE_KRL, NULL, 0}, + { CKA_NETSCAPE_URL, NULL, 0}, + }; + const int fetchCrlSize = sizeof(fetchCrl)/sizeof(fetchCrl[2]); + CK_RV crv; + SECStatus rv = SECFailure; + PRBool adopted = PR_FALSE; /* whether the CRL adopted the DER memory + successfully */ + int i; + + crv = PK11_GetAttributes(NULL,slot,crlID,fetchCrl,fetchCrlSize); + if (CKR_OK != crv) { + PORT_SetError(PK11_MapError(crv)); + goto loser; + } + + if (!fetchCrl[1].pValue) { + /* reject KRLs */ + PORT_SetError(SEC_ERROR_CRL_INVALID); + goto loser; + } + + new_node = (CERTCrlNode *)PORT_ArenaAlloc(head->arena, + sizeof(CERTCrlNode)); + if (new_node == NULL) { + goto loser; + } + + new_node->type = SEC_CRL_TYPE; + + derCrl = SECITEM_AllocItem(NULL, NULL, 0); + if (!derCrl) { + goto loser; + } + derCrl->type = siBuffer; + derCrl->data = (unsigned char *)fetchCrl[0].pValue; + derCrl->len = fetchCrl[0].ulValueLen; + new_node->crl = CERT_DecodeDERCrlWithFlags(NULL, derCrl,new_node->type, + options->decodeOptions); + if (new_node->crl == NULL) { + goto loser; + } + adopted = PR_TRUE; /* now that the CRL has adopted the DER memory, + we won't need to free it upon exit */ + + if (fetchCrl[2].pValue && fetchCrl[2].ulValueLen) { + /* copy the URL if there is one */ + int nnlen = fetchCrl[2].ulValueLen; + new_node->crl->url = (char *)PORT_ArenaAlloc(new_node->crl->arena, + nnlen+1); + if ( !new_node->crl->url ) { + goto loser; + } + PORT_Memcpy(new_node->crl->url, fetchCrl[2].pValue, nnlen); + new_node->crl->url[nnlen] = 0; + } else { + new_node->crl->url = NULL; + } + + new_node->next = NULL; + if (head->last) { + head->last->next = new_node; + head->last = new_node; + } else { + head->first = head->last = new_node; + } + rv = SECSuccess; + new_node->crl->slot = PK11_ReferenceSlot(slot); + new_node->crl->pkcs11ID = crlID; + +loser: + /* free attributes that weren't adopted by the CRL */ + for (i=1;idata = NULL; + derCrl->len = 0; + /* free the memory for the SECItem structure itself */ + SECITEM_FreeItem(derCrl, PR_TRUE); + } + return(rv); +} + +/* + * Return a list of CRLs matching specified issuer and type + * CRLs are not allocated in the list's arena, but rather in their own, + * arena, so that they can be used individually in the CRL cache . + * CRLs are always partially decoded for efficiency. + */ +SECStatus pk11_RetrieveCrls(CERTCrlHeadNode *nodes, SECItem* issuer, + void *wincx) +{ + pk11TraverseSlot creater; + CK_ATTRIBUTE theTemplate[2]; + CK_ATTRIBUTE *attrs; + CK_OBJECT_CLASS crlClass = CKO_NETSCAPE_CRL; + crlOptions options; + + attrs = theTemplate; + PK11_SETATTRS(attrs, CKA_CLASS, &crlClass, sizeof(crlClass)); attrs++; + + options.head = nodes; + + /* - do a partial decoding - we don't need to decode the entries while + fetching + - don't copy the DER for optimal performance - CRL can be very large + - have the CRL objects adopt the DER, so SEC_DestroyCrl will free it + - keep bad CRL objects. The CRL cache is interested in them, for + security purposes. Bad CRL objects are a sign of something amiss. + */ + + options.decodeOptions = CRL_DECODE_SKIP_ENTRIES | CRL_DECODE_DONT_COPY_DER | + CRL_DECODE_ADOPT_HEAP_DER | CRL_DECODE_KEEP_BAD_CRL; + if (issuer) + { + PK11_SETATTRS(attrs, CKA_SUBJECT, issuer->data, issuer->len); attrs++; + } + + creater.callback = pk11_RetrieveCrlsCallback; + creater.callbackArg = (void *) &options; + creater.findTemplate = theTemplate; + creater.templateCount = (attrs - theTemplate); + + return pk11_TraverseAllSlots(PK11_TraverseSlot, &creater, wincx); +} + /* * return the crl associated with a derSubjectName */ @@ -336,6 +488,7 @@ PK11_FindCrlByName(PK11SlotInfo **slot, CK_OBJECT_HANDLE *crlHandle, goto loser; } + /* why are we arbitrarily picking the first CRL ??? */ derCrl->data = crlData[0].pValue; derCrl->len = crlData[0].ulValueLen; diff --git a/security/nss/lib/pk11wrap/pk11priv.h b/security/nss/lib/pk11wrap/pk11priv.h index ac2933ed492c..f54bd35ac2c0 100644 --- a/security/nss/lib/pk11wrap/pk11priv.h +++ b/security/nss/lib/pk11wrap/pk11priv.h @@ -211,6 +211,10 @@ SECStatus PK11_SetObjectNickname(PK11SlotInfo *slot, CK_OBJECT_HANDLE id, SECStatus pk11_TraverseAllSlots( SECStatus (*callback)(PK11SlotInfo *,void *), void *cbArg, void *pwArg); +/* fetch multiple CRLs for a specific issuer */ +SECStatus pk11_RetrieveCrls(CERTCrlHeadNode *nodes, SECItem* issuer, + void *wincx); + SEC_END_PROTOS #endif diff --git a/security/nss/lib/util/secerr.h b/security/nss/lib/util/secerr.h index e917fd8d7682..2cc1bcef3c20 100644 --- a/security/nss/lib/util/secerr.h +++ b/security/nss/lib/util/secerr.h @@ -203,6 +203,7 @@ SEC_ERROR_CRL_UNKNOWN_CRITICAL_EXTENSION = (SEC_ERROR_BASE + 149), SEC_ERROR_UNKNOWN_OBJECT_TYPE = (SEC_ERROR_BASE + 150), SEC_ERROR_INCOMPATIBLE_PKCS11 = (SEC_ERROR_BASE + 151), SEC_ERROR_NO_EVENT = (SEC_ERROR_BASE + 152), +SEC_ERROR_CRL_ALREADY_EXISTS = (SEC_ERROR_BASE + 153), /* Add new error codes above here. */ SEC_ERROR_END_OF_LIST