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