250 lines
8 KiB
C
250 lines
8 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <string.h>
|
|
#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;
|
|
}
|