| /* 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 "pki3hack.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; |
| } |
| |
| NSS_IMPLEMENT NSSCertificate * |
| nssCertificateStore_FindCertificateByEncodedCertificate ( |
| nssCertificateStore *store, |
| NSSDER *encoding |
| ) |
| { |
| PRStatus nssrv = PR_FAILURE; |
| NSSDER issuer, serial; |
| NSSCertificate *rvCert = NULL; |
| nssrv = nssPKIX509_GetIssuerAndSerialFromDER(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; i<c->issuer.size; i++) |
| h = PR_ROTATE_LEFT32(h, 4) ^ ((unsigned char *)c->issuer.data)[i]; |
| for (i=0; i<c->serial.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); |
| } |
| |