diff options
author | jpierre%netscape.com <devnull@localhost> | 2002-08-30 22:57:03 +0000 |
---|---|---|
committer | jpierre%netscape.com <devnull@localhost> | 2002-08-30 22:57:03 +0000 |
commit | cbcb6755d440dca892bb3e27d786a85c6405d750 (patch) | |
tree | 2a20443aacd5ca0999b94186b5afbd428bd54307 | |
parent | ac22502a2f258a6abf263777ab9b91ad35130d22 (diff) | |
download | nss-hg-cbcb6755d440dca892bb3e27d786a85c6405d750.tar.gz |
Implement the CRL cache . Bug 149854
-rw-r--r-- | security/nss/lib/certdb/cert.h | 9 | ||||
-rw-r--r-- | security/nss/lib/certdb/certi.h | 133 | ||||
-rw-r--r-- | security/nss/lib/certdb/crl.c | 957 | ||||
-rw-r--r-- | security/nss/lib/certhigh/certvfy.c | 34 | ||||
-rw-r--r-- | security/nss/lib/dev/ckhelper.c | 16 | ||||
-rw-r--r-- | security/nss/lib/dev/ckhelper.h | 5 | ||||
-rw-r--r-- | security/nss/lib/dev/dev.h | 2 | ||||
-rw-r--r-- | security/nss/lib/nss/nssinit.c | 5 | ||||
-rw-r--r-- | security/nss/lib/pki/certificate.c | 2 | ||||
-rw-r--r-- | security/nss/lib/pki/pkibase.c | 2 |
10 files changed, 1110 insertions, 55 deletions
diff --git a/security/nss/lib/certdb/cert.h b/security/nss/lib/certdb/cert.h index ba28a2713..6af02e964 100644 --- a/security/nss/lib/certdb/cert.h +++ b/security/nss/lib/certdb/cert.h @@ -408,6 +408,7 @@ CERT_DecodeDERCrlEx(PRArenaPool *narena, SECItem *derSignedCrl, int type, #define CRL_DECODE_DONT_COPY_DER 0x00000001 #define CRL_DECODE_SKIP_ENTRIES 0x00000002 +#define CRL_DECODE_KEEP_BAD_CRL 0x00000004 /* complete the decoding of a partially decoded CRL, ie. decode the entries. Note that entries is an optional field in a CRL, so the @@ -1429,6 +1430,14 @@ CERT_SPKDigestValueForCert(PRArenaPool *arena, CERTCertificate *cert, */ extern SECStatus CERT_GetCertType(CERTCertificate *cert); + +SECStatus InitCRLCache(void); +SECStatus ShutdownCRLCache(void); + +SECStatus CERT_CheckCRL(CERTCertificate* cert, CERTCertificate* issuer, + SECItem* dp, int64 t, void* wincx); + + SEC_END_PROTOS #endif /* _CERT_H_ */ diff --git a/security/nss/lib/certdb/certi.h b/security/nss/lib/certdb/certi.h index 05bcd4c7c..164941228 100644 --- a/security/nss/lib/certdb/certi.h +++ b/security/nss/lib/certdb/certi.h @@ -31,7 +31,7 @@ * GPL. */ /* - * certt.h - public data structures for the certificate library + * certi.h - private data structures for the certificate library * * $Id$ */ @@ -39,12 +39,139 @@ #define _CERTI_H_ #include "certt.h" +#include "nssrwlkt.h" -typedef struct OpaqueCRLFieldsStr OpaqueCRLFields; +#define USE_RWLOCK 1 + +/* all definitions in this file are subject to change */ + +typedef struct OpaqueCRLFieldsStr OpaqueCRLFields; +typedef struct CRLEntryCacheStr CRLEntryCache; +typedef struct CRLDPCacheStr CRLDPCache; +typedef struct CRLIssuerCacheStr CRLIssuerCache; +typedef struct CRLCacheStr CRLCache; struct OpaqueCRLFieldsStr { - /* these fields are subject to change */ PRBool partial; + PRBool bad; + PRBool badDER; + PRBool badExtensions; + PRBool deleted; + PRBool heapDER; +}; + +typedef struct PreAllocatorStr PreAllocator; + +struct PreAllocatorStr +{ + PRSize len; + void* data; + PRSize used; + PRArenaPool* arena; + PRSize extra; +}; + +/* CRL entry cache. + This is the same as an entry plus the next/prev pointers for the hash table +*/ + +struct CRLEntryCacheStr { + CERTCrlEntry entry; + CRLEntryCache *prev, *next; +}; + +#define CRL_CACHE_INVALID_CRLS 0x0001 /* this state will be set + if we have CRL objects with an invalid DER or signature. Can be + cleared if the invalid objects are deleted from the token */ +#define CRL_CACHE_LAST_FETCH_FAILED 0x0002 /* this state will be set + if the last CRL fetch encountered an error. Can be cleared if a + new fetch succeeds */ + +/* 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; /* DER of cert issuer */ + SECItem* distributionPoint; /* DER of distribution point. This may be + NULL when distribution points aren't + in use (ie. the CA has a single CRL) */ + + /* 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. + + This won't work anymore when we support delta CRLs and iCRLs, because + the size of the hash table will vary over time. At that point, the best + solution will be to allocate large CRLEntry structures by modifying + the DER decoding template. The extra space would be for next/prev + pointers. This would allow entries from different CRLs to be mixed in + the same hash table. + */ + PLHashTable* entries; + PreAllocator* prebuffer; /* big pre-allocated buffer mentioned above */ + + /* array of CRLs matching this distribution point */ + PRUint32 ncrls; /* total number of CRLs in crls */ + CERTSignedCrl** crls; /* array of all matching DER CRLs + from all tokens */ + /* 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 */ +#if 0 + /* for future use */ + PRInt32 numdeltas; /* number of delta CRLs used for the cache */ + CERTSignedCrl** deltas; /* delta CRLs used for the cache */ +#endif + /* 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 */ +}; + +/* 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 + will be a hash table. For now, it just holds the single CRL distribution + point cache structure. +*/ + +struct CRLIssuerCacheStr { + PRUint32 refcount; + CERTCertificate* issuer; + CRLDPCache dp; + CRLDPCache* dpp; +#if 0 + /* XCRL for future use. + We don't need to lock at the moment because we only have one DP, + which gets created at the same time as this object */ + NSSRWLock* lock; + CRLDPCache** dps; + PLHashTable* distributionpoints; +#endif +}; + +/* CRL revocation cache object + This object tracks all the issuer caches +*/ + +struct CRLCacheStr { + PRLock* lock; + /* hash table of issuer to CRLIssuerCacheStr, + indexed by issuer DER subject */ + PLHashTable* issuers; }; #endif /* _CERTI_H_ */ + diff --git a/security/nss/lib/certdb/crl.c b/security/nss/lib/certdb/crl.c index f34ec4ccc..95aa66ade 100644 --- a/security/nss/lib/certdb/crl.c +++ b/security/nss/lib/certdb/crl.c @@ -36,7 +36,7 @@ * * $Id$ */ - + #include "cert.h" #include "certi.h" #include "secder.h" @@ -47,6 +47,12 @@ #include "prtime.h" #include "secerr.h" #include "pk11func.h" +#include "dev.h" +#include "../pk11wrap/secmodti.h" +#include "../base/nssbase.h" +#ifdef USE_RWLOCK +#include "nssrwlk.h" +#endif const SEC_ASN1Template SEC_CERTExtensionTemplate[] = { { SEC_ASN1_SEQUENCE, @@ -364,6 +370,20 @@ CERT_KeyFromDERCrl(PRArenaPool *arena, SECItem *derCrl, SECItem *key) return(SECSuccess); } +#define GetOpaqueCRLFields(x) ((OpaqueCRLFields*)x->opaque) + +/* +PRBool CERT_CRLIsInvalid(CERTSignedCrl* crl) +{ + OpaqueCRLFields* extended = NULL; + + if (crl && (extended = (OpaqueCRLFields*) crl->opaque)) { + return extended->bad; + } + return PR_TRUE; +} +*/ + SECStatus CERT_CompleteCRLDecodeEntries(CERTSignedCrl* crl) { SECStatus rv = SECSuccess; @@ -422,6 +442,7 @@ CERT_DecodeDERCrlEx(PRArenaPool *narena, SECItem *derSignedCrl, int type, /* allocate the CRL structure */ crl = (CERTSignedCrl *)PORT_ArenaZAlloc(arena, sizeof(CERTSignedCrl)); if ( !crl ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); goto loser; } @@ -460,10 +481,15 @@ CERT_DecodeDERCrlEx(PRArenaPool *narena, SECItem *derSignedCrl, int type, switch (type) { case SEC_CRL_TYPE: rv = SEC_QuickDERDecodeItem(arena, crl, crlTemplate, crl->derCrl); - if (rv != SECSuccess) - break; + if (rv != SECSuccess) { + extended->badDER = PR_TRUE; + break; + } /* check for critical extentions */ rv = cert_check_crl_version (&crl->crl); + if (rv != SECSuccess) { + extended->badExtensions = PR_TRUE; + } break; case SEC_KRL_TYPE: @@ -484,6 +510,11 @@ CERT_DecodeDERCrlEx(PRArenaPool *narena, SECItem *derSignedCrl, int type, return(crl); loser: + if (options & CRL_DECODE_KEEP_BAD_CRL) { + extended->bad = PR_TRUE; + crl->referenceCount = 1; + return(crl); + } if ((narena == NULL) && arena ) { PORT_FreeArena(arena, PR_FALSE); @@ -504,34 +535,57 @@ CERT_DecodeDERCrl(PRArenaPool *narena, SECItem *derSignedCrl, int type) /* * 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 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 + * 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 */ -CERTSignedCrl * -SEC_FindCrlByKeyOnSlot(PK11SlotInfo *slot, SECItem *crlKey, int type) +static SECStatus +SEC_FindCrlByKeyOnSlot(PK11SlotInfo *slot, SECItem *crlKey, int type, + CERTSignedCrl** decoded, PRInt32 decodeoptions) { + SECStatus rv = SECSuccess; CERTSignedCrl *crl = NULL; - SECItem *derCrl; - CK_OBJECT_HANDLE crlHandle; + SECItem *derCrl = NULL; + CK_OBJECT_HANDLE crlHandle = 0; char *url = NULL; + PORT_Assert(decoded); + if (!decoded) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (slot) { PK11_ReferenceSlot(slot); } - + + /* 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 */ derCrl = PK11_FindCrlByName(&slot, &crlHandle, crlKey, type, &url); if (derCrl == NULL) { goto loser; } PORT_Assert(crlHandle != CK_INVALID_HANDLE); - crl = CERT_DecodeDERCrl(NULL, derCrl, type); + crl = CERT_DecodeDERCrlEx(NULL, derCrl, type, decodeoptions); if (crl) { - crl->slot = slot; - slot = NULL; /* adopt it */ - crl->pkcs11ID = crlHandle; - if (url) { - crl->url = PORT_ArenaStrdup(crl->arena,url); - } + crl->slot = slot; + slot = NULL; /* adopt it */ + crl->pkcs11ID = crlHandle; + if (url) { + crl->url = PORT_ArenaStrdup(crl->arena,url); + } + } else { + rv = SECFailure; } + if (url) { PORT_Free(url); } @@ -540,10 +594,22 @@ loser: if (slot) { PK11_FreeSlot(slot); } + if (derCrl) { - SECITEM_FreeItem(derCrl,PR_TRUE); + /* 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 { + SECITEM_FreeItem(derCrl, PR_TRUE); + } } - return(crl); + + *decoded = crl; + + return rv; } SECStatus SEC_DestroyCrl(CERTSignedCrl *crl); @@ -555,7 +621,11 @@ crl_storeCRL (PK11SlotInfo *slot,char *url, CERTSignedCrl *oldCrl = NULL, *crl = NULL; CK_OBJECT_HANDLE crlHandle; - oldCrl = SEC_FindCrlByKeyOnSlot(slot, &newCrl->crl.derName, type); + PORT_Assert(newCrl); + PORT_Assert(derCrl); + + SEC_FindCrlByKeyOnSlot(slot, &newCrl->crl.derName, type, + &oldCrl, CRL_DECODE_SKIP_ENTRIES); /* if there is an old crl, make sure the one we are installing * is newer. If not, exit out, otherwise delete the old crl. @@ -619,7 +689,15 @@ done: CERTSignedCrl * SEC_FindCrlByName(CERTCertDBHandle *handle, SECItem *crlKey, int type) { - return SEC_FindCrlByKeyOnSlot(NULL,crlKey,type); + SECStatus rv = SECSuccess; + CERTSignedCrl* crl = NULL; + /* XXX we should check the return value and fail, unfortunately, we can't, + because of this legacy exported prototype */ + SEC_FindCrlByKeyOnSlot(NULL,crlKey,type, &crl, CRL_DECODE_DEFAULT_OPTIONS); + if (!crl) { + return NULL; + } + return crl; } /* @@ -678,6 +756,9 @@ SEC_DestroyCrl(CERTSignedCrl *crl) if (crl->slot) { PK11_FreeSlot(crl->slot); } + if (PR_TRUE == GetOpaqueCRLFields(crl)->heapDER) { + SECITEM_FreeItem(crl->derCrl, PR_TRUE); + } PORT_FreeArena(crl->arena, PR_FALSE); } } @@ -727,3 +808,841 @@ SEC_ASN1_CHOOSER_IMPLEMENT(CERT_IssuerAndSNTemplate) SEC_ASN1_CHOOSER_IMPLEMENT(CERT_CrlTemplate) SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SetOfSignedCrlTemplate) +static CRLCache crlcache = { NULL, NULL }; + +/* this needs to be called at NSS initialization time */ + +SECStatus InitCRLCache(void) +{ + if (!crlcache.lock) + { + crlcache.lock = PR_NewLock(); + if (!crlcache.lock) + { + return SECFailure; + } + crlcache.issuers = PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare, + PL_CompareValues, NULL, NULL); + if (!crlcache.issuers) + { + PR_DestroyLock(crlcache.lock); + crlcache.lock = PR_FALSE; + return SECFailure; + } + } + return SECSuccess; +} + +SECStatus IssuerCache_Destroy(CRLIssuerCache* cache); + +PRIntn PR_CALLBACK FreeIssuer(PLHashEntry *he, PRIntn i, void *arg) +{ + CRLIssuerCache* issuer = NULL; + PR_ASSERT(he); + if (!he) { + return HT_ENUMERATE_NEXT; + } + issuer = (CRLIssuerCache*) he->value; + PR_ASSERT(issuer); + if (issuer) { + IssuerCache_Destroy(issuer); + } + return HT_ENUMERATE_NEXT; +} + +SECStatus ShutdownCRLCache(void) +{ + PR_ASSERT(crlcache.lock); + PR_ASSERT(crlcache.issuers); + if (!crlcache.lock || !crlcache.issuers) + { + return SECFailure; + } + /* empty the cache */ + PL_HashTableEnumerateEntries(crlcache.issuers, &FreeIssuer, NULL); + PL_HashTableDestroy(crlcache.issuers); + PR_DestroyLock(crlcache.lock); + return SECSuccess; +} + +SECStatus DP_AddCRL(CRLDPCache* cache, CERTSignedCrl* crl) +{ + CERTSignedCrl** newcrls = NULL; + PORT_Assert(cache); + PORT_Assert(crl); + if (!cache || !crl) { + return SECFailure; + } + + newcrls = (CERTSignedCrl**)PORT_Realloc(cache->crls, + (cache->ncrls+1)*sizeof(CERTSignedCrl*)); + if (!newcrls) { + return SECFailure; + } + cache->crls = newcrls; + cache->ncrls++; + cache->crls[cache->ncrls-1] = crl; + return SECSuccess; +} + +PRBool CRLStillExists(CERTSignedCrl* crl) +{ + NSSItem newsubject; + SECItem subject; + CK_ULONG crl_class; + PRStatus status; + PK11SlotInfo* slot = NULL; + NSSToken* token = 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(slot); + if (!slot) { + return PR_FALSE; + } + oldSubject = &crl->crl.derName; + PR_ASSERT(oldSubject); + if (!oldSubject) { + return PR_FALSE; + } + + /* XXX 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 = slot->nssToken; + 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; +} + +/* +** Pre-allocator hash allocator ops. +*/ +static void * PR_CALLBACK +PreAllocTable(void *pool, PRSize size) +{ + PreAllocator* alloc = (PreAllocator*)pool; + PR_ASSERT(alloc); + if (!alloc) + { + /* no allocator, or buffer full */ + return NULL; + } + if (size > (alloc->len - alloc->used)) + { + alloc->extra += size; + return PORT_ArenaAlloc(alloc->arena, size); + } + alloc->used += size; + return (char*) alloc->data + alloc->used - size; +} + +static void PR_CALLBACK +PreFreeTable(void *pool, void *item) +{ +} + +static PLHashEntry * PR_CALLBACK +PreAllocEntry(void *pool, const void *key) +{ + return PreAllocTable(pool, sizeof(PLHashEntry)); +} + +static void PR_CALLBACK +PreFreeEntry(void *pool, PLHashEntry *he, PRUintn flag) +{ +} + +static PLHashAllocOps preAllocOps = { + PreAllocTable, PreFreeTable, + PreAllocEntry, PreFreeEntry +}; + +void PreAllocator_Destroy(PreAllocator* PreAllocator) +{ + if (!PreAllocator) + { + return; + } + if (PreAllocator->arena) + { + PORT_FreeArena(PreAllocator->arena, PR_TRUE); + } + if (PreAllocator->data) + { + PORT_Free(PreAllocator->data); + } + PORT_Free(PreAllocator); +} + +PreAllocator* PreAllocator_Create(PRSize size) +{ + PreAllocator prebuffer; + PreAllocator* prepointer = NULL; + memset(&prebuffer, 0, sizeof(PreAllocator)); + prebuffer.len = size; + prebuffer.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + PR_ASSERT(prebuffer.arena); + if (!prebuffer.arena) { + PreAllocator_Destroy(&prebuffer); + return NULL; + } + if (prebuffer.len) { + prebuffer.data = PR_Malloc(prebuffer.len); + if (!prebuffer.data) { + PreAllocator_Destroy(&prebuffer); + return NULL; + } + } else { + prebuffer.data = NULL; + } + prepointer = (PreAllocator*)PR_Malloc(sizeof(PreAllocator)); + if (!prepointer) { + PreAllocator_Destroy(&prebuffer); + return NULL; + } + *prepointer = prebuffer; + return prepointer; +} + +SECStatus DPCache_Fetch(CRLDPCache* cache, int64 t, void* wincx) +{ + SECStatus rv = SECSuccess; + SECStatus signstatus = 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->issuer->derSubject, SEC_CRL_TYPE, + &crlobject, CRL_DECODE_DONT_COPY_DER | + CRL_DECODE_SKIP_ENTRIES | + CRL_DECODE_KEEP_BAD_CRL); + /* 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) { + cache->invalid |= CRL_CACHE_LAST_FETCH_FAILED; + } else { + cache->invalid &= (~CRL_CACHE_LAST_FETCH_FAILED); + } + + if ((SECSuccess == rv) && (!crlobject)) { + /* no CRL was found. This is OK */ + return SECSuccess; + } + + /* now check if we already have a binary equivalent DER CRL */ + for (i=0;i<cache->ncrls;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); + } + } + } + + /* add the CRL to our array */ + if (SECSuccess == rv) { + rv = DP_AddCRL(cache, crlobject); + } + /* check if it is an invalid CRL */ + if ( (SECSuccess == rv) && + ( (PR_TRUE == GetOpaqueCRLFields(crlobject)->bad) || + (SECSuccess != (signstatus = CERT_VerifySignedData( + &crlobject->signatureWrap, + cache->issuer, t, wincx)) ) ) + ) { + if (SECSuccess != signstatus) { + PORT_SetError(SEC_ERROR_CRL_BAD_SIGNATURE); + } + /* 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 revoked 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 */ + cache->invalid = PR_TRUE; + return SECSuccess; + } + + /* XXX 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 */ + 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; + } else { + cache->invalid = PR_TRUE; + } + } else { + cache->invalid = PR_TRUE; + } + return rv; +} + +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 */ + return SECFailure; + } + if (!cache->full) { + /* 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) + { + return SECFailure; + } + acrlEntry = PL_HashTableLookup(cache->entries, (void*)sn); + if (acrlEntry) + { + *returned = acrlEntry; + } + return SECSuccess; +} + +SECStatus DPCache_Update(CRLDPCache* cache, int64 t, void* wincx, PRBool readlocked) +{ + /* 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; + if (!cache) { + return SECFailure; + } + + if (cache->full) { + /* check if the full CRL still exists */ + if (PR_TRUE != CRLStillExists(cache->full)) { + /* the CRL is gone. But first, we need to check if we are + the first to do the update. We can't acquire the write lock + now or it could create a deadlock */ +#ifdef USE_RWLOCK + if (readlocked){ + NSSRWLock_UnlockRead(cache->lock); + } + NSSRWLock_LockWrite(cache->lock); +#else + /* no extra locking here, we already hold the PRLock, and it's + not re-entrant */ +#endif + /* first, we need to check if another thread updated + it before we did, and abort if it has been created since + we created the lock */ + if (PR_TRUE != CRLStillExists(cache->full)) { + /* the CRL is gone. And we are the one to do the update */ + /* Mark the CRL deleted, and try to fetch a new one */ + GetOpaqueCRLFields(cache->full)->deleted = PR_TRUE; + rv = DPCache_Fetch(cache, t, wincx); + } +#ifdef USE_RWLOCK + if (readlocked){ + NSSRWLock_LockRead(cache->lock); + } + NSSRWLock_UnlockWrite(cache->lock); +#endif + } + } else { + /* this is the first time we fetch a CRL for this DP, or we had + an invalid CRL */ +#ifdef USE_RWLOCK + if (readlocked) { + NSSRWLock_UnlockRead(cache->lock); + } + NSSRWLock_LockWrite(cache->lock); +#endif + /* check if another thread updated before us, and skip update if so */ + if (!cache->full) + { + /* we are the first */ + rv = DPCache_Fetch(cache, t, wincx); + } +#ifdef USE_RWLOCK + if (readlocked) { + NSSRWLock_LockRead(cache->lock); + } + NSSRWLock_UnlockWrite(cache->lock); +#endif + } + + return rv; +} + +SECStatus DPCache_Initialize(CRLDPCache* cache, + CERTCertificate* issuer, SECItem* dp) +{ + CK_OBJECT_HANDLE crloid = 0; + SECItem* crlder = NULL; + + PORT_Assert(cache); + PORT_Assert(issuer); + if (!cache || !issuer) { + return SECFailure; + } + memset(cache, 0, sizeof(CRLDPCache)); +#ifdef USE_RWLOCK + cache->lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL); +#else + cache->lock = PR_NewLock(); +#endif + if (!cache->lock) + { + return SECFailure; + } + cache->issuer = CERT_DupCertificate(issuer); + cache->distributionPoint = SECITEM_DupItem(dp); + return SECSuccess; +} + +SECStatus DPCache_Destroy(CRLDPCache* cache) +{ + PRUint32 i = 0; + PR_ASSERT(cache); + if (!cache) { + return SECFailure; + } + if (cache->lock) + { +#ifdef USE_RWLOCK + NSSRWLock_Destroy(cache->lock); +#else + PR_DestroyLock(cache->lock); +#endif + } + /* destroy all our CRL objects */ + for (i=0;i<cache->ncrls;i++) + { + SEC_DestroyCrl(cache->crls[i]); + } + /* destroy the hash table */ + if (cache->entries) + { + PL_HashTableDestroy(cache->entries); + } + /* free the pre buffer */ + if (cache->prebuffer) + { + PreAllocator_Destroy(cache->prebuffer); + } + /* destroy the cert */ + if (cache->issuer) + { + CERT_DestroyCertificate(cache->issuer); + } + return SECSuccess; +} + +SECStatus IssuerCache_Destroy(CRLIssuerCache* cache) +{ + PORT_Assert(cache); + if (!cache) + { + return SECFailure; + } + if (!--cache->refcount) + { +#if 0 + /* XCRL */ + if (cache->lock) + { + NSSRWLock_Destroy(cache->lock); + } +#endif + if (cache->issuer) + { + CERT_DestroyCertificate(cache->issuer); + } + DPCache_Destroy(&cache->dp); + PR_Free(cache); + } + return SECSuccess; +} + +SECStatus IssuerCache_Create(CRLIssuerCache** returned, + CERTCertificate* issuer, SECItem* dp) +{ + SECStatus rv = SECSuccess; + CRLIssuerCache* cache = NULL; + PORT_Assert(returned); + if (!returned) + { + return SECFailure; + } + cache = (CRLIssuerCache*) PR_Malloc(sizeof(CRLIssuerCache)); + if (!cache) + { + return SECFailure; + } + memset(cache, 0, sizeof(CRLIssuerCache)); +#if 0 + /* XCRL */ + cache->lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL); + if (!cache->lock) + { + rv = SECFailure; + } +#endif + cache->refcount = 0; + if ((SECSuccess == rv) && (!(cache->issuer = CERT_DupCertificate(issuer)))) + { + rv = SECFailure; + } + + if (SECSuccess != rv) + { + return IssuerCache_Destroy(cache); + } + *returned = cache; + return SECSuccess; +} + +SECStatus IssuerCache_AddDP(CRLIssuerCache* cache, + SECItem* dp, CRLDPCache** newdpc) +{ + SECStatus rv = SECSuccess; + /* now create the required DP cache object */ + if (!dp) { + /* default distribution point */ + rv = DPCache_Initialize(&cache->dp, cache->issuer, NULL); + if (SECSuccess == rv) { + cache->dpp = &cache->dp; + if (newdpc) { + *newdpc = cache->dpp; + } + } + } 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; +} + +SECStatus CRLCache_AddIssuer(SECItem* issuerDER, CRLIssuerCache* issuer) +{ + PORT_Assert(issuerDER); + PORT_Assert(issuer); + PORT_Assert(crlcache.issuers); + if (!issuerDER || !issuer || !crlcache.issuers) { + return SECFailure; + } + if (NULL == PL_HashTableAdd(crlcache.issuers, (void*) issuerDER, + (void*) issuer)) { + return SECFailure; + } + return SECSuccess; +} + +SECStatus GetIssuerCache(CRLCache* cache, 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) { + rv = SECFailure; + } + + if (SECSuccess == rv){ + *returned = (CRLIssuerCache*) PL_HashTableLookup(crlcache.issuers, + (void*) subject); + } + + return rv; +} + +CRLDPCache* GetDPCache(CRLIssuerCache* cache, 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) { + return NULL; + } +#if 0 + /* XCRL */ + NSSRWLock_LockRead(cache->lock); +#endif + dpp = cache->dpp; +#if 0 + /* XCRL */ + NSSRWLock_UnlockRead(cache->lock); +#endif + return dpp; +} + +SECStatus +CERT_CheckCRL(CERTCertificate* cert, CERTCertificate* issuer, SECItem* dp, + int64 t, void* wincx) +{ + PRBool lockedwrite = PR_FALSE; + SECStatus rv = SECSuccess; + SECCertTimeValidity validity; + CRLIssuerCache* issuercache = NULL; + CRLDPCache* dpcache = NULL; + if (!cert || !issuer) { + return SECFailure; + } + /* we must check the cert issuer (or more appropriately, the CRL + signer)'s validity time first. If it's expired, then don't go to the + cache. + If we do and the cache is empty, a CRL will be fetched, but it won't + verify because of the expired issuer, causing us to put the cache in + the invalid state. + If we do and the cache is already populated, we will lookup the cert + in the CRL for no good reason. */ + validity = CERT_CheckCertValidTimes(issuer, t, PR_FALSE); + if ( validity != secCertTimeValid ) { + return SECFailure; + } + PORT_Assert(crlcache.lock); + if (!crlcache.lock) { + /* CRL cache is not initialized */ + return SECFailure; + } + PR_Lock(crlcache.lock); + rv = GetIssuerCache(&crlcache, &issuer->derSubject, &issuercache); + if (SECSuccess != rv) { + PR_Unlock(crlcache.lock); + 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. 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 */ + + rv = IssuerCache_Create(&issuercache, issuer, 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, dp, &dpcache); + } + + if (SECSuccess == rv) { + /* lock the DPCache for write to ensure the update happens in this thread */ + lockedwrite = PR_TRUE; +#ifdef USE_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->issuer->derSubject, issuercache); + if (SECSuccess != rv) { + /* failure */ + rv = SECFailure; + } + } + + /* now unlock the global cache. We only want to lock the hash table + addition. Holding it longer would hurt scalability */ + PR_Unlock(crlcache.lock); + + if (SECSuccess != rv && issuercache) { + if (PR_TRUE == lockedwrite) { +#ifdef USE_RWLOCK + NSSRWLock_UnlockWrite(dpcache->lock); +#else + PR_Unlock(dpcache->lock); +#endif + } + IssuerCache_Destroy(issuercache); + issuercache = NULL; + } + + if (SECSuccess != rv) { + return SECFailure; + } + } else { + PR_Unlock(crlcache.lock); + dpcache = 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 == lockedwrite) + { +#ifdef USE_RWLOCK + NSSRWLock_LockRead(dpcache->lock); +#else + PR_Lock(dpcache->lock); +#endif + } + + if (SECSuccess == rv) { + /* currently there is always one and only one DPCache */ + PORT_Assert(dpcache); + if (dpcache) + { + /* make sure the DP cache is up to date before using it */ + rv = DPCache_Update(dpcache, t, wincx, PR_FALSE == lockedwrite); + } + else + { + rv = SECFailure; + } + } + 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) { + /* check the time if we have one */ + if (entry->revocationDate.data && entry->revocationDate.len) { + int64 revocationDate = 0; + if (SECSuccess == DER_UTCTimeToTime(&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 { + /* invalid revocation date, consider the certificate + permanently revoked */ + rv = SECFailure; + } + } else { + /* no revocation date, certificate is permanently revoked */ + rv = SECFailure; + } + if (SECFailure == rv) { + PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE); + } + } + } + if (PR_TRUE == lockedwrite) { +#ifdef USE_RWLOCK + NSSRWLock_UnlockWrite(dpcache->lock); +#else + PR_Unlock(dpcache->lock); +#endif + } else { +#ifdef USE_RWLOCK + NSSRWLock_UnlockRead(dpcache->lock); +#else + PR_Unlock(dpcache->lock); +#endif + } + + PORT_Assert(issuercache); + return rv; +} + diff --git a/security/nss/lib/certhigh/certvfy.c b/security/nss/lib/certhigh/certvfy.c index 0b303eb97..95d2cd37b 100644 --- a/security/nss/lib/certhigh/certvfy.c +++ b/security/nss/lib/certhigh/certvfy.c @@ -113,7 +113,7 @@ CERT_VerifySignedData(CERTSignedData *sd, CERTCertificate *cert, if ( !pubKey ) { return(SECFailure); } - + /* check the signature */ sig = sd->signature; DER_ConvertBitString(&sig); @@ -267,37 +267,7 @@ SECStatus SEC_CheckCRL(CERTCertDBHandle *handle,CERTCertificate *cert, CERTCertificate *caCert, int64 t, void * wincx) { - CERTSignedCrl *crl = NULL; - SECStatus rv = SECSuccess; - CERTCrlEntry **crlEntry; - - /* first look up the CRL */ - crl = SEC_FindCrlByName(handle,&caCert->derSubject, SEC_CRL_TYPE); - if (crl == NULL) { - /* XXX for now no CRL is ok */ - goto done; - } - - /* now verify the CRL signature */ - rv = CERT_VerifySignedData(&crl->signatureWrap, caCert, t, wincx); - if (rv != SECSuccess) { - PORT_SetError(SEC_ERROR_CRL_BAD_SIGNATURE); - rv = SECWouldBlock; /* Soft error, ask the user */ - goto done; - } - - /* now make sure the key is not on the revocation list */ - for (crlEntry = crl->crl.entries; crlEntry && *crlEntry; crlEntry++) { - if (SECITEM_CompareItem(&(*crlEntry)->serialNumber,&cert->serialNumber) == SECEqual) { - PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE); - rv = SECFailure; /* cert is revoked */ - goto done; - } - } - -done: - if (crl) SEC_DestroyCrl(crl); - return rv; + return CERT_CheckCRL(cert, caCert, NULL, t, wincx); } /* diff --git a/security/nss/lib/dev/ckhelper.c b/security/nss/lib/dev/ckhelper.c index 19099df33..c615cf693 100644 --- a/security/nss/lib/dev/ckhelper.c +++ b/security/nss/lib/dev/ckhelper.c @@ -611,6 +611,8 @@ nssCryptokiCRL_GetAttributes nssSession *sessionOpt, NSSArena *arenaOpt, NSSItem *encodingOpt, + NSSItem *subjectOpt, + CK_ULONG* crl_class, NSSUTF8 **urlOpt, PRBool *isKRLOpt ) @@ -619,11 +621,14 @@ nssCryptokiCRL_GetAttributes NSSSlot *slot; nssSession *session; CK_ATTRIBUTE_PTR attr; - CK_ATTRIBUTE crl_template[5]; + CK_ATTRIBUTE crl_template[7]; CK_ULONG crl_size; PRUint32 i; NSS_CK_TEMPLATE_START(crl_template, attr, crl_size); + if (crl_class) { + NSS_CK_SET_ATTRIBUTE_NULL(attr, CKA_CLASS); + } if (encodingOpt) { NSS_CK_SET_ATTRIBUTE_NULL(attr, CKA_VALUE); } @@ -633,6 +638,9 @@ nssCryptokiCRL_GetAttributes if (isKRLOpt) { NSS_CK_SET_ATTRIBUTE_NULL(attr, CKA_NETSCAPE_KRL); } + if (subjectOpt) { + NSS_CK_SET_ATTRIBUTE_NULL(attr, CKA_SUBJECT); + } NSS_CK_TEMPLATE_FINISH(crl_template, attr, crl_size); status = nssToken_GetCachedObjectAttributes(crlObject->token, NULL, @@ -655,6 +663,9 @@ nssCryptokiCRL_GetAttributes } i=0; + if (crl_class) { + NSS_CK_ATTRIBUTE_TO_ULONG(&crl_template[i], *crl_class); i++; + } if (encodingOpt) { NSS_CK_ATTRIBUTE_TO_ITEM(&crl_template[i], encodingOpt); i++; } @@ -664,6 +675,9 @@ nssCryptokiCRL_GetAttributes if (isKRLOpt) { NSS_CK_ATTRIBUTE_TO_BOOL(&crl_template[i], *isKRLOpt); i++; } + if (subjectOpt) { + NSS_CK_ATTRIBUTE_TO_ITEM(&crl_template[i], subjectOpt); i++; + } return PR_SUCCESS; } diff --git a/security/nss/lib/dev/ckhelper.h b/security/nss/lib/dev/ckhelper.h index 9beab920d..aa232e578 100644 --- a/security/nss/lib/dev/ckhelper.h +++ b/security/nss/lib/dev/ckhelper.h @@ -118,6 +118,11 @@ NSS_EXTERN_DATA const NSSItem g_ck_class_privkey; } \ } +#define NSS_CK_ATTRIBUTE_TO_ULONG(attrib, ulongvar) \ + if ((attrib)->ulValueLen > 0) { \ + ulongvar = *((CK_ULONG*)(attrib)->pValue); \ + } + /* NSS_CK_ATTRIBUTE_TO_UTF8(attrib, str) * * Convert a CK_ATTRIBUTE to a string. diff --git a/security/nss/lib/dev/dev.h b/security/nss/lib/dev/dev.h index fc6e2091d..5c8d9245a 100644 --- a/security/nss/lib/dev/dev.h +++ b/security/nss/lib/dev/dev.h @@ -755,6 +755,8 @@ nssCryptokiCRL_GetAttributes nssSession *sessionOpt, NSSArena *arenaOpt, NSSItem *encodingOpt, + NSSItem * subjectOpt, + CK_ULONG * crl_class, NSSUTF8 **urlOpt, PRBool *isKRLOpt ); diff --git a/security/nss/lib/nss/nssinit.c b/security/nss/lib/nss/nssinit.c index 399d10538..eacef5726 100644 --- a/security/nss/lib/nss/nssinit.c +++ b/security/nss/lib/nss/nssinit.c @@ -411,6 +411,10 @@ nss_Init(const char *configdir, const char *certPrefix, const char *keyPrefix, return SECSuccess; } + if (SECSuccess != InitCRLCache()) { + return SECFailure; + } + flags = nss_makeFlags(readOnly,noCertDB,noModDB,forceOpen, pk11_password_required, optimizeSpace); if (flags == NULL) return rv; @@ -536,6 +540,7 @@ NSS_Shutdown(void) { SECStatus rv; + ShutdownCRLCache(); SECOID_Shutdown(); STAN_Shutdown(); rv = SECMOD_Shutdown(); diff --git a/security/nss/lib/pki/certificate.c b/security/nss/lib/pki/certificate.c index 0f82b93bf..b763412c4 100644 --- a/security/nss/lib/pki/certificate.c +++ b/security/nss/lib/pki/certificate.c @@ -1098,6 +1098,8 @@ nssCRL_Create NULL, /* XXX sessionOpt */ arena, &rvCRL->encoding, + NULL, /* subject */ + NULL, /* class */ &rvCRL->url, &rvCRL->isKRL); if (status != PR_SUCCESS) { diff --git a/security/nss/lib/pki/pkibase.c b/security/nss/lib/pki/pkibase.c index 9dab956c9..138e06a07 100644 --- a/security/nss/lib/pki/pkibase.c +++ b/security/nss/lib/pki/pkibase.c @@ -1157,6 +1157,8 @@ crl_getUIDFromInstance(nssCryptokiObject *instance, NSSItem *uid, NULL, /* XXX sessionOpt */ arena, /* arena */ &uid[0], /* encoding */ + NULL, /* subject */ + NULL, /* class */ NULL, /* url */ NULL); /* isKRL */ } |