Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 8 Mar 2020 18:19:09 +0000 (UTC)
From:      Rick Macklem <rmacklem@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-projects@freebsd.org
Subject:   svn commit: r358760 - projects/nfs-over-tls/usr.sbin/rpctlssd
Message-ID:  <202003081819.028IJ9N2005640@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: rmacklem
Date: Sun Mar  8 18:19:08 2020
New Revision: 358760
URL: https://svnweb.freebsd.org/changeset/base/358760

Log:
  Add options to handle client certificates for mutual authentication.
  
  This has only been tested with certificates that are signed by a site
  local CA. However, I think the options will handle trusted CAs as well.
  To do: add support for certificate revolkation.
  And the man page needs to be updated for both daemons.

Modified:
  projects/nfs-over-tls/usr.sbin/rpctlssd/rpctlssd.c

Modified: projects/nfs-over-tls/usr.sbin/rpctlssd/rpctlssd.c
==============================================================================
--- projects/nfs-over-tls/usr.sbin/rpctlssd/rpctlssd.c	Sun Mar  8 18:15:34 2020	(r358759)
+++ projects/nfs-over-tls/usr.sbin/rpctlssd/rpctlssd.c	Sun Mar  8 18:19:08 2020	(r358760)
@@ -33,16 +33,22 @@
 __FBSDID("$FreeBSD$");
 
 #include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/syslog.h>
 #include <err.h>
+#include <netdb.h>
 #include <signal.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdbool.h>
 #include <string.h>
 #include <unistd.h>
 
+#include <arpa/inet.h>
+
 #include <rpc/rpc.h>
 #include <rpc/rpc_com.h>
 #include <rpc/rpcsec_tls.h>
@@ -50,6 +56,7 @@ __FBSDID("$FreeBSD$");
 #include <openssl/bio.h>
 #include <openssl/ssl.h>
 #include <openssl/err.h>
+#include <openssl/x509v3.h>
 
 #include "rpctlssd.h"
 
@@ -61,16 +68,21 @@ __FBSDID("$FreeBSD$");
 #define	_PATH_CERTANDKEY	"/etc/rpctlssd/"
 #endif
 
-static int	rpctls_debug_level;
-static int	rpctls_verbose;
+static int		rpctls_debug_level;
+static bool		rpctls_verbose;
 static int	testnossl;
-static SSL_CTX	*rpctls_ctx = NULL;
-static char	*rpctls_cafiles = NULL;
-static char	*rpctls_verify_loc = NULL;
+static SSL_CTX		*rpctls_ctx = NULL;
+static bool		rpctls_do_mutual = false;
+static const char	*rpctls_verify_cafile = NULL;
+static const char	*rpctls_client_cafiles = NULL;
+static const char	*rpctls_certdir = _PATH_CERTANDKEY;
+static bool		rpctls_comparehost = false;
 
-static void	rpctlssd_terminate(int);
-static SSL_CTX	*rpctls_setup_ssl(char *certdir);
-static SSL	*rpctls_server(SSL_CTX *ctx, int s);
+static void		rpctlssd_terminate(int);
+static SSL_CTX		*rpctls_setup_ssl(const char *certdir);
+static SSL		*rpctls_server(SSL_CTX *ctx, int s,
+			    uint32_t *flags);
+static int		rpctls_checkhost(int s, X509 *cert);
 
 extern void rpctlssd_1(struct svc_req *rqstp, SVCXPRT *transp);
 extern int gssd_syscall(const char *path);
@@ -88,38 +100,43 @@ main(int argc, char **argv)
 	SVCXPRT *xprt;
 
 	debug = 0;
-	rpctls_verbose = 0;
+	rpctls_verbose = false;
 	testnossl = 0;
