зеркало из https://github.com/mozilla/pjs.git
3561 строка
102 KiB
C
3561 строка
102 KiB
C
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is the Netscape security libraries.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1994-2000
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
/*
|
|
* Moved from secpkcs7.c
|
|
*
|
|
* $Id: crl.c,v 1.68 2009/08/10 22:25:44 julien.pierre.boogz%sun.com Exp $
|
|
*/
|
|
|
|
#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, especially the Krl/FORTEZZA ones, 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 }
|
|
};
|
|
|
|
static const SEC_ASN1Template cert_KrlEntryTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE,
|
|
0, NULL, sizeof(CERTCrlEntry) },
|
|
{ SEC_ASN1_OCTET_STRING,
|
|
offsetof(CERTCrlEntry,serialNumber) },
|
|
{ SEC_ASN1_UTC_TIME,
|
|
offsetof(CERTCrlEntry,revocationDate) },
|
|
{ 0 }
|
|
};
|
|
|
|
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
|
|
SEC_ASN1_MKSUB(CERT_TimeChoiceTemplate)
|
|
|
|
static const SEC_ASN1Template cert_KrlTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE,
|
|
0, NULL, sizeof(CERTCrl) },
|
|
{ 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_UTC_TIME,
|
|
offsetof(CERTCrl,lastUpdate) },
|
|
{ SEC_ASN1_UTC_TIME,
|
|
offsetof(CERTCrl,nextUpdate) },
|
|
{ SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF,
|
|
offsetof(CERTCrl,entries),
|
|
cert_KrlEntryTemplate },
|
|
{ 0 }
|
|
};
|
|
|
|
static const SEC_ASN1Template cert_SignedKrlTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE,
|
|
0, NULL, sizeof(CERTSignedCrl) },
|
|
{ SEC_ASN1_SAVE,
|
|
offsetof(CERTSignedCrl,signatureWrap.data) },
|
|
{ SEC_ASN1_INLINE,
|
|
offsetof(CERTSignedCrl,crl),
|
|
cert_KrlTemplate },
|
|
{ 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_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(PRArenaPool *arena, SECItem *derCrl, SECItem *key)
|
|
{
|
|
SECStatus rv;
|
|
CERTSignedData sd;
|
|
CERTCrlKey crlkey;
|
|
PRArenaPool* 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 or KRL and decode it into a CRL structure
|
|
* allow reusing the input DER without making a copy
|
|
*/
|
|
CERTSignedCrl *
|
|
CERT_DecodeDERCrlWithFlags(PRArenaPool *narena, SECItem *derSignedCrl,
|
|
int type, PRInt32 options)
|
|
{
|
|
PRArenaPool *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;
|
|
|
|
case SEC_KRL_TYPE:
|
|
rv = SEC_QuickDERDecodeItem
|
|
(arena, crl, cert_SignedKrlTemplate, derSignedCrl);
|
|
break;
|
|
default:
|
|
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 or KRL and decode it into a CRL structure
|
|
*/
|
|
CERTSignedCrl *
|
|
CERT_DecodeDERCrl(PRArenaPool *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;
|
|
SECStatus rv;
|
|
|
|
PORT_Assert(newCrl);
|
|
PORT_Assert(derCrl);
|
|
|
|
/* we can't use the cache here because we must look in the same
|
|
token */
|
|
rv = 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)) {
|
|
|
|
if (type == SEC_CRL_TYPE) {
|
|
PORT_SetError(SEC_ERROR_OLD_CRL);
|
|
} else {
|
|
PORT_SetError(SEC_ERROR_OLD_KRL);
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
|
|
if ((SECITEM_CompareItem(&newCrl->crl.derName,
|
|
&oldCrl->crl.derName) != SECEqual) &&
|
|
(type == SEC_KRL_TYPE) ) {
|
|
|
|
PORT_SetError(SEC_ERROR_CKL_CONFLICT);
|
|
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)
|
|
{
|
|
PRArenaPool *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_AtomicIncrement(&acrl->referenceCount);
|
|
return acrl;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
SECStatus
|
|
SEC_DestroyCrl(CERTSignedCrl *crl)
|
|
{
|
|
if (crl) {
|
|
if (PR_AtomicDecrement(&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;
|
|
PRArenaPool *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* PreAllocator)
|
|
{
|
|
if (!PreAllocator)
|
|
{
|
|
return;
|
|
}
|
|
if (PreAllocator->arena)
|
|
{
|
|
PORT_FreeArena(PreAllocator->arena, PR_TRUE);
|
|
}
|
|
}
|
|
|
|
/* constructor for PreAllocator object */
|
|
PreAllocator* PreAllocator_Create(PRSize size)
|
|
{
|
|
PRArenaPool* 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->issuer)
|
|
{
|
|
CERT_DestroyCertificate(cache->issuer);
|
|
}
|
|
/* 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_NETSCAPE_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 revoked from now on. The cache
|
|
object will remain in this state until the bad CRL object
|
|
is removed from the token it was fetched from. 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->issuer)
|
|
{
|
|
signstatus = CERT_VerifyCRL(crlobject->crl, cache->issuer, vfdate,
|
|
wincx);
|
|
}
|
|
if (SECSuccess != signstatus)
|
|
{
|
|
if (!cache->issuer)
|
|
{
|
|
/* we tried to verify without an issuer cert . This is
|
|
because this CRL came through a call to SEC_FindCrlByName.
|
|
So, we don't cache this verification failure. We'll try
|
|
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, 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, 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.
|
|
consider all certs revoked as a security measure */
|
|
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->issuer) &&
|
|
(SECSuccess == CERT_CheckCertUsage(issuer, KU_CRL_SIGN)))
|
|
{
|
|
/* if we didn't have a valid issuer cert yet, but we do now. add it */
|
|
DPCache_LockWrite();
|
|
if (!cache->issuer)
|
|
{
|
|
dirty = PR_TRUE;
|
|
cache->issuer = CERT_DupCertificate(issuer);
|
|
}
|
|
DPCache_UnlockWrite();
|
|
}
|
|
|
|
/* verify CRLs that couldn't be checked when inserted into the cache
|
|
because the issuer cert or a verification date was unavailable.
|
|
These are CRLs that were inserted into the cache through
|
|
SEC_FindCrlByName, or through manual insertion, rather than through a
|
|
certificate verification (CERT_CheckCRL) */
|
|
|
|
if (cache->issuer && vfdate )
|
|
{
|
|
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;
|
|
}
|
|
/* all CRLs are good, sort them by thisUpdate */
|
|
qsort(cache->crls, cache->ncrls, sizeof(CachedCrl*),
|
|
SortCRLsByThisUpdate);
|
|
|
|
if (cache->ncrls)
|
|
{
|
|
/* pick the newest CRL */
|
|
selected = cache->crls[cache->ncrls-1];
|
|
|
|
/* and populate the cache */
|
|
if (SECSuccess != CachedCrl_Populate(selected))
|
|
{
|
|
return SECFailure;
|
|
}
|
|
}
|
|
|
|
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->issuer = CERT_DupCertificate(issuer);
|
|
}
|
|
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 && SECSuccess != CERT_CheckCertValidTimes(issuer, t, PR_FALSE))
|
|
{
|
|
/* we won't be able to check the CRL's signature if the issuer cert
|
|
is expired as of the time we are verifying. This may cause a valid
|
|
CRL to be cached as bad. short-circuit to avoid this case. */
|
|
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)
|
|
{
|
|
SECStatus rv2 = 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:
|
|
/* t of zero may have caused the CRL cache to fail to verify
|
|
* a CRL. treat it as unknown */
|
|
if (!t)
|
|
{
|
|
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, rv2;
|
|
|
|
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)
|
|
{
|
|
rv = 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);
|
|
rv2 = NamedCRLCacheEntry_Destroy(newEntry);
|
|
PORT_Assert(SECSuccess == rv2);
|
|
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
|
|
{
|
|
rv2 = NamedCRLCacheEntry_Destroy(oldEntry);
|
|
PORT_Assert(SECSuccess == rv2);
|
|
}
|
|
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
|
|
{
|
|
rv2 = NamedCRLCacheEntry_Destroy(oldEntry);
|
|
PORT_Assert(SECSuccess == rv2);
|
|
}
|
|
if (NULL == PL_HashTableAdd(namedCRLCache.entries,
|
|
(void*) newEntry->canonicalizedName,
|
|
(void*) newEntry))
|
|
{
|
|
PORT_Assert(0);
|
|
rv = SECFailure;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
rv2 = cert_ReleaseNamedCRLCache(ncc);
|
|
PORT_Assert(SECSuccess == rv2);
|
|
|
|
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;
|
|
}
|
|
|
|
/* this function assumes the caller holds a read lock on the DPCache */
|
|
SECStatus DPCache_GetAllCRLs(CRLDPCache* dpc, PRArenaPool* arena,
|
|
CERTSignedCrl*** crls, PRUint16* status)
|
|
{
|
|
CERTSignedCrl** allcrls;
|
|
PRUint32 index;
|
|
if (!dpc || !crls || !status)
|
|
{
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
|
|
*status = dpc->invalid;
|
|
*crls = NULL;
|
|
if (!dpc->ncrls)
|
|
{
|
|
/* no CRLs to return */
|
|
return SECSuccess;
|
|
}
|
|
allcrls = PORT_ArenaZNewArray(arena, CERTSignedCrl*, dpc->ncrls +1);
|
|
if (!allcrls)
|
|
{
|
|
return SECFailure;
|
|
}
|
|
for (index=0; index < dpc->ncrls ; index ++) {
|
|
CachedCrl* cachedcrl = dpc->crls[index];
|
|
if (!cachedcrl || !cachedcrl->crl)
|
|
{
|
|
PORT_Assert(0); /* this should never happen */
|
|
continue;
|
|
}
|
|
allcrls[index] = SEC_DupCrl(cachedcrl->crl);
|
|
}
|
|
*crls = allcrls;
|
|
return SECSuccess;
|
|
}
|
|
|
|
static CachedCrl* DPCache_FindCRL(CRLDPCache* cache, CERTSignedCrl* crl)
|
|
{
|
|
PRUint32 index;
|
|
CachedCrl* cachedcrl = NULL;
|
|
for (index=0; index < cache->ncrls ; index ++) {
|
|
cachedcrl = cache->crls[index];
|
|
if (!cachedcrl || !cachedcrl->crl)
|
|
{
|
|
PORT_Assert(0); /* this should never happen */
|
|
continue;
|
|
}
|
|
if (cachedcrl->crl == crl) {
|
|
break;
|
|
}
|
|
}
|
|
return cachedcrl;
|
|
}
|
|
|
|
/* this function assumes the caller holds a lock on the DPCache */
|
|
SECStatus DPCache_GetCRLEntry(CRLDPCache* cache, PRBool readlocked,
|
|
CERTSignedCrl* crl, SECItem* sn,
|
|
CERTCrlEntry** returned)
|
|
{
|
|
CachedCrl* cachedcrl = NULL;
|
|
if (!cache || !crl || !sn || !returned)
|
|
{
|
|
PORT_Assert(0);
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
*returned = NULL;
|
|
/* first, we need to find the CachedCrl* that matches this CERTSignedCRL */
|
|
cachedcrl = DPCache_FindCRL(cache, crl);
|
|
if (!cachedcrl) {
|
|
PORT_SetError(SEC_ERROR_CRL_NOT_FOUND);
|
|
return SECFailure;
|
|
}
|
|
|
|
if (cachedcrl->unbuildable) {
|
|
/* this CRL could not be fully decoded */
|
|
PORT_SetError(SEC_ERROR_BAD_DER);
|
|
return SECFailure;
|
|
}
|
|
/* now, make sure it has a hash table. Otherwise, we'll need to build one */
|
|
if (!cachedcrl->entries || !cachedcrl->prebuffer) {
|
|
DPCache_LockWrite();
|
|
CachedCrl_Populate(cachedcrl);
|
|
DPCache_UnlockWrite();
|
|
}
|
|
|
|
/* finally, get the CRL entry */
|
|
return CachedCrl_GetEntry(cachedcrl, sn, returned);
|
|
}
|
|
|