diff options
Diffstat (limited to '')
-rw-r--r-- | security/nss/lib/pki/pkistore.c | 675 |
1 files changed, 675 insertions, 0 deletions
diff --git a/security/nss/lib/pki/pkistore.c b/security/nss/lib/pki/pkistore.c new file mode 100644 index 0000000000..6113442a1d --- /dev/null +++ b/security/nss/lib/pki/pkistore.c @@ -0,0 +1,675 @@ +/* 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); +} |