diff options
Diffstat (limited to 'security/nss/lib/pki/tdcache.c')
-rw-r--r-- | security/nss/lib/pki/tdcache.c | 1113 |
1 files changed, 1113 insertions, 0 deletions
diff --git a/security/nss/lib/pki/tdcache.c b/security/nss/lib/pki/tdcache.c new file mode 100644 index 0000000000..14cc563c48 --- /dev/null +++ b/security/nss/lib/pki/tdcache.c @@ -0,0 +1,1113 @@ +/* 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 PKIT_H +#include "pkit.h" +#endif /* PKIT_H */ + +#ifndef NSSPKI_H +#include "nsspki.h" +#endif /* NSSPKI_H */ + +#ifndef PKI_H +#include "pki.h" +#endif /* PKI_H */ + +#ifndef NSSBASE_H +#include "nssbase.h" +#endif /* NSSBASE_H */ + +#ifndef BASE_H +#include "base.h" +#endif /* BASE_H */ + +#include "cert.h" +#include "dev.h" +#include "pki3hack.h" + +#ifdef DEBUG_CACHE +static PRLogModuleInfo *s_log = NULL; +#endif + +#ifdef DEBUG_CACHE +static void +log_item_dump(const char *msg, NSSItem *it) +{ + char buf[33]; + int i, j; + for (i = 0; i < 10 && i < it->size; i++) { + snprintf(&buf[2 * i], sizeof(buf) - 2 * i, "%02X", ((PRUint8 *)it->data)[i]); + } + if (it->size > 10) { + snprintf(&buf[2 * i], sizeof(buf) - 2 * i, ".."); + i += 1; + for (j = it->size - 1; i <= 16 && j > 10; i++, j--) { + snprintf(&buf[2 * i], sizeof(buf) - 2 * i, "%02X", ((PRUint8 *)it->data)[j]); + } + } + PR_LOG(s_log, PR_LOG_DEBUG, ("%s: %s", msg, buf)); +} +#endif + +#ifdef DEBUG_CACHE +static void +log_cert_ref(const char *msg, NSSCertificate *c) +{ + PR_LOG(s_log, PR_LOG_DEBUG, ("%s: %s", msg, (c->nickname) ? c->nickname : c->email)); + log_item_dump("\tserial", &c->serial); + log_item_dump("\tsubject", &c->subject); +} +#endif + +/* Certificate cache routines */ + +/* XXX + * Locking is not handled well at all. A single, global lock with sub-locks + * in the collection types. Cleanup needed. + */ + +/* should it live in its own arena? */ +struct nssTDCertificateCacheStr { + PZLock *lock; /* Must not be held when calling nssSlot_IsTokenPresent. See bug 1625791. */ + NSSArena *arena; + nssHash *issuerAndSN; + nssHash *subject; + nssHash *nickname; + nssHash *email; +}; + +struct cache_entry_str { + union { + NSSCertificate *cert; + nssList *list; + void *value; + } entry; + PRUint32 hits; + PRTime lastHit; + NSSArena *arena; + NSSUTF8 *nickname; +}; + +typedef struct cache_entry_str cache_entry; + +static cache_entry * +new_cache_entry(NSSArena *arena, void *value, PRBool ownArena) +{ + cache_entry *ce = nss_ZNEW(arena, cache_entry); + if (ce) { + ce->entry.value = value; + ce->hits = 1; + ce->lastHit = PR_Now(); + if (ownArena) { + ce->arena = arena; + } + ce->nickname = NULL; + } + return ce; +} + +/* this should not be exposed in a header, but is here to keep the above + * types/functions static + */ +NSS_IMPLEMENT PRStatus +nssTrustDomain_InitializeCache( + NSSTrustDomain *td, + PRUint32 cacheSize) +{ + NSSArena *arena; + nssTDCertificateCache *cache = td->cache; +#ifdef DEBUG_CACHE + s_log = PR_NewLogModule("nss_cache"); + PR_ASSERT(s_log); +#endif + PR_ASSERT(!cache); + arena = nssArena_Create(); + if (!arena) { + return PR_FAILURE; + } + cache = nss_ZNEW(arena, nssTDCertificateCache); + if (!cache) { + nssArena_Destroy(arena); + return PR_FAILURE; + } + cache->lock = PZ_NewLock(nssILockCache); + if (!cache->lock) { + nssArena_Destroy(arena); + return PR_FAILURE; + } + /* Create the issuer and serial DER --> certificate hash */ + cache->issuerAndSN = nssHash_CreateCertificate(arena, cacheSize); + if (!cache->issuerAndSN) { + goto loser; + } + /* Create the subject DER --> subject list hash */ + cache->subject = nssHash_CreateItem(arena, cacheSize); + if (!cache->subject) { + goto loser; + } + /* Create the nickname --> subject list hash */ + cache->nickname = nssHash_CreateString(arena, cacheSize); + if (!cache->nickname) { + goto loser; + } + /* Create the email --> list of subject lists hash */ + cache->email = nssHash_CreateString(arena, cacheSize); + if (!cache->email) { + goto loser; + } + cache->arena = arena; + td->cache = cache; +#ifdef DEBUG_CACHE + PR_LOG(s_log, PR_LOG_DEBUG, ("Cache initialized.")); +#endif + return PR_SUCCESS; +loser: + PZ_DestroyLock(cache->lock); + nssArena_Destroy(arena); + td->cache = NULL; +#ifdef DEBUG_CACHE + PR_LOG(s_log, PR_LOG_DEBUG, ("Cache initialization failed.")); +#endif + return PR_FAILURE; +} + +/* The entries of the hashtable are currently dependent on the certificate(s) + * that produced them. That is, the entries will be freed when the cert is + * released from the cache. If there are certs in the cache at any time, + * including shutdown, the hash table entries will hold memory. In order for + * clean shutdown, it is necessary for there to be no certs in the cache. + */ + +extern const NSSError NSS_ERROR_INTERNAL_ERROR; +extern const NSSError NSS_ERROR_BUSY; + +NSS_IMPLEMENT PRStatus +nssTrustDomain_DestroyCache(NSSTrustDomain *td) +{ + if (!td->cache) { + nss_SetError(NSS_ERROR_INTERNAL_ERROR); + return PR_FAILURE; + } + if (nssHash_Count(td->cache->issuerAndSN) > 0) { + nss_SetError(NSS_ERROR_BUSY); + return PR_FAILURE; + } + PZ_DestroyLock(td->cache->lock); + nssHash_Destroy(td->cache->issuerAndSN); + nssHash_Destroy(td->cache->subject); + nssHash_Destroy(td->cache->nickname); + nssHash_Destroy(td->cache->email); + nssArena_Destroy(td->cache->arena); + td->cache = NULL; +#ifdef DEBUG_CACHE + PR_LOG(s_log, PR_LOG_DEBUG, ("Cache destroyed.")); +#endif + return PR_SUCCESS; +} + +static PRStatus +remove_issuer_and_serial_entry( + nssTDCertificateCache *cache, + NSSCertificate *cert) +{ + /* Remove the cert from the issuer/serial hash */ + nssHash_Remove(cache->issuerAndSN, cert); +#ifdef DEBUG_CACHE + log_cert_ref("removed issuer/sn", cert); +#endif + return PR_SUCCESS; +} + +static PRStatus +remove_subject_entry( + nssTDCertificateCache *cache, + NSSCertificate *cert, + nssList **subjectList, + NSSUTF8 **nickname, + NSSArena **arena) +{ + PRStatus nssrv; + cache_entry *ce; + *subjectList = NULL; + *arena = NULL; + /* Get the subject list for the cert's subject */ + ce = (cache_entry *)nssHash_Lookup(cache->subject, &cert->subject); + if (ce) { + /* Remove the cert from the subject hash */ + nssList_Remove(ce->entry.list, cert); + *subjectList = ce->entry.list; + *nickname = ce->nickname; + *arena = ce->arena; + nssrv = PR_SUCCESS; +#ifdef DEBUG_CACHE + log_cert_ref("removed cert", cert); + log_item_dump("from subject list", &cert->subject); +#endif + } else { + nssrv = PR_FAILURE; + } + return nssrv; +} + +static PRStatus +remove_nickname_entry( + nssTDCertificateCache *cache, + NSSUTF8 *nickname, + nssList *subjectList) +{ + PRStatus nssrv; + if (nickname) { + nssHash_Remove(cache->nickname, nickname); + nssrv = PR_SUCCESS; +#ifdef DEBUG_CACHE + PR_LOG(s_log, PR_LOG_DEBUG, ("removed nickname %s", nickname)); +#endif + } else { + nssrv = PR_FAILURE; + } + return nssrv; +} + +static PRStatus +remove_email_entry( + nssTDCertificateCache *cache, + NSSCertificate *cert, + nssList *subjectList) +{ + PRStatus nssrv = PR_FAILURE; + cache_entry *ce; + /* Find the subject list in the email hash */ + if (cert->email) { + ce = (cache_entry *)nssHash_Lookup(cache->email, cert->email); + if (ce) { + nssList *subjects = ce->entry.list; + /* Remove the subject list from the email hash */ + if (subjects) { + nssList_Remove(subjects, subjectList); +#ifdef DEBUG_CACHE + log_item_dump("removed subject list", &cert->subject); + PR_LOG(s_log, PR_LOG_DEBUG, ("for email %s", cert->email)); +#endif + if (nssList_Count(subjects) == 0) { + /* No more subject lists for email, delete list and + * remove hash entry + */ + (void)nssList_Destroy(subjects); + nssHash_Remove(cache->email, cert->email); + /* there are no entries left for this address, free space + * used for email entries + */ + nssArena_Destroy(ce->arena); +#ifdef DEBUG_CACHE + PR_LOG(s_log, PR_LOG_DEBUG, ("removed email %s", cert->email)); +#endif + } + } + nssrv = PR_SUCCESS; + } + } + return nssrv; +} + +NSS_IMPLEMENT void +nssTrustDomain_RemoveCertFromCacheLOCKED( + NSSTrustDomain *td, + NSSCertificate *cert) +{ + nssList *subjectList; + cache_entry *ce; + NSSArena *arena; + NSSUTF8 *nickname = NULL; + +#ifdef DEBUG_CACHE + log_cert_ref("attempt to remove cert", cert); +#endif + ce = (cache_entry *)nssHash_Lookup(td->cache->issuerAndSN, cert); + if (!ce || ce->entry.cert != cert) { +/* If it's not in the cache, or a different cert is (this is really + * for safety reasons, though it shouldn't happen), do nothing + */ +#ifdef DEBUG_CACHE + PR_LOG(s_log, PR_LOG_DEBUG, ("but it wasn't in the cache")); +#endif + return; + } + (void)remove_issuer_and_serial_entry(td->cache, cert); + (void)remove_subject_entry(td->cache, cert, &subjectList, + &nickname, &arena); + if (nssList_Count(subjectList) == 0) { + (void)remove_nickname_entry(td->cache, nickname, subjectList); + (void)remove_email_entry(td->cache, cert, subjectList); + (void)nssList_Destroy(subjectList); + nssHash_Remove(td->cache->subject, &cert->subject); + /* there are no entries left for this subject, free the space used + * for both the nickname and subject entries + */ + if (arena) { + nssArena_Destroy(arena); + } + } +} + +NSS_IMPLEMENT void +nssTrustDomain_LockCertCache(NSSTrustDomain *td) +{ + PZ_Lock(td->cache->lock); +} + +NSS_IMPLEMENT void +nssTrustDomain_UnlockCertCache(NSSTrustDomain *td) +{ + PZ_Unlock(td->cache->lock); +} + +struct token_cert_dtor { + NSSToken *token; + nssTDCertificateCache *cache; + NSSCertificate **certs; + PRUint32 numCerts, arrSize; +}; + +static void +remove_token_certs(const void *k, void *v, void *a) +{ + NSSCertificate *c = (NSSCertificate *)k; + nssPKIObject *object = &c->object; + struct token_cert_dtor *dtor = a; + PRUint32 i; + nssPKIObject_AddRef(object); + nssPKIObject_Lock(object); + for (i = 0; i < object->numInstances; i++) { + if (object->instances[i]->token == dtor->token) { + nssCryptokiObject_Destroy(object->instances[i]); + object->instances[i] = object->instances[object->numInstances - 1]; + object->instances[object->numInstances - 1] = NULL; + object->numInstances--; + dtor->certs[dtor->numCerts++] = c; + if (dtor->numCerts == dtor->arrSize) { + dtor->arrSize *= 2; + dtor->certs = nss_ZREALLOCARRAY(dtor->certs, + NSSCertificate *, + dtor->arrSize); + } + break; + } + } + nssPKIObject_Unlock(object); + nssPKIObject_Destroy(object); + return; +} + +/* + * Remove all certs for the given token from the cache. This is + * needed if the token is removed. + */ +NSS_IMPLEMENT PRStatus +nssTrustDomain_RemoveTokenCertsFromCache( + NSSTrustDomain *td, + NSSToken *token) +{ + NSSCertificate **certs; + PRUint32 i, arrSize = 10; + struct token_cert_dtor dtor; + certs = nss_ZNEWARRAY(NULL, NSSCertificate *, arrSize); + if (!certs) { + return PR_FAILURE; + } + dtor.cache = td->cache; + dtor.token = token; + dtor.certs = certs; + dtor.numCerts = 0; + dtor.arrSize = arrSize; + PZ_Lock(td->cache->lock); + nssHash_Iterate(td->cache->issuerAndSN, remove_token_certs, &dtor); + for (i = 0; i < dtor.numCerts; i++) { + if (dtor.certs[i]->object.numInstances == 0) { + nssTrustDomain_RemoveCertFromCacheLOCKED(td, dtor.certs[i]); + dtor.certs[i] = NULL; /* skip this cert in the second for loop */ + } else { + /* make sure it doesn't disappear on us before we finish */ + nssCertificate_AddRef(dtor.certs[i]); + } + } + PZ_Unlock(td->cache->lock); + for (i = 0; i < dtor.numCerts; i++) { + if (dtor.certs[i]) { + STAN_ForceCERTCertificateUpdate(dtor.certs[i]); + nssCertificate_Destroy(dtor.certs[i]); + } + } + nss_ZFreeIf(dtor.certs); + return PR_SUCCESS; +} + +NSS_IMPLEMENT PRStatus +nssTrustDomain_UpdateCachedTokenCerts( + NSSTrustDomain *td, + NSSToken *token) +{ + NSSCertificate **cp, **cached = NULL; + nssList *certList; + PRUint32 count; + certList = nssList_Create(NULL, PR_FALSE); + if (!certList) + return PR_FAILURE; + (void)nssTrustDomain_GetCertsFromCache(td, certList); + count = nssList_Count(certList); + if (count > 0) { + cached = nss_ZNEWARRAY(NULL, NSSCertificate *, count + 1); + if (!cached) { + nssList_Destroy(certList); + return PR_FAILURE; + } + nssList_GetArray(certList, (void **)cached, count); + for (cp = cached; *cp; cp++) { + nssCryptokiObject *instance; + NSSCertificate *c = *cp; + nssTokenSearchType tokenOnly = nssTokenSearchType_TokenOnly; + instance = nssToken_FindCertificateByIssuerAndSerialNumber( + token, + NULL, + &c->issuer, + &c->serial, + tokenOnly, + NULL); + if (instance) { + nssPKIObject_AddInstance(&c->object, instance); + STAN_ForceCERTCertificateUpdate(c); + } + } + nssCertificateArray_Destroy(cached); + } + nssList_Destroy(certList); + return PR_SUCCESS; +} + +static PRStatus +add_issuer_and_serial_entry( + NSSArena *arena, + nssTDCertificateCache *cache, + NSSCertificate *cert) +{ + cache_entry *ce; + ce = new_cache_entry(arena, (void *)cert, PR_FALSE); +#ifdef DEBUG_CACHE + log_cert_ref("added to issuer/sn", cert); +#endif + return nssHash_Add(cache->issuerAndSN, cert, (void *)ce); +} + +static PRStatus +add_subject_entry( + NSSArena *arena, + nssTDCertificateCache *cache, + NSSCertificate *cert, + NSSUTF8 *nickname, + nssList **subjectList) +{ + PRStatus nssrv; + nssList *list; + cache_entry *ce; + *subjectList = NULL; /* this is only set if a new one is created */ + ce = (cache_entry *)nssHash_Lookup(cache->subject, &cert->subject); + if (ce) { + ce->hits++; + ce->lastHit = PR_Now(); + /* The subject is already in, add this cert to the list */ + nssrv = nssList_AddUnique(ce->entry.list, cert); +#ifdef DEBUG_CACHE + log_cert_ref("added to existing subject list", cert); +#endif + } else { + NSSDER *subject; + /* Create a new subject list for the subject */ + list = nssList_Create(arena, PR_FALSE); + if (!list) { + return PR_FAILURE; + } + ce = new_cache_entry(arena, (void *)list, PR_TRUE); + if (!ce) { + return PR_FAILURE; + } + if (nickname) { + ce->nickname = nssUTF8_Duplicate(nickname, arena); + } + nssList_SetSortFunction(list, nssCertificate_SubjectListSort); + /* Add the cert entry to this list of subjects */ + nssrv = nssList_AddUnique(list, cert); + if (nssrv != PR_SUCCESS) { + return nssrv; + } + /* Add the subject list to the cache */ + subject = nssItem_Duplicate(&cert->subject, arena, NULL); + if (!subject) { + return PR_FAILURE; + } + nssrv = nssHash_Add(cache->subject, subject, ce); + if (nssrv != PR_SUCCESS) { + return nssrv; + } + *subjectList = list; +#ifdef DEBUG_CACHE + log_cert_ref("created subject list", cert); +#endif + } + return nssrv; +} + +static PRStatus +add_nickname_entry( + NSSArena *arena, + nssTDCertificateCache *cache, + NSSUTF8 *certNickname, + nssList *subjectList) +{ + PRStatus nssrv = PR_SUCCESS; + cache_entry *ce; + ce = (cache_entry *)nssHash_Lookup(cache->nickname, certNickname); + if (ce) { + /* This is a collision. A nickname entry already exists for this + * subject, but a subject entry didn't. This would imply there are + * two subjects using the same nickname, which is not allowed. + */ + return PR_FAILURE; + } else { + NSSUTF8 *nickname; + ce = new_cache_entry(arena, subjectList, PR_FALSE); + if (!ce) { + return PR_FAILURE; + } + nickname = nssUTF8_Duplicate(certNickname, arena); + if (!nickname) { + return PR_FAILURE; + } + nssrv = nssHash_Add(cache->nickname, nickname, ce); +#ifdef DEBUG_CACHE + log_cert_ref("created nickname for", cert); +#endif + } + return nssrv; +} + +static PRStatus +add_email_entry( + nssTDCertificateCache *cache, + NSSCertificate *cert, + nssList *subjectList) +{ + PRStatus nssrv = PR_SUCCESS; + nssList *subjects; + cache_entry *ce; + ce = (cache_entry *)nssHash_Lookup(cache->email, cert->email); + if (ce) { + /* Already have an entry for this email address, but not subject */ + subjects = ce->entry.list; + nssrv = nssList_AddUnique(subjects, subjectList); + ce->hits++; + ce->lastHit = PR_Now(); +#ifdef DEBUG_CACHE + log_cert_ref("added subject to email for", cert); +#endif + } else { + NSSASCII7 *email; + NSSArena *arena; + arena = nssArena_Create(); + if (!arena) { + return PR_FAILURE; + } + /* Create a new list of subject lists, add this subject */ + subjects = nssList_Create(arena, PR_TRUE); + if (!subjects) { + nssArena_Destroy(arena); + return PR_FAILURE; + } + /* Add the new subject to the list */ + nssrv = nssList_AddUnique(subjects, subjectList); + if (nssrv != PR_SUCCESS) { + nssArena_Destroy(arena); + return nssrv; + } + /* Add the new entry to the cache */ + ce = new_cache_entry(arena, (void *)subjects, PR_TRUE); + if (!ce) { + nssArena_Destroy(arena); + return PR_FAILURE; + } + email = nssUTF8_Duplicate(cert->email, arena); + if (!email) { + nssArena_Destroy(arena); + return PR_FAILURE; + } + nssrv = nssHash_Add(cache->email, email, ce); + if (nssrv != PR_SUCCESS) { + nssArena_Destroy(arena); + return nssrv; + } +#ifdef DEBUG_CACHE + log_cert_ref("created email for", cert); +#endif + } + return nssrv; +} + +extern const NSSError NSS_ERROR_CERTIFICATE_IN_CACHE; + +static void +remove_object_instances( + nssPKIObject *object, + nssCryptokiObject **instances, + int numInstances) +{ + int i; + + for (i = 0; i < numInstances; i++) { + nssPKIObject_RemoveInstanceForToken(object, instances[i]->token); + } +} + +static SECStatus +merge_object_instances( + nssPKIObject *to, + nssPKIObject *from) +{ + nssCryptokiObject **instances, **ci; + int i; + SECStatus rv = SECSuccess; + + instances = nssPKIObject_GetInstances(from); + if (instances == NULL) { + return SECFailure; + } + for (ci = instances, i = 0; *ci; ci++, i++) { + nssCryptokiObject *instance = nssCryptokiObject_Clone(*ci); + if (instance) { + if (nssPKIObject_AddInstance(to, instance) == PR_SUCCESS) { + continue; + } + nssCryptokiObject_Destroy(instance); + } + remove_object_instances(to, instances, i); + rv = SECFailure; + break; + } + nssCryptokiObjectArray_Destroy(instances); + return rv; +} + +static NSSCertificate * +add_cert_to_cache( + NSSTrustDomain *td, + NSSCertificate *cert) +{ + NSSArena *arena = NULL; + nssList *subjectList = NULL; + PRStatus nssrv; + PRUint32 added = 0; + cache_entry *ce; + NSSCertificate *rvCert = NULL; + NSSUTF8 *certNickname = nssCertificate_GetNickname(cert, NULL); + + /* Set cc->trust and cc->nssCertificate before taking td->cache->lock. + * Otherwise, the sorter in add_subject_entry may eventually call + * nssSlot_IsTokenPresent, which must not occur while the cache lock + * is held. See bugs 1625791 and 1651564 for details. */ + if (cert->type == NSSCertificateType_PKIX) { + (void)STAN_GetCERTCertificate(cert); + } + + PZ_Lock(td->cache->lock); + /* If it exists in the issuer/serial hash, it's already in all */ + ce = (cache_entry *)nssHash_Lookup(td->cache->issuerAndSN, cert); + if (ce) { + ce->hits++; + ce->lastHit = PR_Now(); + rvCert = nssCertificate_AddRef(ce->entry.cert); +#ifdef DEBUG_CACHE + log_cert_ref("attempted to add cert already in cache", cert); +#endif + PZ_Unlock(td->cache->lock); + nss_ZFreeIf(certNickname); + /* collision - somebody else already added the cert + * to the cache before this thread got around to it. + */ + /* merge the instances of the cert */ + if (merge_object_instances(&rvCert->object, &cert->object) != SECSuccess) { + nssCertificate_Destroy(rvCert); + return NULL; + } + STAN_ForceCERTCertificateUpdate(rvCert); + nssCertificate_Destroy(cert); + return rvCert; + } + /* create a new cache entry for this cert within the cert's arena*/ + nssrv = add_issuer_and_serial_entry(cert->object.arena, td->cache, cert); + if (nssrv != PR_SUCCESS) { + goto loser; + } + added++; + /* create an arena for the nickname and subject entries */ + arena = nssArena_Create(); + if (!arena) { + goto loser; + } + /* create a new subject list for this cert, or add to existing */ + nssrv = add_subject_entry(arena, td->cache, cert, + certNickname, &subjectList); + if (nssrv != PR_SUCCESS) { + goto loser; + } + added++; + /* If a new subject entry was created, also need nickname and/or email */ + if (subjectList != NULL) { +#ifdef nodef + PRBool handle = PR_FALSE; +#endif + if (certNickname) { + nssrv = add_nickname_entry(arena, td->cache, + certNickname, subjectList); + if (nssrv != PR_SUCCESS) { + goto loser; + } +#ifdef nodef + handle = PR_TRUE; +#endif + added++; + } + if (cert->email) { + nssrv = add_email_entry(td->cache, cert, subjectList); + if (nssrv != PR_SUCCESS) { + goto loser; + } +#ifdef nodef + handle = PR_TRUE; +#endif + added += 2; + } +#ifdef nodef + /* I think either a nickname or email address must be associated + * with the cert. However, certs are passed to NewTemp without + * either. This worked in the old code, so it must work now. + */ + if (!handle) { + /* Require either nickname or email handle */ + nssrv = PR_FAILURE; + goto loser; + } +#endif + } else { + /* A new subject entry was not created. arena is unused. */ + nssArena_Destroy(arena); + } + rvCert = cert; + PZ_Unlock(td->cache->lock); + nss_ZFreeIf(certNickname); + return rvCert; +loser: + nss_ZFreeIf(certNickname); + certNickname = NULL; + /* Remove any handles that have been created */ + subjectList = NULL; + if (added >= 1) { + (void)remove_issuer_and_serial_entry(td->cache, cert); + } + if (added >= 2) { + (void)remove_subject_entry(td->cache, cert, &subjectList, + &certNickname, &arena); + } + if (added == 3 || added == 5) { + (void)remove_nickname_entry(td->cache, certNickname, subjectList); + } + if (added >= 4) { + (void)remove_email_entry(td->cache, cert, subjectList); + } + if (subjectList) { + nssHash_Remove(td->cache->subject, &cert->subject); + nssList_Destroy(subjectList); + } + if (arena) { + nssArena_Destroy(arena); + } + PZ_Unlock(td->cache->lock); + return NULL; +} + +NSS_IMPLEMENT PRStatus +nssTrustDomain_AddCertsToCache( + NSSTrustDomain *td, + NSSCertificate **certs, + PRUint32 numCerts) +{ + PRUint32 i; + NSSCertificate *c; + for (i = 0; i < numCerts && certs[i]; i++) { + c = add_cert_to_cache(td, certs[i]); + if (c == NULL) { + return PR_FAILURE; + } else { + certs[i] = c; + } + } + return PR_SUCCESS; +} + +static NSSCertificate ** +collect_subject_certs( + nssList *subjectList, + nssList *rvCertListOpt) +{ + NSSCertificate *c; + NSSCertificate **rvArray = NULL; + PRUint32 count; + nssCertificateList_AddReferences(subjectList); + if (rvCertListOpt) { + nssListIterator *iter = nssList_CreateIterator(subjectList); + if (!iter) { + return (NSSCertificate **)NULL; + } + for (c = (NSSCertificate *)nssListIterator_Start(iter); + c != (NSSCertificate *)NULL; + c = (NSSCertificate *)nssListIterator_Next(iter)) { + nssList_Add(rvCertListOpt, c); + } + nssListIterator_Finish(iter); + nssListIterator_Destroy(iter); + } else { + count = nssList_Count(subjectList); + rvArray = nss_ZNEWARRAY(NULL, NSSCertificate *, count + 1); + if (!rvArray) { + return (NSSCertificate **)NULL; + } + nssList_GetArray(subjectList, (void **)rvArray, count); + } + return rvArray; +} + +/* + * Find all cached certs with this subject. + */ +NSS_IMPLEMENT NSSCertificate ** +nssTrustDomain_GetCertsForSubjectFromCache( + NSSTrustDomain *td, + NSSDER *subject, + nssList *certListOpt) +{ + NSSCertificate **rvArray = NULL; + cache_entry *ce; +#ifdef DEBUG_CACHE + log_item_dump("looking for cert by subject", subject); +#endif + PZ_Lock(td->cache->lock); + ce = (cache_entry *)nssHash_Lookup(td->cache->subject, subject); + if (ce) { + ce->hits++; + ce->lastHit = PR_Now(); +#ifdef DEBUG_CACHE + PR_LOG(s_log, PR_LOG_DEBUG, ("... found, %d hits", ce->hits)); +#endif + rvArray = collect_subject_certs(ce->entry.list, certListOpt); + } + PZ_Unlock(td->cache->lock); + return rvArray; +} + +/* + * Find all cached certs with this label. + */ +NSS_IMPLEMENT NSSCertificate ** +nssTrustDomain_GetCertsForNicknameFromCache( + NSSTrustDomain *td, + const NSSUTF8 *nickname, + nssList *certListOpt) +{ + NSSCertificate **rvArray = NULL; + cache_entry *ce; +#ifdef DEBUG_CACHE + PR_LOG(s_log, PR_LOG_DEBUG, ("looking for cert by nick %s", nickname)); +#endif + PZ_Lock(td->cache->lock); + ce = (cache_entry *)nssHash_Lookup(td->cache->nickname, nickname); + if (ce) { + ce->hits++; + ce->lastHit = PR_Now(); +#ifdef DEBUG_CACHE + PR_LOG(s_log, PR_LOG_DEBUG, ("... found, %d hits", ce->hits)); +#endif + rvArray = collect_subject_certs(ce->entry.list, certListOpt); + } + PZ_Unlock(td->cache->lock); + return rvArray; +} + +/* + * Find all cached certs with this email address. + */ +NSS_IMPLEMENT NSSCertificate ** +nssTrustDomain_GetCertsForEmailAddressFromCache( + NSSTrustDomain *td, + NSSASCII7 *email, + nssList *certListOpt) +{ + NSSCertificate **rvArray = NULL; + cache_entry *ce; + nssList *collectList = NULL; + nssListIterator *iter = NULL; + nssList *subjectList; +#ifdef DEBUG_CACHE + PR_LOG(s_log, PR_LOG_DEBUG, ("looking for cert by email %s", email)); +#endif + PZ_Lock(td->cache->lock); + ce = (cache_entry *)nssHash_Lookup(td->cache->email, email); + if (ce) { + ce->hits++; + ce->lastHit = PR_Now(); +#ifdef DEBUG_CACHE + PR_LOG(s_log, PR_LOG_DEBUG, ("... found, %d hits", ce->hits)); +#endif + /* loop over subject lists and get refs for certs */ + if (certListOpt) { + collectList = certListOpt; + } else { + collectList = nssList_Create(NULL, PR_FALSE); + if (!collectList) { + PZ_Unlock(td->cache->lock); + return NULL; + } + } + iter = nssList_CreateIterator(ce->entry.list); + if (!iter) { + PZ_Unlock(td->cache->lock); + if (!certListOpt) { + nssList_Destroy(collectList); + } + return NULL; + } + for (subjectList = (nssList *)nssListIterator_Start(iter); + subjectList != (nssList *)NULL; + subjectList = (nssList *)nssListIterator_Next(iter)) { + (void)collect_subject_certs(subjectList, collectList); + } + nssListIterator_Finish(iter); + nssListIterator_Destroy(iter); + } + PZ_Unlock(td->cache->lock); + if (!certListOpt && collectList) { + PRUint32 count = nssList_Count(collectList); + rvArray = nss_ZNEWARRAY(NULL, NSSCertificate *, count); + if (rvArray) { + nssList_GetArray(collectList, (void **)rvArray, count); + } + nssList_Destroy(collectList); + } + return rvArray; +} + +/* + * Look for a specific cert in the cache + */ +NSS_IMPLEMENT NSSCertificate * +nssTrustDomain_GetCertForIssuerAndSNFromCache( + NSSTrustDomain *td, + NSSDER *issuer, + NSSDER *serial) +{ + NSSCertificate certkey; + NSSCertificate *rvCert = NULL; + cache_entry *ce; + certkey.issuer.data = issuer->data; + certkey.issuer.size = issuer->size; + certkey.serial.data = serial->data; + certkey.serial.size = serial->size; +#ifdef DEBUG_CACHE + log_item_dump("looking for cert by issuer/sn, issuer", issuer); + log_item_dump(" serial", serial); +#endif + PZ_Lock(td->cache->lock); + ce = (cache_entry *)nssHash_Lookup(td->cache->issuerAndSN, &certkey); + if (ce) { + ce->hits++; + ce->lastHit = PR_Now(); + rvCert = nssCertificate_AddRef(ce->entry.cert); +#ifdef DEBUG_CACHE + PR_LOG(s_log, PR_LOG_DEBUG, ("... found, %d hits", ce->hits)); +#endif + } + PZ_Unlock(td->cache->lock); + return rvCert; +} + +/* + * Look for a specific cert in the cache + */ +NSS_IMPLEMENT NSSCertificate * +nssTrustDomain_GetCertByDERFromCache( + NSSTrustDomain *td, + NSSDER *der) +{ + PRStatus nssrv = PR_FAILURE; + NSSDER issuer, serial; + NSSCertificate *rvCert; + nssrv = nssPKIX509_GetIssuerAndSerialFromDER(der, &issuer, &serial); + if (nssrv != PR_SUCCESS) { + return NULL; + } +#ifdef DEBUG_CACHE + log_item_dump("looking for cert by DER", der); +#endif + rvCert = nssTrustDomain_GetCertForIssuerAndSNFromCache(td, + &issuer, &serial); + PORT_Free(issuer.data); + PORT_Free(serial.data); + return rvCert; +} + +static void +cert_iter(const void *k, void *v, void *a) +{ + nssList *certList = (nssList *)a; + NSSCertificate *c = (NSSCertificate *)k; + nssList_Add(certList, nssCertificate_AddRef(c)); +} + +NSS_EXTERN NSSCertificate ** +nssTrustDomain_GetCertsFromCache( + NSSTrustDomain *td, + nssList *certListOpt) +{ + NSSCertificate **rvArray = NULL; + nssList *certList; + if (certListOpt) { + certList = certListOpt; + } else { + certList = nssList_Create(NULL, PR_FALSE); + if (!certList) { + return NULL; + } + } + PZ_Lock(td->cache->lock); + nssHash_Iterate(td->cache->issuerAndSN, cert_iter, (void *)certList); + PZ_Unlock(td->cache->lock); + if (!certListOpt) { + PRUint32 count = nssList_Count(certList); + rvArray = nss_ZNEWARRAY(NULL, NSSCertificate *, count); + nssList_GetArray(certList, (void **)rvArray, count); + /* array takes the references */ + nssList_Destroy(certList); + } + return rvArray; +} + +NSS_IMPLEMENT void +nssTrustDomain_DumpCacheInfo( + NSSTrustDomain *td, + void (*cert_dump_iter)(const void *, void *, void *), + void *arg) +{ + PZ_Lock(td->cache->lock); + nssHash_Iterate(td->cache->issuerAndSN, cert_dump_iter, arg); + PZ_Unlock(td->cache->lock); +} |