/* * NSS utility functions * * 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/. */ #include #include #include "prerror.h" #include "secitem.h" #include "prnetdb.h" #include "cert.h" #include "nspr.h" #include "secder.h" #include "keyhi.h" #include "nss.h" #include "ssl.h" #include "pk11func.h" /* for PK11_ function calls */ #include "sslimpl.h" /* convert a CERTDistNameStr to an array ascii strings. * we ignore caNames which we can't convert, so n could be less than nnames * n is always set, even on failure. * This function allows us to use the existing CERT_FilterCertListByCANames. */ static char ** ssl_DistNamesToStrings(struct CERTDistNamesStr *caNames, int *n) { char **names; int i; SECStatus rv; PLArenaPool *arena; *n = 0; names = PORT_ZNewArray(char *, caNames->nnames); if (names == NULL) { return NULL; } arena = PORT_NewArena(2048); if (arena == NULL) { PORT_Free(names); return NULL; } for (i = 0; i < caNames->nnames; ++i) { CERTName dn; rv = SEC_QuickDERDecodeItem(arena, &dn, SEC_ASN1_GET(CERT_NameTemplate), caNames->names + i); if (rv != SECSuccess) { continue; } names[*n] = CERT_NameToAscii(&dn); if (names[*n]) (*n)++; } PORT_FreeArena(arena, PR_FALSE); return names; } /* free the dist names we allocated in the above function. n must be the * returned n from that function. */ static void ssl_FreeDistNamesStrings(char **strings, int n) { int i; for (i = 0; i < n; i++) { PORT_Free(strings[i]); } PORT_Free(strings); } PRBool ssl_CertIsUsable(sslSocket *ss, CERTCertificate *cert) { SECStatus rv; SSLSignatureScheme scheme; if ((ss == NULL) || (cert == NULL)) { return PR_FALSE; } /* There are two ways of handling the old style handshake: * 1) check the actual record we are using and return true, * if (!ss->ssl3.hs.hashType == handshake_hash_record && * ss->ssl3.hs.hashType == handshake_hash_single) { * return PR_TRUE; * 2) assume if ss->ss->ssl3.hs.clientAuthSignatureSchemesLen == 0 we are using the * old handshake. * There is one case where using 2 will be wrong: we somehow call this * function outside the case where of out GetClientAuthData context. * In that case we don't know that the 'real' peerScheme list is, so the * best we can do is either always assume good or always assume bad. * I think the best results is to always assume good, so we use * option 2 here to handle that case as well.*/ if (ss->ssl3.hs.clientAuthSignatureSchemesLen == 0) { return PR_TRUE; } if (ss->ssl3.hs.clientAuthSignatureSchemes == NULL) { return PR_FALSE; /* should this really be an assert? */ } rv = ssl_PickClientSignatureScheme(ss, cert, NULL, ss->ssl3.hs.clientAuthSignatureSchemes, ss->ssl3.hs.clientAuthSignatureSchemesLen, &scheme); if (rv != SECSuccess) { return PR_FALSE; } return PR_TRUE; } SECStatus ssl_FilterClientCertListBySSLSocket(sslSocket *ss, CERTCertList *certList) { CERTCertListNode *node; CERTCertificate *cert; if (!certList) { return SECFailure; } node = CERT_LIST_HEAD(certList); while (!CERT_LIST_END(node, certList)) { cert = node->cert; if (PR_TRUE != ssl_CertIsUsable(ss, cert)) { /* cert doesn't match the socket criteria, remove it */ CERTCertListNode *freenode = node; node = CERT_LIST_NEXT(node); CERT_RemoveCertListNode(freenode); } else { /* this cert is good, go to the next cert */ node = CERT_LIST_NEXT(node); } } return (SECSuccess); } /* This function can be called by the application's custom GetClientAuthHook * to filter out any certs in the cert list that doesn't match the negotiated * requirements of the current SSL connection. */ SECStatus SSL_FilterClientCertListBySocket(PRFileDesc *fd, CERTCertList *certList) { sslSocket *ss = ssl_FindSocket(fd); if (ss == NULL) { return SECFailure; } return ssl_FilterClientCertListBySSLSocket(ss, certList); } /* This function can be called by the application's custom GetClientAuthHook * to determine if a single certificate matches the negotiated requirements of * the current SSL connection. */ PRBool SSL_CertIsUsable(PRFileDesc *fd, CERTCertificate *cert) { sslSocket *ss = ssl_FindSocket(fd); if (ss == NULL) { return PR_FALSE; } return ssl_CertIsUsable(ss, cert); } /* * This callback used by SSL to pull client certificate upon * server request */ SECStatus NSS_GetClientAuthData(void *arg, PRFileDesc *fd, struct CERTDistNamesStr *caNames, struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey) { CERTCertificate *cert = NULL; CERTCertList *certList = NULL; SECKEYPrivateKey *privkey = NULL; char *chosenNickName = (char *)arg; /* CONST */ SECStatus rv = SECFailure; sslSocket *ss = ssl_FindSocket(fd); if (!ss) { return SECFailure; } void *pw_arg = SSL_RevealPinArg(fd); /* first, handle any token authentication that may be needed */ if (chosenNickName && pw_arg) { certList = PK11_FindCertsFromNickname(chosenNickName, pw_arg); if (certList) { CERT_FilterCertListForUserCerts(certList); rv = CERT_FilterCertListByUsage(certList, certUsageSSLClient, PR_FALSE); if ((rv != SECSuccess) || CERT_LIST_EMPTY(certList)) { CERT_DestroyCertList(certList); certList = NULL; } } } /* otherwise look through the cache based on usage * if chosenNickname is set, we ignore the expiration date */ if (certList == NULL) { certList = CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(), certUsageSSLClient, PR_FALSE, chosenNickName == NULL, pw_arg); if (certList == NULL) { return SECFailure; } /* filter only the certs that meet the nickname requirements */ if (chosenNickName) { rv = CERT_FilterCertListByNickname(certList, chosenNickName, pw_arg); } else { int nnames = 0; char **names = ssl_DistNamesToStrings(caNames, &nnames); rv = CERT_FilterCertListByCANames(certList, nnames, names, certUsageSSLClient); ssl_FreeDistNamesStrings(names, nnames); } if ((rv != SECSuccess) || CERT_LIST_EMPTY(certList)) { CERT_DestroyCertList(certList); return SECFailure; } } /* now remove any certs that can't meet the connection requirements */ rv = ssl_FilterClientCertListBySSLSocket(ss, certList); if ((rv != SECSuccess) || CERT_LIST_EMPTY(certList)) { // no certs left. CERT_DestroyCertList(certList); return SECFailure; } /* now return the top cert in the list. We've strived to make the * list ordered by the most likely usable cert, so it should be the best * match. */ cert = CERT_DupCertificate(CERT_LIST_HEAD(certList)->cert); CERT_DestroyCertList(certList); privkey = PK11_FindKeyByAnyCert(cert, pw_arg); if (privkey == NULL) { CERT_DestroyCertificate(cert); return SECFailure; } *pRetCert = cert; *pRetKey = privkey; return SECSuccess; }