diff options
Diffstat (limited to 'security/nss/lib/pk11wrap/pk11cert.c')
-rw-r--r-- | security/nss/lib/pk11wrap/pk11cert.c | 2983 |
1 files changed, 2983 insertions, 0 deletions
diff --git a/security/nss/lib/pk11wrap/pk11cert.c b/security/nss/lib/pk11wrap/pk11cert.c new file mode 100644 index 0000000000..580d02b613 --- /dev/null +++ b/security/nss/lib/pk11wrap/pk11cert.c @@ -0,0 +1,2983 @@ +/* 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/. */ +/* + * This file manages PKCS #11 instances of certificates. + */ + +#include <stddef.h> + +#include "secport.h" +#include "seccomon.h" +#include "secmod.h" +#include "secmodi.h" +#include "secmodti.h" +#include "pkcs11.h" +#include "pk11func.h" +#include "cert.h" +#include "certi.h" +#include "secitem.h" +#include "keyhi.h" +#include "secoid.h" +#include "pkcs7t.h" +#include "cmsreclist.h" + +#include "certdb.h" +#include "secerr.h" +#include "sslerr.h" + +#include "pki3hack.h" +#include "dev3hack.h" + +#include "devm.h" +#include "nsspki.h" +#include "pki.h" +#include "pkim.h" +#include "pkitm.h" +#include "pkistore.h" /* to remove temp cert */ +#include "devt.h" +#include "ckhelper.h" +#include "pkcs11uri.h" + +extern const NSSError NSS_ERROR_NOT_FOUND; +extern const NSSError NSS_ERROR_INVALID_CERTIFICATE; + +struct nss3_cert_cbstr { + SECStatus (*callback)(CERTCertificate *, void *); + nssList *cached; + void *arg; +}; + +/* Translate from NSSCertificate to CERTCertificate, then pass the latter + * to a callback. + */ +static PRStatus +convert_cert(NSSCertificate *c, void *arg) +{ + CERTCertificate *nss3cert; + SECStatus secrv; + struct nss3_cert_cbstr *nss3cb = (struct nss3_cert_cbstr *)arg; + /* 'c' is not adopted. caller will free it */ + nss3cert = STAN_GetCERTCertificate(c); + if (!nss3cert) + return PR_FAILURE; + secrv = (*nss3cb->callback)(nss3cert, nss3cb->arg); + return (secrv) ? PR_FAILURE : PR_SUCCESS; +} + +/* + * build a cert nickname based on the token name and the label of the + * certificate If the label in NULL, build a label based on the ID. + */ +static int +toHex(int x) +{ + return (x < 10) ? (x + '0') : (x + 'a' - 10); +} +#define MAX_CERT_ID 4 +#define DEFAULT_STRING "Cert ID " +static char * +pk11_buildNickname(PK11SlotInfo *slot, CK_ATTRIBUTE *cert_label, + CK_ATTRIBUTE *key_label, CK_ATTRIBUTE *cert_id) +{ + int prefixLen = PORT_Strlen(slot->token_name); + int suffixLen = 0; + char *suffix = NULL; + char buildNew[sizeof(DEFAULT_STRING) + MAX_CERT_ID * 2]; + char *next, *nickname; + + if (cert_label && (cert_label->ulValueLen)) { + suffixLen = cert_label->ulValueLen; + suffix = (char *)cert_label->pValue; + } else if (key_label && (key_label->ulValueLen)) { + suffixLen = key_label->ulValueLen; + suffix = (char *)key_label->pValue; + } else if (cert_id && cert_id->ulValueLen > 0) { + int i, first = cert_id->ulValueLen - MAX_CERT_ID; + int offset = sizeof(DEFAULT_STRING); + char *idValue = (char *)cert_id->pValue; + + PORT_Memcpy(buildNew, DEFAULT_STRING, sizeof(DEFAULT_STRING) - 1); + next = buildNew + offset; + if (first < 0) + first = 0; + for (i = first; i < (int)cert_id->ulValueLen; i++) { + *next++ = toHex((idValue[i] >> 4) & 0xf); + *next++ = toHex(idValue[i] & 0xf); + } + *next++ = 0; + suffix = buildNew; + suffixLen = PORT_Strlen(buildNew); + } else { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return NULL; + } + + /* if is internal key slot, add code to skip the prefix!! */ + next = nickname = (char *)PORT_Alloc(prefixLen + 1 + suffixLen + 1); + if (nickname == NULL) + return NULL; + + PORT_Memcpy(next, slot->token_name, prefixLen); + next += prefixLen; + *next++ = ':'; + PORT_Memcpy(next, suffix, suffixLen); + next += suffixLen; + *next++ = 0; + return nickname; +} + +PRBool +PK11_IsUserCert(PK11SlotInfo *slot, CERTCertificate *cert, + CK_OBJECT_HANDLE certID) +{ + CK_OBJECT_CLASS theClass; + + if (slot == NULL) + return PR_FALSE; + if (cert == NULL) + return PR_FALSE; + + theClass = CKO_PRIVATE_KEY; + if (pk11_LoginStillRequired(slot, NULL)) { + theClass = CKO_PUBLIC_KEY; + } + if (PK11_MatchItem(slot, certID, theClass) != CK_INVALID_HANDLE) { + return PR_TRUE; + } + + if (theClass == CKO_PUBLIC_KEY) { + SECKEYPublicKey *pubKey = CERT_ExtractPublicKey(cert); + CK_ATTRIBUTE theTemplate; + + if (pubKey == NULL) { + return PR_FALSE; + } + + PK11_SETATTRS(&theTemplate, 0, NULL, 0); + switch (pubKey->keyType) { + case rsaKey: + case rsaPssKey: + case rsaOaepKey: + PK11_SETATTRS(&theTemplate, CKA_MODULUS, pubKey->u.rsa.modulus.data, + pubKey->u.rsa.modulus.len); + break; + case dsaKey: + PK11_SETATTRS(&theTemplate, CKA_VALUE, pubKey->u.dsa.publicValue.data, + pubKey->u.dsa.publicValue.len); + break; + case dhKey: + PK11_SETATTRS(&theTemplate, CKA_VALUE, pubKey->u.dh.publicValue.data, + pubKey->u.dh.publicValue.len); + break; + case ecKey: + PK11_SETATTRS(&theTemplate, CKA_EC_POINT, + pubKey->u.ec.publicValue.data, + pubKey->u.ec.publicValue.len); + break; + case keaKey: + case fortezzaKey: + case kyberKey: + case nullKey: + /* fall through and return false */ + break; + } + + if (theTemplate.ulValueLen == 0) { + SECKEY_DestroyPublicKey(pubKey); + return PR_FALSE; + } + if (pubKey->keyType != ecKey) { + pk11_SignedToUnsigned(&theTemplate); + } + if (pk11_FindObjectByTemplate(slot, &theTemplate, 1) != CK_INVALID_HANDLE) { + SECKEY_DestroyPublicKey(pubKey); + return PR_TRUE; + } + SECKEY_DestroyPublicKey(pubKey); + } + return PR_FALSE; +} + +/* + * Check out if a cert has ID of zero. This is a magic ID that tells + * NSS that this cert may be an automagically trusted cert. + * The Cert has to be self signed as well. That check is done elsewhere. + * + */ +PRBool +pk11_isID0(PK11SlotInfo *slot, CK_OBJECT_HANDLE certID) +{ + CK_ATTRIBUTE keyID = { CKA_ID, NULL, 0 }; + PRBool isZero = PR_FALSE; + int i; + CK_RV crv; + + crv = PK11_GetAttributes(NULL, slot, certID, &keyID, 1); + if (crv != CKR_OK) { + return isZero; + } + + if (keyID.ulValueLen != 0) { + char *value = (char *)keyID.pValue; + isZero = PR_TRUE; /* ID exists, may be zero */ + for (i = 0; i < (int)keyID.ulValueLen; i++) { + if (value[i] != 0) { + isZero = PR_FALSE; /* nope */ + break; + } + } + } + PORT_Free(keyID.pValue); + return isZero; +} + +/* + * Create an NSSCertificate from a slot/certID pair, return it as a + * CERTCertificate. Optionally, output the nickname string. + */ +static CERTCertificate * +pk11_fastCert(PK11SlotInfo *slot, CK_OBJECT_HANDLE certID, + CK_ATTRIBUTE *privateLabel, char **nickptr) +{ + NSSCertificate *c; + nssCryptokiObject *co = NULL; + nssPKIObject *pkio; + NSSTrustDomain *td = STAN_GetDefaultTrustDomain(); + + /* Get the cryptoki object from the handle */ + NSSToken *token = PK11Slot_GetNSSToken(slot); + if (!token || !token->defaultSession) { + (void)nssToken_Destroy(token); /* null token is ok */ + PORT_SetError(SEC_ERROR_NO_TOKEN); + return NULL; + } + co = nssCryptokiObject_Create(token, token->defaultSession, certID); + (void)nssToken_Destroy(token); + if (!co) { + return NULL; + } + + /* Create a PKI object from the cryptoki instance */ + pkio = nssPKIObject_Create(NULL, co, td, NULL, nssPKIMonitor); + if (!pkio) { + nssCryptokiObject_Destroy(co); + return NULL; + } + + /* Create a certificate */ + c = nssCertificate_Create(pkio); + if (!c) { + nssPKIObject_Destroy(pkio); + return NULL; + } + + /* Build and output a nickname, if desired. + * This must be done before calling nssTrustDomain_AddCertsToCache + * because that function may destroy c, pkio and co! + */ + if ((nickptr) && (co->label)) { + CK_ATTRIBUTE label, id; + + label.type = CKA_LABEL; + label.pValue = co->label; + label.ulValueLen = PORT_Strlen(co->label); + + id.type = CKA_ID; + id.pValue = c->id.data; + id.ulValueLen = c->id.size; + + *nickptr = pk11_buildNickname(slot, &label, privateLabel, &id); + } + + /* This function may destroy the cert in "c" and all its subordinate + * structures, and replace the value in "c" with the address of a + * different NSSCertificate that it found in the cache. + * Presumably, the nickname which we just output above remains valid. :) + */ + (void)nssTrustDomain_AddCertsToCache(td, &c, 1); + return STAN_GetCERTCertificateOrRelease(c); +} + +/* + * Build an CERTCertificate structure from a PKCS#11 object ID.... certID + * Must be a CertObject. This code does not explicitly checks that. + */ +CERTCertificate * +PK11_MakeCertFromHandle(PK11SlotInfo *slot, CK_OBJECT_HANDLE certID, + CK_ATTRIBUTE *privateLabel) +{ + char *nickname = NULL; + CERTCertificate *cert = NULL; + CERTCertTrust *trust; + + if (slot == NULL || certID == CK_INVALID_HANDLE) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + cert = pk11_fastCert(slot, certID, privateLabel, &nickname); + if (cert == NULL) { + goto loser; + } + + if (nickname) { + if (cert->nickname != NULL) { + cert->dbnickname = cert->nickname; + } + cert->nickname = PORT_ArenaStrdup(cert->arena, nickname); + PORT_Free(nickname); + nickname = NULL; + } + + /* remember where this cert came from.... If we have just looked + * it up from the database and it already has a slot, don't add a new + * one. */ + if (cert->slot == NULL) { + cert->slot = PK11_ReferenceSlot(slot); + cert->pkcs11ID = certID; + cert->ownSlot = PR_TRUE; + cert->series = slot->series; + } + + trust = (CERTCertTrust *)PORT_ArenaAlloc(cert->arena, sizeof(CERTCertTrust)); + if (trust == NULL) + goto loser; + PORT_Memset(trust, 0, sizeof(CERTCertTrust)); + + if (!pk11_HandleTrustObject(slot, cert, trust)) { + unsigned int type; + + /* build some cert trust flags */ + if (CERT_IsCACert(cert, &type)) { + unsigned int trustflags = CERTDB_VALID_CA; + + /* Allow PKCS #11 modules to give us trusted CA's. We only accept + * valid CA's which are self-signed here. They must have an object + * ID of '0'. */ + if (pk11_isID0(slot, certID) && + cert->isRoot) { + trustflags |= CERTDB_TRUSTED_CA; + /* is the slot a fortezza card? allow the user or + * admin to turn on objectSigning, but don't turn + * full trust on explicitly */ + if (PK11_DoesMechanism(slot, CKM_KEA_KEY_DERIVE)) { + trust->objectSigningFlags |= CERTDB_VALID_CA; + } + } + if ((type & NS_CERT_TYPE_SSL_CA) == NS_CERT_TYPE_SSL_CA) { + trust->sslFlags |= trustflags; + } + if ((type & NS_CERT_TYPE_EMAIL_CA) == NS_CERT_TYPE_EMAIL_CA) { + trust->emailFlags |= trustflags; + } + if ((type & NS_CERT_TYPE_OBJECT_SIGNING_CA) == NS_CERT_TYPE_OBJECT_SIGNING_CA) { + trust->objectSigningFlags |= trustflags; + } + } + } + + if (PK11_IsUserCert(slot, cert, certID)) { + trust->sslFlags |= CERTDB_USER; + trust->emailFlags |= CERTDB_USER; + /* trust->objectSigningFlags |= CERTDB_USER; */ + } + CERT_LockCertTrust(cert); + cert->trust = trust; + CERT_UnlockCertTrust(cert); + + return cert; + +loser: + if (nickname) + PORT_Free(nickname); + if (cert) + CERT_DestroyCertificate(cert); + return NULL; +} + +/* + * Build get a certificate from a private key + */ +CERTCertificate * +PK11_GetCertFromPrivateKey(SECKEYPrivateKey *privKey) +{ + PK11SlotInfo *slot = privKey->pkcs11Slot; + CK_OBJECT_HANDLE handle = privKey->pkcs11ID; + CK_OBJECT_HANDLE certID = + PK11_MatchItem(slot, handle, CKO_CERTIFICATE); + CERTCertificate *cert; + + if (certID == CK_INVALID_HANDLE) { + PORT_SetError(SSL_ERROR_NO_CERTIFICATE); + return NULL; + } + cert = PK11_MakeCertFromHandle(slot, certID, NULL); + return (cert); +} + +CK_OBJECT_HANDLE * +PK11_FindCertHandlesForKeyHandle(PK11SlotInfo *slot, CK_OBJECT_HANDLE keyHandle, + int *certHandleCountOut) +{ + if (!slot || !certHandleCountOut || keyHandle == CK_INVALID_HANDLE) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + PORTCheapArenaPool arena; + PORT_InitCheapArena(&arena, DER_DEFAULT_CHUNKSIZE); + CK_ATTRIBUTE idTemplate[] = { + { CKA_ID, NULL, 0 }, + }; + const int idAttrCount = sizeof(idTemplate) / sizeof(idTemplate[0]); + CK_RV crv = PK11_GetAttributes(&arena.arena, slot, keyHandle, idTemplate, idAttrCount); + if (crv != CKR_OK) { + PORT_DestroyCheapArena(&arena); + PORT_SetError(PK11_MapError(crv)); + return NULL; + } + + if ((idTemplate[0].ulValueLen == 0) || (idTemplate[0].ulValueLen == -1)) { + PORT_DestroyCheapArena(&arena); + PORT_SetError(SEC_ERROR_BAD_KEY); + return NULL; + } + + CK_OBJECT_CLASS searchClass = CKO_CERTIFICATE; + CK_ATTRIBUTE searchTemplate[] = { + idTemplate[0], + { CKA_CLASS, &searchClass, sizeof(searchClass) } + }; + const size_t searchAttrCount = sizeof(searchTemplate) / sizeof(searchTemplate[0]); + CK_OBJECT_HANDLE *ids = pk11_FindObjectsByTemplate(slot, searchTemplate, searchAttrCount, certHandleCountOut); + + PORT_DestroyCheapArena(&arena); + return ids; +} + +CERTCertList * +PK11_GetCertsMatchingPrivateKey(SECKEYPrivateKey *privKey) +{ + if (!privKey) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + CERTCertList *certs = CERT_NewCertList(); + if (!certs) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + PK11SlotInfo *slot = privKey->pkcs11Slot; + CK_OBJECT_HANDLE handle = privKey->pkcs11ID; + CK_OBJECT_HANDLE certID = PK11_MatchItem(slot, handle, CKO_CERTIFICATE); + /* If we can't get a matching certID, there are no matching certificates, + * which is not an error. */ + if (certID == CK_INVALID_HANDLE) { + return certs; + } + int certHandleCount = 0; + CK_OBJECT_HANDLE *certHandles = PK11_FindCertHandlesForKeyHandle(slot, handle, &certHandleCount); + if (!certHandles) { + /* If certHandleCount is 0, there are no matching certificates, which is + * not an error. */ + if (certHandleCount == 0) { + return certs; + } + CERT_DestroyCertList(certs); + return NULL; + } + int i; + for (i = 0; i < certHandleCount; i++) { + CERTCertificate *cert = PK11_MakeCertFromHandle(slot, certHandles[i], NULL); + /* If PK11_MakeCertFromHandle fails for one handle, optimistically + assume other handles may succeed (i.e. this is best-effort). */ + if (!cert) { + continue; + } + if (CERT_AddCertToListTail(certs, cert) != SECSuccess) { + CERT_DestroyCertificate(cert); + } + } + PORT_Free(certHandles); + return certs; +} + +/* + * delete a cert and it's private key (if no other certs are pointing to the + * private key. + */ +SECStatus +PK11_DeleteTokenCertAndKey(CERTCertificate *cert, void *wincx) +{ + SECKEYPrivateKey *privKey = PK11_FindKeyByAnyCert(cert, wincx); + CK_OBJECT_HANDLE pubKey; + PK11SlotInfo *slot = NULL; + + pubKey = pk11_FindPubKeyByAnyCert(cert, &slot, wincx); + if (privKey) { + /* For 3.4, utilize the generic cert delete function */ + SEC_DeletePermCertificate(cert); + PK11_DeleteTokenPrivateKey(privKey, PR_FALSE); + } + if ((pubKey != CK_INVALID_HANDLE) && (slot != NULL)) { + PK11_DestroyTokenObject(slot, pubKey); + PK11_FreeSlot(slot); + } + return SECSuccess; +} + +/* + * cert callback structure + */ +typedef struct pk11DoCertCallbackStr { + SECStatus (*callback)(PK11SlotInfo *slot, CERTCertificate *, void *); + SECStatus (*noslotcallback)(CERTCertificate *, void *); + SECStatus (*itemcallback)(CERTCertificate *, SECItem *, void *); + void *callbackArg; +} pk11DoCertCallback; + +typedef struct pk11CertCallbackStr { + SECStatus (*callback)(CERTCertificate *, SECItem *, void *); + void *callbackArg; +} pk11CertCallback; + +struct fake_der_cb_argstr { + SECStatus (*callback)(CERTCertificate *, SECItem *, void *); + void *arg; +}; + +static SECStatus +fake_der_cb(CERTCertificate *c, void *a) +{ + struct fake_der_cb_argstr *fda = (struct fake_der_cb_argstr *)a; + return (*fda->callback)(c, &c->derCert, fda->arg); +} + +/* + * Extract all the certs on a card from a slot. + */ +SECStatus +PK11_TraverseSlotCerts(SECStatus (*callback)(CERTCertificate *, SECItem *, void *), + void *arg, void *wincx) +{ + NSSTrustDomain *defaultTD = STAN_GetDefaultTrustDomain(); + struct fake_der_cb_argstr fda; + struct nss3_cert_cbstr pk11cb; + + /* authenticate to the tokens first */ + (void)pk11_TraverseAllSlots(NULL, NULL, PR_TRUE, wincx); + + fda.callback = callback; + fda.arg = arg; + pk11cb.callback = fake_der_cb; + pk11cb.arg = &fda; + NSSTrustDomain_TraverseCertificates(defaultTD, convert_cert, &pk11cb); + return SECSuccess; +} + +static void +transfer_token_certs_to_collection(nssList *certList, NSSToken *token, + nssPKIObjectCollection *collection) +{ + NSSCertificate **certs; + PRUint32 i, count; + NSSToken **tokens, **tp; + count = nssList_Count(certList); + if (count == 0) { + return; + } + certs = nss_ZNEWARRAY(NULL, NSSCertificate *, count); + if (!certs) { + return; + } + nssList_GetArray(certList, (void **)certs, count); + for (i = 0; i < count; i++) { + tokens = nssPKIObject_GetTokens(&certs[i]->object, NULL); + if (tokens) { + for (tp = tokens; *tp; tp++) { + if (*tp == token) { + nssPKIObjectCollection_AddObject(collection, + (nssPKIObject *)certs[i]); + } + } + nssTokenArray_Destroy(tokens); + } + CERT_DestroyCertificate(STAN_GetCERTCertificateOrRelease(certs[i])); + } + nss_ZFreeIf(certs); +} + +static void +transfer_uri_certs_to_collection(nssList *certList, PK11URI *uri, + nssPKIObjectCollection *collection) +{ + + NSSCertificate **certs; + PRUint32 i, count; + NSSToken **tokens, **tp; + PK11SlotInfo *slot; + const SECItem *id; + + count = nssList_Count(certList); + if (count == 0) { + return; + } + certs = nss_ZNEWARRAY(NULL, NSSCertificate *, count); + if (!certs) { + return; + } + id = PK11URI_GetPathAttributeItem(uri, PK11URI_PATTR_ID); + nssList_GetArray(certList, (void **)certs, count); + for (i = 0; i < count; i++) { + /* + * Filter the subject matched certs based on the + * CKA_ID from the URI + */ + if (id && (id->len != certs[i]->id.size || + memcmp(id, certs[i]->id.data, certs[i]->id.size))) + continue; + tokens = nssPKIObject_GetTokens(&certs[i]->object, NULL); + if (tokens) { + for (tp = tokens; *tp; tp++) { + const char *value; + slot = (*tp)->pk11slot; + + value = PK11URI_GetPathAttribute(uri, PK11URI_PATTR_TOKEN); + if (value && + !pk11_MatchString(value, + (char *)slot->tokenInfo.label, + sizeof(slot->tokenInfo.label))) { + continue; + } + + value = PK11URI_GetPathAttribute(uri, PK11URI_PATTR_MANUFACTURER); + if (value && + !pk11_MatchString(value, + (char *)slot->tokenInfo.manufacturerID, + sizeof(slot->tokenInfo.manufacturerID))) { + continue; + } + + value = PK11URI_GetPathAttribute(uri, PK11URI_PATTR_MODEL); + if (value && + !pk11_MatchString(value, + (char *)slot->tokenInfo.model, + sizeof(slot->tokenInfo.model))) { + continue; + } + + value = PK11URI_GetPathAttribute(uri, PK11URI_PATTR_SERIAL); + if (value && + !pk11_MatchString(value, + (char *)slot->tokenInfo.serialNumber, + sizeof(slot->tokenInfo.serialNumber))) { + continue; + } + + nssPKIObjectCollection_AddObject(collection, + (nssPKIObject *)certs[i]); + break; + } + nssTokenArray_Destroy(tokens); + } + CERT_DestroyCertificate(STAN_GetCERTCertificateOrRelease(certs[i])); + } + nss_ZFreeIf(certs); +} + +static NSSCertificate ** +find_certs_from_uri(const char *uriString, void *wincx) +{ + PK11URI *uri = NULL; + CK_ATTRIBUTE attributes[10]; + CK_ULONG nattributes = 0; + const SECItem *id; + const char *label, *type; + PK11SlotInfo *slotinfo; + nssCryptokiObject **instances; + PRStatus status; + nssPKIObjectCollection *collection = NULL; + NSSTrustDomain *defaultTD = STAN_GetDefaultTrustDomain(); + NSSCertificate **certs = NULL; + nssList *certList = NULL; + SECStatus rv; + CK_OBJECT_CLASS s_class = CKO_CERTIFICATE; + static const CK_BBOOL s_true = CK_TRUE; + NSSToken **tokens, **tok; + + uri = PK11URI_ParseURI(uriString); + if (uri == NULL) { + goto loser; + } + + collection = nssCertificateCollection_Create(defaultTD, NULL); + if (!collection) { + goto loser; + } + certList = nssList_Create(NULL, PR_FALSE); + if (!certList) { + goto loser; + } + + /* if the "type" attribute is specified its value must be "cert" */ + type = PK11URI_GetPathAttribute(uri, PK11URI_PATTR_TYPE); + if (type && strcmp(type, "cert")) { + goto loser; + } + + label = PK11URI_GetPathAttribute(uri, PK11URI_PATTR_OBJECT); + if (label) { + (void)nssTrustDomain_GetCertsForNicknameFromCache(defaultTD, + label, + certList); + } else { + (void)nssTrustDomain_GetCertsFromCache(defaultTD, certList); + } + + transfer_uri_certs_to_collection(certList, uri, collection); + + /* add the CKA_CLASS and CKA_TOKEN attributes manually */ + attributes[nattributes].type = CKA_CLASS; + attributes[nattributes].pValue = (void *)&s_class; + attributes[nattributes].ulValueLen = sizeof(s_class); + nattributes++; + + attributes[nattributes].type = CKA_TOKEN; + attributes[nattributes].pValue = (void *)&s_true; + attributes[nattributes].ulValueLen = sizeof(s_true); + nattributes++; + + if (label) { + attributes[nattributes].type = CKA_LABEL; + attributes[nattributes].pValue = (void *)label; + attributes[nattributes].ulValueLen = strlen(label); + nattributes++; + } + + id = PK11URI_GetPathAttributeItem(uri, PK11URI_PATTR_ID); + if (id) { + attributes[nattributes].type = CKA_ID; + attributes[nattributes].pValue = (void *)id->data; + attributes[nattributes].ulValueLen = id->len; + nattributes++; + } + + tokens = NSSTrustDomain_FindTokensByURI(defaultTD, uri); + for (tok = tokens; tok && *tok; tok++) { + if (nssToken_IsPresent(*tok)) { + slotinfo = (*tok)->pk11slot; + + rv = pk11_AuthenticateUnfriendly(slotinfo, PR_TRUE, wincx); + if (rv != SECSuccess) { + continue; + } + instances = nssToken_FindObjectsByTemplate(*tok, NULL, + attributes, + nattributes, + 0, &status); + nssPKIObjectCollection_AddInstances(collection, instances, 0); + nss_ZFreeIf(instances); + } + (void)nssToken_Destroy(*tok); + } + nss_ZFreeIf(tokens); + nssList_Destroy(certList); + certs = nssPKIObjectCollection_GetCertificates(collection, NULL, 0, NULL); + +loser: + if (collection) { + nssPKIObjectCollection_Destroy(collection); + } + if (uri) { + PK11URI_DestroyURI(uri); + } + return certs; +} + +CERTCertificate * +PK11_FindCertFromURI(const char *uri, void *wincx) +{ + static const NSSUsage usage = { PR_TRUE /* ... */ }; + NSSCertificate *cert = NULL; + NSSCertificate **certs = NULL; + CERTCertificate *rvCert = NULL; + + certs = find_certs_from_uri(uri, wincx); + if (certs) { + cert = nssCertificateArray_FindBestCertificate(certs, NULL, + &usage, NULL); + if (cert) { + rvCert = STAN_GetCERTCertificateOrRelease(cert); + } + nssCertificateArray_Destroy(certs); + } + return rvCert; +} + +CERTCertList * +PK11_FindCertsFromURI(const char *uri, void *wincx) +{ + int i; + CERTCertList *certList = NULL; + NSSCertificate **foundCerts; + NSSCertificate *c; + + foundCerts = find_certs_from_uri(uri, wincx); + if (foundCerts) { + PRTime now = PR_Now(); + certList = CERT_NewCertList(); + for (i = 0, c = *foundCerts; c; c = foundCerts[++i]) { + if (certList) { + CERTCertificate *certCert = STAN_GetCERTCertificateOrRelease(c); + /* c may be invalid after this, don't reference it */ + if (certCert) { + /* CERT_AddCertToListSorted adopts certCert */ + CERT_AddCertToListSorted(certList, certCert, + CERT_SortCBValidity, &now); + } + } else { + nssCertificate_Destroy(c); + } + } + if (certList && CERT_LIST_HEAD(certList) == NULL) { + CERT_DestroyCertList(certList); + certList = NULL; + } + /* all the certs have been adopted or freed, free the raw array */ + nss_ZFreeIf(foundCerts); + } + return certList; +} + +static NSSCertificate ** +find_certs_from_nickname(const char *nickname, void *wincx) +{ + PRStatus status; + NSSCertificate **certs = NULL; + NSSToken *token = NULL; + NSSTrustDomain *defaultTD = STAN_GetDefaultTrustDomain(); + PK11SlotInfo *slot = NULL; + SECStatus rv; + char *nickCopy; + char *delimit = NULL; + char *tokenName; + + if (!PORT_Strncasecmp(nickname, "pkcs11:", strlen("pkcs11:"))) { + certs = find_certs_from_uri(nickname, wincx); + if (certs) + return certs; + } + nickCopy = PORT_Strdup(nickname); + if (!nickCopy) { + /* error code is set */ + return NULL; + } + if ((delimit = PORT_Strchr(nickCopy, ':')) != NULL) { + tokenName = nickCopy; + nickname = delimit + 1; + *delimit = '\0'; + /* find token by name */ + token = NSSTrustDomain_FindTokenByName(defaultTD, (NSSUTF8 *)tokenName); + if (token) { + slot = PK11_ReferenceSlot(token->pk11slot); + } else { + PORT_SetError(SEC_ERROR_NO_TOKEN); + } + *delimit = ':'; + } else { + slot = PK11_GetInternalKeySlot(); + token = PK11Slot_GetNSSToken(slot); + if (!token) { + PORT_SetError(SEC_ERROR_NO_TOKEN); + } + } + if (token) { + nssList *certList; + nssCryptokiObject **instances; + nssPKIObjectCollection *collection; + nssTokenSearchType tokenOnly = nssTokenSearchType_TokenOnly; + if (!PK11_IsPresent(slot)) { + goto loser; + } + rv = pk11_AuthenticateUnfriendly(slot, PR_TRUE, wincx); + if (rv != SECSuccess) { + goto loser; + } + collection = nssCertificateCollection_Create(defaultTD, NULL); + if (!collection) { + goto loser; + } + certList = nssList_Create(NULL, PR_FALSE); + if (!certList) { + nssPKIObjectCollection_Destroy(collection); + goto loser; + } + (void)nssTrustDomain_GetCertsForNicknameFromCache(defaultTD, + nickname, + certList); + transfer_token_certs_to_collection(certList, token, collection); + instances = nssToken_FindCertificatesByNickname(token, + NULL, + nickname, + tokenOnly, + 0, + &status); + nssPKIObjectCollection_AddInstances(collection, instances, 0); + nss_ZFreeIf(instances); + /* if it wasn't found, repeat the process for email address */ + if (nssPKIObjectCollection_Count(collection) == 0 && + PORT_Strchr(nickname, '@') != NULL) { + char *lowercaseName = CERT_FixupEmailAddr(nickname); + if (lowercaseName) { + (void)nssTrustDomain_GetCertsForEmailAddressFromCache(defaultTD, + lowercaseName, + certList); + transfer_token_certs_to_collection(certList, token, collection); + instances = nssToken_FindCertificatesByEmail(token, + NULL, + lowercaseName, + tokenOnly, + 0, + &status); + nssPKIObjectCollection_AddInstances(collection, instances, 0); + nss_ZFreeIf(instances); + PORT_Free(lowercaseName); + } + } + certs = nssPKIObjectCollection_GetCertificates(collection, + NULL, 0, NULL); + nssPKIObjectCollection_Destroy(collection); + nssList_Destroy(certList); + } +loser: + if (token) { + (void)nssToken_Destroy(token); + } + if (slot) { + PK11_FreeSlot(slot); + } + if (nickCopy) + PORT_Free(nickCopy); + return certs; +} + +CERTCertificate * +PK11_FindCertFromNickname(const char *nickname, void *wincx) +{ + CERTCertificate *rvCert = NULL; + NSSCertificate *cert = NULL; + NSSCertificate **certs = NULL; + static const NSSUsage usage = { PR_TRUE /* ... */ }; + + certs = find_certs_from_nickname(nickname, wincx); + if (certs) { + cert = nssCertificateArray_FindBestCertificate(certs, NULL, + &usage, NULL); + if (cert) { + rvCert = STAN_GetCERTCertificateOrRelease(cert); + } + nssCertificateArray_Destroy(certs); + } + return rvCert; +} + +/* Traverse slots callback */ +typedef struct FindCertsEmailArgStr { + char *email; + CERTCertList *certList; +} FindCertsEmailArg; + +SECStatus +FindCertsEmailCallback(CERTCertificate *cert, SECItem *item, void *arg) +{ + FindCertsEmailArg *cbparam = (FindCertsEmailArg *)arg; + const char *cert_email = CERT_GetFirstEmailAddress(cert); + PRBool found = PR_FALSE; + + /* Email address present in certificate? */ + if (cert_email == NULL) { + return SECSuccess; + } + + /* Parameter correctly set? */ + if (cbparam->email == NULL) { + return SECFailure; + } + + /* Loop over all email addresses */ + do { + if (!strcmp(cert_email, cbparam->email)) { + /* found one matching email address */ + PRTime now = PR_Now(); + found = PR_TRUE; + CERT_AddCertToListSorted(cbparam->certList, + CERT_DupCertificate(cert), + CERT_SortCBValidity, &now); + } + cert_email = CERT_GetNextEmailAddress(cert, cert_email); + } while (cert_email && !found); + + return SECSuccess; +} + +/* Find all certificates with matching email address */ +CERTCertList * +PK11_FindCertsFromEmailAddress(const char *email, void *wincx) +{ + FindCertsEmailArg cbparam; + SECStatus rv; + + cbparam.certList = CERT_NewCertList(); + if (cbparam.certList == NULL) { + return NULL; + } + + cbparam.email = CERT_FixupEmailAddr(email); + if (cbparam.email == NULL) { + CERT_DestroyCertList(cbparam.certList); + return NULL; + } + + rv = PK11_TraverseSlotCerts(FindCertsEmailCallback, &cbparam, NULL); + if (rv != SECSuccess) { + CERT_DestroyCertList(cbparam.certList); + PORT_Free(cbparam.email); + return NULL; + } + + /* empty list? */ + if (CERT_LIST_EMPTY(cbparam.certList)) { + CERT_DestroyCertList(cbparam.certList); + cbparam.certList = NULL; + } + + PORT_Free(cbparam.email); + return cbparam.certList; +} + +CERTCertList * +PK11_FindCertsFromNickname(const char *nickname, void *wincx) +{ + int i; + CERTCertList *certList = NULL; + NSSCertificate **foundCerts = NULL; + NSSCertificate *c; + + foundCerts = find_certs_from_nickname(nickname, wincx); + if (foundCerts) { + PRTime now = PR_Now(); + certList = CERT_NewCertList(); + for (i = 0, c = *foundCerts; c; c = foundCerts[++i]) { + if (certList) { + CERTCertificate *certCert = STAN_GetCERTCertificateOrRelease(c); + /* c may be invalid after this, don't reference it */ + if (certCert) { + /* CERT_AddCertToListSorted adopts certCert */ + CERT_AddCertToListSorted(certList, certCert, + CERT_SortCBValidity, &now); + } + } else { + nssCertificate_Destroy(c); + } + } + /* all the certs have been adopted or freed, free the raw array */ + nss_ZFreeIf(foundCerts); + } + return certList; +} + +/* + * extract a key ID for a certificate... + * NOTE: We call this function from PKCS11.c If we ever use + * pkcs11 to extract the public key (we currently do not), this will break. + */ +SECItem * +PK11_GetPubIndexKeyID(CERTCertificate *cert) +{ + SECKEYPublicKey *pubk; + SECItem *newItem = NULL; + + pubk = CERT_ExtractPublicKey(cert); + if (pubk == NULL) + return NULL; + + switch (pubk->keyType) { + case rsaKey: + newItem = SECITEM_DupItem(&pubk->u.rsa.modulus); + break; + case dsaKey: + newItem = SECITEM_DupItem(&pubk->u.dsa.publicValue); + break; + case dhKey: + newItem = SECITEM_DupItem(&pubk->u.dh.publicValue); + break; + case ecKey: + newItem = SECITEM_DupItem(&pubk->u.ec.publicValue); + break; + case fortezzaKey: + default: + newItem = NULL; /* Fortezza Fix later... */ + } + SECKEY_DestroyPublicKey(pubk); + /* make hash of it */ + return newItem; +} + +/* + * generate a CKA_ID from a certificate. + */ +SECItem * +pk11_mkcertKeyID(CERTCertificate *cert) +{ + SECItem *pubKeyData = PK11_GetPubIndexKeyID(cert); + SECItem *certCKA_ID; + + if (pubKeyData == NULL) + return NULL; + + certCKA_ID = PK11_MakeIDFromPubKey(pubKeyData); + SECITEM_FreeItem(pubKeyData, PR_TRUE); + return certCKA_ID; +} + +/* + * Write the cert into the token. + */ +SECStatus +PK11_ImportCert(PK11SlotInfo *slot, CERTCertificate *cert, + CK_OBJECT_HANDLE key, const char *nickname, + PRBool includeTrust) +{ + PRStatus status; + NSSCertificate *c; + nssCryptokiObject *keyobj, *certobj; + NSSToken *token = NULL; + char *emailAddr = NULL; + nssCertificateStoreTrace lockTrace = { NULL, NULL, PR_FALSE, PR_FALSE }; + nssCertificateStoreTrace unlockTrace = { NULL, NULL, PR_FALSE, PR_FALSE }; + SECItem *keyID = pk11_mkcertKeyID(cert); + if (keyID == NULL) { + goto loser; /* error code should be set already */ + } + token = PK11Slot_GetNSSToken(slot); + if (!token) { + PORT_SetError(SEC_ERROR_NO_TOKEN); + goto loser; + } + + if (PK11_IsInternal(slot) && cert->emailAddr && cert->emailAddr[0]) { + emailAddr = cert->emailAddr; + } + + /* need to get the cert as a stan cert */ + CERT_LockCertTempPerm(cert); + NSSCertificate *nssCert = cert->nssCertificate; + CERT_UnlockCertTempPerm(cert); + if (nssCert) { + c = nssCert; + } else { + c = STAN_GetNSSCertificate(cert); + if (c == NULL) { + goto loser; + } + } + + /* set the id for the cert */ + nssItem_Create(c->object.arena, &c->id, keyID->len, keyID->data); + if (!c->id.data) { + goto loser; + } + + if (key != CK_INVALID_HANDLE) { + /* create an object for the key, ... */ + keyobj = nss_ZNEW(NULL, nssCryptokiObject); + if (!keyobj) { + goto loser; + } + keyobj->token = nssToken_AddRef(token); + keyobj->handle = key; + keyobj->isTokenObject = PR_TRUE; + + /* ... in order to set matching attributes for the key */ + status = nssCryptokiPrivateKey_SetCertificate(keyobj, NULL, nickname, + &c->id, &c->subject); + nssCryptokiObject_Destroy(keyobj); + if (status != PR_SUCCESS) { + goto loser; + } + } + + /* do the token import */ + certobj = nssToken_ImportCertificate(token, NULL, + NSSCertificateType_PKIX, + &c->id, + nickname, + &c->encoding, + &c->issuer, + &c->subject, + &c->serial, + emailAddr, + PR_TRUE); + if (!certobj) { + if (NSS_GetError() == NSS_ERROR_INVALID_CERTIFICATE) { + PORT_SetError(SEC_ERROR_REUSED_ISSUER_AND_SERIAL); + SECITEM_FreeItem(keyID, PR_TRUE); + return SECFailure; + } + goto loser; + } + + if (c->object.cryptoContext) { + /* Delete the temp instance */ + NSSCryptoContext *cc = c->object.cryptoContext; + nssCertificateStore_Lock(cc->certStore, &lockTrace); + nssCertificateStore_RemoveCertLOCKED(cc->certStore, c); + nssCertificateStore_Unlock(cc->certStore, &lockTrace, &unlockTrace); + c->object.cryptoContext = NULL; + CERT_LockCertTempPerm(cert); + cert->istemp = PR_FALSE; + cert->isperm = PR_TRUE; + CERT_UnlockCertTempPerm(cert); + } + + /* add the new instance to the cert, force an update of the + * CERTCertificate, and finish + */ + nssPKIObject_AddInstance(&c->object, certobj); + /* nssTrustDomain_AddCertsToCache may release a reference to 'c' and + * replace 'c' with a different value. So we add a reference to 'c' to + * prevent 'c' from being destroyed. */ + nssCertificate_AddRef(c); + nssTrustDomain_AddCertsToCache(STAN_GetDefaultTrustDomain(), &c, 1); + (void)STAN_ForceCERTCertificateUpdate(c); + nssCertificate_Destroy(c); + SECITEM_FreeItem(keyID, PR_TRUE); + (void)nssToken_Destroy(token); + return SECSuccess; +loser: + if (token) { + (void)nssToken_Destroy(token); + } + CERT_MapStanError(); + SECITEM_FreeItem(keyID, PR_TRUE); + if (PORT_GetError() != SEC_ERROR_TOKEN_NOT_LOGGED_IN) { + PORT_SetError(SEC_ERROR_ADDING_CERT); + } + return SECFailure; +} + +SECStatus +PK11_ImportDERCert(PK11SlotInfo *slot, SECItem *derCert, + CK_OBJECT_HANDLE key, char *nickname, PRBool includeTrust) +{ + CERTCertificate *cert; + SECStatus rv; + + cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), + derCert, NULL, PR_FALSE, PR_TRUE); + if (cert == NULL) + return SECFailure; + + rv = PK11_ImportCert(slot, cert, key, nickname, includeTrust); + CERT_DestroyCertificate(cert); + return rv; +} + +/* + * return the private key From a given Cert + */ +SECKEYPrivateKey * +PK11_FindPrivateKeyFromCert(PK11SlotInfo *slot, CERTCertificate *cert, + void *wincx) +{ + int err; + CK_OBJECT_HANDLE certh; + CK_OBJECT_HANDLE keyh; + PRBool needLogin; + SECStatus rv; + + certh = PK11_FindCertInSlot(slot, cert, wincx); + if (certh == CK_INVALID_HANDLE) { + return NULL; + } + /* + * prevent a login race condition. If slot is logged in between + * our call to pk11_LoginStillRequired and the + * PK11_MatchItem. The matchItem call will either succeed, or + * we will call it one more time after calling PK11_Authenticate + * (which is a noop on an authenticated token). + */ + needLogin = pk11_LoginStillRequired(slot, wincx); + keyh = PK11_MatchItem(slot, certh, CKO_PRIVATE_KEY); + if ((keyh == CK_INVALID_HANDLE) && needLogin && + (SSL_ERROR_NO_CERTIFICATE == (err = PORT_GetError()) || + SEC_ERROR_TOKEN_NOT_LOGGED_IN == err)) { + /* try it again authenticated */ + rv = PK11_Authenticate(slot, PR_TRUE, wincx); + if (rv != SECSuccess) { + return NULL; + } + keyh = PK11_MatchItem(slot, certh, CKO_PRIVATE_KEY); + } + if (keyh == CK_INVALID_HANDLE) { + return NULL; + } + return PK11_MakePrivKey(slot, nullKey, PR_TRUE, keyh, wincx); +} + +/* + * import a cert for a private key we have already generated. Set the label + * on both to be the nickname. This is for the Key Gen, orphaned key case. + */ +PK11SlotInfo * +PK11_KeyForCertExists(CERTCertificate *cert, CK_OBJECT_HANDLE *keyPtr, + void *wincx) +{ + PK11SlotList *list; + PK11SlotListElement *le; + SECItem *keyID; + CK_OBJECT_HANDLE key; + PK11SlotInfo *slot = NULL; + SECStatus rv; + int err; + + keyID = pk11_mkcertKeyID(cert); + /* get them all! */ + list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, PR_FALSE, PR_TRUE, wincx); + if ((keyID == NULL) || (list == NULL)) { + if (keyID) + SECITEM_FreeItem(keyID, PR_TRUE); + if (list) + PK11_FreeSlotList(list); + return NULL; + } + + /* Look for the slot that holds the Key */ + for (le = list->head; le; le = le->next) { + /* + * prevent a login race condition. If le->slot is logged in between + * our call to pk11_LoginStillRequired and the + * pk11_FindPrivateKeyFromCertID, the find will either succeed, or + * we will call it one more time after calling PK11_Authenticate + * (which is a noop on an authenticated token). + */ + PRBool needLogin = pk11_LoginStillRequired(le->slot, wincx); + key = pk11_FindPrivateKeyFromCertID(le->slot, keyID); + if ((key == CK_INVALID_HANDLE) && needLogin && + (SSL_ERROR_NO_CERTIFICATE == (err = PORT_GetError()) || + SEC_ERROR_TOKEN_NOT_LOGGED_IN == err)) { + /* authenticate and try again */ + rv = PK11_Authenticate(le->slot, PR_TRUE, wincx); + if (rv != SECSuccess) + continue; + key = pk11_FindPrivateKeyFromCertID(le->slot, keyID); + } + if (key != CK_INVALID_HANDLE) { + slot = PK11_ReferenceSlot(le->slot); + if (keyPtr) + *keyPtr = key; + break; + } + } + + SECITEM_FreeItem(keyID, PR_TRUE); + PK11_FreeSlotList(list); + return slot; +} +/* + * import a cert for a private key we have already generated. Set the label + * on both to be the nickname. This is for the Key Gen, orphaned key case. + */ +PK11SlotInfo * +PK11_KeyForDERCertExists(SECItem *derCert, CK_OBJECT_HANDLE *keyPtr, + void *wincx) +{ + CERTCertificate *cert; + PK11SlotInfo *slot = NULL; + + /* letting this use go -- the only thing that the cert is used for is + * to get the ID attribute. + */ + cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL); + if (cert == NULL) + return NULL; + + slot = PK11_KeyForCertExists(cert, keyPtr, wincx); + CERT_DestroyCertificate(cert); + return slot; +} + +PK11SlotInfo * +PK11_ImportCertForKey(CERTCertificate *cert, const char *nickname, + void *wincx) +{ + PK11SlotInfo *slot = NULL; + CK_OBJECT_HANDLE key; + + slot = PK11_KeyForCertExists(cert, &key, wincx); + + if (slot) { + if (PK11_ImportCert(slot, cert, key, nickname, PR_FALSE) != SECSuccess) { + PK11_FreeSlot(slot); + slot = NULL; + } + } else { + PORT_SetError(SEC_ERROR_ADDING_CERT); + } + + return slot; +} + +PK11SlotInfo * +PK11_ImportDERCertForKey(SECItem *derCert, char *nickname, void *wincx) +{ + CERTCertificate *cert; + PK11SlotInfo *slot = NULL; + + cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), + derCert, NULL, PR_FALSE, PR_TRUE); + if (cert == NULL) + return NULL; + + slot = PK11_ImportCertForKey(cert, nickname, wincx); + CERT_DestroyCertificate(cert); + return slot; +} + +static CK_OBJECT_HANDLE +pk11_FindCertObjectByTemplate(PK11SlotInfo **slotPtr, + CK_ATTRIBUTE *searchTemplate, size_t count, void *wincx) +{ + PK11SlotList *list; + PK11SlotListElement *le; + CK_OBJECT_HANDLE certHandle = CK_INVALID_HANDLE; + PK11SlotInfo *slot = NULL; + SECStatus rv; + + *slotPtr = NULL; + + /* get them all! */ + list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, PR_FALSE, PR_TRUE, wincx); + if (list == NULL) { + return CK_INVALID_HANDLE; + } + + /* Look for the slot that holds the Key */ + for (le = list->head; le; le = le->next) { + rv = pk11_AuthenticateUnfriendly(le->slot, PR_TRUE, wincx); + if (rv != SECSuccess) + continue; + + certHandle = pk11_FindObjectByTemplate(le->slot, searchTemplate, count); + if (certHandle != CK_INVALID_HANDLE) { + slot = PK11_ReferenceSlot(le->slot); + break; + } + } + + PK11_FreeSlotList(list); + + if (slot == NULL) { + return CK_INVALID_HANDLE; + } + *slotPtr = slot; + return certHandle; +} + +CERTCertificate * +PK11_FindCertByIssuerAndSNOnToken(PK11SlotInfo *slot, + CERTIssuerAndSN *issuerSN, void *wincx) +{ + CERTCertificate *rvCert = NULL; + NSSCertificate *cert = NULL; + NSSDER issuer, serial; + NSSTrustDomain *td = STAN_GetDefaultTrustDomain(); + NSSToken *token = NULL; + nssSession *session; + nssCryptokiObject *instance = NULL; + nssPKIObject *object = NULL; + SECItem *derSerial; + PRStatus status; + + if (!issuerSN || !issuerSN->derIssuer.data || !issuerSN->derIssuer.len || + !issuerSN->serialNumber.data || !issuerSN->serialNumber.len || + issuerSN->derIssuer.len > CERT_MAX_DN_BYTES || + issuerSN->serialNumber.len > CERT_MAX_SERIAL_NUMBER_BYTES) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + token = PK11Slot_GetNSSToken(slot); + if (!token) { + PORT_SetError(SEC_ERROR_NO_TOKEN); + return NULL; + } + + session = nssToken_GetDefaultSession(token); /* non-owning */ + if (!session) { + (void)nssToken_Destroy(token); + return NULL; + } + + /* PKCS#11 needs to use DER-encoded serial numbers. Create a + * CERTIssuerAndSN that actually has the encoded value and pass that + * to PKCS#11 (and the crypto context). + */ + derSerial = SEC_ASN1EncodeItem(NULL, NULL, + &issuerSN->serialNumber, + SEC_ASN1_GET(SEC_IntegerTemplate)); + if (!derSerial) { + (void)nssToken_Destroy(token); + return NULL; + } + + NSSITEM_FROM_SECITEM(&issuer, &issuerSN->derIssuer); + NSSITEM_FROM_SECITEM(&serial, derSerial); + + instance = nssToken_FindCertificateByIssuerAndSerialNumber(token, session, + &issuer, &serial, nssTokenSearchType_TokenForced, &status); + + (void)nssToken_Destroy(token); + SECITEM_FreeItem(derSerial, PR_TRUE); + + if (!instance) { + goto loser; + } + object = nssPKIObject_Create(NULL, instance, td, NULL, nssPKIMonitor); + if (!object) { + goto loser; + } + instance = NULL; /* adopted by the previous call */ + cert = nssCertificate_Create(object); + if (!cert) { + goto loser; + } + object = NULL; /* adopted by the previous call */ + nssTrustDomain_AddCertsToCache(td, &cert, 1); + /* on failure, cert is freed below */ + rvCert = STAN_GetCERTCertificate(cert); + if (!rvCert) { + goto loser; + } + return rvCert; + +loser: + if (instance) { + nssCryptokiObject_Destroy(instance); + } + if (object) { + nssPKIObject_Destroy(object); + } + if (cert) { + nssCertificate_Destroy(cert); + } + return NULL; +} + +static PRCallOnceType keyIDHashCallOnce; + +static PRStatus PR_CALLBACK +pk11_keyIDHash_populate(void *wincx) +{ + CERTCertList *certList; + CERTCertListNode *node = NULL; + SECItem subjKeyID = { siBuffer, NULL, 0 }; + SECItem *slotid = NULL; + SECMODModuleList *modules, *mlp; + SECMODListLock *moduleLock; + int i; + + certList = PK11_ListCerts(PK11CertListUser, wincx); + if (!certList) { + return PR_FAILURE; + } + + for (node = CERT_LIST_HEAD(certList); + !CERT_LIST_END(node, certList); + node = CERT_LIST_NEXT(node)) { + if (CERT_FindSubjectKeyIDExtension(node->cert, + &subjKeyID) == SECSuccess && + subjKeyID.data != NULL) { + cert_AddSubjectKeyIDMapping(&subjKeyID, node->cert); + SECITEM_FreeItem(&subjKeyID, PR_FALSE); + } + } + CERT_DestroyCertList(certList); + + /* + * Record the state of each slot in a hash. The concatenation of slotID + * and moduleID is used as its key, with the slot series as its value. + */ + slotid = SECITEM_AllocItem(NULL, NULL, + sizeof(CK_SLOT_ID) + sizeof(SECMODModuleID)); + if (!slotid) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return PR_FAILURE; + } + moduleLock = SECMOD_GetDefaultModuleListLock(); + if (!moduleLock) { + SECITEM_FreeItem(slotid, PR_TRUE); + PORT_SetError(SEC_ERROR_NOT_INITIALIZED); + return PR_FAILURE; + } + SECMOD_GetReadLock(moduleLock); + modules = SECMOD_GetDefaultModuleList(); + for (mlp = modules; mlp; mlp = mlp->next) { + for (i = 0; i < mlp->module->slotCount; i++) { + memcpy(slotid->data, &mlp->module->slots[i]->slotID, + sizeof(CK_SLOT_ID)); + memcpy(&slotid->data[sizeof(CK_SLOT_ID)], &mlp->module->moduleID, + sizeof(SECMODModuleID)); + cert_UpdateSubjectKeyIDSlotCheck(slotid, + mlp->module->slots[i]->series); + } + } + SECMOD_ReleaseReadLock(moduleLock); + SECITEM_FreeItem(slotid, PR_TRUE); + + return PR_SUCCESS; +} + +/* + * We're looking for a cert which we have the private key for that's on the + * list of recipients. This searches one slot. + * this is the new version for NSS SMIME code + * this stuff should REALLY be in the SMIME code, but some things in here are not public + * (they should be!) + */ +static CERTCertificate * +pk11_FindCertObjectByRecipientNew(PK11SlotInfo *slot, NSSCMSRecipient **recipientlist, + int *rlIndex, void *pwarg) +{ + NSSCMSRecipient *ri = NULL; + int i; + PRBool tokenRescanDone = PR_FALSE; + CERTCertTrust trust; + + for (i = 0; (ri = recipientlist[i]) != NULL; i++) { + CERTCertificate *cert = NULL; + if (ri->kind == RLSubjKeyID) { + SECItem *derCert = cert_FindDERCertBySubjectKeyID(ri->id.subjectKeyID); + if (!derCert && !tokenRescanDone) { + /* + * We didn't find the cert by its key ID. If we have slots + * with removable tokens, a failure from + * cert_FindDERCertBySubjectKeyID doesn't necessarily imply + * that the cert is unavailable - the token might simply + * have been inserted after the initial run of + * pk11_keyIDHash_populate (wrapped by PR_CallOnceWithArg), + * or a different token might have been present in that + * slot, initially. Let's check for new tokens... + */ + PK11SlotList *sl = PK11_GetAllTokens(CKM_INVALID_MECHANISM, + PR_FALSE, PR_FALSE, pwarg); + if (sl) { + PK11SlotListElement *le; + SECItem *slotid = SECITEM_AllocItem(NULL, NULL, + sizeof(CK_SLOT_ID) + sizeof(SECMODModuleID)); + if (!slotid) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + PK11_FreeSlotList(sl); + return NULL; + } + for (le = sl->head; le; le = le->next) { + memcpy(slotid->data, &le->slot->slotID, + sizeof(CK_SLOT_ID)); + memcpy(&slotid->data[sizeof(CK_SLOT_ID)], + &le->slot->module->moduleID, + sizeof(SECMODModuleID)); + /* + * Any changes with the slot since our last check? + * If so, re-read the certs in that specific slot. + */ + if (cert_SubjectKeyIDSlotCheckSeries(slotid) != PK11_GetSlotSeries(le->slot)) { + CERTCertListNode *node = NULL; + SECItem subjKeyID = { siBuffer, NULL, 0 }; + CERTCertList *cl = PK11_ListCertsInSlot(le->slot); + if (!cl) { + continue; + } + for (node = CERT_LIST_HEAD(cl); + !CERT_LIST_END(node, cl); + node = CERT_LIST_NEXT(node)) { + if (CERT_IsUserCert(node->cert) && + CERT_FindSubjectKeyIDExtension(node->cert, + &subjKeyID) == SECSuccess) { + if (subjKeyID.data) { + cert_AddSubjectKeyIDMapping(&subjKeyID, + node->cert); + cert_UpdateSubjectKeyIDSlotCheck(slotid, + PK11_GetSlotSeries(le->slot)); + } + SECITEM_FreeItem(&subjKeyID, PR_FALSE); + } + } + CERT_DestroyCertList(cl); + } + } + PK11_FreeSlotList(sl); + SECITEM_FreeItem(slotid, PR_TRUE); + } + /* only check once per message/recipientlist */ + tokenRescanDone = PR_TRUE; + /* do another lookup (hopefully we found that cert...) */ + derCert = cert_FindDERCertBySubjectKeyID(ri->id.subjectKeyID); + } + if (derCert) { + cert = PK11_FindCertFromDERCertItem(slot, derCert, pwarg); + SECITEM_FreeItem(derCert, PR_TRUE); + } + } else { + cert = PK11_FindCertByIssuerAndSNOnToken(slot, ri->id.issuerAndSN, + pwarg); + } + if (cert) { + /* this isn't our cert */ + if (CERT_GetCertTrust(cert, &trust) != SECSuccess || + ((trust.emailFlags & CERTDB_USER) != CERTDB_USER)) { + CERT_DestroyCertificate(cert); + continue; + } + ri->slot = PK11_ReferenceSlot(slot); + *rlIndex = i; + return cert; + } + } + *rlIndex = -1; + return NULL; +} + +/* + * This function is the same as above, but it searches all the slots. + * this is the new version for NSS SMIME code + * this stuff should REALLY be in the SMIME code, but some things in here are not public + * (they should be!) + */ +static CERTCertificate * +pk11_AllFindCertObjectByRecipientNew(NSSCMSRecipient **recipientlist, void *wincx, int *rlIndex) +{ + PK11SlotList *list; + PK11SlotListElement *le; + CERTCertificate *cert = NULL; + SECStatus rv; + + /* get them all! */ + list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, PR_FALSE, PR_TRUE, wincx); + if (list == NULL) { + return CK_INVALID_HANDLE; + } + + /* Look for the slot that holds the Key */ + for (le = list->head; le; le = le->next) { + rv = pk11_AuthenticateUnfriendly(le->slot, PR_TRUE, wincx); + if (rv != SECSuccess) + continue; + + cert = pk11_FindCertObjectByRecipientNew(le->slot, + recipientlist, rlIndex, wincx); + if (cert) + break; + } + + PK11_FreeSlotList(list); + + return cert; +} + +/* + * We're looking for a cert which we have the private key for that's on the + * list of recipients. This searches one slot. + */ +static CERTCertificate * +pk11_FindCertObjectByRecipient(PK11SlotInfo *slot, + SEC_PKCS7RecipientInfo **recipientArray, + SEC_PKCS7RecipientInfo **rip, void *pwarg) +{ + SEC_PKCS7RecipientInfo *ri = NULL; + CERTCertTrust trust; + int i; + + for (i = 0; (ri = recipientArray[i]) != NULL; i++) { + CERTCertificate *cert; + + cert = PK11_FindCertByIssuerAndSNOnToken(slot, ri->issuerAndSN, + pwarg); + if (cert) { + /* this isn't our cert */ + if (CERT_GetCertTrust(cert, &trust) != SECSuccess || + ((trust.emailFlags & CERTDB_USER) != CERTDB_USER)) { + CERT_DestroyCertificate(cert); + continue; + } + *rip = ri; + return cert; + } + } + *rip = NULL; + return NULL; +} + +/* + * This function is the same as above, but it searches all the slots. + */ +static CERTCertificate * +pk11_AllFindCertObjectByRecipient(PK11SlotInfo **slotPtr, + SEC_PKCS7RecipientInfo **recipientArray, + SEC_PKCS7RecipientInfo **rip, + void *wincx) +{ + PK11SlotList *list; + PK11SlotListElement *le; + CERTCertificate *cert = NULL; + PK11SlotInfo *slot = NULL; + SECStatus rv; + + *slotPtr = NULL; + + /* get them all! */ + list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, PR_FALSE, PR_TRUE, wincx); + if (list == NULL) { + return CK_INVALID_HANDLE; + } + + *rip = NULL; + + /* Look for the slot that holds the Key */ + for (le = list->head; le; le = le->next) { + rv = pk11_AuthenticateUnfriendly(le->slot, PR_TRUE, wincx); + if (rv != SECSuccess) + continue; + + cert = pk11_FindCertObjectByRecipient(le->slot, recipientArray, + rip, wincx); + if (cert) { + slot = PK11_ReferenceSlot(le->slot); + break; + } + } + + PK11_FreeSlotList(list); + + if (slot == NULL) { + return NULL; + } + *slotPtr = slot; + PORT_Assert(cert != NULL); + return cert; +} + +/* + * We need to invert the search logic for PKCS 7 because if we search for + * each cert on the list over all the slots, we wind up with lots of spurious + * password prompts. This way we get only one password prompt per slot, at + * the max, and most of the time we can find the cert, and only prompt for + * the key... + */ +CERTCertificate * +PK11_FindCertAndKeyByRecipientList(PK11SlotInfo **slotPtr, + SEC_PKCS7RecipientInfo **array, + SEC_PKCS7RecipientInfo **rip, + SECKEYPrivateKey **privKey, void *wincx) +{ + CERTCertificate *cert = NULL; + + *privKey = NULL; + *slotPtr = NULL; + cert = pk11_AllFindCertObjectByRecipient(slotPtr, array, rip, wincx); + if (!cert) { + return NULL; + } + + *privKey = PK11_FindKeyByAnyCert(cert, wincx); + if (*privKey == NULL) { + goto loser; + } + + return cert; +loser: + if (cert) + CERT_DestroyCertificate(cert); + if (*slotPtr) + PK11_FreeSlot(*slotPtr); + *slotPtr = NULL; + return NULL; +} + +/* + * This is the new version of the above function for NSS SMIME code + * this stuff should REALLY be in the SMIME code, but some things in here are not public + * (they should be!) + */ +int +PK11_FindCertAndKeyByRecipientListNew(NSSCMSRecipient **recipientlist, void *wincx) +{ + CERTCertificate *cert; + NSSCMSRecipient *rl; + PRStatus rv; + int rlIndex; + + rv = PR_CallOnceWithArg(&keyIDHashCallOnce, pk11_keyIDHash_populate, wincx); + if (rv != PR_SUCCESS) + return -1; + + cert = pk11_AllFindCertObjectByRecipientNew(recipientlist, wincx, &rlIndex); + if (!cert) { + return -1; + } + + rl = recipientlist[rlIndex]; + + /* at this point, rl->slot is set */ + + rl->privkey = PK11_FindKeyByAnyCert(cert, wincx); + if (rl->privkey == NULL) { + goto loser; + } + + /* make a cert from the cert handle */ + rl->cert = cert; + return rlIndex; + +loser: + if (cert) + CERT_DestroyCertificate(cert); + if (rl->slot) + PK11_FreeSlot(rl->slot); + rl->slot = NULL; + return -1; +} + +CERTCertificate * +PK11_FindCertByIssuerAndSN(PK11SlotInfo **slotPtr, CERTIssuerAndSN *issuerSN, + void *wincx) +{ + CERTCertificate *rvCert = NULL; + NSSCertificate *cert; + NSSDER issuer, serial; + NSSCryptoContext *cc; + SECItem *derSerial; + + if (!issuerSN || !issuerSN->derIssuer.data || !issuerSN->derIssuer.len || + !issuerSN->serialNumber.data || !issuerSN->serialNumber.len || + issuerSN->derIssuer.len > CERT_MAX_DN_BYTES || + issuerSN->serialNumber.len > CERT_MAX_SERIAL_NUMBER_BYTES) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + if (slotPtr) + *slotPtr = NULL; + + /* PKCS#11 needs to use DER-encoded serial numbers. Create a + * CERTIssuerAndSN that actually has the encoded value and pass that + * to PKCS#11 (and the crypto context). + */ + derSerial = SEC_ASN1EncodeItem(NULL, NULL, + &issuerSN->serialNumber, + SEC_ASN1_GET(SEC_IntegerTemplate)); + if (!derSerial) { + return NULL; + } + + NSSITEM_FROM_SECITEM(&issuer, &issuerSN->derIssuer); + NSSITEM_FROM_SECITEM(&serial, derSerial); + + cc = STAN_GetDefaultCryptoContext(); + cert = NSSCryptoContext_FindCertificateByIssuerAndSerialNumber(cc, + &issuer, + &serial); + if (cert) { + SECITEM_FreeItem(derSerial, PR_TRUE); + return STAN_GetCERTCertificateOrRelease(cert); + } + + do { + /* free the old cert on retry. Associated slot was not present */ + if (rvCert) { + CERT_DestroyCertificate(rvCert); + rvCert = NULL; + } + + cert = NSSTrustDomain_FindCertificateByIssuerAndSerialNumber( + STAN_GetDefaultTrustDomain(), + &issuer, + &serial); + if (!cert) { + break; + } + + rvCert = STAN_GetCERTCertificateOrRelease(cert); + if (rvCert == NULL) { + break; + } + + /* Check to see if the cert's token is still there */ + } while (!PK11_IsPresent(rvCert->slot)); + + if (rvCert && slotPtr) + *slotPtr = PK11_ReferenceSlot(rvCert->slot); + + SECITEM_FreeItem(derSerial, PR_TRUE); + return rvCert; +} + +CK_OBJECT_HANDLE +PK11_FindObjectForCert(CERTCertificate *cert, void *wincx, PK11SlotInfo **pSlot) +{ + CK_OBJECT_HANDLE certHandle; + CK_OBJECT_CLASS certClass = CKO_CERTIFICATE; + CK_ATTRIBUTE *attr; + CK_ATTRIBUTE searchTemplate[] = { + { CKA_CLASS, NULL, 0 }, + { CKA_VALUE, NULL, 0 }, + }; + const size_t templateSize = sizeof(searchTemplate) / sizeof(searchTemplate[0]); + + attr = searchTemplate; + PK11_SETATTRS(attr, CKA_CLASS, &certClass, sizeof(certClass)); + attr++; + PK11_SETATTRS(attr, CKA_VALUE, cert->derCert.data, cert->derCert.len); + + if (cert->slot) { + certHandle = PK11_FindCertInSlot(cert->slot, cert, wincx); + if (certHandle != CK_INVALID_HANDLE) { + *pSlot = PK11_ReferenceSlot(cert->slot); + return certHandle; + } + } + + certHandle = pk11_FindCertObjectByTemplate(pSlot, searchTemplate, + templateSize, wincx); + if (certHandle != CK_INVALID_HANDLE) { + if (cert->slot == NULL) { + cert->slot = PK11_ReferenceSlot(*pSlot); + cert->pkcs11ID = certHandle; + cert->ownSlot = PR_TRUE; + cert->series = cert->slot->series; + } + } + + return (certHandle); +} + +SECKEYPrivateKey * +PK11_FindKeyByAnyCert(CERTCertificate *cert, void *wincx) +{ + CK_OBJECT_HANDLE certHandle; + CK_OBJECT_HANDLE keyHandle; + PK11SlotInfo *slot = NULL; + SECKEYPrivateKey *privKey = NULL; + PRBool needLogin; + SECStatus rv; + int err; + + certHandle = PK11_FindObjectForCert(cert, wincx, &slot); + if (certHandle == CK_INVALID_HANDLE) { + return NULL; + } + /* + * prevent a login race condition. If slot is logged in between + * our call to pk11_LoginStillRequired and the + * PK11_MatchItem. The matchItem call will either succeed, or + * we will call it one more time after calling PK11_Authenticate + * (which is a noop on an authenticated token). + */ + needLogin = pk11_LoginStillRequired(slot, wincx); + keyHandle = PK11_MatchItem(slot, certHandle, CKO_PRIVATE_KEY); + if ((keyHandle == CK_INVALID_HANDLE) && needLogin && + (SSL_ERROR_NO_CERTIFICATE == (err = PORT_GetError()) || + SEC_ERROR_TOKEN_NOT_LOGGED_IN == err)) { + /* authenticate and try again */ + rv = PK11_Authenticate(slot, PR_TRUE, wincx); + if (rv == SECSuccess) { + keyHandle = PK11_MatchItem(slot, certHandle, CKO_PRIVATE_KEY); + } + } + if (keyHandle != CK_INVALID_HANDLE) { + privKey = PK11_MakePrivKey(slot, nullKey, PR_TRUE, keyHandle, wincx); + } + if (slot) { + PK11_FreeSlot(slot); + } + return privKey; +} + +CK_OBJECT_HANDLE +pk11_FindPubKeyByAnyCert(CERTCertificate *cert, PK11SlotInfo **slot, void *wincx) +{ + CK_OBJECT_HANDLE certHandle; + CK_OBJECT_HANDLE keyHandle; + + certHandle = PK11_FindObjectForCert(cert, wincx, slot); + if (certHandle == CK_INVALID_HANDLE) { + return CK_INVALID_HANDLE; + } + keyHandle = PK11_MatchItem(*slot, certHandle, CKO_PUBLIC_KEY); + if (keyHandle == CK_INVALID_HANDLE) { + PK11_FreeSlot(*slot); + return CK_INVALID_HANDLE; + } + return keyHandle; +} + +/* + * find the number of certs in the slot with the same subject name + */ +int +PK11_NumberCertsForCertSubject(CERTCertificate *cert) +{ + CK_OBJECT_CLASS certClass = CKO_CERTIFICATE; + CK_ATTRIBUTE theTemplate[] = { + { CKA_CLASS, NULL, 0 }, + { CKA_SUBJECT, NULL, 0 }, + }; + CK_ATTRIBUTE *attr = theTemplate; + int templateSize = sizeof(theTemplate) / sizeof(theTemplate[0]); + + PK11_SETATTRS(attr, CKA_CLASS, &certClass, sizeof(certClass)); + attr++; + PK11_SETATTRS(attr, CKA_SUBJECT, cert->derSubject.data, cert->derSubject.len); + + if (cert->slot == NULL) { + PK11SlotList *list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, + PR_FALSE, PR_TRUE, NULL); + PK11SlotListElement *le; + int count = 0; + + if (!list) { + /* error code is set */ + return 0; + } + + /* loop through all the fortezza tokens */ + for (le = list->head; le; le = le->next) { + count += PK11_NumberObjectsFor(le->slot, theTemplate, templateSize); + } + PK11_FreeSlotList(list); + return count; + } + + return PK11_NumberObjectsFor(cert->slot, theTemplate, templateSize); +} + +/* + * Walk all the certs with the same subject + */ +SECStatus +PK11_TraverseCertsForSubject(CERTCertificate *cert, + SECStatus (*callback)(CERTCertificate *, void *), void *arg) +{ + if (!cert) { + return SECFailure; + } + if (cert->slot == NULL) { + PK11SlotList *list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, + PR_FALSE, PR_TRUE, NULL); + PK11SlotListElement *le; + + if (!list) { + /* error code is set */ + return SECFailure; + } + /* loop through all the tokens */ + for (le = list->head; le; le = le->next) { + PK11_TraverseCertsForSubjectInSlot(cert, le->slot, callback, arg); + } + PK11_FreeSlotList(list); + return SECSuccess; + } + + return PK11_TraverseCertsForSubjectInSlot(cert, cert->slot, callback, arg); +} + +SECStatus +PK11_TraverseCertsForSubjectInSlot(CERTCertificate *cert, PK11SlotInfo *slot, + SECStatus (*callback)(CERTCertificate *, void *), void *arg) +{ + PRStatus nssrv = PR_SUCCESS; + NSSToken *token; + NSSDER subject; + NSSTrustDomain *td; + nssList *subjectList; + nssPKIObjectCollection *collection; + nssCryptokiObject **instances; + NSSCertificate **certs; + nssTokenSearchType tokenOnly = nssTokenSearchType_TokenOnly; + td = STAN_GetDefaultTrustDomain(); + NSSITEM_FROM_SECITEM(&subject, &cert->derSubject); + token = PK11Slot_GetNSSToken(slot); + if (!token) { + return SECSuccess; + } + if (!nssToken_IsPresent(token)) { + (void)nssToken_Destroy(token); + return SECSuccess; + } + collection = nssCertificateCollection_Create(td, NULL); + if (!collection) { + (void)nssToken_Destroy(token); + return SECFailure; + } + subjectList = nssList_Create(NULL, PR_FALSE); + if (!subjectList) { + nssPKIObjectCollection_Destroy(collection); + (void)nssToken_Destroy(token); + return SECFailure; + } + (void)nssTrustDomain_GetCertsForSubjectFromCache(td, &subject, + subjectList); + transfer_token_certs_to_collection(subjectList, token, collection); + instances = nssToken_FindCertificatesBySubject(token, NULL, + &subject, + tokenOnly, 0, &nssrv); + nssPKIObjectCollection_AddInstances(collection, instances, 0); + nss_ZFreeIf(instances); + nssList_Destroy(subjectList); + certs = nssPKIObjectCollection_GetCertificates(collection, + NULL, 0, NULL); + nssPKIObjectCollection_Destroy(collection); + (void)nssToken_Destroy(token); + if (certs) { + CERTCertificate *oldie; + NSSCertificate **cp; + for (cp = certs; *cp; cp++) { + oldie = STAN_GetCERTCertificate(*cp); + if (!oldie) { + continue; + } + if ((*callback)(oldie, arg) != SECSuccess) { + nssrv = PR_FAILURE; + break; + } + } + nssCertificateArray_Destroy(certs); + } + return (nssrv == PR_SUCCESS) ? SECSuccess : SECFailure; +} + +SECStatus +PK11_TraverseCertsForNicknameInSlot(SECItem *nickname, PK11SlotInfo *slot, + SECStatus (*callback)(CERTCertificate *, void *), void *arg) +{ + PRStatus nssrv = PR_SUCCESS; + NSSToken *token; + NSSTrustDomain *td; + NSSUTF8 *nick; + PRBool created = PR_FALSE; + nssCryptokiObject **instances; + nssPKIObjectCollection *collection = NULL; + NSSCertificate **certs; + nssList *nameList = NULL; + nssTokenSearchType tokenOnly = nssTokenSearchType_TokenOnly; + token = PK11Slot_GetNSSToken(slot); + if (!token || !nssToken_IsPresent(token)) { + (void)nssToken_Destroy(token); + return SECSuccess; + } + if (nickname->data[nickname->len - 1] != '\0') { + nick = nssUTF8_Create(NULL, nssStringType_UTF8String, + nickname->data, nickname->len); + created = PR_TRUE; + } else { + nick = (NSSUTF8 *)nickname->data; + } + td = STAN_GetDefaultTrustDomain(); + collection = nssCertificateCollection_Create(td, NULL); + if (!collection) { + goto loser; + } + nameList = nssList_Create(NULL, PR_FALSE); + if (!nameList) { + goto loser; + } + (void)nssTrustDomain_GetCertsForNicknameFromCache(td, nick, nameList); + transfer_token_certs_to_collection(nameList, token, collection); + instances = nssToken_FindCertificatesByNickname(token, NULL, + nick, + tokenOnly, 0, &nssrv); + nssPKIObjectCollection_AddInstances(collection, instances, 0); + nss_ZFreeIf(instances); + nssList_Destroy(nameList); + certs = nssPKIObjectCollection_GetCertificates(collection, + NULL, 0, NULL); + nssPKIObjectCollection_Destroy(collection); + (void)nssToken_Destroy(token); + if (certs) { + CERTCertificate *oldie; + NSSCertificate **cp; + for (cp = certs; *cp; cp++) { + oldie = STAN_GetCERTCertificate(*cp); + if (!oldie) { + continue; + } + if ((*callback)(oldie, arg) != SECSuccess) { + nssrv = PR_FAILURE; + break; + } + } + nssCertificateArray_Destroy(certs); + } + if (created) + nss_ZFreeIf(nick); + return (nssrv == PR_SUCCESS) ? SECSuccess : SECFailure; +loser: + (void)nssToken_Destroy(token); + if (created) { + nss_ZFreeIf(nick); + } + if (collection) { + nssPKIObjectCollection_Destroy(collection); + } + if (nameList) { + nssList_Destroy(nameList); + } + return SECFailure; +} + +SECStatus +PK11_TraverseCertsInSlot(PK11SlotInfo *slot, + SECStatus (*callback)(CERTCertificate *, void *), void *arg) +{ + PRStatus nssrv; + NSSTrustDomain *td = STAN_GetDefaultTrustDomain(); + NSSToken *tok; + nssList *certList = NULL; + nssCryptokiObject **instances; + nssPKIObjectCollection *collection; + NSSCertificate **certs; + nssTokenSearchType tokenOnly = nssTokenSearchType_TokenOnly; + tok = PK11Slot_GetNSSToken(slot); + if (!tok) { + return SECSuccess; + } + if (!nssToken_IsPresent(tok)) { + (void)nssToken_Destroy(tok); + return SECSuccess; + } + collection = nssCertificateCollection_Create(td, NULL); + if (!collection) { + (void)nssToken_Destroy(tok); + return SECFailure; + } + certList = nssList_Create(NULL, PR_FALSE); + if (!certList) { + nssPKIObjectCollection_Destroy(collection); + (void)nssToken_Destroy(tok); + return SECFailure; + } + (void)nssTrustDomain_GetCertsFromCache(td, certList); + transfer_token_certs_to_collection(certList, tok, collection); + instances = nssToken_FindObjects(tok, NULL, CKO_CERTIFICATE, + tokenOnly, 0, &nssrv); + nssPKIObjectCollection_AddInstances(collection, instances, 0); + nss_ZFreeIf(instances); + nssList_Destroy(certList); + certs = nssPKIObjectCollection_GetCertificates(collection, + NULL, 0, NULL); + nssPKIObjectCollection_Destroy(collection); + (void)nssToken_Destroy(tok); + if (certs) { + CERTCertificate *oldie; + NSSCertificate **cp; + for (cp = certs; *cp; cp++) { + oldie = STAN_GetCERTCertificate(*cp); + if (!oldie) { + continue; + } + if ((*callback)(oldie, arg) != SECSuccess) { + nssrv = PR_FAILURE; + break; + } + } + nssCertificateArray_Destroy(certs); + } + return (nssrv == PR_SUCCESS) ? SECSuccess : SECFailure; +} + +/* + * return the certificate associated with a derCert + */ +CERTCertificate * +PK11_FindCertFromDERCert(PK11SlotInfo *slot, CERTCertificate *cert, + void *wincx) +{ + return PK11_FindCertFromDERCertItem(slot, &cert->derCert, wincx); +} + +CERTCertificate * +PK11_FindCertFromDERCertItem(PK11SlotInfo *slot, const SECItem *inDerCert, + void *wincx) + +{ + NSSDER derCert; + NSSToken *tok; + nssCryptokiObject *co = NULL; + SECStatus rv; + CERTCertificate *cert = NULL; + + NSSITEM_FROM_SECITEM(&derCert, inDerCert); + rv = pk11_AuthenticateUnfriendly(slot, PR_TRUE, wincx); + if (rv != SECSuccess) { + PK11_FreeSlot(slot); + return NULL; + } + + tok = PK11Slot_GetNSSToken(slot); + if (!tok) { + PK11_FreeSlot(slot); + return NULL; + } + co = nssToken_FindCertificateByEncodedCertificate(tok, NULL, &derCert, + nssTokenSearchType_TokenOnly, NULL); + (void)nssToken_Destroy(tok); + + if (co) { + cert = PK11_MakeCertFromHandle(slot, co->handle, NULL); + nssCryptokiObject_Destroy(co); + } + + return cert; +} + +/* + * import a cert for a private key we have already generated. Set the label + * on both to be the nickname. + */ +static CK_OBJECT_HANDLE +pk11_findKeyObjectByDERCert(PK11SlotInfo *slot, CERTCertificate *cert, + void *wincx) +{ + SECItem *keyID; + CK_OBJECT_HANDLE key; + SECStatus rv; + PRBool needLogin; + int err; + + if ((slot == NULL) || (cert == NULL)) { + return CK_INVALID_HANDLE; + } + + keyID = pk11_mkcertKeyID(cert); + if (keyID == NULL) { + return CK_INVALID_HANDLE; + } + + /* + * prevent a login race condition. If slot is logged in between + * our call to pk11_LoginStillRequired and the + * pk11_FindPrivateKeyFromCerID. The matchItem call will either succeed, or + * we will call it one more time after calling PK11_Authenticate + * (which is a noop on an authenticated token). + */ + needLogin = pk11_LoginStillRequired(slot, wincx); + key = pk11_FindPrivateKeyFromCertID(slot, keyID); + if ((key == CK_INVALID_HANDLE) && needLogin && + (SSL_ERROR_NO_CERTIFICATE == (err = PORT_GetError()) || + SEC_ERROR_TOKEN_NOT_LOGGED_IN == err)) { + /* authenticate and try again */ + rv = PK11_Authenticate(slot, PR_TRUE, wincx); + if (rv != SECSuccess) + goto loser; + key = pk11_FindPrivateKeyFromCertID(slot, keyID); + } + +loser: + SECITEM_ZfreeItem(keyID, PR_TRUE); + return key; +} + +SECKEYPrivateKey * +PK11_FindKeyByDERCert(PK11SlotInfo *slot, CERTCertificate *cert, + void *wincx) +{ + CK_OBJECT_HANDLE keyHandle; + + if ((slot == NULL) || (cert == NULL)) { + return NULL; + } + + keyHandle = pk11_findKeyObjectByDERCert(slot, cert, wincx); + if (keyHandle == CK_INVALID_HANDLE) { + return NULL; + } + + return PK11_MakePrivKey(slot, nullKey, PR_TRUE, keyHandle, wincx); +} + +SECStatus +PK11_ImportCertForKeyToSlot(PK11SlotInfo *slot, CERTCertificate *cert, + char *nickname, + PRBool addCertUsage, void *wincx) +{ + CK_OBJECT_HANDLE keyHandle; + + if ((slot == NULL) || (cert == NULL) || (nickname == NULL)) { + return SECFailure; + } + + keyHandle = pk11_findKeyObjectByDERCert(slot, cert, wincx); + if (keyHandle == CK_INVALID_HANDLE) { + return SECFailure; + } + + return PK11_ImportCert(slot, cert, keyHandle, nickname, addCertUsage); +} + +/* remove when the real version comes out */ +#define SEC_OID_MISSI_KEA 300 /* until we have v3 stuff merged */ +PRBool +KEAPQGCompare(CERTCertificate *server, CERTCertificate *cert) +{ + + /* not implemented */ + return PR_FALSE; +} + +PRBool +PK11_FortezzaHasKEA(CERTCertificate *cert) +{ + /* look at the subject and see if it is a KEA for MISSI key */ + SECOidData *oid; + CERTCertTrust trust; + + if (CERT_GetCertTrust(cert, &trust) != SECSuccess || + ((trust.sslFlags & CERTDB_USER) != CERTDB_USER)) { + return PR_FALSE; + } + + oid = SECOID_FindOID(&cert->subjectPublicKeyInfo.algorithm.algorithm); + if (!oid) { + return PR_FALSE; + } + + return (PRBool)((oid->offset == SEC_OID_MISSI_KEA_DSS_OLD) || + (oid->offset == SEC_OID_MISSI_KEA_DSS) || + (oid->offset == SEC_OID_MISSI_KEA)); +} + +/* + * Find a kea cert on this slot that matches the domain of it's peer + */ +static CERTCertificate + * + pk11_GetKEAMate(PK11SlotInfo *slot, CERTCertificate *peer) +{ + int i; + CERTCertificate *returnedCert = NULL; + + for (i = 0; i < slot->cert_count; i++) { + CERTCertificate *cert = slot->cert_array[i]; + + if (PK11_FortezzaHasKEA(cert) && KEAPQGCompare(peer, cert)) { + returnedCert = CERT_DupCertificate(cert); + break; + } + } + return returnedCert; +} + +/* + * The following is a FORTEZZA only Certificate request. We call this when we + * are doing a non-client auth SSL connection. We are only interested in the + * fortezza slots, and we are only interested in certs that share the same root + * key as the server. + */ +CERTCertificate * +PK11_FindBestKEAMatch(CERTCertificate *server, void *wincx) +{ + PK11SlotList *keaList = PK11_GetAllTokens(CKM_KEA_KEY_DERIVE, + PR_FALSE, PR_TRUE, wincx); + PK11SlotListElement *le; + CERTCertificate *returnedCert = NULL; + SECStatus rv; + + if (!keaList) { + /* error code is set */ + return NULL; + } + + /* loop through all the fortezza tokens */ + for (le = keaList->head; le; le = le->next) { + rv = PK11_Authenticate(le->slot, PR_TRUE, wincx); + if (rv != SECSuccess) + continue; + if (le->slot->session == CK_INVALID_HANDLE) { + continue; + } + returnedCert = pk11_GetKEAMate(le->slot, server); + if (returnedCert) + break; + } + PK11_FreeSlotList(keaList); + + return returnedCert; +} + +/* + * find a matched pair of kea certs to key exchange parameters from one + * fortezza card to another as necessary. + */ +SECStatus +PK11_GetKEAMatchedCerts(PK11SlotInfo *slot1, PK11SlotInfo *slot2, + CERTCertificate **cert1, CERTCertificate **cert2) +{ + CERTCertificate *returnedCert = NULL; + int i; + + for (i = 0; i < slot1->cert_count; i++) { + CERTCertificate *cert = slot1->cert_array[i]; + + if (PK11_FortezzaHasKEA(cert)) { + returnedCert = pk11_GetKEAMate(slot2, cert); + if (returnedCert != NULL) { + *cert2 = returnedCert; + *cert1 = CERT_DupCertificate(cert); + return SECSuccess; + } + } + } + return SECFailure; +} + +CK_OBJECT_HANDLE +PK11_FindEncodedCertInSlot(PK11SlotInfo *slot, SECItem *derCert, void *wincx) +{ + if (!slot || !derCert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + CK_OBJECT_CLASS certClass = CKO_CERTIFICATE; + CK_ATTRIBUTE theTemplate[] = { + { CKA_VALUE, NULL, 0 }, + { CKA_CLASS, NULL, 0 } + }; + const size_t tsize = sizeof(theTemplate) / sizeof(theTemplate[0]); + CK_ATTRIBUTE *attrs = theTemplate; + + PK11_SETATTRS(attrs, CKA_VALUE, derCert->data, derCert->len); + attrs++; + PK11_SETATTRS(attrs, CKA_CLASS, &certClass, sizeof(certClass)); + + SECStatus rv = pk11_AuthenticateUnfriendly(slot, PR_TRUE, wincx); + if (rv != SECSuccess) { + return CK_INVALID_HANDLE; + } + + return pk11_FindObjectByTemplate(slot, theTemplate, tsize); +} + +CK_OBJECT_HANDLE +PK11_FindCertInSlot(PK11SlotInfo *slot, CERTCertificate *cert, void *wincx) +{ + CK_OBJECT_HANDLE certh; + + if (cert->slot == slot) { + certh = cert->pkcs11ID; + if ((certh == CK_INVALID_HANDLE) || + (cert->series != slot->series)) { + certh = PK11_FindEncodedCertInSlot(slot, &cert->derCert, wincx); + cert->pkcs11ID = certh; + cert->series = slot->series; + } + } else { + certh = PK11_FindEncodedCertInSlot(slot, &cert->derCert, wincx); + } + return certh; +} + +/* Looking for PK11_GetKeyIDFromCert? + * Use PK11_GetLowLevelKeyIDForCert instead. + */ + +struct listCertsStr { + PK11CertListType type; + CERTCertList *certList; +}; + +static PRStatus +pk11ListCertCallback(NSSCertificate *c, void *arg) +{ + struct listCertsStr *listCertP = (struct listCertsStr *)arg; + CERTCertificate *newCert = NULL; + PK11CertListType type = listCertP->type; + CERTCertList *certList = listCertP->certList; + PRBool isUnique = PR_FALSE; + PRBool isCA = PR_FALSE; + char *nickname = NULL; + unsigned int certType; + SECStatus rv; + + if ((type == PK11CertListUnique) || (type == PK11CertListRootUnique) || + (type == PK11CertListCAUnique) || (type == PK11CertListUserUnique)) { + /* only list one instance of each certificate, even if several exist */ + isUnique = PR_TRUE; + } + if ((type == PK11CertListCA) || (type == PK11CertListRootUnique) || + (type == PK11CertListCAUnique)) { + isCA = PR_TRUE; + } + + /* if we want user certs and we don't have one skip this cert */ + if (((type == PK11CertListUser) || (type == PK11CertListUserUnique)) && + !NSSCertificate_IsPrivateKeyAvailable(c, NULL, NULL)) { + return PR_SUCCESS; + } + + /* PK11CertListRootUnique means we want CA certs without a private key. + * This is for legacy app support . PK11CertListCAUnique should be used + * instead to get all CA certs, regardless of private key + */ + if ((type == PK11CertListRootUnique) && + NSSCertificate_IsPrivateKeyAvailable(c, NULL, NULL)) { + return PR_SUCCESS; + } + + /* caller still owns the reference to 'c' */ + newCert = STAN_GetCERTCertificate(c); + if (!newCert) { + return PR_SUCCESS; + } + /* if we want CA certs and it ain't one, skip it */ + if (isCA && (!CERT_IsCACert(newCert, &certType))) { + return PR_SUCCESS; + } + if (isUnique) { + CERT_DupCertificate(newCert); + + nickname = STAN_GetCERTCertificateName(certList->arena, c); + + /* put slot certs at the end */ + if (newCert->slot && !PK11_IsInternal(newCert->slot)) { + rv = CERT_AddCertToListTailWithData(certList, newCert, nickname); + } else { + rv = CERT_AddCertToListHeadWithData(certList, newCert, nickname); + } + /* if we didn't add the cert to the list, don't leak it */ + if (rv != SECSuccess) { + CERT_DestroyCertificate(newCert); + } + } else { + /* add multiple instances to the cert list */ + nssCryptokiObject **ip; + nssCryptokiObject **instances = nssPKIObject_GetInstances(&c->object); + if (!instances) { + return PR_SUCCESS; + } + for (ip = instances; *ip; ip++) { + nssCryptokiObject *instance = *ip; + PK11SlotInfo *slot = instance->token->pk11slot; + + /* put the same CERTCertificate in the list for all instances */ + CERT_DupCertificate(newCert); + + nickname = STAN_GetCERTCertificateNameForInstance( + certList->arena, c, instance); + + /* put slot certs at the end */ + if (slot && !PK11_IsInternal(slot)) { + rv = CERT_AddCertToListTailWithData(certList, newCert, nickname); + } else { + rv = CERT_AddCertToListHeadWithData(certList, newCert, nickname); + } + /* if we didn't add the cert to the list, don't leak it */ + if (rv != SECSuccess) { + CERT_DestroyCertificate(newCert); + } + } + nssCryptokiObjectArray_Destroy(instances); + } + return PR_SUCCESS; +} + +CERTCertList * +PK11_ListCerts(PK11CertListType type, void *pwarg) +{ + NSSTrustDomain *defaultTD = STAN_GetDefaultTrustDomain(); + CERTCertList *certList = NULL; + struct listCertsStr listCerts; + certList = CERT_NewCertList(); + listCerts.type = type; + listCerts.certList = certList; + + /* authenticate to the slots */ + (void)pk11_TraverseAllSlots(NULL, NULL, PR_TRUE, pwarg); + NSSTrustDomain_TraverseCertificates(defaultTD, pk11ListCertCallback, + &listCerts); + return certList; +} + +SECItem * +PK11_GetLowLevelKeyIDForCert(PK11SlotInfo *slot, + CERTCertificate *cert, void *wincx) +{ + CK_OBJECT_HANDLE certHandle; + PK11SlotInfo *slotRef = NULL; + SECItem *item; + + if (slot) { + certHandle = PK11_FindCertInSlot(slot, cert, wincx); + } else { + certHandle = PK11_FindObjectForCert(cert, wincx, &slotRef); + if (certHandle == CK_INVALID_HANDLE) { + return pk11_mkcertKeyID(cert); + } + slot = slotRef; + } + + if (certHandle == CK_INVALID_HANDLE) { + return NULL; + } + + item = pk11_GetLowLevelKeyFromHandle(slot, certHandle); + if (slotRef) + PK11_FreeSlot(slotRef); + return item; +} + +/* argument type for listCertsCallback */ +typedef struct { + CERTCertList *list; + PK11SlotInfo *slot; +} ListCertsArg; + +static SECStatus +listCertsCallback(CERTCertificate *cert, void *arg) +{ + ListCertsArg *cdata = (ListCertsArg *)arg; + char *nickname = NULL; + nssCryptokiObject *instance, **ci; + nssCryptokiObject **instances; + NSSCertificate *c = STAN_GetNSSCertificate(cert); + SECStatus rv; + + if (c == NULL) { + return SECFailure; + } + instances = nssPKIObject_GetInstances(&c->object); + if (!instances) { + return SECFailure; + } + instance = NULL; + for (ci = instances; *ci; ci++) { + if ((*ci)->token->pk11slot == cdata->slot) { + instance = *ci; + break; + } + } + PORT_Assert(instance != NULL); + if (!instance) { + nssCryptokiObjectArray_Destroy(instances); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + nickname = STAN_GetCERTCertificateNameForInstance(cdata->list->arena, + c, instance); + nssCryptokiObjectArray_Destroy(instances); + + CERT_DupCertificate(cert); + rv = CERT_AddCertToListTailWithData(cdata->list, cert, nickname); + if (rv != SECSuccess) { + CERT_DestroyCertificate(cert); + } + return rv; +} + +CERTCertList * +PK11_ListCertsInSlot(PK11SlotInfo *slot) +{ + SECStatus status; + CERTCertList *certs; + ListCertsArg cdata; + + certs = CERT_NewCertList(); + if (certs == NULL) + return NULL; + cdata.list = certs; + cdata.slot = slot; + + status = PK11_TraverseCertsInSlot(slot, listCertsCallback, + &cdata); + + if (status != SECSuccess) { + CERT_DestroyCertList(certs); + certs = NULL; + } + + return certs; +} + +PK11SlotList * +PK11_GetAllSlotsForCert(CERTCertificate *cert, void *arg) +{ + nssCryptokiObject **ip; + PK11SlotList *slotList; + NSSCertificate *c; + nssCryptokiObject **instances; + PRBool found = PR_FALSE; + + if (!cert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + c = STAN_GetNSSCertificate(cert); + if (!c) { + CERT_MapStanError(); + return NULL; + } + + /* add multiple instances to the cert list */ + instances = nssPKIObject_GetInstances(&c->object); + if (!instances) { + PORT_SetError(SEC_ERROR_NO_TOKEN); + return NULL; + } + + slotList = PK11_NewSlotList(); + if (!slotList) { + nssCryptokiObjectArray_Destroy(instances); + return NULL; + } + + for (ip = instances; *ip; ip++) { + nssCryptokiObject *instance = *ip; + PK11SlotInfo *slot = instance->token->pk11slot; + if (slot) { + PK11_AddSlotToList(slotList, slot, PR_TRUE); + found = PR_TRUE; + } + } + if (!found) { + PK11_FreeSlotList(slotList); + PORT_SetError(SEC_ERROR_NO_TOKEN); + slotList = NULL; + } + + nssCryptokiObjectArray_Destroy(instances); + return slotList; +} + +/* + * Using __PK11_SetCertificateNickname is *DANGEROUS*. + * + * The API will update the NSS database, but it *will NOT* update the in-memory data. + * As a result, after calling this API, there will be INCONSISTENCY between + * in-memory data and the database. + * + * Use of the API should be limited to short-lived tools, which will exit immediately + * after using this API. + * + * If you ignore this warning, your process is TAINTED and will most likely misbehave. + */ +SECStatus +__PK11_SetCertificateNickname(CERTCertificate *cert, const char *nickname) +{ + /* Can't set nickname of temp cert. */ + if (!cert->slot || cert->pkcs11ID == CK_INVALID_HANDLE) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + return PK11_SetObjectNickname(cert->slot, cert->pkcs11ID, nickname); +} |