/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * Moved from secpkcs7.c */ #include "cert.h" #include "certi.h" #include "secder.h" #include "secasn1.h" #include "secoid.h" #include "certdb.h" #include "certxutl.h" #include "prtime.h" #include "secerr.h" #include "pk11func.h" #include "dev.h" #include "dev3hack.h" #include "nssbase.h" #if defined(DPC_RWLOCK) || defined(GLOBAL_RWLOCK) #include "nssrwlk.h" #endif #include "pk11priv.h" const SEC_ASN1Template SEC_CERTExtensionTemplate[] = { { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertExtension) }, { SEC_ASN1_OBJECT_ID, offsetof(CERTCertExtension, id) }, { SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN, /* XXX DER_DEFAULT */ offsetof(CERTCertExtension, critical) }, { SEC_ASN1_OCTET_STRING, offsetof(CERTCertExtension, value) }, { 0 } }; static const SEC_ASN1Template SEC_CERTExtensionsTemplate[] = { { SEC_ASN1_SEQUENCE_OF, 0, SEC_CERTExtensionTemplate } }; /* * XXX Also, these templates need to be tested; Lisa did the obvious * translation but they still should be verified. */ const SEC_ASN1Template CERT_IssuerAndSNTemplate[] = { { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTIssuerAndSN) }, { SEC_ASN1_SAVE, offsetof(CERTIssuerAndSN, derIssuer) }, { SEC_ASN1_INLINE, offsetof(CERTIssuerAndSN, issuer), CERT_NameTemplate }, { SEC_ASN1_INTEGER, offsetof(CERTIssuerAndSN, serialNumber) }, { 0 } }; SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) SEC_ASN1_MKSUB(CERT_TimeChoiceTemplate) static const SEC_ASN1Template cert_CrlKeyTemplate[] = { { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrlKey) }, { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(CERTCrlKey, dummy) }, { SEC_ASN1_SKIP }, { SEC_ASN1_ANY, offsetof(CERTCrlKey, derName) }, { SEC_ASN1_SKIP_REST }, { 0 } }; static const SEC_ASN1Template cert_CrlEntryTemplate[] = { { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrlEntry) }, { SEC_ASN1_INTEGER, offsetof(CERTCrlEntry, serialNumber) }, { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrlEntry, revocationDate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, { SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF, offsetof(CERTCrlEntry, extensions), SEC_CERTExtensionTemplate }, { 0 } }; const SEC_ASN1Template CERT_CrlTemplate[] = { { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrl) }, { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(CERTCrl, version) }, { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, signatureAlg), SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, { SEC_ASN1_SAVE, offsetof(CERTCrl, derName) }, { SEC_ASN1_INLINE, offsetof(CERTCrl, name), CERT_NameTemplate }, { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, lastUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, { SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL | SEC_ASN1_XTRN, offsetof(CERTCrl, nextUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, { SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF, offsetof(CERTCrl, entries), cert_CrlEntryTemplate }, { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_EXPLICIT | 0, offsetof(CERTCrl, extensions), SEC_CERTExtensionsTemplate }, { 0 } }; const SEC_ASN1Template CERT_CrlTemplateNoEntries[] = { { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrl) }, { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(CERTCrl, version) }, { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, signatureAlg), SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, { SEC_ASN1_SAVE, offsetof(CERTCrl, derName) }, { SEC_ASN1_INLINE, offsetof(CERTCrl, name), CERT_NameTemplate }, { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, lastUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, { SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL | SEC_ASN1_XTRN, offsetof(CERTCrl, nextUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, { SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF | SEC_ASN1_SKIP }, /* skip entries */ { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_EXPLICIT | 0, offsetof(CERTCrl, extensions), SEC_CERTExtensionsTemplate }, { 0 } }; const SEC_ASN1Template CERT_CrlTemplateEntriesOnly[] = { { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrl) }, { SEC_ASN1_SKIP | SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL }, { SEC_ASN1_SKIP }, { SEC_ASN1_SKIP }, { SEC_ASN1_SKIP | SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, lastUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, { SEC_ASN1_SKIP | SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL | SEC_ASN1_XTRN, offsetof(CERTCrl, nextUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, { SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF, offsetof(CERTCrl, entries), cert_CrlEntryTemplate }, /* decode entries */ { SEC_ASN1_SKIP_REST }, { 0 } }; const SEC_ASN1Template CERT_SignedCrlTemplate[] = { { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTSignedCrl) }, { SEC_ASN1_SAVE, offsetof(CERTSignedCrl, signatureWrap.data) }, { SEC_ASN1_INLINE, offsetof(CERTSignedCrl, crl), CERT_CrlTemplate }, { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTSignedCrl, signatureWrap.signatureAlgorithm), SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, { SEC_ASN1_BIT_STRING, offsetof(CERTSignedCrl, signatureWrap.signature) }, { 0 } }; static const SEC_ASN1Template cert_SignedCrlTemplateNoEntries[] = { { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTSignedCrl) }, { SEC_ASN1_SAVE, offsetof(CERTSignedCrl, signatureWrap.data) }, { SEC_ASN1_INLINE, offsetof(CERTSignedCrl, crl), CERT_CrlTemplateNoEntries }, { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTSignedCrl, signatureWrap.signatureAlgorithm), SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, { SEC_ASN1_BIT_STRING, offsetof(CERTSignedCrl, signatureWrap.signature) }, { 0 } }; const SEC_ASN1Template CERT_SetOfSignedCrlTemplate[] = { { SEC_ASN1_SET_OF, 0, CERT_SignedCrlTemplate }, }; /* get CRL version */ int cert_get_crl_version(CERTCrl* crl) { /* CRL version is defaulted to v1 */ int version = SEC_CRL_VERSION_1; if (crl && crl->version.data != 0) { version = (int)DER_GetUInteger(&crl->version); } return version; } /* check the entries in the CRL */ SECStatus cert_check_crl_entries(CERTCrl* crl) { CERTCrlEntry** entries; CERTCrlEntry* entry; PRBool hasCriticalExten = PR_FALSE; SECStatus rv = SECSuccess; if (!crl) { return SECFailure; } if (crl->entries == NULL) { /* CRLs with no entries are valid */ return (SECSuccess); } /* Look in the crl entry extensions. If there is a critical extension, then the crl version must be v2; otherwise, it should be v1. */ entries = crl->entries; while (*entries) { entry = *entries; if (entry->extensions) { /* If there is a critical extension in the entries, then the CRL must be of version 2. If we already saw a critical extension, there is no need to check the version again. */ if (hasCriticalExten == PR_FALSE) { hasCriticalExten = cert_HasCriticalExtension(entry->extensions); if (hasCriticalExten) { if (cert_get_crl_version(crl) != SEC_CRL_VERSION_2) { /* only CRL v2 critical extensions are supported */ PORT_SetError(SEC_ERROR_CRL_V1_CRITICAL_EXTENSION); rv = SECFailure; break; } } } /* For each entry, make sure that it does not contain an unknown critical extension. If it does, we must reject the CRL since we don't know how to process the extension. */ if (cert_HasUnknownCriticalExten(entry->extensions) == PR_TRUE) { PORT_SetError(SEC_ERROR_CRL_UNKNOWN_CRITICAL_EXTENSION); rv = SECFailure; break; } } ++entries; } return (rv); } /* Check the version of the CRL. If there is a critical extension in the crl or crl entry, then the version must be v2. Otherwise, it should be v1. If the crl contains critical extension(s), then we must recognized the extension's OID. */ SECStatus cert_check_crl_version(CERTCrl* crl) { PRBool hasCriticalExten = PR_FALSE; int version = cert_get_crl_version(crl); if (version > SEC_CRL_VERSION_2) { PORT_SetError(SEC_ERROR_CRL_INVALID_VERSION); return (SECFailure); } /* Check the crl extensions for a critial extension. If one is found, and the version is not v2, then we are done. */ if (crl->extensions) { hasCriticalExten = cert_HasCriticalExtension(crl->extensions); if (hasCriticalExten) { if (version != SEC_CRL_VERSION_2) { /* only CRL v2 critical extensions are supported */ PORT_SetError(SEC_ERROR_CRL_V1_CRITICAL_EXTENSION); return (SECFailure); } /* make sure that there is no unknown critical extension */ if (cert_HasUnknownCriticalExten(crl->extensions) == PR_TRUE) { PORT_SetError(SEC_ERROR_CRL_UNKNOWN_CRITICAL_EXTENSION); return (SECFailure); } } } return (SECSuccess); } /* * Generate a database key, based on the issuer name from a * DER crl. */ SECStatus CERT_KeyFromDERCrl(PLArenaPool* arena, SECItem* derCrl, SECItem* key) { SECStatus rv; CERTSignedData sd; CERTCrlKey crlkey; PLArenaPool* myArena; if (!arena) { /* arena needed for QuickDER */ myArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); } else { myArena = arena; } PORT_Memset(&sd, 0, sizeof(sd)); rv = SEC_QuickDERDecodeItem(myArena, &sd, CERT_SignedDataTemplate, derCrl); if (SECSuccess == rv) { PORT_Memset(&crlkey, 0, sizeof(crlkey)); rv = SEC_QuickDERDecodeItem(myArena, &crlkey, cert_CrlKeyTemplate, &sd.data); } /* make a copy so the data doesn't point to memory inside derCrl, which may be temporary */ if (SECSuccess == rv) { rv = SECITEM_CopyItem(arena, key, &crlkey.derName); } if (myArena != arena) { PORT_FreeArena(myArena, PR_FALSE); } return rv; } #define GetOpaqueCRLFields(x) ((OpaqueCRLFields*)x->opaque) SECStatus CERT_CompleteCRLDecodeEntries(CERTSignedCrl* crl) { SECStatus rv = SECSuccess; SECItem* crldata = NULL; OpaqueCRLFields* extended = NULL; if ((!crl) || (!(extended = (OpaqueCRLFields*)crl->opaque)) || (PR_TRUE == extended->decodingError)) { rv = SECFailure; } else { if (PR_FALSE == extended->partial) { /* the CRL has already been fully decoded */ return SECSuccess; } if (PR_TRUE == extended->badEntries) { /* the entries decoding already failed */ return SECFailure; } crldata = &crl->signatureWrap.data; if (!crldata) { rv = SECFailure; } } if (SECSuccess == rv) { rv = SEC_QuickDERDecodeItem(crl->arena, &crl->crl, CERT_CrlTemplateEntriesOnly, crldata); if (SECSuccess == rv) { extended->partial = PR_FALSE; /* successful decode, avoid decoding again */ } else { extended->decodingError = PR_TRUE; extended->badEntries = PR_TRUE; /* cache the decoding failure. If it fails the first time, it will fail again, which will grow the arena and leak memory, so we want to avoid it */ } rv = cert_check_crl_entries(&crl->crl); if (rv != SECSuccess) { extended->badExtensions = PR_TRUE; } } return rv; } /* * take a DER CRL and decode it into a CRL structure * allow reusing the input DER without making a copy */ CERTSignedCrl* CERT_DecodeDERCrlWithFlags(PLArenaPool* narena, SECItem* derSignedCrl, int type, PRInt32 options) { PLArenaPool* arena; CERTSignedCrl* crl; SECStatus rv; OpaqueCRLFields* extended = NULL; const SEC_ASN1Template* crlTemplate = CERT_SignedCrlTemplate; PRInt32 testOptions = options; PORT_Assert(derSignedCrl); if (!derSignedCrl) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return NULL; } /* Adopting DER requires not copying it. Code that sets ADOPT flag * but doesn't set DONT_COPY probably doesn't know What it is doing. * That condition is a programming error in the caller. */ testOptions &= (CRL_DECODE_ADOPT_HEAP_DER | CRL_DECODE_DONT_COPY_DER); PORT_Assert(testOptions != CRL_DECODE_ADOPT_HEAP_DER); if (testOptions == CRL_DECODE_ADOPT_HEAP_DER) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return NULL; } /* make a new arena if needed */ if (narena == NULL) { arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (!arena) { return NULL; } } else { arena = narena; } /* allocate the CRL structure */ crl = (CERTSignedCrl*)PORT_ArenaZAlloc(arena, sizeof(CERTSignedCrl)); if (!crl) { PORT_SetError(SEC_ERROR_NO_MEMORY); goto loser; } crl->arena = arena; /* allocate opaque fields */ crl->opaque = (void*)PORT_ArenaZAlloc(arena, sizeof(OpaqueCRLFields)); if (!crl->opaque) { goto loser; } extended = (OpaqueCRLFields*)crl->opaque; if (options & CRL_DECODE_ADOPT_HEAP_DER) { extended->heapDER = PR_TRUE; } if (options & CRL_DECODE_DONT_COPY_DER) { crl->derCrl = derSignedCrl; /* DER is not copied . The application must keep derSignedCrl until it destroys the CRL */ } else { crl->derCrl = (SECItem*)PORT_ArenaZAlloc(arena, sizeof(SECItem)); if (crl->derCrl == NULL) { goto loser; } rv = SECITEM_CopyItem(arena, crl->derCrl, derSignedCrl); if (rv != SECSuccess) { goto loser; } } /* Save the arena in the inner crl for CRL extensions support */ crl->crl.arena = arena; if (options & CRL_DECODE_SKIP_ENTRIES) { crlTemplate = cert_SignedCrlTemplateNoEntries; extended->partial = PR_TRUE; } /* decode the CRL info */ switch (type) { case SEC_CRL_TYPE: rv = SEC_QuickDERDecodeItem(arena, crl, crlTemplate, crl->derCrl); if (rv != SECSuccess) { extended->badDER = PR_TRUE; break; } /* check for critical extensions */ rv = cert_check_crl_version(&crl->crl); if (rv != SECSuccess) { extended->badExtensions = PR_TRUE; break; } if (PR_TRUE == extended->partial) { /* partial decoding, don't verify entries */ break; } rv = cert_check_crl_entries(&crl->crl); if (rv != SECSuccess) { extended->badExtensions = PR_TRUE; } break; default: PORT_SetError(SEC_ERROR_INVALID_ARGS); rv = SECFailure; break; } if (rv != SECSuccess) { goto loser; } crl->referenceCount = 1; return (crl); loser: if (options & CRL_DECODE_KEEP_BAD_CRL) { if (extended) { extended->decodingError = PR_TRUE; } if (crl) { crl->referenceCount = 1; return (crl); } } if ((narena == NULL) && arena) { PORT_FreeArena(arena, PR_FALSE); } return (0); } /* * take a DER CRL and decode it into a CRL structure */ CERTSignedCrl* CERT_DecodeDERCrl(PLArenaPool* narena, SECItem* derSignedCrl, int type) { return CERT_DecodeDERCrlWithFlags(narena, derSignedCrl, type, CRL_DECODE_DEFAULT_OPTIONS); } /* * Lookup a CRL in the databases. We mirror the same fast caching data base * caching stuff used by certificates....? * return values : * * SECSuccess means we got a valid decodable DER CRL, or no CRL at all. * Caller may distinguish those cases by the value returned in "decoded". * When DER CRL is not found, error code will be SEC_ERROR_CRL_NOT_FOUND. * * SECFailure means we got a fatal error - most likely, we found a CRL, * and it failed decoding, or there was an out of memory error. Do NOT ignore * it and specifically do NOT treat it the same as having no CRL, as this * can compromise security !!! Ideally, you should treat this case as if you * received a "catch-all" CRL where all certs you were looking up are * considered to be revoked */ static SECStatus SEC_FindCrlByKeyOnSlot(PK11SlotInfo* slot, SECItem* crlKey, int type, CERTSignedCrl** decoded, PRInt32 decodeoptions) { SECStatus rv = SECSuccess; CERTSignedCrl* crl = NULL; SECItem* derCrl = NULL; CK_OBJECT_HANDLE crlHandle = 0; char* url = NULL; PORT_Assert(decoded); if (!decoded) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } derCrl = PK11_FindCrlByName(&slot, &crlHandle, crlKey, type, &url); if (derCrl == NULL) { /* if we had a problem other than the CRL just didn't exist, return * a failure to the upper level */ int nsserror = PORT_GetError(); if (nsserror != SEC_ERROR_CRL_NOT_FOUND) { rv = SECFailure; } goto loser; } PORT_Assert(crlHandle != CK_INVALID_HANDLE); /* PK11_FindCrlByName obtained a slot reference. */ /* derCRL is a fresh HEAP copy made for us by PK11_FindCrlByName. Force adoption of the DER CRL from the heap - this will cause it to be automatically freed when SEC_DestroyCrl is invoked */ decodeoptions |= (CRL_DECODE_ADOPT_HEAP_DER | CRL_DECODE_DONT_COPY_DER); crl = CERT_DecodeDERCrlWithFlags(NULL, derCrl, type, decodeoptions); if (crl) { crl->slot = slot; slot = NULL; /* adopt it */ derCrl = NULL; /* adopted by the crl struct */ crl->pkcs11ID = crlHandle; if (url) { crl->url = PORT_ArenaStrdup(crl->arena, url); } } else { rv = SECFailure; } if (url) { PORT_Free(url); } if (slot) { PK11_FreeSlot(slot); } loser: if (derCrl) { SECITEM_FreeItem(derCrl, PR_TRUE); } *decoded = crl; return rv; } CERTSignedCrl* crl_storeCRL(PK11SlotInfo* slot, char* url, CERTSignedCrl* newCrl, SECItem* derCrl, int type) { CERTSignedCrl *oldCrl = NULL, *crl = NULL; PRBool deleteOldCrl = PR_FALSE; CK_OBJECT_HANDLE crlHandle = CK_INVALID_HANDLE; PORT_Assert(newCrl); PORT_Assert(derCrl); PORT_Assert(type == SEC_CRL_TYPE); if (type != SEC_CRL_TYPE) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return NULL; } /* we can't use the cache here because we must look in the same token */ (void)SEC_FindCrlByKeyOnSlot(slot, &newCrl->crl.derName, type, &oldCrl, CRL_DECODE_SKIP_ENTRIES); /* if there is an old crl on the token, make sure the one we are installing is newer. If not, exit out, otherwise delete the old crl. */ if (oldCrl != NULL) { /* if it's already there, quietly continue */ if (SECITEM_CompareItem(newCrl->derCrl, oldCrl->derCrl) == SECEqual) { crl = newCrl; crl->slot = PK11_ReferenceSlot(slot); crl->pkcs11ID = oldCrl->pkcs11ID; if (oldCrl->url && !url) url = oldCrl->url; if (url) crl->url = PORT_ArenaStrdup(crl->arena, url); goto done; } if (!SEC_CrlIsNewer(&newCrl->crl, &oldCrl->crl)) { PORT_SetError(SEC_ERROR_OLD_CRL); goto done; } /* if we have a url in the database, use that one */ if (oldCrl->url && !url) { url = oldCrl->url; } /* really destroy this crl */ /* first drum it out of the permanment Data base */ deleteOldCrl = PR_TRUE; } /* invalidate CRL cache for this issuer */ CERT_CRLCacheRefreshIssuer(NULL, &newCrl->crl.derName); /* Write the new entry into the data base */ crlHandle = PK11_PutCrl(slot, derCrl, &newCrl->crl.derName, url, type); if (crlHandle != CK_INVALID_HANDLE) { crl = newCrl; crl->slot = PK11_ReferenceSlot(slot); crl->pkcs11ID = crlHandle; if (url) { crl->url = PORT_ArenaStrdup(crl->arena, url); } } done: if (oldCrl) { if (deleteOldCrl && crlHandle != CK_INVALID_HANDLE) { SEC_DeletePermCRL(oldCrl); } SEC_DestroyCrl(oldCrl); } return crl; } /* * * create a new CRL from DER material. * * The signature on this CRL must be checked before you * load it. ??? */ CERTSignedCrl* SEC_NewCrl(CERTCertDBHandle* handle, char* url, SECItem* derCrl, int type) { CERTSignedCrl* retCrl = NULL; PK11SlotInfo* slot = PK11_GetInternalKeySlot(); retCrl = PK11_ImportCRL(slot, derCrl, url, type, NULL, CRL_IMPORT_BYPASS_CHECKS, NULL, CRL_DECODE_DEFAULT_OPTIONS); PK11_FreeSlot(slot); return retCrl; } CERTSignedCrl* SEC_FindCrlByDERCert(CERTCertDBHandle* handle, SECItem* derCrl, int type) { PLArenaPool* arena; SECItem crlKey; SECStatus rv; CERTSignedCrl* crl = NULL; /* create a scratch arena */ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (arena == NULL) { return (NULL); } /* extract the database key from the cert */ rv = CERT_KeyFromDERCrl(arena, derCrl, &crlKey); if (rv != SECSuccess) { goto loser; } /* find the crl */ crl = SEC_FindCrlByName(handle, &crlKey, type); loser: PORT_FreeArena(arena, PR_FALSE); return (crl); } CERTSignedCrl* SEC_DupCrl(CERTSignedCrl* acrl) { if (acrl) { PR_ATOMIC_INCREMENT(&acrl->referenceCount); return acrl; } return NULL; } SECStatus SEC_DestroyCrl(CERTSignedCrl* crl) { if (crl) { if (PR_ATOMIC_DECREMENT(&crl->referenceCount) < 1) { if (crl->slot) { PK11_FreeSlot(crl->slot); } if (GetOpaqueCRLFields(crl) && PR_TRUE == GetOpaqueCRLFields(crl)->heapDER) { SECITEM_FreeItem(crl->derCrl, PR_TRUE); } if (crl->arena) { PORT_FreeArena(crl->arena, PR_FALSE); } } return SECSuccess; } else { return SECFailure; } } SECStatus SEC_LookupCrls(CERTCertDBHandle* handle, CERTCrlHeadNode** nodes, int type) { CERTCrlHeadNode* head; PLArenaPool* arena = NULL; SECStatus rv; *nodes = NULL; arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (arena == NULL) { return SECFailure; } /* build a head structure */ head = (CERTCrlHeadNode*)PORT_ArenaAlloc(arena, sizeof(CERTCrlHeadNode)); head->arena = arena; head->first = NULL; head->last = NULL; head->dbhandle = handle; /* Look up the proper crl types */ *nodes = head; rv = PK11_LookupCrls(head, type, NULL); if (rv != SECSuccess) { if (arena) { PORT_FreeArena(arena, PR_FALSE); *nodes = NULL; } } return rv; } /* These functions simply return the address of the above-declared templates. ** This is necessary for Windows DLLs. Sigh. */ SEC_ASN1_CHOOSER_IMPLEMENT(CERT_IssuerAndSNTemplate) SEC_ASN1_CHOOSER_IMPLEMENT(CERT_CrlTemplate) SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SignedCrlTemplate) SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SetOfSignedCrlTemplate) /* CRL cache code starts here */ /* constructor */ static SECStatus CachedCrl_Create(CachedCrl** returned, CERTSignedCrl* crl, CRLOrigin origin); /* destructor */ static SECStatus CachedCrl_Destroy(CachedCrl* crl); /* create hash table of CRL entries */ static SECStatus CachedCrl_Populate(CachedCrl* crlobject); /* empty the cache content */ static SECStatus CachedCrl_Depopulate(CachedCrl* crl); /* are these CRLs the same, as far as the cache is concerned ? Or are they the same token object, but with different DER ? */ static SECStatus CachedCrl_Compare(CachedCrl* a, CachedCrl* b, PRBool* isDupe, PRBool* isUpdated); /* create a DPCache object */ static SECStatus DPCache_Create(CRLDPCache** returned, CERTCertificate* issuer, const SECItem* subject, SECItem* dp); /* destructor for CRL DPCache object */ static SECStatus DPCache_Destroy(CRLDPCache* cache); /* add a new CRL object to the dynamic array of CRLs of the DPCache, and returns the cached CRL object . Needs write access to DPCache. */ static SECStatus DPCache_AddCRL(CRLDPCache* cache, CachedCrl* crl, PRBool* added); /* fetch the CRL for this DP from the PKCS#11 tokens */ static SECStatus DPCache_FetchFromTokens(CRLDPCache* cache, PRTime vfdate, void* wincx); /* update the content of the CRL cache, including fetching of CRLs, and reprocessing with specified issuer and date */ static SECStatus DPCache_GetUpToDate(CRLDPCache* cache, CERTCertificate* issuer, PRBool readlocked, PRTime vfdate, void* wincx); /* returns true if there are CRLs from PKCS#11 slots */ static PRBool DPCache_HasTokenCRLs(CRLDPCache* cache); /* remove CRL at offset specified */ static SECStatus DPCache_RemoveCRL(CRLDPCache* cache, PRUint32 offset); /* Pick best CRL to use . needs write access */ static SECStatus DPCache_SelectCRL(CRLDPCache* cache); /* create an issuer cache object (per CA subject ) */ static SECStatus IssuerCache_Create(CRLIssuerCache** returned, CERTCertificate* issuer, const SECItem* subject, const SECItem* dp); /* destructor for CRL IssuerCache object */ SECStatus IssuerCache_Destroy(CRLIssuerCache* cache); /* add a DPCache to the issuer cache */ static SECStatus IssuerCache_AddDP(CRLIssuerCache* cache, CERTCertificate* issuer, const SECItem* subject, const SECItem* dp, CRLDPCache** newdpc); /* get a particular DPCache object from an IssuerCache */ static CRLDPCache* IssuerCache_GetDPCache(CRLIssuerCache* cache, const SECItem* dp); /* ** Pre-allocator hash allocator ops. */ /* allocate memory for hash table */ static void* PR_CALLBACK PreAllocTable(void* pool, PRSize size) { PreAllocator* alloc = (PreAllocator*)pool; PORT_Assert(alloc); if (!alloc) { /* no allocator, or buffer full */ return NULL; } if (size > (alloc->len - alloc->used)) { /* initial buffer full, let's use the arena */ alloc->extra += size; return PORT_ArenaAlloc(alloc->arena, size); } /* use the initial buffer */ alloc->used += size; return (char*)alloc->data + alloc->used - size; } /* free hash table memory. Individual PreAllocator elements cannot be freed, so this is a no-op. */ static void PR_CALLBACK PreFreeTable(void* pool, void* item) { } /* allocate memory for hash table */ static PLHashEntry* PR_CALLBACK PreAllocEntry(void* pool, const void* key) { return PreAllocTable(pool, sizeof(PLHashEntry)); } /* free hash table entry. Individual PreAllocator elements cannot be freed, so this is a no-op. */ static void PR_CALLBACK PreFreeEntry(void* pool, PLHashEntry* he, PRUintn flag) { } /* methods required for PL hash table functions */ static PLHashAllocOps preAllocOps = { PreAllocTable, PreFreeTable, PreAllocEntry, PreFreeEntry }; /* destructor for PreAllocator object */ void PreAllocator_Destroy(PreAllocator* allocator) { if (!allocator) { return; } if (allocator->arena) { PORT_FreeArena(allocator->arena, PR_TRUE); } } /* constructor for PreAllocator object */ PreAllocator* PreAllocator_Create(PRSize size) { PLArenaPool* arena = NULL; PreAllocator* prebuffer = NULL; arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (!arena) { return NULL; } prebuffer = (PreAllocator*)PORT_ArenaZAlloc(arena, sizeof(PreAllocator)); if (!prebuffer) { PORT_FreeArena(arena, PR_TRUE); return NULL; } prebuffer->arena = arena; if (size) { prebuffer->len = size; prebuffer->data = PORT_ArenaAlloc(arena, size); if (!prebuffer->data) { PORT_FreeArena(arena, PR_TRUE); return NULL; } } return prebuffer; } /* global Named CRL cache object */ static NamedCRLCache namedCRLCache = { NULL, NULL }; /* global CRL cache object */ static CRLCache crlcache = { NULL, NULL }; /* initial state is off */ static PRBool crlcache_initialized = PR_FALSE; PRTime CRLCache_Empty_TokenFetch_Interval = 60 * 1000000; /* how often to query the tokens for CRL objects, in order to discover new objects, if the cache does not contain any token CRLs . In microseconds */ PRTime CRLCache_TokenRefetch_Interval = 600 * 1000000; /* how often to query the tokens for CRL objects, in order to discover new objects, if the cache already contains token CRLs In microseconds */ PRTime CRLCache_ExistenceCheck_Interval = 60 * 1000000; /* how often to check if a token CRL object still exists. In microseconds */ /* this function is called at NSS initialization time */ SECStatus InitCRLCache(void) { if (PR_FALSE == crlcache_initialized) { PORT_Assert(NULL == crlcache.lock); PORT_Assert(NULL == crlcache.issuers); PORT_Assert(NULL == namedCRLCache.lock); PORT_Assert(NULL == namedCRLCache.entries); if (crlcache.lock || crlcache.issuers || namedCRLCache.lock || namedCRLCache.entries) { /* CRL cache already partially initialized */ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } #ifdef GLOBAL_RWLOCK crlcache.lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL); #else crlcache.lock = PR_NewLock(); #endif namedCRLCache.lock = PR_NewLock(); crlcache.issuers = PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare, PL_CompareValues, NULL, NULL); namedCRLCache.entries = PL_NewHashTable( 0, SECITEM_Hash, SECITEM_HashCompare, PL_CompareValues, NULL, NULL); if (!crlcache.lock || !namedCRLCache.lock || !crlcache.issuers || !namedCRLCache.entries) { if (crlcache.lock) { #ifdef GLOBAL_RWLOCK NSSRWLock_Destroy(crlcache.lock); #else PR_DestroyLock(crlcache.lock); #endif crlcache.lock = NULL; } if (namedCRLCache.lock) { PR_DestroyLock(namedCRLCache.lock); namedCRLCache.lock = NULL; } if (crlcache.issuers) { PL_HashTableDestroy(crlcache.issuers); crlcache.issuers = NULL; } if (namedCRLCache.entries) { PL_HashTableDestroy(namedCRLCache.entries); namedCRLCache.entries = NULL; } return SECFailure; } crlcache_initialized = PR_TRUE; return SECSuccess; } else { PORT_Assert(crlcache.lock); PORT_Assert(crlcache.issuers); if ((NULL == crlcache.lock) || (NULL == crlcache.issuers)) { /* CRL cache not fully initialized */ return SECFailure; } else { /* CRL cache already initialized */ return SECSuccess; } } } /* destructor for CRL DPCache object */ static SECStatus DPCache_Destroy(CRLDPCache* cache) { PRUint32 i = 0; PORT_Assert(cache); if (!cache) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } if (cache->lock) { #ifdef DPC_RWLOCK NSSRWLock_Destroy(cache->lock); #else PR_DestroyLock(cache->lock); #endif } else { PORT_Assert(0); return SECFailure; } /* destroy all our CRL objects */ for (i = 0; i < cache->ncrls; i++) { if (!cache->crls || !cache->crls[i] || SECSuccess != CachedCrl_Destroy(cache->crls[i])) { return SECFailure; } } /* free the array of CRLs */ if (cache->crls) { PORT_Free(cache->crls); } /* destroy the cert */ if (cache->issuerDERCert) { SECITEM_FreeItem(cache->issuerDERCert, PR_TRUE); } /* free the subject */ if (cache->subject) { SECITEM_FreeItem(cache->subject, PR_TRUE); } /* free the distribution points */ if (cache->distributionPoint) { SECITEM_FreeItem(cache->distributionPoint, PR_TRUE); } PORT_Free(cache); return SECSuccess; } /* destructor for CRL IssuerCache object */ SECStatus IssuerCache_Destroy(CRLIssuerCache* cache) { PORT_Assert(cache); if (!cache) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } #ifdef XCRL if (cache->lock) { NSSRWLock_Destroy(cache->lock); } else { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } if (cache->issuer) { CERT_DestroyCertificate(cache->issuer); } #endif /* free the subject */ if (cache->subject) { SECITEM_FreeItem(cache->subject, PR_TRUE); } if (SECSuccess != DPCache_Destroy(cache->dpp)) { PORT_Assert(0); return SECFailure; } PORT_Free(cache); return SECSuccess; } /* create a named CRL entry object */ static SECStatus NamedCRLCacheEntry_Create(NamedCRLCacheEntry** returned) { NamedCRLCacheEntry* entry = NULL; if (!returned) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } *returned = NULL; entry = (NamedCRLCacheEntry*)PORT_ZAlloc(sizeof(NamedCRLCacheEntry)); if (!entry) { return SECFailure; } *returned = entry; return SECSuccess; } /* destroy a named CRL entry object */ static SECStatus NamedCRLCacheEntry_Destroy(NamedCRLCacheEntry* entry) { if (!entry) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } if (entry->crl) { /* named CRL cache owns DER memory */ SECITEM_ZfreeItem(entry->crl, PR_TRUE); } if (entry->canonicalizedName) { SECITEM_FreeItem(entry->canonicalizedName, PR_TRUE); } PORT_Free(entry); return SECSuccess; } /* callback function used in hash table destructor */ static PRIntn PR_CALLBACK FreeIssuer(PLHashEntry* he, PRIntn i, void* arg) { CRLIssuerCache* issuer = NULL; SECStatus* rv = (SECStatus*)arg; PORT_Assert(he); if (!he) { return HT_ENUMERATE_NEXT; } issuer = (CRLIssuerCache*)he->value; PORT_Assert(issuer); if (issuer) { if (SECSuccess != IssuerCache_Destroy(issuer)) { PORT_Assert(rv); if (rv) { *rv = SECFailure; } return HT_ENUMERATE_NEXT; } } return HT_ENUMERATE_NEXT; } /* callback function used in hash table destructor */ static PRIntn PR_CALLBACK FreeNamedEntries(PLHashEntry* he, PRIntn i, void* arg) { NamedCRLCacheEntry* entry = NULL; SECStatus* rv = (SECStatus*)arg; PORT_Assert(he); if (!he) { return HT_ENUMERATE_NEXT; } entry = (NamedCRLCacheEntry*)he->value; PORT_Assert(entry); if (entry) { if (SECSuccess != NamedCRLCacheEntry_Destroy(entry)) { PORT_Assert(rv); if (rv) { *rv = SECFailure; } return HT_ENUMERATE_NEXT; } } return HT_ENUMERATE_NEXT; } /* needs to be called at NSS shutdown time This will destroy the global CRL cache, including - the hash table of issuer cache objects - the issuer cache objects - DPCache objects in issuer cache objects */ SECStatus ShutdownCRLCache(void) { SECStatus rv = SECSuccess; if (PR_FALSE == crlcache_initialized && !crlcache.lock && !crlcache.issuers) { /* CRL cache has already been shut down */ return SECSuccess; } if (PR_TRUE == crlcache_initialized && (!crlcache.lock || !crlcache.issuers || !namedCRLCache.lock || !namedCRLCache.entries)) { /* CRL cache has partially been shut down */ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } /* empty the CRL cache */ /* free the issuers */ PL_HashTableEnumerateEntries(crlcache.issuers, &FreeIssuer, &rv); /* free the hash table of issuers */ PL_HashTableDestroy(crlcache.issuers); crlcache.issuers = NULL; /* free the global lock */ #ifdef GLOBAL_RWLOCK NSSRWLock_Destroy(crlcache.lock); #else PR_DestroyLock(crlcache.lock); #endif crlcache.lock = NULL; /* empty the named CRL cache. This must be done after freeing the CRL * cache, since some CRLs in this cache are in the memory for the other */ /* free the entries */ PL_HashTableEnumerateEntries(namedCRLCache.entries, &FreeNamedEntries, &rv); /* free the hash table of issuers */ PL_HashTableDestroy(namedCRLCache.entries); namedCRLCache.entries = NULL; /* free the global lock */ PR_DestroyLock(namedCRLCache.lock); namedCRLCache.lock = NULL; crlcache_initialized = PR_FALSE; return rv; } /* add a new CRL object to the dynamic array of CRLs of the DPCache, and returns the cached CRL object . Needs write access to DPCache. */ static SECStatus DPCache_AddCRL(CRLDPCache* cache, CachedCrl* newcrl, PRBool* added) { CachedCrl** newcrls = NULL; PRUint32 i = 0; PORT_Assert(cache); PORT_Assert(newcrl); PORT_Assert(added); if (!cache || !newcrl || !added) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } *added = PR_FALSE; /* before adding a new CRL, check if it is a duplicate */ for (i = 0; i < cache->ncrls; i++) { CachedCrl* existing = NULL; SECStatus rv = SECSuccess; PRBool dupe = PR_FALSE, updated = PR_FALSE; if (!cache->crls) { PORT_Assert(0); return SECFailure; } existing = cache->crls[i]; if (!existing) { PORT_Assert(0); return SECFailure; } rv = CachedCrl_Compare(existing, newcrl, &dupe, &updated); if (SECSuccess != rv) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } if (PR_TRUE == dupe) { /* dupe */ PORT_SetError(SEC_ERROR_CRL_ALREADY_EXISTS); return SECSuccess; } if (PR_TRUE == updated) { /* this token CRL is in the same slot and has the same object ID, but different content. We need to remove the old object */ if (SECSuccess != DPCache_RemoveCRL(cache, i)) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return PR_FALSE; } } } newcrls = (CachedCrl**)PORT_Realloc(cache->crls, (cache->ncrls + 1) * sizeof(CachedCrl*)); if (!newcrls) { return SECFailure; } cache->crls = newcrls; cache->ncrls++; cache->crls[cache->ncrls - 1] = newcrl; *added = PR_TRUE; return SECSuccess; } /* remove CRL at offset specified */ static SECStatus DPCache_RemoveCRL(CRLDPCache* cache, PRUint32 offset) { CachedCrl* acrl = NULL; PORT_Assert(cache); if (!cache || (!cache->crls) || (!(offset < cache->ncrls))) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } acrl = cache->crls[offset]; PORT_Assert(acrl); if (!acrl) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } cache->crls[offset] = cache->crls[cache->ncrls - 1]; cache->crls[cache->ncrls - 1] = NULL; cache->ncrls--; if (cache->selected == acrl) { cache->selected = NULL; } if (SECSuccess != CachedCrl_Destroy(acrl)) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } return SECSuccess; } /* check whether a CRL object stored in a PKCS#11 token still exists in that token . This has to be efficient (the entire CRL value cannot be transferred accross the token boundaries), so this is accomplished by simply fetching the subject attribute and making sure it hasn't changed . Note that technically, the CRL object could have been replaced with a new PKCS#11 object of the same ID and subject (which actually happens in softoken), but this function has no way of knowing that the object value changed, since CKA_VALUE isn't checked. */ static PRBool TokenCRLStillExists(CERTSignedCrl* crl) { NSSItem newsubject; SECItem subject; CK_ULONG crl_class; PRStatus status; PK11SlotInfo* slot = NULL; nssCryptokiObject instance; NSSArena* arena; PRBool xstatus = PR_TRUE; SECItem* oldSubject = NULL; PORT_Assert(crl); if (!crl) { return PR_FALSE; } slot = crl->slot; PORT_Assert(crl->slot); if (!slot) { return PR_FALSE; } oldSubject = &crl->crl.derName; PORT_Assert(oldSubject); if (!oldSubject) { return PR_FALSE; } /* query subject and type attributes in order to determine if the object has been deleted */ /* first, make an nssCryptokiObject */ instance.handle = crl->pkcs11ID; PORT_Assert(instance.handle); if (!instance.handle) { return PR_FALSE; } instance.token = PK11Slot_GetNSSToken(slot); PORT_Assert(instance.token); if (!instance.token) { return PR_FALSE; } instance.isTokenObject = PR_TRUE; instance.label = NULL; arena = NSSArena_Create(); PORT_Assert(arena); if (!arena) { (void)nssToken_Destroy(instance.token); return PR_FALSE; } status = nssCryptokiCRL_GetAttributes(&instance, NULL, /* XXX sessionOpt */ arena, NULL, &newsubject, /* subject */ &crl_class, /* class */ NULL, NULL); if (PR_SUCCESS == status) { subject.data = newsubject.data; subject.len = newsubject.size; if (SECITEM_CompareItem(oldSubject, &subject) != SECEqual) { xstatus = PR_FALSE; } if (CKO_NSS_CRL != crl_class) { xstatus = PR_FALSE; } } else { xstatus = PR_FALSE; } NSSArena_Destroy(arena); (void)nssToken_Destroy(instance.token); return xstatus; } /* verify the signature of a CRL against its issuer at a given date */ static SECStatus CERT_VerifyCRL(CERTSignedCrl* crlobject, CERTCertificate* issuer, PRTime vfdate, void* wincx) { return CERT_VerifySignedData(&crlobject->signatureWrap, issuer, vfdate, wincx); } /* verify a CRL and update cache state */ static SECStatus CachedCrl_Verify(CRLDPCache* cache, CachedCrl* crlobject, PRTime vfdate, void* wincx) { /* Check if it is an invalid CRL if we got a bad CRL, we want to cache it in order to avoid subsequent fetches of this same identical bad CRL. We set the cache to the invalid state to ensure that all certs on this DP are considered to have unknown status from now on. The cache object will remain in this state until the bad CRL object is removed from the token it was fetched from. If the cause of the failure is that we didn't have the issuer cert to verify the signature, this state can be cleared when the issuer certificate becomes available if that causes the signature to verify */ if (!cache || !crlobject) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } if (PR_TRUE == GetOpaqueCRLFields(crlobject->crl)->decodingError) { crlobject->sigChecked = PR_TRUE; /* we can never verify a CRL with bogus DER. Mark it checked so we won't try again */ PORT_SetError(SEC_ERROR_BAD_DER); return SECSuccess; } else { SECStatus signstatus = SECFailure; if (cache->issuerDERCert) { CERTCertificate* issuer = CERT_NewTempCertificate( cache->dbHandle, cache->issuerDERCert, NULL, PR_FALSE, PR_TRUE); if (issuer) { signstatus = CERT_VerifyCRL(crlobject->crl, issuer, vfdate, wincx); CERT_DestroyCertificate(issuer); } } if (SECSuccess != signstatus) { if (!cache->issuerDERCert) { /* we tried to verify without an issuer cert . This is because this CRL came through a call to SEC_FindCrlByName. So, we don't cache this verification failure. We'll try to verify the CRL again when a certificate from that issuer becomes available */ } else { crlobject->sigChecked = PR_TRUE; } PORT_SetError(SEC_ERROR_CRL_BAD_SIGNATURE); return SECSuccess; } else { crlobject->sigChecked = PR_TRUE; crlobject->sigValid = PR_TRUE; } } return SECSuccess; } /* fetch the CRLs for this DP from the PKCS#11 tokens */ static SECStatus DPCache_FetchFromTokens(CRLDPCache* cache, PRTime vfdate, void* wincx) { SECStatus rv = SECSuccess; CERTCrlHeadNode head; if (!cache) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } /* first, initialize list */ memset(&head, 0, sizeof(head)); head.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); rv = pk11_RetrieveCrls(&head, cache->subject, wincx); /* if this function fails, something very wrong happened, such as an out of memory error during CRL decoding. We don't want to proceed and must mark the cache object invalid */ if (SECFailure == rv) { /* fetch failed, add error bit */ cache->invalid |= CRL_CACHE_LAST_FETCH_FAILED; } else { /* fetch was successful, clear this error bit */ cache->invalid &= (~CRL_CACHE_LAST_FETCH_FAILED); } /* add any CRLs found to our array */ if (SECSuccess == rv) { CERTCrlNode* crlNode = NULL; for (crlNode = head.first; crlNode; crlNode = crlNode->next) { CachedCrl* returned = NULL; CERTSignedCrl* crlobject = crlNode->crl; if (!crlobject) { PORT_Assert(0); continue; } rv = CachedCrl_Create(&returned, crlobject, CRL_OriginToken); if (SECSuccess == rv) { PRBool added = PR_FALSE; rv = DPCache_AddCRL(cache, returned, &added); if (PR_TRUE != added) { rv = CachedCrl_Destroy(returned); returned = NULL; } else if (vfdate) { rv = CachedCrl_Verify(cache, returned, vfdate, wincx); } } else { /* not enough memory to add the CRL to the cache. mark it invalid so we will try again . */ cache->invalid |= CRL_CACHE_LAST_FETCH_FAILED; } if (SECFailure == rv) { break; } } } if (head.arena) { CERTCrlNode* crlNode = NULL; /* clean up the CRL list in case we got a partial one during a failed fetch */ for (crlNode = head.first; crlNode; crlNode = crlNode->next) { if (crlNode->crl) { SEC_DestroyCrl(crlNode->crl); /* free the CRL. Either it got added to the cache and the refcount got bumped, or not, and thus we need to free its RAM */ } } PORT_FreeArena(head.arena, PR_FALSE); /* destroy CRL list */ } return rv; } static SECStatus CachedCrl_GetEntry(CachedCrl* crl, const SECItem* sn, CERTCrlEntry** returned) { CERTCrlEntry* acrlEntry; PORT_Assert(crl); PORT_Assert(crl->entries); PORT_Assert(sn); PORT_Assert(returned); if (!crl || !sn || !returned || !crl->entries) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } acrlEntry = PL_HashTableLookup(crl->entries, (void*)sn); if (acrlEntry) { *returned = acrlEntry; } else { *returned = NULL; } return SECSuccess; } /* check if a particular SN is in the CRL cache and return its entry */ dpcacheStatus DPCache_Lookup(CRLDPCache* cache, const SECItem* sn, CERTCrlEntry** returned) { SECStatus rv; if (!cache || !sn || !returned) { PORT_SetError(SEC_ERROR_INVALID_ARGS); /* no cache or SN to look up, or no way to return entry */ return dpcacheCallerError; } *returned = NULL; if (0 != cache->invalid) { /* the cache contains a bad CRL, or there was a CRL fetching error. */ PORT_SetError(SEC_ERROR_CRL_INVALID); return dpcacheInvalidCacheError; } if (!cache->selected) { /* no CRL means no entry to return. This is OK, except for * NIST policy */ return dpcacheEmpty; } rv = CachedCrl_GetEntry(cache->selected, sn, returned); if (SECSuccess != rv) { return dpcacheLookupError; } else { if (*returned) { return dpcacheFoundEntry; } else { return dpcacheNoEntry; } } } #if defined(DPC_RWLOCK) #define DPCache_LockWrite() \ { \ if (readlocked) { \ NSSRWLock_UnlockRead(cache->lock); \ } \ NSSRWLock_LockWrite(cache->lock); \ } #define DPCache_UnlockWrite() \ { \ if (readlocked) { \ NSSRWLock_LockRead(cache->lock); \ } \ NSSRWLock_UnlockWrite(cache->lock); \ } #else /* with a global lock, we are always locked for read before we need write access, so do nothing */ #define DPCache_LockWrite() \ { \ } #define DPCache_UnlockWrite() \ { \ } #endif /* update the content of the CRL cache, including fetching of CRLs, and reprocessing with specified issuer and date . We are always holding either the read or write lock on DPCache upon entry. */ static SECStatus DPCache_GetUpToDate(CRLDPCache* cache, CERTCertificate* issuer, PRBool readlocked, PRTime vfdate, void* wincx) { /* Update the CRLDPCache now. We don't cache token CRL lookup misses yet, as we have no way of getting notified of new PKCS#11 object creation that happens in a token */ SECStatus rv = SECSuccess; PRUint32 i = 0; PRBool forcedrefresh = PR_FALSE; PRBool dirty = PR_FALSE; /* whether something was changed in the cache state during this update cycle */ PRBool hastokenCRLs = PR_FALSE; PRTime now = 0; PRTime lastfetch = 0; PRBool mustunlock = PR_FALSE; if (!cache) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } /* first, make sure we have obtained all the CRLs we need. We do an expensive token fetch in the following cases : 1) cache is empty because no fetch was ever performed yet 2) cache is explicitly set to refresh state 3) cache is in invalid state because last fetch failed 4) cache contains no token CRLs, and it's been more than one minute since the last fetch 5) cache contains token CRLs, and it's been more than 10 minutes since the last fetch */ forcedrefresh = cache->refresh; lastfetch = cache->lastfetch; if (PR_TRUE != forcedrefresh && (!(cache->invalid & CRL_CACHE_LAST_FETCH_FAILED))) { now = PR_Now(); hastokenCRLs = DPCache_HasTokenCRLs(cache); } if ((0 == lastfetch) || (PR_TRUE == forcedrefresh) || (cache->invalid & CRL_CACHE_LAST_FETCH_FAILED) || ((PR_FALSE == hastokenCRLs) && ((now - cache->lastfetch > CRLCache_Empty_TokenFetch_Interval) || (now < cache->lastfetch))) || ((PR_TRUE == hastokenCRLs) && ((now - cache->lastfetch > CRLCache_TokenRefetch_Interval) || (now < cache->lastfetch)))) { /* the cache needs to be refreshed, and/or we had zero CRL for this DP. Try to get one from PKCS#11 tokens */ DPCache_LockWrite(); /* check if another thread updated before us, and skip update if so */ if (lastfetch == cache->lastfetch) { /* we are the first */ rv = DPCache_FetchFromTokens(cache, vfdate, wincx); if (PR_TRUE == cache->refresh) { cache->refresh = PR_FALSE; /* clear refresh state */ } dirty = PR_TRUE; cache->lastfetch = PR_Now(); } DPCache_UnlockWrite(); } /* now, make sure we have no extraneous CRLs (deleted token objects) we'll do this inexpensive existence check either 1) if there was a token object fetch 2) every minute */ if ((PR_TRUE != dirty) && (!now)) { now = PR_Now(); } if ((PR_TRUE == dirty) || ((now - cache->lastcheck > CRLCache_ExistenceCheck_Interval) || (now < cache->lastcheck))) { PRTime lastcheck = cache->lastcheck; mustunlock = PR_FALSE; /* check if all CRLs still exist */ for (i = 0; (i < cache->ncrls); i++) { CachedCrl* savcrl = cache->crls[i]; if ((!savcrl) || (savcrl && CRL_OriginToken != savcrl->origin)) { /* we only want to check token CRLs */ continue; } if ((PR_TRUE != TokenCRLStillExists(savcrl->crl))) { /* this CRL is gone */ if (PR_TRUE != mustunlock) { DPCache_LockWrite(); mustunlock = PR_TRUE; } /* first, we need to check if another thread did an update before we did */ if (lastcheck == cache->lastcheck) { /* the CRL is gone. And we are the one to do the update */ DPCache_RemoveCRL(cache, i); dirty = PR_TRUE; } /* stay locked here intentionally so we do all the other updates in this thread for the remaining CRLs */ } } if (PR_TRUE == mustunlock) { cache->lastcheck = PR_Now(); DPCache_UnlockWrite(); mustunlock = PR_FALSE; } } /* add issuer certificate if it was previously unavailable */ if (issuer && (NULL == cache->issuerDERCert) && (SECSuccess == CERT_CheckCertUsage(issuer, KU_CRL_SIGN))) { /* if we didn't have a valid issuer cert yet, but we do now. add it */ DPCache_LockWrite(); if (!cache->issuerDERCert) { dirty = PR_TRUE; cache->dbHandle = issuer->dbhandle; cache->issuerDERCert = SECITEM_DupItem(&issuer->derCert); } DPCache_UnlockWrite(); } /* verify CRLs that couldn't be checked when inserted into the cache because the issuer cert or a verification date was unavailable. These are CRLs that were inserted into the cache through SEC_FindCrlByName, or through manual insertion, rather than through a certificate verification (CERT_CheckCRL) */ if (cache->issuerDERCert && vfdate) { mustunlock = PR_FALSE; /* re-process all unverified CRLs */ for (i = 0; i < cache->ncrls; i++) { CachedCrl* savcrl = cache->crls[i]; if (!savcrl) { continue; } if (PR_TRUE != savcrl->sigChecked) { if (!mustunlock) { DPCache_LockWrite(); mustunlock = PR_TRUE; } /* first, we need to check if another thread updated it before we did, and abort if it has been modified since we acquired the lock. Make sure first that the CRL is still in the array at the same position */ if ((i < cache->ncrls) && (savcrl == cache->crls[i]) && (PR_TRUE != savcrl->sigChecked)) { /* the CRL is still there, unverified. Do it */ CachedCrl_Verify(cache, savcrl, vfdate, wincx); dirty = PR_TRUE; } /* stay locked here intentionally so we do all the other updates in this thread for the remaining CRLs */ } if (mustunlock && !dirty) { DPCache_UnlockWrite(); mustunlock = PR_FALSE; } } } if (dirty || cache->mustchoose) { /* changes to the content of the CRL cache necessitate examining all CRLs for selection of the most appropriate one to cache */ if (!mustunlock) { DPCache_LockWrite(); mustunlock = PR_TRUE; } DPCache_SelectCRL(cache); cache->mustchoose = PR_FALSE; } if (mustunlock) DPCache_UnlockWrite(); return rv; } /* callback for qsort to sort by thisUpdate */ static int SortCRLsByThisUpdate(const void* arg1, const void* arg2) { PRTime timea, timeb; SECStatus rv = SECSuccess; CachedCrl *a, *b; a = *(CachedCrl**)arg1; b = *(CachedCrl**)arg2; if (!a || !b) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); rv = SECFailure; } if (SECSuccess == rv) { rv = DER_DecodeTimeChoice(&timea, &a->crl->crl.lastUpdate); } if (SECSuccess == rv) { rv = DER_DecodeTimeChoice(&timeb, &b->crl->crl.lastUpdate); } if (SECSuccess == rv) { if (timea > timeb) { return 1; /* a is better than b */ } if (timea < timeb) { return -1; /* a is not as good as b */ } } /* if they are equal, or if all else fails, use pointer differences */ PORT_Assert(a != b); /* they should never be equal */ return a > b ? 1 : -1; } /* callback for qsort to sort a set of disparate CRLs, some of which are invalid DER or failed signature check. Validated CRLs are differentiated by thisUpdate . Validated CRLs are preferred over non-validated CRLs . Proper DER CRLs are preferred over non-DER data . */ static int SortImperfectCRLs(const void* arg1, const void* arg2) { CachedCrl *a, *b; a = *(CachedCrl**)arg1; b = *(CachedCrl**)arg2; if (!a || !b) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); PORT_Assert(0); } else { PRBool aDecoded = PR_FALSE, bDecoded = PR_FALSE; if ((PR_TRUE == a->sigValid) && (PR_TRUE == b->sigValid)) { /* both CRLs have been validated, choose the latest one */ return SortCRLsByThisUpdate(arg1, arg2); } if (PR_TRUE == a->sigValid) { return 1; /* a is greater than b */ } if (PR_TRUE == b->sigValid) { return -1; /* a is not as good as b */ } aDecoded = GetOpaqueCRLFields(a->crl)->decodingError; bDecoded = GetOpaqueCRLFields(b->crl)->decodingError; /* neither CRL had its signature check pass */ if ((PR_FALSE == aDecoded) && (PR_FALSE == bDecoded)) { /* both CRLs are proper DER, choose the latest one */ return SortCRLsByThisUpdate(arg1, arg2); } if (PR_FALSE == aDecoded) { return 1; /* a is better than b */ } if (PR_FALSE == bDecoded) { return -1; /* a is not as good as b */ } /* both are invalid DER. sigh. */ } /* if they are equal, or if all else fails, use pointer differences */ PORT_Assert(a != b); /* they should never be equal */ return a > b ? 1 : -1; } /* Pick best CRL to use . needs write access */ static SECStatus DPCache_SelectCRL(CRLDPCache* cache) { PRUint32 i; PRBool valid = PR_TRUE; CachedCrl* selected = NULL; PORT_Assert(cache); if (!cache) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } /* if any invalid CRL is present, then the CRL cache is considered invalid, for security reasons */ for (i = 0; i < cache->ncrls; i++) { if (!cache->crls[i] || !cache->crls[i]->sigChecked || !cache->crls[i]->sigValid) { valid = PR_FALSE; break; } } if (PR_TRUE == valid) { /* all CRLs are valid, clear this error */ cache->invalid &= (~CRL_CACHE_INVALID_CRLS); } else { /* some CRLs are invalid, set this error */ cache->invalid |= CRL_CACHE_INVALID_CRLS; } if (cache->invalid) { /* cache is in an invalid state, so reset it */ if (cache->selected) { cache->selected = NULL; } /* also sort the CRLs imperfectly */ qsort(cache->crls, cache->ncrls, sizeof(CachedCrl*), SortImperfectCRLs); return SECSuccess; } if (cache->ncrls) { /* all CRLs are good, sort them by thisUpdate */ qsort(cache->crls, cache->ncrls, sizeof(CachedCrl*), SortCRLsByThisUpdate); /* pick the newest CRL */ selected = cache->crls[cache->ncrls - 1]; /* and populate the cache */ if (SECSuccess != CachedCrl_Populate(selected)) { return SECFailure; } } cache->selected = selected; return SECSuccess; } /* initialize a DPCache object */ static SECStatus DPCache_Create(CRLDPCache** returned, CERTCertificate* issuer, const SECItem* subject, SECItem* dp) { CRLDPCache* cache = NULL; PORT_Assert(returned); /* issuer and dp are allowed to be NULL */ if (!returned || !subject) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } *returned = NULL; cache = PORT_ZAlloc(sizeof(CRLDPCache)); if (!cache) { return SECFailure; } #ifdef DPC_RWLOCK cache->lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL); #else cache->lock = PR_NewLock(); #endif if (!cache->lock) { PORT_Free(cache); return SECFailure; } if (issuer) { cache->dbHandle = issuer->dbhandle; cache->issuerDERCert = SECITEM_DupItem(&issuer->derCert); } cache->distributionPoint = SECITEM_DupItem(dp); cache->subject = SECITEM_DupItem(subject); cache->lastfetch = 0; cache->lastcheck = 0; *returned = cache; return SECSuccess; } /* create an issuer cache object (per CA subject ) */ static SECStatus IssuerCache_Create(CRLIssuerCache** returned, CERTCertificate* issuer, const SECItem* subject, const SECItem* dp) { SECStatus rv = SECSuccess; CRLIssuerCache* cache = NULL; PORT_Assert(returned); PORT_Assert(subject); /* issuer and dp are allowed to be NULL */ if (!returned || !subject) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } *returned = NULL; cache = (CRLIssuerCache*)PORT_ZAlloc(sizeof(CRLIssuerCache)); if (!cache) { return SECFailure; } cache->subject = SECITEM_DupItem(subject); #ifdef XCRL cache->lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL); if (!cache->lock) { rv = SECFailure; } if (SECSuccess == rv && issuer) { cache->issuer = CERT_DupCertificate(issuer); if (!cache->issuer) { rv = SECFailure; } } #endif if (SECSuccess != rv) { PORT_Assert(SECSuccess == IssuerCache_Destroy(cache)); return SECFailure; } *returned = cache; return SECSuccess; } /* add a DPCache to the issuer cache */ static SECStatus IssuerCache_AddDP(CRLIssuerCache* cache, CERTCertificate* issuer, const SECItem* subject, const SECItem* dp, CRLDPCache** newdpc) { /* now create the required DP cache object */ if (!cache || !subject || !newdpc) { PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } if (!dp) { /* default distribution point */ SECStatus rv = DPCache_Create(&cache->dpp, issuer, subject, NULL); if (SECSuccess == rv) { *newdpc = cache->dpp; return SECSuccess; } } else { /* we should never hit this until we support multiple DPs */ PORT_Assert(dp); /* XCRL allocate a new distribution point cache object, initialize it, and add it to the hash table of DPs */ } return SECFailure; } /* add an IssuerCache to the global hash table of issuers */ static SECStatus CRLCache_AddIssuer(CRLIssuerCache* issuer) { PORT_Assert(issuer); PORT_Assert(crlcache.issuers); if (!issuer || !crlcache.issuers) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } if (NULL == PL_HashTableAdd(crlcache.issuers, (void*)issuer->subject, (void*)issuer)) { return SECFailure; } return SECSuccess; } /* retrieve the issuer cache object for a given issuer subject */ static SECStatus CRLCache_GetIssuerCache(CRLCache* cache, const SECItem* subject, CRLIssuerCache** returned) { /* we need to look up the issuer in the hash table */ SECStatus rv = SECSuccess; PORT_Assert(cache); PORT_Assert(subject); PORT_Assert(returned); PORT_Assert(crlcache.issuers); if (!cache || !subject || !returned || !crlcache.issuers) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); rv = SECFailure; } if (SECSuccess == rv) { *returned = (CRLIssuerCache*)PL_HashTableLookup(crlcache.issuers, (void*)subject); } return rv; } /* retrieve the full CRL object that best matches the content of a DPCache */ static CERTSignedCrl* GetBestCRL(CRLDPCache* cache, PRBool entries) { CachedCrl* acrl = NULL; PORT_Assert(cache); if (!cache) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return NULL; } if (0 == cache->ncrls) { /* empty cache*/ PORT_SetError(SEC_ERROR_CRL_NOT_FOUND); return NULL; } /* if we have a valid full CRL selected, return it */ if (cache->selected) { return SEC_DupCrl(cache->selected->crl); } /* otherwise, use latest valid DER CRL */ acrl = cache->crls[cache->ncrls - 1]; if (acrl && (PR_FALSE == GetOpaqueCRLFields(acrl->crl)->decodingError)) { SECStatus rv = SECSuccess; if (PR_TRUE == entries) { rv = CERT_CompleteCRLDecodeEntries(acrl->crl); } if (SECSuccess == rv) { return SEC_DupCrl(acrl->crl); } } PORT_SetError(SEC_ERROR_CRL_NOT_FOUND); return NULL; } /* get a particular DPCache object from an IssuerCache */ static CRLDPCache* IssuerCache_GetDPCache(CRLIssuerCache* cache, const SECItem* dp) { CRLDPCache* dpp = NULL; PORT_Assert(cache); /* XCRL for now we only support the "default" DP, ie. the full CRL. So we can return the global one without locking. In the future we will have a lock */ PORT_Assert(NULL == dp); if (!cache || dp) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return NULL; } #ifdef XCRL NSSRWLock_LockRead(cache->lock); #endif dpp = cache->dpp; #ifdef XCRL NSSRWLock_UnlockRead(cache->lock); #endif return dpp; } /* get a DPCache object for the given issuer subject and dp Automatically creates the cache object if it doesn't exist yet. */ SECStatus AcquireDPCache(CERTCertificate* issuer, const SECItem* subject, const SECItem* dp, PRTime t, void* wincx, CRLDPCache** dpcache, PRBool* writeLocked) { SECStatus rv = SECSuccess; CRLIssuerCache* issuercache = NULL; #ifdef GLOBAL_RWLOCK PRBool globalwrite = PR_FALSE; #endif PORT_Assert(crlcache.lock); if (!crlcache.lock) { /* CRL cache is not initialized */ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } #ifdef GLOBAL_RWLOCK NSSRWLock_LockRead(crlcache.lock); #else PR_Lock(crlcache.lock); #endif rv = CRLCache_GetIssuerCache(&crlcache, subject, &issuercache); if (SECSuccess != rv) { #ifdef GLOBAL_RWLOCK NSSRWLock_UnlockRead(crlcache.lock); #else PR_Unlock(crlcache.lock); #endif PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } if (!issuercache) { /* there is no cache for this issuer yet. This means this is the first time we look up a cert from that issuer, and we need to create the cache. */ rv = IssuerCache_Create(&issuercache, issuer, subject, dp); if (SECSuccess == rv && !issuercache) { PORT_Assert(issuercache); rv = SECFailure; } if (SECSuccess == rv) { /* This is the first time we look up a cert of this issuer. Create the DPCache for this DP . */ rv = IssuerCache_AddDP(issuercache, issuer, subject, dp, dpcache); } if (SECSuccess == rv) { /* lock the DPCache for write to ensure the update happens in this thread */ *writeLocked = PR_TRUE; #ifdef DPC_RWLOCK NSSRWLock_LockWrite((*dpcache)->lock); #else PR_Lock((*dpcache)->lock); #endif } if (SECSuccess == rv) { /* now add the new issuer cache to the global hash table of issuers */ #ifdef GLOBAL_RWLOCK CRLIssuerCache* existing = NULL; NSSRWLock_UnlockRead(crlcache.lock); /* when using a r/w lock for the global cache, check if the issuer already exists before adding to the hash table */ NSSRWLock_LockWrite(crlcache.lock); globalwrite = PR_TRUE; rv = CRLCache_GetIssuerCache(&crlcache, subject, &existing); if (!existing) { #endif rv = CRLCache_AddIssuer(issuercache); if (SECSuccess != rv) { /* failure */ rv = SECFailure; } #ifdef GLOBAL_RWLOCK } else { /* somebody else updated before we did */ IssuerCache_Destroy(issuercache); /* destroy the new object */ issuercache = existing; /* use the existing one */ *dpcache = IssuerCache_GetDPCache(issuercache, dp); } #endif } /* now unlock the global cache. We only want to lock the issuer hash table addition. Holding it longer would hurt scalability */ #ifdef GLOBAL_RWLOCK if (PR_TRUE == globalwrite) { NSSRWLock_UnlockWrite(crlcache.lock); globalwrite = PR_FALSE; } else { NSSRWLock_UnlockRead(crlcache.lock); } #else PR_Unlock(crlcache.lock); #endif /* if there was a failure adding an issuer cache object, destroy it */ if (SECSuccess != rv && issuercache) { if (PR_TRUE == *writeLocked) { #ifdef DPC_RWLOCK NSSRWLock_UnlockWrite((*dpcache)->lock); #else PR_Unlock((*dpcache)->lock); #endif } IssuerCache_Destroy(issuercache); issuercache = NULL; } if (SECSuccess != rv) { return SECFailure; } } else { #ifdef GLOBAL_RWLOCK NSSRWLock_UnlockRead(crlcache.lock); #else PR_Unlock(crlcache.lock); #endif *dpcache = IssuerCache_GetDPCache(issuercache, dp); } /* we now have a DPCache that we can use for lookups */ /* lock it for read, unless we already locked for write */ if (PR_FALSE == *writeLocked) { #ifdef DPC_RWLOCK NSSRWLock_LockRead((*dpcache)->lock); #else PR_Lock((*dpcache)->lock); #endif } if (SECSuccess == rv) { /* currently there is always one and only one DPCache per issuer */ PORT_Assert(*dpcache); if (*dpcache) { /* make sure the DP cache is up to date before using it */ rv = DPCache_GetUpToDate(*dpcache, issuer, PR_FALSE == *writeLocked, t, wincx); } else { rv = SECFailure; } } return rv; } /* unlock access to the DPCache */ void ReleaseDPCache(CRLDPCache* dpcache, PRBool writeLocked) { if (!dpcache) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return; } #ifdef DPC_RWLOCK if (PR_TRUE == writeLocked) { NSSRWLock_UnlockWrite(dpcache->lock); } else { NSSRWLock_UnlockRead(dpcache->lock); } #else PR_Unlock(dpcache->lock); #endif } SECStatus cert_CheckCertRevocationStatus(CERTCertificate* cert, CERTCertificate* issuer, const SECItem* dp, PRTime t, void* wincx, CERTRevocationStatus* revStatus, CERTCRLEntryReasonCode* revReason) { PRBool lockedwrite = PR_FALSE; SECStatus rv = SECSuccess; CRLDPCache* dpcache = NULL; CERTRevocationStatus status = certRevocationStatusRevoked; CERTCRLEntryReasonCode reason = crlEntryReasonUnspecified; CERTCrlEntry* entry = NULL; dpcacheStatus ds; if (!cert || !issuer) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } if (revStatus) { *revStatus = status; } if (revReason) { *revReason = reason; } if (t && secCertTimeValid != CERT_CheckCertValidTimes(issuer, t, PR_FALSE)) { /* we won't be able to check the CRL's signature if the issuer cert is expired as of the time we are verifying. This may cause a valid CRL to be cached as bad. short-circuit to avoid this case. */ PORT_SetError(SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE); return SECFailure; } rv = AcquireDPCache(issuer, &issuer->derSubject, dp, t, wincx, &dpcache, &lockedwrite); PORT_Assert(SECSuccess == rv); if (SECSuccess != rv) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } /* now look up the certificate SN in the DP cache's CRL */ ds = DPCache_Lookup(dpcache, &cert->serialNumber, &entry); switch (ds) { case dpcacheFoundEntry: PORT_Assert(entry); /* check the time if we have one */ if (entry->revocationDate.data && entry->revocationDate.len) { PRTime revocationDate = 0; if (SECSuccess == DER_DecodeTimeChoice(&revocationDate, &entry->revocationDate)) { /* we got a good revocation date, only consider the certificate revoked if the time we are inquiring about is past the revocation date */ if (t >= revocationDate) { rv = SECFailure; } else { status = certRevocationStatusValid; } } else { /* invalid revocation date, consider the certificate permanently revoked */ rv = SECFailure; } } else { /* no revocation date, certificate is permanently revoked */ rv = SECFailure; } if (SECFailure == rv) { (void)CERT_FindCRLEntryReasonExten(entry, &reason); PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE); } break; case dpcacheEmpty: /* useful for NIST policy */ status = certRevocationStatusUnknown; break; case dpcacheNoEntry: status = certRevocationStatusValid; break; case dpcacheInvalidCacheError: /* treat it as unknown and let the caller decide based on the policy */ status = certRevocationStatusUnknown; break; default: /* leave status as revoked */ break; } ReleaseDPCache(dpcache, lockedwrite); if (revStatus) { *revStatus = status; } if (revReason) { *revReason = reason; } return rv; } /* check CRL revocation status of given certificate and issuer */ SECStatus CERT_CheckCRL(CERTCertificate* cert, CERTCertificate* issuer, const SECItem* dp, PRTime t, void* wincx) { return cert_CheckCertRevocationStatus(cert, issuer, dp, t, wincx, NULL, NULL); } /* retrieve full CRL object that best matches the cache status */ CERTSignedCrl* SEC_FindCrlByName(CERTCertDBHandle* handle, SECItem* crlKey, int type) { CERTSignedCrl* acrl = NULL; CRLDPCache* dpcache = NULL; SECStatus rv = SECSuccess; PRBool writeLocked = PR_FALSE; if (!crlKey) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return NULL; } rv = AcquireDPCache(NULL, crlKey, NULL, 0, NULL, &dpcache, &writeLocked); if (SECSuccess == rv) { acrl = GetBestCRL(dpcache, PR_TRUE); /* decode entries, because SEC_FindCrlByName always returned fully decoded CRLs in the past */ ReleaseDPCache(dpcache, writeLocked); } return acrl; } /* invalidate the CRL cache for a given issuer, which forces a refetch of CRL objects from PKCS#11 tokens */ void CERT_CRLCacheRefreshIssuer(CERTCertDBHandle* dbhandle, SECItem* crlKey) { CRLDPCache* cache = NULL; SECStatus rv = SECSuccess; PRBool writeLocked = PR_FALSE; PRBool readlocked; (void)dbhandle; /* silence compiler warnings */ /* XCRL we will need to refresh all the DPs of the issuer in the future, not just the default one */ rv = AcquireDPCache(NULL, crlKey, NULL, 0, NULL, &cache, &writeLocked); if (SECSuccess != rv) { return; } /* we need to invalidate the DPCache here */ readlocked = (writeLocked == PR_TRUE ? PR_FALSE : PR_TRUE); DPCache_LockWrite(); cache->refresh = PR_TRUE; DPCache_UnlockWrite(); ReleaseDPCache(cache, writeLocked); return; } /* add the specified RAM CRL object to the cache */ SECStatus CERT_CacheCRL(CERTCertDBHandle* dbhandle, SECItem* newdercrl) { CRLDPCache* cache = NULL; SECStatus rv = SECSuccess; PRBool writeLocked = PR_FALSE; PRBool readlocked; CachedCrl* returned = NULL; PRBool added = PR_FALSE; CERTSignedCrl* newcrl = NULL; int realerror = 0; if (!dbhandle || !newdercrl) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } /* first decode the DER CRL to make sure it's OK */ newcrl = CERT_DecodeDERCrlWithFlags(NULL, newdercrl, SEC_CRL_TYPE, CRL_DECODE_DONT_COPY_DER | CRL_DECODE_SKIP_ENTRIES); if (!newcrl) { return SECFailure; } /* XXX check if it has IDP extension. If so, do not proceed and set error */ rv = AcquireDPCache(NULL, &newcrl->crl.derName, NULL, 0, NULL, &cache, &writeLocked); if (SECSuccess == rv) { readlocked = (writeLocked == PR_TRUE ? PR_FALSE : PR_TRUE); rv = CachedCrl_Create(&returned, newcrl, CRL_OriginExplicit); if (SECSuccess == rv && returned) { DPCache_LockWrite(); rv = DPCache_AddCRL(cache, returned, &added); if (PR_TRUE != added) { realerror = PORT_GetError(); CachedCrl_Destroy(returned); returned = NULL; } DPCache_UnlockWrite(); } ReleaseDPCache(cache, writeLocked); if (!added) { rv = SECFailure; } } SEC_DestroyCrl(newcrl); /* free the CRL. Either it got added to the cache and the refcount got bumped, or not, and thus we need to free its RAM */ if (realerror) { PORT_SetError(realerror); } return rv; } /* remove the specified RAM CRL object from the cache */ SECStatus CERT_UncacheCRL(CERTCertDBHandle* dbhandle, SECItem* olddercrl) { CRLDPCache* cache = NULL; SECStatus rv = SECSuccess; PRBool writeLocked = PR_FALSE; PRBool readlocked; PRBool removed = PR_FALSE; PRUint32 i; CERTSignedCrl* oldcrl = NULL; if (!dbhandle || !olddercrl) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } /* first decode the DER CRL to make sure it's OK */ oldcrl = CERT_DecodeDERCrlWithFlags(NULL, olddercrl, SEC_CRL_TYPE, CRL_DECODE_DONT_COPY_DER | CRL_DECODE_SKIP_ENTRIES); if (!oldcrl) { /* if this DER CRL can't decode, it can't be in the cache */ return SECFailure; } rv = AcquireDPCache(NULL, &oldcrl->crl.derName, NULL, 0, NULL, &cache, &writeLocked); if (SECSuccess == rv) { CachedCrl* returned = NULL; readlocked = (writeLocked == PR_TRUE ? PR_FALSE : PR_TRUE); rv = CachedCrl_Create(&returned, oldcrl, CRL_OriginExplicit); if (SECSuccess == rv && returned) { DPCache_LockWrite(); for (i = 0; i < cache->ncrls; i++) { PRBool dupe = PR_FALSE, updated = PR_FALSE; rv = CachedCrl_Compare(returned, cache->crls[i], &dupe, &updated); if (SECSuccess != rv) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); break; } if (PR_TRUE == dupe) { rv = DPCache_RemoveCRL(cache, i); /* got a match */ if (SECSuccess == rv) { cache->mustchoose = PR_TRUE; removed = PR_TRUE; } break; } } DPCache_UnlockWrite(); if (SECSuccess != CachedCrl_Destroy(returned)) { rv = SECFailure; } } ReleaseDPCache(cache, writeLocked); } if (SECSuccess != SEC_DestroyCrl(oldcrl)) { /* need to do this because object is refcounted */ rv = SECFailure; } if (SECSuccess == rv && PR_TRUE != removed) { PORT_SetError(SEC_ERROR_CRL_NOT_FOUND); } return rv; } SECStatus cert_AcquireNamedCRLCache(NamedCRLCache** returned) { PORT_Assert(returned); if (!namedCRLCache.lock) { PORT_Assert(0); return SECFailure; } PR_Lock(namedCRLCache.lock); *returned = &namedCRLCache; return SECSuccess; } /* This must be called only while cache is acquired, and the entry is only * valid until cache is released. */ SECStatus cert_FindCRLByGeneralName(NamedCRLCache* ncc, const SECItem* canonicalizedName, NamedCRLCacheEntry** retEntry) { if (!ncc || !canonicalizedName || !retEntry) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } *retEntry = (NamedCRLCacheEntry*)PL_HashTableLookup( namedCRLCache.entries, (void*)canonicalizedName); return SECSuccess; } SECStatus cert_ReleaseNamedCRLCache(NamedCRLCache* ncc) { if (!ncc) { return SECFailure; } if (!ncc->lock) { PORT_Assert(0); return SECFailure; } PR_Unlock(namedCRLCache.lock); return SECSuccess; } /* creates new named cache entry from CRL, and tries to add it to CRL cache */ static SECStatus addCRLToCache(CERTCertDBHandle* dbhandle, SECItem* crl, const SECItem* canonicalizedName, NamedCRLCacheEntry** newEntry) { SECStatus rv = SECSuccess; NamedCRLCacheEntry* entry = NULL; /* create new named entry */ if (SECSuccess != NamedCRLCacheEntry_Create(newEntry) || !*newEntry) { /* no need to keep unused CRL around */ SECITEM_ZfreeItem(crl, PR_TRUE); return SECFailure; } entry = *newEntry; entry->crl = crl; /* named CRL cache owns DER */ entry->lastAttemptTime = PR_Now(); entry->canonicalizedName = SECITEM_DupItem(canonicalizedName); if (!entry->canonicalizedName) { rv = NamedCRLCacheEntry_Destroy(entry); /* destroys CRL too */ PORT_Assert(SECSuccess == rv); return SECFailure; } /* now, attempt to insert CRL into CRL cache */ if (SECSuccess == CERT_CacheCRL(dbhandle, entry->crl)) { entry->inCRLCache = PR_TRUE; entry->successfulInsertionTime = entry->lastAttemptTime; } else { switch (PR_GetError()) { case SEC_ERROR_CRL_ALREADY_EXISTS: entry->dupe = PR_TRUE; break; case SEC_ERROR_BAD_DER: entry->badDER = PR_TRUE; break; /* all other reasons */ default: entry->unsupported = PR_TRUE; break; } rv = SECFailure; /* no need to keep unused CRL around */ SECITEM_ZfreeItem(entry->crl, PR_TRUE); entry->crl = NULL; } return rv; } /* take ownership of CRL, and insert it into the named CRL cache * and indexed CRL cache */ SECStatus cert_CacheCRLByGeneralName(CERTCertDBHandle* dbhandle, SECItem* crl, const SECItem* canonicalizedName) { NamedCRLCacheEntry *oldEntry, *newEntry = NULL; NamedCRLCache* ncc = NULL; SECStatus rv = SECSuccess; PORT_Assert(namedCRLCache.lock); PORT_Assert(namedCRLCache.entries); if (!crl || !canonicalizedName) { PORT_Assert(0); PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = cert_AcquireNamedCRLCache(&ncc); PORT_Assert(SECSuccess == rv); if (SECSuccess != rv) { SECITEM_ZfreeItem(crl, PR_TRUE); return SECFailure; } rv = cert_FindCRLByGeneralName(ncc, canonicalizedName, &oldEntry); PORT_Assert(SECSuccess == rv); if (SECSuccess != rv) { (void)cert_ReleaseNamedCRLCache(ncc); SECITEM_ZfreeItem(crl, PR_TRUE); return SECFailure; } if (SECSuccess == addCRLToCache(dbhandle, crl, canonicalizedName, &newEntry)) { if (!oldEntry) { /* add new good entry to the hash table */ if (NULL == PL_HashTableAdd(namedCRLCache.entries, (void*)newEntry->canonicalizedName, (void*)newEntry)) { PORT_Assert(0); NamedCRLCacheEntry_Destroy(newEntry); rv = SECFailure; } } else { PRBool removed; /* remove the old CRL from the cache if needed */ if (oldEntry->inCRLCache) { rv = CERT_UncacheCRL(dbhandle, oldEntry->crl); PORT_Assert(SECSuccess == rv); } removed = PL_HashTableRemove(namedCRLCache.entries, (void*)oldEntry->canonicalizedName); PORT_Assert(removed); if (!removed) { rv = SECFailure; /* leak old entry since we couldn't remove it from the hash * table */ } else { PORT_CheckSuccess(NamedCRLCacheEntry_Destroy(oldEntry)); } if (NULL == PL_HashTableAdd(namedCRLCache.entries, (void*)newEntry->canonicalizedName, (void*)newEntry)) { PORT_Assert(0); rv = SECFailure; } } } else { /* error adding new CRL to cache */ if (!oldEntry) { /* no old cache entry, use the new one even though it's bad */ if (NULL == PL_HashTableAdd(namedCRLCache.entries, (void*)newEntry->canonicalizedName, (void*)newEntry)) { PORT_Assert(0); rv = SECFailure; } } else { if (oldEntry->inCRLCache) { /* previous cache entry was good, keep it and update time */ oldEntry->lastAttemptTime = newEntry->lastAttemptTime; /* throw away new bad entry */ rv = NamedCRLCacheEntry_Destroy(newEntry); PORT_Assert(SECSuccess == rv); } else { /* previous cache entry was bad, just replace it */ PRBool removed = PL_HashTableRemove( namedCRLCache.entries, (void*)oldEntry->canonicalizedName); PORT_Assert(removed); if (!removed) { /* leak old entry since we couldn't remove it from the hash * table */ rv = SECFailure; } else { PORT_CheckSuccess(NamedCRLCacheEntry_Destroy(oldEntry)); } if (NULL == PL_HashTableAdd(namedCRLCache.entries, (void*)newEntry->canonicalizedName, (void*)newEntry)) { PORT_Assert(0); rv = SECFailure; } } } } PORT_CheckSuccess(cert_ReleaseNamedCRLCache(ncc)); return rv; } static SECStatus CachedCrl_Create(CachedCrl** returned, CERTSignedCrl* crl, CRLOrigin origin) { CachedCrl* newcrl = NULL; if (!returned) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } newcrl = PORT_ZAlloc(sizeof(CachedCrl)); if (!newcrl) { return SECFailure; } newcrl->crl = SEC_DupCrl(crl); newcrl->origin = origin; *returned = newcrl; return SECSuccess; } /* empty the cache content */ static SECStatus CachedCrl_Depopulate(CachedCrl* crl) { if (!crl) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } /* destroy the hash table */ if (crl->entries) { PL_HashTableDestroy(crl->entries); crl->entries = NULL; } /* free the pre buffer */ if (crl->prebuffer) { PreAllocator_Destroy(crl->prebuffer); crl->prebuffer = NULL; } return SECSuccess; } static SECStatus CachedCrl_Destroy(CachedCrl* crl) { if (!crl) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } CachedCrl_Depopulate(crl); SEC_DestroyCrl(crl->crl); PORT_Free(crl); return SECSuccess; } /* create hash table of CRL entries */ static SECStatus CachedCrl_Populate(CachedCrl* crlobject) { SECStatus rv = SECFailure; CERTCrlEntry** crlEntry = NULL; PRUint32 numEntries = 0; if (!crlobject) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } /* complete the entry decoding . XXX thread-safety of CRL object */ rv = CERT_CompleteCRLDecodeEntries(crlobject->crl); if (SECSuccess != rv) { crlobject->unbuildable = PR_TRUE; /* don't try to build this again */ return SECFailure; } if (crlobject->entries && crlobject->prebuffer) { /* cache is already built */ return SECSuccess; } /* build the hash table from the full CRL */ /* count CRL entries so we can pre-allocate space for hash table entries */ for (crlEntry = crlobject->crl->crl.entries; crlEntry && *crlEntry; crlEntry++) { numEntries++; } crlobject->prebuffer = PreAllocator_Create(numEntries * sizeof(PLHashEntry)); PORT_Assert(crlobject->prebuffer); if (!crlobject->prebuffer) { return SECFailure; } /* create a new hash table */ crlobject->entries = PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare, PL_CompareValues, &preAllocOps, crlobject->prebuffer); PORT_Assert(crlobject->entries); if (!crlobject->entries) { return SECFailure; } /* add all serial numbers to the hash table */ for (crlEntry = crlobject->crl->crl.entries; crlEntry && *crlEntry; crlEntry++) { PL_HashTableAdd(crlobject->entries, &(*crlEntry)->serialNumber, *crlEntry); } return SECSuccess; } /* returns true if there are CRLs from PKCS#11 slots */ static PRBool DPCache_HasTokenCRLs(CRLDPCache* cache) { PRBool answer = PR_FALSE; PRUint32 i; for (i = 0; i < cache->ncrls; i++) { if (cache->crls[i] && (CRL_OriginToken == cache->crls[i]->origin)) { answer = PR_TRUE; break; } } return answer; } /* are these CRLs the same, as far as the cache is concerned ? */ /* are these CRLs the same token object but with different DER ? This can happen if the DER CRL got updated in the token, but the PKCS#11 object ID did not change. NSS softoken has the unfortunate property to never change the object ID for CRL objects. */ static SECStatus CachedCrl_Compare(CachedCrl* a, CachedCrl* b, PRBool* isDupe, PRBool* isUpdated) { PORT_Assert(a); PORT_Assert(b); PORT_Assert(isDupe); PORT_Assert(isUpdated); if (!a || !b || !isDupe || !isUpdated || !a->crl || !b->crl) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } *isDupe = *isUpdated = PR_FALSE; if (a == b) { /* dupe */ *isDupe = PR_TRUE; *isUpdated = PR_FALSE; return SECSuccess; } if (b->origin != a->origin) { /* CRLs of different origins are not considered dupes, and can't be updated either */ return SECSuccess; } if (CRL_OriginToken == b->origin) { /* for token CRLs, slot and PKCS#11 object handle must match for CRL to truly be a dupe */ if ((b->crl->slot == a->crl->slot) && (b->crl->pkcs11ID == a->crl->pkcs11ID)) { /* ASN.1 DER needs to match for dupe check */ /* could optimize by just checking a few fields like thisUpdate */ if (SECEqual == SECITEM_CompareItem(b->crl->derCrl, a->crl->derCrl)) { *isDupe = PR_TRUE; } else { *isUpdated = PR_TRUE; } } return SECSuccess; } if (CRL_OriginExplicit == b->origin) { /* We need to make sure this is the same object that the user provided to CERT_CacheCRL previously. That API takes a SECItem*, thus, we just do a pointer comparison here. */ if (b->crl->derCrl == a->crl->derCrl) { *isDupe = PR_TRUE; } } return SECSuccess; }