| /* 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++) { |
| sprintf(&buf[2 * i], "%02X", ((PRUint8 *)it->data)[i]); |
| } |
| if (it->size > 10) { |
| sprintf(&buf[2 * i], ".."); |
| i += 1; |
| for (j = it->size - 1; i <= 16 && j > 10; i++, j--) { |
| sprintf(&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); |
| } |