summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/ssl/authcert.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/ssl/authcert.c')
-rw-r--r--security/nss/lib/ssl/authcert.c250
1 files changed, 250 insertions, 0 deletions
diff --git a/security/nss/lib/ssl/authcert.c b/security/nss/lib/ssl/authcert.c
new file mode 100644
index 0000000000..3d64173b41
--- /dev/null
+++ b/security/nss/lib/ssl/authcert.c
@@ -0,0 +1,250 @@
+/*
+ * 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;
+}