/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #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 */ #include "cert.h" #include "prbit.h" /* * 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; }; /* forward static declarations */ static NSSCertificate * nssCertStore_FindCertByIssuerAndSerialNumberLocked ( nssCertificateStore *store, NSSDER *issuer, NSSDER *serial ); 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; } extern const NSSError NSS_ERROR_BUSY; NSS_IMPLEMENT PRStatus nssCertificateStore_Destroy ( nssCertificateStore *store ) { if (nssHash_Count(store->issuer_and_serial) > 0) { nss_SetError(NSS_ERROR_BUSY); return PR_FAILURE; } 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); } return PR_SUCCESS; } 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, nssCertificate_SubjectListSort); /* 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 ); /* Caller must hold store->lock */ static PRStatus nssCertificateStore_AddLocked ( nssCertificateStore *store, NSSCertificate *cert ) { PRStatus nssrv = add_certificate_entry(store, cert); if (nssrv == PR_SUCCESS) { nssrv = add_subject_entry(store, cert); if (nssrv == PR_FAILURE) { remove_certificate_entry(store, cert); } } return nssrv; } NSS_IMPLEMENT NSSCertificate * nssCertificateStore_FindOrAdd ( nssCertificateStore *store, NSSCertificate *c ) { PRStatus nssrv; NSSCertificate *rvCert = NULL; PZ_Lock(store->lock); rvCert = nssCertStore_FindCertByIssuerAndSerialNumberLocked( store, &c->issuer, &c->serial); if (!rvCert) { nssrv = nssCertificateStore_AddLocked(store, c); if (PR_SUCCESS == nssrv) { rvCert = nssCertificate_AddRef(c); } } PZ_Unlock(store->lock); return rvCert; } 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_RemoveCertLOCKED ( nssCertificateStore *store, NSSCertificate *cert ) { certificate_hash_entry *entry; 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); } } NSS_IMPLEMENT void nssCertificateStore_Lock ( nssCertificateStore *store, nssCertificateStoreTrace* out ) { #ifdef DEBUG PORT_Assert(out); out->store = store; out->lock = store->lock; out->locked = PR_TRUE; PZ_Lock(out->lock); #else PZ_Lock(store->lock); #endif } NSS_IMPLEMENT void nssCertificateStore_Unlock ( nssCertificateStore *store, const nssCertificateStoreTrace* in, nssCertificateStoreTrace* out ) { #ifdef DEBUG PORT_Assert(in); PORT_Assert(out); out->store = store; out->lock = store->lock; PORT_Assert(!out->locked); out->unlocked = PR_TRUE; PORT_Assert(in->store == out->store); PORT_Assert(in->lock == out->lock); PORT_Assert(in->locked); PORT_Assert(!in->unlocked); PZ_Unlock(out->lock); #else PZ_Unlock(store->lock); #endif } 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; } nss_ZFreeIf(nickname); } /* * Find all cached certs with this label. */ NSS_IMPLEMENT NSSCertificate ** nssCertificateStore_FindCertificatesByNickname ( nssCertificateStore *store, const NSSUTF8 *nickname, NSSCertificate *rvOpt[], PRUint32 maximumOpt, NSSArena *arenaOpt ) { NSSCertificate **rvArray = NULL; struct nickname_template_str nt; nt.nickname = (char*) 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; } /* Caller holds store->lock */ static NSSCertificate * nssCertStore_FindCertByIssuerAndSerialNumberLocked ( nssCertificateStore *store, NSSDER *issuer, NSSDER *serial ) { certificate_hash_entry *entry; NSSCertificate *rvCert = NULL; NSSCertificate index; index.issuer = *issuer; index.serial = *serial; entry = (certificate_hash_entry *) nssHash_Lookup(store->issuer_and_serial, &index); if (entry) { rvCert = nssCertificate_AddRef(entry->cert); } return rvCert; } NSS_IMPLEMENT NSSCertificate * nssCertificateStore_FindCertificateByIssuerAndSerialNumber ( nssCertificateStore *store, NSSDER *issuer, NSSDER *serial ) { NSSCertificate *rvCert = NULL; PZ_Lock(store->lock); rvCert = nssCertStore_FindCertByIssuerAndSerialNumberLocked ( store, issuer, serial); PZ_Unlock(store->lock); return rvCert; } 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; } NSS_IMPLEMENT NSSCertificate * nssCertificateStore_FindCertificateByEncodedCertificate ( nssCertificateStore *store, NSSDER *encoding ) { PRStatus nssrv = PR_FAILURE; NSSDER issuer, serial; NSSCertificate *rvCert = NULL; nssrv = issuer_and_serial_from_encoding(encoding, &issuer, &serial); if (nssrv != PR_SUCCESS) { return NULL; } rvCert = nssCertificateStore_FindCertificateByIssuerAndSerialNumber(store, &issuer, &serial); PORT_Free(issuer.data); PORT_Free(serial.data); 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) { NSSTrust* newTrust = nssTrust_AddRef(trust); if (entry->trust) { nssTrust_Destroy(entry->trust); } entry->trust = newTrust; } 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) { nssSMIMEProfile* newProfile = nssSMIMEProfile_AddRef(profile); if (entry->profile) { nssSMIMEProfile_Destroy(entry->profile); } entry->profile = newProfile; } 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; iissuer.size; i++) h = PR_ROTATE_LEFT32(h, 4) ^ ((unsigned char *)c->issuer.data)[i]; for (i=0; iserial.size; i++) h = PR_ROTATE_LEFT32(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); }