-	while ((ch = getopt(argc, argv, "c:dl:tv")) != -1) {
+	while ((ch = getopt(argc, argv, "C:D:dhl:mtv")) != -1) {
 		switch (ch) {
-		case 'c':
-			rpctls_cafiles = optarg;
+		case 'C':
+			rpctls_client_cafiles = optarg;
 			break;
+		case 'D':
+			rpctls_certdir = optarg;
+			break;
 		case 'd':
 			rpctls_debug_level++;
 			break;
+		case 'h':
+			rpctls_comparehost = true;
+			break;
 		case 'l':
-			rpctls_verify_loc = optarg;
+			rpctls_verify_cafile = optarg;
 			break;
+		case 'm':
+			rpctls_do_mutual = true;
+			break;
 		case 't':
 			testnossl = 1;
 			break;
 		case 'v':
-			rpctls_verbose = 1;
+			rpctls_verbose = true;
 			break;
 		default:
-			fprintf(stderr, "usage: %s [-c <cafile>] [-d] "
-			    "[-l <verify locations>] [-v]\n", argv[0]);
+			fprintf(stderr, "usage: %s [-C client_calist] "
+			    "[-D certdir] [-d] [-h] "
+			    "[-l verify_locations_file] "
+			    "[-m] [-v]\n", argv[0]);
 			exit(1);
 			break;
 		}
 	}
-	if ((rpctls_cafiles != NULL && rpctls_verify_loc == NULL) ||
-	    (rpctls_cafiles == NULL && rpctls_verify_loc != NULL)) {
-		fprintf(stderr, "usage: %s [-c <cafile>] [-d] "
-		    "[-l <verify locations>] [-v]\n", argv[0]);
-		exit(1);
-	}
 
 	if (rpctls_debug_level == 0) {
 		if (daemon(0, 0) != 0)
@@ -179,7 +196,7 @@ main(int argc, char **argv)
 		err(1, "Can't register service for local rpctlssd socket");
 	}
 
-	rpctls_ctx = rpctls_setup_ssl(_PATH_CERTANDKEY);
+	rpctls_ctx = rpctls_setup_ssl(rpctls_certdir);
 	if (rpctls_ctx == NULL) {
 		if (rpctls_debug_level == 0) {
 			syslog(LOG_ERR, "Can't create SSL context");
@@ -202,7 +219,7 @@ rpctlssd_verbose_out(const char *fmt, ...)
 {
 	va_list ap;
 
-	if (rpctls_verbose != 0) {
+	if (rpctls_verbose) {
 		va_start(ap, fmt);
 		if (rpctls_debug_level == 0)
 			vsyslog(LOG_INFO | LOG_DAEMON, fmt, ap);
@@ -221,12 +238,15 @@ rpctlssd_null_1_svc(void *argp, void *result, struct s
 }
 
 bool_t
-rpctlssd_connect_1_svc(void *argp, void *result, struct svc_req *rqstp)
+rpctlssd_connect_1_svc(void *argp,
+    struct rpctlssd_connect_res *result, struct svc_req *rqstp)
 {
 	int s;
 	SSL *ssl;
+	uint32_t flags;
 
 	rpctlssd_verbose_out("rpctlsd_connect_svc: started\n");
+	memset(result, 0, sizeof(*result));
 	/* Get the socket fd from the kernel. */
 	s = gssd_syscall("E");
 rpctlssd_verbose_out("rpctlsd_connect_svc s=%d\n", s);
@@ -235,13 +255,15 @@ rpctlssd_verbose_out("rpctlsd_connect_svc s=%d\n", s);
 
 	if (testnossl == 0) {
 		/* Do the server side of a TLS handshake. */
-		ssl = rpctls_server(rpctls_ctx, s);
+		ssl = rpctls_server(rpctls_ctx, s, &flags);
 		if (ssl == NULL)
-			rpctlssd_verbose_out("rpctlssd_connect_svc: ssl accept "
-			    "failed\n");
-		else
+			rpctlssd_verbose_out("rpctlssd_connect_svc: ssl "
+			    "accept failed\n");
+		else {
 			rpctlssd_verbose_out("rpctlssd_connect_svc: "
-			    "succeeded\n");
+			    "succeeded flags=0x%x\n", flags);
+			result->flags = flags;
+		}
 	}
 
 	/* Done with socket fd, so let the kernel know. */
@@ -266,8 +288,16 @@ rpctlssd_terminate(int sig __unused)
 	exit(0);
 }
 
+/* Allow the handshake to proceed. */
+static int
+rpctls_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
+{
+
+	return (1);
+}
+
 static SSL_CTX *
-rpctls_setup_ssl(char *certdir)
+rpctls_setup_ssl(const char *certdir)
 {
 	SSL_CTX *ctx;
 	char path[PATH_MAX];
@@ -310,32 +340,36 @@ rpctls_setup_ssl(char *certdir)
 	}
 
 	/* Set Mutual authentication, as required. */
-	if (rpctls_cafiles != NULL && rpctls_verify_loc != NULL) {
-		rpctlssd_verbose_out("rpctls_setup_ssl: set mutual "
-		    "authentication cafiles=%s verf_loc=%s\n", rpctls_cafiles,
-		    rpctls_verify_loc);
-		ret = SSL_CTX_load_verify_locations(ctx, rpctls_verify_loc,
-		    NULL);
-		if (ret != 1) {
-			rpctlssd_verbose_out("rpctls_setup_ssl: Can't load "
-			    "verify locations\n");
-			SSL_CTX_free(ctx);
-			return (NULL);
+	if (rpctls_do_mutual) {
+		rpctlssd_verbose_out("rpctls_setup_ssl: set mutual\n");
+		if (rpctls_verify_cafile != NULL) {
+			ret = SSL_CTX_load_verify_locations(ctx,
+			    rpctls_verify_cafile, NULL);
+			if (ret != 1) {
+				rpctlssd_verbose_out("rpctls_setup_ssl: "
+				    "Can't load verify locations\n");
+				SSL_CTX_free(ctx);
+				return (NULL);
+			}
 		}
-		SSL_CTX_set_client_CA_list(ctx,
-		    SSL_load_client_CA_file(rpctls_cafiles));
-		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER |
-		    SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
+		if (rpctls_client_cafiles != NULL)
+			SSL_CTX_set_client_CA_list(ctx,
+			    SSL_load_client_CA_file(rpctls_client_cafiles));
+		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER,
+		    rpctls_verify_callback);
 	}
 	return (ctx);
 }
 
 static SSL *
-rpctls_server(SSL_CTX *ctx, int s)
+rpctls_server(SSL_CTX *ctx, int s, uint32_t *flags)
 {
 	SSL *ssl;
+	X509 *cert;
 	int ret;
+	char *cp;
 
+	*flags = 0;
 	ssl = SSL_new(ctx);
 	if (ssl == NULL) {
 		rpctlssd_verbose_out("rpctls_server: SSL_new failed\n");
@@ -348,11 +382,104 @@ rpctls_server(SSL_CTX *ctx, int s)
 	}
 	ret = SSL_accept(ssl);
 	if (ret != 1) {
-		rpctlssd_verbose_out("rpctls_server: SS_accept failed ret=%d\n",
-		    ret);
+		rpctlssd_verbose_out("rpctls_server: SSL_accept "
+		    "failed ret=%d\n", ret);
 		SSL_free(ssl);
 		return (NULL);
 	}
+	*flags |= RPCTLS_FLAGS_HANDSHAKE;
+	if (rpctls_do_mutual) {
+		cert = SSL_get_peer_certificate(ssl);
+		if (cert == NULL)
+			rpctlssd_verbose_out("rpctls_server: "
+			    "No peer certificate\n");
+		else {
+			cp = X509_NAME_oneline(X509_get_subject_name(cert),
+			    NULL, 0);
+			rpctlssd_verbose_out("rpctls_server: cert "
+			    "subjectName=%s\n", cp);
+			*flags |= RPCTLS_FLAGS_GOTCERT;
+			ret = SSL_get_verify_result(ssl);
+			rpctlssd_verbose_out("rpctls_server: get "
+			    "verify result=%d\n", ret);
+			if (ret ==
+			    X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
+			    ret == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN)
+				*flags |= RPCTLS_FLAGS_SELFSIGNED;
+			else if (ret == X509_V_OK) {
+				if (rpctls_comparehost) {
+					ret = rpctls_checkhost(s, cert);
+					if (ret != 1) {
+						*flags |=
+						    RPCTLS_FLAGS_DISABLED;
+						rpctlssd_verbose_out(
+						    "rpctls_server: "
+						    "checkhost "
+						    "failed\n");
+					}
+				}
+				*flags |= RPCTLS_FLAGS_VERIFIED;
+			}
+			X509_free(cert);
+		}
+	}
 	return (ssl);
+}
+
+/*
+ * Check a client IP address against any host address in the
+ * certificate.  Basically getpeername(2), getnameinfo(3) and
+ * X509_check_host().
+ */
+static int
+rpctls_checkhost(int s, X509 *cert)
+{
+	struct sockaddr *sad;
+	struct sockaddr_in *sin;
+	struct sockaddr_in6 *sin6;
+	struct sockaddr_storage ad;
+	char hostnam[NI_MAXHOST + 1], addrstr[INET6_ADDRSTRLEN + 1];
+	const char *cp;
+	socklen_t slen;
+	int ret;
+
+	sad = (struct sockaddr *)&ad;
+	slen = sizeof(ad);
+	if (getpeername(s, sad, &slen) < 0)
+		return (0);
+	switch (sad->sa_family) {
+	case AF_INET:
+		sin = (struct sockaddr_in *)sad;
+		cp = inet_ntop(sad->sa_family, &sin->sin_addr.s_addr,
+		    addrstr, sizeof(addrstr));
+		if (cp != NULL)
+			rpctlssd_verbose_out("rpctls_checkhost: "
+			    "peer ip %s\n", cp);
+		if (getnameinfo((const struct sockaddr *)sad,
+		    sizeof(struct sockaddr_in), hostnam,
+		    sizeof(hostnam), NULL, 0, NI_NAMEREQD) != 0)
+			return (0);
+		break;
+	case AF_INET6:
+		sin6 = (struct sockaddr_in6 *)sad;
+		cp = inet_ntop(sad->sa_family, &sin6->sin6_addr,
+		    addrstr, sizeof(addrstr));
+		if (cp != NULL)
+			rpctlssd_verbose_out("rpctls_checkhost: "
+			    "peer ip %s\n", cp);
+		if (getnameinfo((const struct sockaddr *)sad,
+		    sizeof(struct sockaddr_in6), hostnam,
+		    sizeof(hostnam), NULL, 0, NI_NAMEREQD) != 0)
+			return (0);
+		break;
+	default:
+		return (0);
+	}
+	rpctlssd_verbose_out("rpctls_checkhost: hostname %s\n",
+	    hostnam);
+	ret = X509_check_host(cert, hostnam, strlen(hostnam), 0, NULL);
+	rpctlssd_verbose_out("rpctls_checkhost: X509_check_host ret=%d\n",
+	    ret);
+	return (ret);
 }
 



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202003081819.028IJ9N2005640>