gecko-dev/security/nss/lib/pki/pkistore.c
2002-09-27 21:23:00 +00:00

730 lines
19 KiB
C

/*
* 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 Netscape are
* Copyright (C) 1994-2000 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU General Public License Version 2 or later (the
* "GPL"), in which case the provisions of the GPL are applicable
* instead of those above. If you wish to allow use of your
* version of this file only under the terms of the GPL and not to
* allow others to use your version of this file under the MPL,
* indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by
* the GPL. If you do not delete the provisions above, a recipient
* may use your version of this file under either the MPL or the
* GPL.
*/
#ifdef DEBUG
static const char CVS_ID[] = "@(#) $RCSfile: pkistore.c,v $ $Revision: 1.20 $ $Date: 2002/09/27 21:23:00 $ $Name: $";
#endif /* DEBUG */
#ifndef PKIM_H
#include "pkim.h"
#endif /* PKIM_H */
#ifndef PKI_H
#include "pki.h"
#endif /* PKI_H */
#ifndef NSSPKI_H
#include "nsspki.h"
#endif /* NSSPKI_H */
#ifndef BASE_H
#include "base.h"
#endif /* BASE_H */
#ifndef PKISTORE_H
#include "pkistore.h"
#endif /* PKISTORE_H */
#ifdef NSS_3_4_CODE
#include "cert.h"
#endif
/*
* Certificate Store
*
* This differs from the cache in that it is a true storage facility. Items
* stay in until they are explicitly removed. It is only used by crypto
* contexts at this time, but may be more generally useful...
*
*/
struct nssCertificateStoreStr
{
PRBool i_alloced_arena;
NSSArena *arena;
PZLock *lock;
nssHash *subject;
nssHash *issuer_and_serial;
};
typedef struct certificate_hash_entry_str certificate_hash_entry;
struct certificate_hash_entry_str
{
NSSCertificate *cert;
NSSTrust *trust;
nssSMIMEProfile *profile;
};
/* XXX This a common function that should be moved out, possibly an
* nssSubjectCertificateList should be created?
*/
/* sort the subject list from newest to oldest */
static PRIntn subject_list_sort(void *v1, void *v2)
{
NSSCertificate *c1 = (NSSCertificate *)v1;
NSSCertificate *c2 = (NSSCertificate *)v2;
nssDecodedCert *dc1 = nssCertificate_GetDecoding(c1);
nssDecodedCert *dc2 = nssCertificate_GetDecoding(c2);
if (dc1->isNewerThan(dc1, dc2)) {
return -1;
} else {
return 1;
}
}
NSS_IMPLEMENT nssCertificateStore *
nssCertificateStore_Create (
NSSArena *arenaOpt
)
{
NSSArena *arena;
nssCertificateStore *store;
PRBool i_alloced_arena;
if (arenaOpt) {
arena = arenaOpt;
i_alloced_arena = PR_FALSE;
} else {
arena = nssArena_Create();
if (!arena) {
return NULL;
}
i_alloced_arena = PR_TRUE;
}
store = nss_ZNEW(arena, nssCertificateStore);
if (!store) {
goto loser;
}
store->lock = PZ_NewLock(nssILockOther);
if (!store->lock) {
goto loser;
}
/* Create the issuer/serial --> {cert, trust, S/MIME profile } hash */
store->issuer_and_serial = nssHash_CreateCertificate(arena, 0);
if (!store->issuer_and_serial) {
goto loser;
}
/* Create the subject DER --> subject list hash */
store->subject = nssHash_CreateItem(arena, 0);
if (!store->subject) {
goto loser;
}
store->arena = arena;
store->i_alloced_arena = i_alloced_arena;
return store;
loser:
if (store) {
if (store->lock) {
PZ_DestroyLock(store->lock);
}
if (store->issuer_and_serial) {
nssHash_Destroy(store->issuer_and_serial);
}
if (store->subject) {
nssHash_Destroy(store->subject);
}
}
if (i_alloced_arena) {
nssArena_Destroy(arena);
}
return NULL;
}
NSS_IMPLEMENT void
nssCertificateStore_Destroy (
nssCertificateStore *store
)
{
PZ_DestroyLock(store->lock);
nssHash_Destroy(store->issuer_and_serial);
nssHash_Destroy(store->subject);
if (store->i_alloced_arena) {
nssArena_Destroy(store->arena);
} else {
nss_ZFreeIf(store);
}
}
static PRStatus
add_certificate_entry (
nssCertificateStore *store,
NSSCertificate *cert
)
{
PRStatus nssrv;
certificate_hash_entry *entry;
entry = nss_ZNEW(cert->object.arena, certificate_hash_entry);
if (!entry) {
return PR_FAILURE;
}
entry->cert = cert;
nssrv = nssHash_Add(store->issuer_and_serial, cert, entry);
if (nssrv != PR_SUCCESS) {
nss_ZFreeIf(entry);
}
return nssrv;
}
static PRStatus
add_subject_entry (
nssCertificateStore *store,
NSSCertificate *cert
)
{
PRStatus nssrv;
nssList *subjectList;
subjectList = (nssList *)nssHash_Lookup(store->subject, &cert->subject);
if (subjectList) {
/* The subject is already in, add this cert to the list */
nssrv = nssList_AddUnique(subjectList, cert);
} else {
/* Create a new subject list for the subject */
subjectList = nssList_Create(NULL, PR_FALSE);
if (!subjectList) {
return PR_FAILURE;
}
nssList_SetSortFunction(subjectList, subject_list_sort);
/* Add the cert entry to this list of subjects */
nssrv = nssList_Add(subjectList, cert);
if (nssrv != PR_SUCCESS) {
return nssrv;
}
/* Add the subject list to the cache */
nssrv = nssHash_Add(store->subject, &cert->subject, subjectList);
}
return nssrv;
}
/* declared below */
static void
remove_certificate_entry (
nssCertificateStore *store,
NSSCertificate *cert
);
NSS_IMPLEMENT PRStatus
nssCertificateStore_Add (
nssCertificateStore *store,
NSSCertificate *cert
)
{
PRStatus nssrv;
PZ_Lock(store->lock);
if (nssHash_Exists(store->issuer_and_serial, cert)) {
PZ_Unlock(store->lock);
return PR_SUCCESS;
}
nssrv = add_certificate_entry(store, cert);
if (nssrv == PR_SUCCESS) {
nssrv = add_subject_entry(store, cert);
if (nssrv == PR_SUCCESS) {
nssCertificate_AddRef(cert); /* obtain a reference for the store */
} else {
remove_certificate_entry(store, cert);
}
}
PZ_Unlock(store->lock);
return nssrv;
}
static void
remove_certificate_entry (
nssCertificateStore *store,
NSSCertificate *cert
)
{
certificate_hash_entry *entry;
entry = (certificate_hash_entry *)
nssHash_Lookup(store->issuer_and_serial, cert);
if (entry) {
nssHash_Remove(store->issuer_and_serial, cert);
if (entry->trust) {
nssTrust_Destroy(entry->trust);
}
if (entry->profile) {
nssSMIMEProfile_Destroy(entry->profile);
}
nss_ZFreeIf(entry);
}
}
static void
remove_subject_entry (
nssCertificateStore *store,
NSSCertificate *cert
)
{
nssList *subjectList;
/* Get the subject list for the cert's subject */
subjectList = (nssList *)nssHash_Lookup(store->subject, &cert->subject);
if (subjectList) {
/* Remove the cert from the subject hash */
nssList_Remove(subjectList, cert);
nssHash_Remove(store->subject, &cert->subject);
if (nssList_Count(subjectList) == 0) {
nssList_Destroy(subjectList);
} else {
/* The cert being released may have keyed the subject entry.
* Since there are still subject certs around, get another and
* rekey the entry just in case.
*/
NSSCertificate *subjectCert;
(void)nssList_GetArray(subjectList, (void **)&subjectCert, 1);
nssHash_Add(store->subject, &subjectCert->subject, subjectList);
}
}
}
NSS_IMPLEMENT void
nssCertificateStore_Remove (
nssCertificateStore *store,
NSSCertificate *cert,
PRBool force /* described in bug 171198 */
)
{
certificate_hash_entry *entry;
PZ_Lock(store->lock);
#ifdef NSS_3_4_CODE
if (!force && cert->object.refCount > 2) {
/* This continues the hack described in CERT_DestroyCertificate.
* Because NSS 3.4 maintains a single, global, crypto context,
* certs must be explicitly removed from it when there are no
* more references to them. This is done by destroying the cert
* when there are two references left, the one being destroyed,
* and the one here (read: temp db).
* However, there is a race condition with timing the removal
* of the cert from the temp store and deleting the last
* reference. In CERT_DestroyCertificate, the refCount is checked,
* and if it is two, a call is made here to remove the temp cert.
* But by the time it gets here (and within the safety of the
* store's lock), another thread could have grabbed a reference
* to it. Removing it now will wreak havoc.
* Therefore, it is necessary to check the refCount again,
* after obtaining the store's lock, to make sure the cert is
* actually ready to be deleted. This check is safe, because
* within the store's lock a cert that has only two references
* *must* have one in the store, and the one being deleted.
* See bug 125263.
*/
PZ_Unlock(store->lock);
return;
}
#endif
entry = (certificate_hash_entry *)
nssHash_Lookup(store->issuer_and_serial, cert);
if (entry && entry->cert == cert) {
remove_certificate_entry(store, cert);
remove_subject_entry(store, cert);
NSSCertificate_Destroy(cert); /* release the store's reference */
}
PZ_Unlock(store->lock);
}
static NSSCertificate **
get_array_from_list (
nssList *certList,
NSSCertificate *rvOpt[],
PRUint32 maximumOpt,
NSSArena *arenaOpt
)
{
PRUint32 count;
NSSCertificate **rvArray = NULL;
count = nssList_Count(certList);
if (count == 0) {
return NULL;
}
if (maximumOpt > 0) {
count = PR_MIN(maximumOpt, count);
}
if (rvOpt) {
nssList_GetArray(certList, (void **)rvOpt, count);
} else {
rvArray = nss_ZNEWARRAY(arenaOpt, NSSCertificate *, count + 1);
if (rvArray) {
nssList_GetArray(certList, (void **)rvArray, count);
}
}
return rvArray;
}
NSS_IMPLEMENT NSSCertificate **
nssCertificateStore_FindCertificatesBySubject (
nssCertificateStore *store,
NSSDER *subject,
NSSCertificate *rvOpt[],
PRUint32 maximumOpt,
NSSArena *arenaOpt
)
{
NSSCertificate **rvArray = NULL;
nssList *subjectList;
PZ_Lock(store->lock);
subjectList = (nssList *)nssHash_Lookup(store->subject, subject);
if (subjectList) {
nssCertificateList_AddReferences(subjectList);
rvArray = get_array_from_list(subjectList,
rvOpt, maximumOpt, arenaOpt);
}
PZ_Unlock(store->lock);
return rvArray;
}
/* Because only subject indexing is implemented, all other lookups require
* full traversal (unfortunately, PLHashTable doesn't allow you to exit
* early from the enumeration). The assumptions are that 1) lookups by
* fields other than subject will be rare, and 2) the hash will not have
* a large number of entries. These assumptions will be tested.
*
* XXX
* For NSS 3.4, it is worth consideration to do all forms of indexing,
* because the only crypto context is global and persistent.
*/
struct nickname_template_str
{
NSSUTF8 *nickname;
nssList *subjectList;
};
static void match_nickname(const void *k, void *v, void *a)
{
PRStatus nssrv;
NSSCertificate *c;
NSSUTF8 *nickname;
nssList *subjectList = (nssList *)v;
struct nickname_template_str *nt = (struct nickname_template_str *)a;
nssrv = nssList_GetArray(subjectList, (void **)&c, 1);
nickname = nssCertificate_GetNickname(c, NULL);
if (nssrv == PR_SUCCESS && nickname &&
nssUTF8_Equal(nickname, nt->nickname, &nssrv))
{
nt->subjectList = subjectList;
}
}
/*
* Find all cached certs with this label.
*/
NSS_IMPLEMENT NSSCertificate **
nssCertificateStore_FindCertificatesByNickname (
nssCertificateStore *store,
NSSUTF8 *nickname,
NSSCertificate *rvOpt[],
PRUint32 maximumOpt,
NSSArena *arenaOpt
)
{
NSSCertificate **rvArray = NULL;
struct nickname_template_str nt;
nt.nickname = nickname;
nt.subjectList = NULL;
PZ_Lock(store->lock);
nssHash_Iterate(store->subject, match_nickname, &nt);
if (nt.subjectList) {
nssCertificateList_AddReferences(nt.subjectList);
rvArray = get_array_from_list(nt.subjectList,
rvOpt, maximumOpt, arenaOpt);
}
PZ_Unlock(store->lock);
return rvArray;
}
struct email_template_str
{
NSSASCII7 *email;
nssList *emailList;
};
static void match_email(const void *k, void *v, void *a)
{
PRStatus nssrv;
NSSCertificate *c;
nssList *subjectList = (nssList *)v;
struct email_template_str *et = (struct email_template_str *)a;
nssrv = nssList_GetArray(subjectList, (void **)&c, 1);
if (nssrv == PR_SUCCESS &&
nssUTF8_Equal(c->email, et->email, &nssrv))
{
nssListIterator *iter = nssList_CreateIterator(subjectList);
if (iter) {
for (c = (NSSCertificate *)nssListIterator_Start(iter);
c != (NSSCertificate *)NULL;
c = (NSSCertificate *)nssListIterator_Next(iter))
{
nssList_Add(et->emailList, c);
}
nssListIterator_Finish(iter);
nssListIterator_Destroy(iter);
}
}
}
/*
* Find all cached certs with this email address.
*/
NSS_IMPLEMENT NSSCertificate **
nssCertificateStore_FindCertificatesByEmail (
nssCertificateStore *store,
NSSASCII7 *email,
NSSCertificate *rvOpt[],
PRUint32 maximumOpt,
NSSArena *arenaOpt
)
{
NSSCertificate **rvArray = NULL;
struct email_template_str et;
et.email = email;
et.emailList = nssList_Create(NULL, PR_FALSE);
if (!et.emailList) {
return NULL;
}
PZ_Lock(store->lock);
nssHash_Iterate(store->subject, match_email, &et);
if (et.emailList) {
/* get references before leaving the store's lock protection */
nssCertificateList_AddReferences(et.emailList);
}
PZ_Unlock(store->lock);
if (et.emailList) {
rvArray = get_array_from_list(et.emailList,
rvOpt, maximumOpt, arenaOpt);
nssList_Destroy(et.emailList);
}
return rvArray;
}
NSS_IMPLEMENT NSSCertificate *
nssCertificateStore_FindCertificateByIssuerAndSerialNumber (
nssCertificateStore *store,
NSSDER *issuer,
NSSDER *serial
)
{
certificate_hash_entry *entry;
NSSCertificate index;
NSSCertificate *rvCert = NULL;
index.issuer = *issuer;
index.serial = *serial;
PZ_Lock(store->lock);
entry = (certificate_hash_entry *)
nssHash_Lookup(store->issuer_and_serial, &index);
if (entry) {
rvCert = nssCertificate_AddRef(entry->cert);
}
PZ_Unlock(store->lock);
return rvCert;
}
#ifdef NSS_3_4_CODE
static PRStatus
issuer_and_serial_from_encoding (
NSSBER *encoding,
NSSDER *issuer,
NSSDER *serial
)
{
SECItem derCert, derIssuer, derSerial;
SECStatus secrv;
derCert.data = (unsigned char *)encoding->data;
derCert.len = encoding->size;
secrv = CERT_IssuerNameFromDERCert(&derCert, &derIssuer);
if (secrv != SECSuccess) {
return PR_FAILURE;
}
secrv = CERT_SerialNumberFromDERCert(&derCert, &derSerial);
if (secrv != SECSuccess) {
PORT_Free(derIssuer.data);
return PR_FAILURE;
}
issuer->data = derIssuer.data;
issuer->size = derIssuer.len;
serial->data = derSerial.data;
serial->size = derSerial.len;
return PR_SUCCESS;
}
#endif
NSS_IMPLEMENT NSSCertificate *
nssCertificateStore_FindCertificateByEncodedCertificate (
nssCertificateStore *store,
NSSDER *encoding
)
{
PRStatus nssrv = PR_FAILURE;
NSSDER issuer, serial;
NSSCertificate *rvCert = NULL;
#ifdef NSS_3_4_CODE
nssrv = issuer_and_serial_from_encoding(encoding, &issuer, &serial);
#endif
if (nssrv != PR_SUCCESS) {
return NULL;
}
rvCert = nssCertificateStore_FindCertificateByIssuerAndSerialNumber(store,
&issuer,
&serial);
#ifdef NSS_3_4_CODE
PORT_Free(issuer.data);
PORT_Free(serial.data);
#endif
return rvCert;
}
NSS_EXTERN PRStatus
nssCertificateStore_AddTrust (
nssCertificateStore *store,
NSSTrust *trust
)
{
NSSCertificate *cert;
certificate_hash_entry *entry;
cert = trust->certificate;
PZ_Lock(store->lock);
entry = (certificate_hash_entry *)
nssHash_Lookup(store->issuer_and_serial, cert);
if (entry) {
entry->trust = nssTrust_AddRef(trust);
}
PZ_Unlock(store->lock);
return (entry) ? PR_SUCCESS : PR_FAILURE;
}
NSS_IMPLEMENT NSSTrust *
nssCertificateStore_FindTrustForCertificate (
nssCertificateStore *store,
NSSCertificate *cert
)
{
certificate_hash_entry *entry;
NSSTrust *rvTrust = NULL;
PZ_Lock(store->lock);
entry = (certificate_hash_entry *)
nssHash_Lookup(store->issuer_and_serial, cert);
if (entry && entry->trust) {
rvTrust = nssTrust_AddRef(entry->trust);
}
PZ_Unlock(store->lock);
return rvTrust;
}
NSS_EXTERN PRStatus
nssCertificateStore_AddSMIMEProfile (
nssCertificateStore *store,
nssSMIMEProfile *profile
)
{
NSSCertificate *cert;
certificate_hash_entry *entry;
cert = profile->certificate;
PZ_Lock(store->lock);
entry = (certificate_hash_entry *)
nssHash_Lookup(store->issuer_and_serial, cert);
if (entry) {
entry->profile = nssSMIMEProfile_AddRef(profile);
}
PZ_Unlock(store->lock);
return (entry) ? PR_SUCCESS : PR_FAILURE;
}
NSS_IMPLEMENT nssSMIMEProfile *
nssCertificateStore_FindSMIMEProfileForCertificate (
nssCertificateStore *store,
NSSCertificate *cert
)
{
certificate_hash_entry *entry;
nssSMIMEProfile *rvProfile = NULL;
PZ_Lock(store->lock);
entry = (certificate_hash_entry *)
nssHash_Lookup(store->issuer_and_serial, cert);
if (entry && entry->profile) {
rvProfile = nssSMIMEProfile_AddRef(entry->profile);
}
PZ_Unlock(store->lock);
return rvProfile;
}
/* XXX this is also used by cache and should be somewhere else */
static PLHashNumber
nss_certificate_hash (
const void *key
)
{
unsigned int i;
PLHashNumber h;
NSSCertificate *c = (NSSCertificate *)key;
h = 0;
for (i=0; i<c->issuer.size; i++)
h = (h >> 28) ^ (h << 4) ^ ((unsigned char *)c->issuer.data)[i];
for (i=0; i<c->serial.size; i++)
h = (h >> 28) ^ (h << 4) ^ ((unsigned char *)c->serial.data)[i];
return h;
}
static int
nss_compare_certs(const void *v1, const void *v2)
{
PRStatus ignore;
NSSCertificate *c1 = (NSSCertificate *)v1;
NSSCertificate *c2 = (NSSCertificate *)v2;
return (int)(nssItem_Equal(&c1->issuer, &c2->issuer, &ignore) &&
nssItem_Equal(&c1->serial, &c2->serial, &ignore));
}
NSS_IMPLEMENT nssHash *
nssHash_CreateCertificate (
NSSArena *arenaOpt,
PRUint32 numBuckets
)
{
return nssHash_Create(arenaOpt,
numBuckets,
nss_certificate_hash,
nss_compare_certs,
PL_CompareValues);
}
NSS_IMPLEMENT void
nssCertificateStore_DumpStoreInfo (
nssCertificateStore *store,
void (* cert_dump_iter)(const void *, void *, void *),
void *arg
)
{
PZ_Lock(store->lock);
nssHash_Iterate(store->issuer_and_serial, cert_dump_iter, arg);
PZ_Unlock(store->lock);
}