summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/pki/tdcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/pki/tdcache.c')
-rw-r--r--security/nss/lib/pki/tdcache.c1113
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);
+}