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