git: b9cbc85d7272 - main - nfs-over-tls: add user space daemons rpc.tlsclntd and rpc.tlsservd

Rick Macklem rmacklem at FreeBSD.org
Thu Feb 18 22:17:44 UTC 2021


The branch main has been updated by rmacklem:

URL: https://cgit.FreeBSD.org/src/commit/?id=b9cbc85d727214cf3e13196ab7e7564e53037f77

commit b9cbc85d727214cf3e13196ab7e7564e53037f77
Author:     Rick Macklem <rmacklem at FreeBSD.org>
AuthorDate: 2021-02-18 22:08:19 +0000
Commit:     Rick Macklem <rmacklem at FreeBSD.org>
CommitDate: 2021-02-18 22:15:03 +0000

    nfs-over-tls: add user space daemons rpc.tlsclntd and rpc.tlsservd
    
    The kernel changes needed for nfs-over-tls have been committed to main.
    However, nfs-over-tls requires user space daemons to handle the
    TLS handshake and other non-application data TLS records.
    There is one daemon (rpc.tlsclntd) for the client side and one daemon
    (rpc.tlsservd) for the server side, although they share a fair amount
    of code found in rpc.tlscommon.c and rpc.tlscommon.h.
    They use a KTLS enabled OpenSSL to perform the actual work and, as such,
    are only built when MK_OPENSSL_KTLS is set.
    Communication with the kernel is done via upcall RPCs done on AF_LOCAL
    sockets and the custom system call rpctls_syscall.
    
    Reviewed by:    gbe (man pages only), jhb (usr.sbin/Makefile only)
    Comments by:    jhb
    MFC after:      2 weeks
    Differential Revision:  https://reviews.freebsd.org/D28430
    Relnotes:       yes
---
 usr.sbin/Makefile                     |   2 +
 usr.sbin/rpc.tlsclntd/Makefile        |  29 ++
 usr.sbin/rpc.tlsclntd/rpc.tlsclntd.8  | 201 ++++++++
 usr.sbin/rpc.tlsclntd/rpc.tlsclntd.c  | 730 ++++++++++++++++++++++++++++
 usr.sbin/rpc.tlsservd/Makefile        |  29 ++
 usr.sbin/rpc.tlsservd/rpc.tlscommon.c | 295 +++++++++++
 usr.sbin/rpc.tlsservd/rpc.tlscommon.h |  68 +++
 usr.sbin/rpc.tlsservd/rpc.tlsservd.8  | 348 +++++++++++++
 usr.sbin/rpc.tlsservd/rpc.tlsservd.c  | 886 ++++++++++++++++++++++++++++++++++
 9 files changed, 2588 insertions(+)

diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile
index 39913a327b87..259ab72f2281 100644
--- a/usr.sbin/Makefile
+++ b/usr.sbin/Makefile
@@ -182,6 +182,8 @@ SUBDIR.${MK_NIS}+=	ypserv
 SUBDIR.${MK_NIS}+=	ypset
 SUBDIR.${MK_NTP}+=	ntp
 SUBDIR.${MK_OPENSSL}+=	keyserv
+SUBDIR.${MK_OPENSSL_KTLS}+=	rpc.tlsclntd
+SUBDIR.${MK_OPENSSL_KTLS}+=	rpc.tlsservd
 SUBDIR.${MK_PF}+=	ftp-proxy
 SUBDIR.${MK_PKGBOOTSTRAP}+=	pkg
 SUBDIR.${MK_PMC}+=	pmc pmcannotate pmccontrol pmcstat pmcstudy
diff --git a/usr.sbin/rpc.tlsclntd/Makefile b/usr.sbin/rpc.tlsclntd/Makefile
new file mode 100644
index 000000000000..1c8481a7889c
--- /dev/null
+++ b/usr.sbin/rpc.tlsclntd/Makefile
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG=	rpc.tlsclntd
+MAN=	rpc.tlsclntd.8
+SRCS=	rpc.tlsclntd.c rpc.tlscommon.c rpctlscd.h rpctlscd_svc.c rpctlscd_xdr.c
+
+CFLAGS+= -I. -I${SRCTOP}/usr.sbin/rpc.tlsservd
+
+LIBADD=	ssl crypto util
+
+CLEANFILES= rpctlscd_svc.c rpctlscd_xdr.c rpctlscd.h
+
+RPCSRC=	${SRCTOP}/sys/rpc/rpcsec_tls/rpctlscd.x
+RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -L -C -M
+
+rpctlscd_svc.c: ${RPCSRC} rpctlscd.h
+	${RPCGEN} -m -o ${.TARGET} ${RPCSRC}
+
+rpctlscd_xdr.c: ${RPCSRC} rpctlscd.h
+	${RPCGEN} -c -o ${.TARGET} ${RPCSRC}
+
+rpctlscd.h: ${RPCSRC}
+	${RPCGEN} -h -o ${.TARGET} ${RPCSRC}
+
+.PATH:	${SRCTOP}/sys/rpc/rpcsec_tls ${SRCTOP}/usr.sbin/rpc.tlsservd
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rpc.tlsclntd/rpc.tlsclntd.8 b/usr.sbin/rpc.tlsclntd/rpc.tlsclntd.8
new file mode 100644
index 000000000000..23a9d05495c1
--- /dev/null
+++ b/usr.sbin/rpc.tlsclntd/rpc.tlsclntd.8
@@ -0,0 +1,201 @@
+.\" Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+.\" Authors: Doug Rabson <dfr at rabson.org>
+.\" Developed with Red Inc: Alfred Perlstein <alfred at FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.\" Modified from gssd.8 for rpc.tlsclntd.8 by Rick Macklem.
+.Dd February 17, 2021
+.Dt RPC.TLSCLNTD 8
+.Os
+.Sh NAME
+.Nm rpc.tlsclntd
+.Nd "Sun RPC over TLS Client Daemon"
+.Sh SYNOPSIS
+.Nm
+.Op Fl C Ar preferred_ciphers
+.Op Fl D Ar certdir
+.Op Fl d
+.Op Fl l Ar CAfile
+.Op Fl m
+.Op Fl p Ar CApath
+.Op Fl r Ar CRLfile
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+program provides support for the client side of the kernel Sun RPC over TLS
+implementation.
+This daemon must be running for the kernel RPC to be able to do a TLS
+connection to a server for an NFS over TLS mount.
+This daemon requires that the kernel be built with
+.Dq options KERNEL_TLS
+and be running on an architecture such as
+.Dq amd64
+that supports a direct map (not i386) with
+.Xr ktls 4
+enabled.
+.Pp
+If either of the
+.Fl l
+or
+.Fl p
+options have been specified, the daemon will require the server's
+certificate to verify
+and have a Fully Qualified Domain Name (FQDN) in it.
+This FQDN must match
+the reverse DNS name for the IP address that
+the server is using for the TCP connection.
+The FQDN may be
+in either the DNS field of the subjectAltName or the CN field of the
+subjectName in the certificate and
+cannot have a wildcard
+.Dq *
+in it.
+.Pp
+If a SIGHUP signal is sent to the daemon it will reload the
+.Dq CRLfile
+and will shut down any extant connections that presented certificates
+during TLS handshake that have been revoked.
+If the
+.Fl r
+option was not specified, the SIGHUP signal will be ignored.
+.Pp
+The daemon will log failed certificate verifications via
+.Xr syslogd 8
+using LOG_INFO | LOG_DAEMON when the
+.Fl l
+or
+.Fl p
+option has been specified.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl C Ar preferred_ciphers , Fl Fl ciphers= Ns Ar preferred_ciphers
+Specify what preferred ciphers are to be used.
+If this option is specified,
+.Dq SSL_CTX_set_cipher_list()
+will be called with
+.Dq preferred_ciphers
+as the argument.
+If this option is not specified, the cipher will be chosen by
+.Xr ssl 7 .
+.It Fl D Ar certdir , Fl Fl certdir= Ns Ar certdir
+Use
+.Dq certdir
+instead of /etc/rpc.tlsclntd for the
+.Fl m
+option.
+.It Fl d , Fl Fl debuglevel
+Run in debug mode.
+In this mode,
+.Nm
+will not fork when it starts.
+.It Fl l Ar CAfile , Fl Fl verifylocs= Ns Ar CAfile
+This specifies the path name of a CAfile which holds the information
+for server certificate verification.
+This path name is used in
+.Dq SSL_CTX_load_verify_locations(ctx,CAfile,NULL)
+and
+.Dq SSL_CTX_set0_CA_list(ctx,SSL_load_client_CA_file(CAfile))
+openssl library calls.
+Note that this is a path name for the file and is not assumed to be
+in
+.Dq certdir .
+.It Fl m , Fl Fl mutualverf
+Enable support for mutual authentication.
+A certificate and associated key must be found in /etc/rpc.tlsclntd
+(or the directory specified by the
+.Fl D
+option)
+in case a server requests a peer certificate.
+The first certificate needs to be in a file named
+.Dq cert.pem
+and the associated key in a file named
+.Dq certkey.pem .
+The
+.Xr mount_nfs 8
+option
+.Fl tlscertname
+can be used to override the default certificate for a given
+NFS mount, where the files use the alternate naming specified by the option.
+If there is a passphrase on the
+.Dq certkey.pem
+file, this daemon will prompt for the passphrase during startup.
+The keys for alternate certificates cannot have passphrases.
+.It Fl p Ar CApath , Fl Fl verifydir= Ns Ar CApath
+This option is similar to the
+.Fl l
+option, but specifies the path of a directory with CA
+certificates in it.
+When this option is used,
+.Dq SSL_CTX_set0_CA_list(ctx,SSL_load_client_CA_file())
+is not called, so a list of CA names is not be passed
+to the server during the TLS handshake.
+The openssl documentation indicates this call is rarely needed.
+.It Fl r Ar CRLfile , Fl Fl crl= Ns Ar CRLfile
+This option specifies a Certificate Revocation List (CRL) file
+that is to be loaded into the verify certificate store and
+checked during verification of the server's certificate.
+This option is meaningless unless either the
+.Fl l
+or
+.Fl p
+have been specified.
+.It Fl v , Fl Fl verbose
+Run in verbose mode.
+In this mode,
+.Nm
+will log activity messages to syslog using LOG_INFO | LOG_DAEMON or to
+stderr, if the
+.Fl d
+option has also been specified.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr openssl 1 ,
+.Xr ktls 4 ,
+.Xr mount_nfs 8 ,
+.Xr rpc.tlsservd 8 ,
+.Xr ssl 7 ,
+.Xr syslogd 8
+.Sh STANDARDS
+The implementation is based on the specification in
+.Rs
+.%B "RFC NNNN"
+.%T "Towards Remote Procedure Call Encryption By Default"
+.Re
+.Sh HISTORY
+The
+.Nm
+manual page first appeared in
+.Fx 13.0 .
+.Sh BUGS
+This daemon cannot be safely shut down and restarted if there are
+any active RPC-over-TLS connections.
+Doing so will orphan the KERNEL_TLS connections, so that they
+can no longer do upcalls successfully, since the
+.Dq SSL *
+structures in userspace have been lost.
diff --git a/usr.sbin/rpc.tlsclntd/rpc.tlsclntd.c b/usr.sbin/rpc.tlsclntd/rpc.tlsclntd.c
new file mode 100644
index 000000000000..af803f203ffd
--- /dev/null
+++ b/usr.sbin/rpc.tlsclntd/rpc.tlsclntd.c
@@ -0,0 +1,730 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+ * Authors: Doug Rabson <dfr at rabson.org>
+ * Developed with Red Inc: Alfred Perlstein <alfred at freebsd.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Extensively modified from /usr/src/usr.sbin/gssd.c r344402 for
+ * the client side of kernel RPC-over-TLS by Rick Macklem.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/syslog.h>
+#include <sys/time.h>
+#include <err.h>
+#include <getopt.h>
+#include <libutil.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpc_com.h>
+#include <rpc/rpcsec_tls.h>
+
+#include <openssl/opensslconf.h>
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509v3.h>
+
+#include "rpctlscd.h"
+#include "rpc.tlscommon.h"
+
+#ifndef _PATH_RPCTLSCDSOCK
+#define _PATH_RPCTLSCDSOCK	"/var/run/rpc.tlsclntd.sock"
+#endif
+#ifndef	_PATH_CERTANDKEY
+#define	_PATH_CERTANDKEY	"/etc/rpc.tlsclntd/"
+#endif
+#ifndef	_PATH_RPCTLSCDPID
+#define	_PATH_RPCTLSCDPID	"/var/run/rpc.tlsclntd.pid"
+#endif
+
+/* Global variables also used by rpc.tlscommon.c. */
+int			rpctls_debug_level;
+bool			rpctls_verbose;
+SSL_CTX			*rpctls_ctx = NULL;
+const char		*rpctls_verify_cafile = NULL;
+const char		*rpctls_verify_capath = NULL;
+char			*rpctls_crlfile = NULL;
+bool			rpctls_cert = false;
+bool			rpctls_gothup = false;
+struct ssl_list		rpctls_ssllist;
+
+static struct pidfh	*rpctls_pfh = NULL;
+static const char	*rpctls_certdir = _PATH_CERTANDKEY;
+static const char	*rpctls_ciphers = NULL;
+static uint64_t		rpctls_ssl_refno = 0;
+static uint64_t		rpctls_ssl_sec = 0;
+static uint64_t		rpctls_ssl_usec = 0;
+
+static void		rpctlscd_terminate(int);
+static SSL_CTX		*rpctls_setupcl_ssl(void);
+static SSL		*rpctls_connect(SSL_CTX *ctx, int s, char *certname,
+			    u_int certlen, X509 **certp);
+static void		rpctls_huphandler(int sig __unused);
+
+extern void rpctlscd_1(struct svc_req *rqstp, SVCXPRT *transp);
+
+static struct option longopts[] = {
+	{ "certdir",		required_argument,	NULL,	'D' },
+	{ "ciphers",		required_argument,	NULL,	'C' },
+	{ "debuglevel",		no_argument,		NULL,	'd' },
+	{ "verifylocs",		required_argument,	NULL,	'l' },
+	{ "mutualverf",		no_argument,		NULL,	'm' },
+	{ "verifydir",		required_argument,	NULL,	'p' },
+	{ "crl",		required_argument,	NULL,	'r' },
+	{ "verbose",		no_argument,		NULL,	'v' },
+	{ NULL,			0,			NULL,	0  }
+};
+
+int
+main(int argc, char **argv)
+{
+	/*
+	 * We provide an RPC service on a local-domain socket. The
+	 * kernel rpctls code will upcall to this daemon to do the initial
+	 * TLS handshake.
+	 */
+	struct sockaddr_un sun;
+	int ch, fd, oldmask;
+	SVCXPRT *xprt;
+	bool tls_enable;
+	struct timeval tm;
+	struct timezone tz;
+	pid_t otherpid;
+	size_t tls_enable_len;
+
+	/* Check that another rpctlscd isn't already running. */
+	rpctls_pfh = pidfile_open(_PATH_RPCTLSCDPID, 0600, &otherpid);
+	if (rpctls_pfh == NULL) {
+		if (errno == EEXIST)
+			errx(1, "rpctlscd already running, pid: %d.", otherpid);
+		warn("cannot open or create pidfile");
+	}
+
+	/* Check to see that the ktls is enabled. */
+	tls_enable_len = sizeof(tls_enable);
+	if (sysctlbyname("kern.ipc.tls.enable", &tls_enable, &tls_enable_len,
+	    NULL, 0) != 0 || !tls_enable)
+		errx(1, "Kernel TLS not enabled");
+
+	/* Get the time when this daemon is started. */
+	gettimeofday(&tm, &tz);
+	rpctls_ssl_sec = tm.tv_sec;
+	rpctls_ssl_usec = tm.tv_usec;
+
+	rpctls_verbose = false;
+	while ((ch = getopt_long(argc, argv, "CD:dl:mp:r:v", longopts, NULL)) !=
+	    -1) {
+		switch (ch) {
+		case 'C':
+			rpctls_ciphers = optarg;
+			break;
+		case 'D':
+			rpctls_certdir = optarg;
+			break;
+		case 'd':
+			rpctls_debug_level++;
+			break;
+		case 'l':
+			rpctls_verify_cafile = optarg;
+			break;
+		case 'm':
+			rpctls_cert = true;
+			break;
+		case 'p':
+			rpctls_verify_capath = optarg;
+			break;
+		case 'r':
+			rpctls_crlfile = optarg;
+			break;
+		case 'v':
+			rpctls_verbose = true;
+			break;
+		default:
+			fprintf(stderr, "usage: %s "
+			    "[-C/--ciphers preferred_ciphers] "
+			    "[-D/--certdir certdir] [-d/--debuglevel] "
+			    "[-l/--verifylocs CAfile] [-m/--mutualverf] "
+			    "[-p/--verifydir CApath] [-r/--crl CRLfile] "
+			    "[-v/--verbose]\n", argv[0]);
+			exit(1);
+			break;
+		}
+	}
+	if (rpctls_crlfile != NULL && rpctls_verify_cafile == NULL &&
+	    rpctls_verify_capath == NULL)
+		errx(1, "-r requires the -l <CAfile> and/or "
+		    "-p <CApath> options");
+
+	if (modfind("krpc") < 0) {
+		/* Not present in kernel, try loading it */
+		if (kldload("krpc") < 0 || modfind("krpc") < 0)
+			errx(1, "Kernel RPC is not available");
+	}
+
+	/*
+	 * Set up the SSL_CTX *.
+	 * Do it now, before daemonizing, in case the private key
+	 * is encrypted and requires a passphrase to be entered.
+	 */
+	rpctls_ctx = rpctls_setupcl_ssl();
+	if (rpctls_ctx == NULL) {
+		if (rpctls_debug_level == 0) {
+			syslog(LOG_ERR, "Can't set up TLS context");
+			exit(1);
+		}
+		err(1, "Can't set up TLS context");
+	}
+	LIST_INIT(&rpctls_ssllist);
+
+	if (!rpctls_debug_level) {
+		if (daemon(0, 0) != 0)
+			err(1, "Can't daemonize");
+		signal(SIGINT, SIG_IGN);
+		signal(SIGQUIT, SIG_IGN);
+		signal(SIGHUP, SIG_IGN);
+	}
+	signal(SIGTERM, rpctlscd_terminate);
+	signal(SIGPIPE, SIG_IGN);
+	signal(SIGHUP, rpctls_huphandler);
+
+	pidfile_write(rpctls_pfh);
+
+	memset(&sun, 0, sizeof sun);
+	sun.sun_family = AF_LOCAL;
+	unlink(_PATH_RPCTLSCDSOCK);
+	strcpy(sun.sun_path, _PATH_RPCTLSCDSOCK);
+	sun.sun_len = SUN_LEN(&sun);
+	fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+	if (fd < 0) {
+		if (rpctls_debug_level == 0) {
+			syslog(LOG_ERR, "Can't create local rpctlscd socket");
+			exit(1);
+		}
+		err(1, "Can't create local rpctlscd socket");
+	}
+	oldmask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
+	if (bind(fd, (struct sockaddr *)&sun, sun.sun_len) < 0) {
+		if (rpctls_debug_level == 0) {
+			syslog(LOG_ERR, "Can't bind local rpctlscd socket");
+			exit(1);
+		}
+		err(1, "Can't bind local rpctlscd socket");
+	}
+	umask(oldmask);
+	if (listen(fd, SOMAXCONN) < 0) {
+		if (rpctls_debug_level == 0) {
+			syslog(LOG_ERR,
+			    "Can't listen on local rpctlscd socket");
+			exit(1);
+		}
+		err(1, "Can't listen on local rpctlscd socket");
+	}
+	xprt = svc_vc_create(fd, RPC_MAXDATASIZE, RPC_MAXDATASIZE);
+	if (!xprt) {
+		if (rpctls_debug_level == 0) {
+			syslog(LOG_ERR,
+			    "Can't create transport for local rpctlscd socket");
+			exit(1);
+		}
+		err(1, "Can't create transport for local rpctlscd socket");
+	}
+	if (!svc_reg(xprt, RPCTLSCD, RPCTLSCDVERS, rpctlscd_1, NULL)) {
+		if (rpctls_debug_level == 0) {
+			syslog(LOG_ERR,
+			    "Can't register service for local rpctlscd socket");
+			exit(1);
+		}
+		err(1, "Can't register service for local rpctlscd socket");
+	}
+
+	rpctls_syscall(RPCTLS_SYSC_CLSETPATH, _PATH_RPCTLSCDSOCK);
+
+	rpctls_svc_run();
+
+	rpctls_syscall(RPCTLS_SYSC_CLSHUTDOWN, "");
+
+	SSL_CTX_free(rpctls_ctx);
+	EVP_cleanup();
+	return (0);
+}
+
+bool_t
+rpctlscd_null_1_svc(__unused void *argp, __unused void *result,
+    __unused struct svc_req *rqstp)
+{
+
+	rpctls_verbose_out("rpctlscd_null: done\n");
+	return (TRUE);
+}
+
+bool_t
+rpctlscd_connect_1_svc(struct rpctlscd_connect_arg *argp,
+    struct rpctlscd_connect_res *result, __unused struct svc_req *rqstp)
+{
+	int s;
+	SSL *ssl;
+	struct ssl_entry *newslp;
+	X509 *cert;
+
+	rpctls_verbose_out("rpctlsd_connect: started\n");
+	/* Get the socket fd from the kernel. */
+	s = rpctls_syscall(RPCTLS_SYSC_CLSOCKET, "");
+	if (s < 0) {
+		result->reterr = RPCTLSERR_NOSOCKET;
+		return (TRUE);
+	}
+
+	/* Do a TLS connect handshake. */
+	ssl = rpctls_connect(rpctls_ctx, s, argp->certname.certname_val,
+	    argp->certname.certname_len, &cert);
+	if (ssl == NULL) {
+		rpctls_verbose_out("rpctlsd_connect: can't do TLS "
+		    "handshake\n");
+		result->reterr = RPCTLSERR_NOSSL;
+	} else {
+		result->reterr = RPCTLSERR_OK;
+		result->sec = rpctls_ssl_sec;
+		result->usec = rpctls_ssl_usec;
+		result->ssl = ++rpctls_ssl_refno;
+		/* Hard to believe this will ever wrap around.. */
+		if (rpctls_ssl_refno == 0)
+			result->ssl = ++rpctls_ssl_refno;
+	}
+
+	if (ssl == NULL) {
+		/*
+		 * For RPC-over-TLS, this upcall is expected
+		 * to close off the socket.
+		 */
+		close(s);
+		return (TRUE);
+	}
+
+	/* Maintain list of all current SSL *'s */
+	newslp = malloc(sizeof(*newslp));
+	newslp->refno = rpctls_ssl_refno;
+	newslp->s = s;
+	newslp->shutoff = false;
+	newslp->ssl = ssl;
+	newslp->cert = cert;
+	LIST_INSERT_HEAD(&rpctls_ssllist, newslp, next);
+	return (TRUE);
+}
+
+bool_t
+rpctlscd_handlerecord_1_svc(struct rpctlscd_handlerecord_arg *argp,
+    struct rpctlscd_handlerecord_res *result, __unused struct svc_req *rqstp)
+{
+	struct ssl_entry *slp;
+	int ret;
+	char junk;
+
+	slp = NULL;
+	if (argp->sec == rpctls_ssl_sec && argp->usec ==
+	    rpctls_ssl_usec) {
+		LIST_FOREACH(slp, &rpctls_ssllist, next) {
+			if (slp->refno == argp->ssl)
+				break;
+		}
+	}
+
+	if (slp != NULL) {
+		rpctls_verbose_out("rpctlscd_handlerecord fd=%d\n",
+		    slp->s);
+		/*
+		 * An SSL_read() of 0 bytes should fail, but it should
+		 * handle the non-application data record before doing so.
+		 */
+		ret = SSL_read(slp->ssl, &junk, 0);
+		if (ret <= 0) {
+			/* Check to see if this was a close alert. */
+			ret = SSL_get_shutdown(slp->ssl);
+			if ((ret & (SSL_SENT_SHUTDOWN |
+			    SSL_RECEIVED_SHUTDOWN)) == SSL_RECEIVED_SHUTDOWN)
+				SSL_shutdown(slp->ssl);
+		} else {
+			if (rpctls_debug_level == 0)
+				syslog(LOG_ERR, "SSL_read returned %d", ret);
+			else
+				fprintf(stderr, "SSL_read returned %d\n", ret);
+		}
+		result->reterr = RPCTLSERR_OK;
+	} else
+		result->reterr = RPCTLSERR_NOSSL;
+	return (TRUE);
+}
+
+bool_t
+rpctlscd_disconnect_1_svc(struct rpctlscd_disconnect_arg *argp,
+    struct rpctlscd_disconnect_res *result, __unused struct svc_req *rqstp)
+{
+	struct ssl_entry *slp;
+	int ret;
+
+	slp = NULL;
+	if (argp->sec == rpctls_ssl_sec && argp->usec ==
+	    rpctls_ssl_usec) {
+		LIST_FOREACH(slp, &rpctls_ssllist, next) {
+			if (slp->refno == argp->ssl)
+				break;
+		}
+	}
+
+	if (slp != NULL) {
+		rpctls_verbose_out("rpctlscd_disconnect: fd=%d closed\n",
+		    slp->s);
+		LIST_REMOVE(slp, next);
+		if (!slp->shutoff) {
+			ret = SSL_get_shutdown(slp->ssl);
+			/*
+			 * Do an SSL_shutdown() unless a close alert has
+			 * already been sent.
+			 */
+			if ((ret & SSL_SENT_SHUTDOWN) == 0)
+				SSL_shutdown(slp->ssl);
+		}
+		SSL_free(slp->ssl);
+		if (slp->cert != NULL)
+			X509_free(slp->cert);
+		/*
+		 * For RPC-over-TLS, this upcall is expected
+		 * to close off the socket.
+		 */
+		if (!slp->shutoff)
+			shutdown(slp->s, SHUT_WR);
+		close(slp->s);
+		free(slp);
+		result->reterr = RPCTLSERR_OK;
+	} else
+		result->reterr = RPCTLSERR_NOCLOSE;
+	return (TRUE);
+}
+
+int
+rpctlscd_1_freeresult(__unused SVCXPRT *transp, __unused xdrproc_t xdr_result,
+    __unused caddr_t result)
+{
+
+	return (TRUE);
+}
+
+static void
+rpctlscd_terminate(int sig __unused)
+{
+
+	rpctls_syscall(RPCTLS_SYSC_CLSHUTDOWN, "");
+	pidfile_remove(rpctls_pfh);
+	exit(0);
+}
+
+static SSL_CTX *
+rpctls_setupcl_ssl(void)
+{
+	SSL_CTX *ctx;
+	long flags;
+	char path[PATH_MAX];
+	size_t len, rlen;
+	int ret;
+
+	SSL_library_init();
+	SSL_load_error_strings();
+	OpenSSL_add_all_algorithms();
+
+	ctx = SSL_CTX_new(TLS_client_method());
+	if (ctx == NULL) {
+		rpctls_verbose_out("rpctls_setupcl_ssl: SSL_CTX_new "
+		    "failed\n");
+		return (NULL);
+	}
+	SSL_CTX_set_ecdh_auto(ctx, 1);
+
+	if (rpctls_ciphers != NULL) {
+		/*
+		 * Set preferred ciphers, since KERN_TLS only supports a
+		 * few of them.
+		 */
+		ret = SSL_CTX_set_cipher_list(ctx, rpctls_ciphers);
+		if (ret == 0) {
+			rpctls_verbose_out("rpctls_setupcl_ssl: "
+			    "SSL_CTX_set_cipher_list failed: %s\n",
+			    rpctls_ciphers);
+			SSL_CTX_free(ctx);
+			return (NULL);
+		}
+	}
+
+	/*
+	 * If rpctls_cert is true, a certificate and key exists in
+	 * rpctls_certdir, so that it can do mutual authentication.
+	 */
+	if (rpctls_cert) {
+		/* Get the cert.pem and certkey.pem files. */
+		len = strlcpy(path, rpctls_certdir, sizeof(path));
+		rlen = sizeof(path) - len;
+		if (strlcpy(&path[len], "cert.pem", rlen) != 8) {
+			SSL_CTX_free(ctx);
+			return (NULL);
+		}
+		ret = SSL_CTX_use_certificate_file(ctx, path,
+		    SSL_FILETYPE_PEM);
+		if (ret != 1) {
+			rpctls_verbose_out("rpctls_setupcl_ssl: can't use "
+			    "certificate file path=%s ret=%d\n", path, ret);
+			SSL_CTX_free(ctx);
+			return (NULL);
+		}
+		if (strlcpy(&path[len], "certkey.pem", rlen) != 11) {
+			SSL_CTX_free(ctx);
+			return (NULL);
+		}
+		ret = SSL_CTX_use_PrivateKey_file(ctx, path,
+		    SSL_FILETYPE_PEM);
+		if (ret != 1) {
+			rpctls_verbose_out("rpctls_setupcl_ssl: Can't use "
+			    "private key path=%s ret=%d\n", path, ret);
+			SSL_CTX_free(ctx);
+			return (NULL);
+		}
+	}
+
+	if (rpctls_verify_cafile != NULL || rpctls_verify_capath != NULL) {
+		if (rpctls_crlfile != NULL) {
+			ret = rpctls_loadcrlfile(ctx);
+			if (ret == 0) {
+				rpctls_verbose_out("rpctls_setupcl_ssl: "
+				    "Load CRLfile failed\n");
+				SSL_CTX_free(ctx);
+				return (NULL);
+			}
+		}
+#if OPENSSL_VERSION_NUMBER >= 0x30000000
+		ret = 1;
+		if (rpctls_verify_cafile != NULL)
+			ret = SSL_CTX_load_verify_file(ctx,
+			    rpctls_verify_cafile);
+		if (ret != 0 && rpctls_verify_capath != NULL)
+			ret = SSL_CTX_load_verify_dir(ctx,
+			    rpctls_verify_capath);
+#else
+		ret = SSL_CTX_load_verify_locations(ctx,
+		    rpctls_verify_cafile, rpctls_verify_capath);
+#endif
+		if (ret == 0) {
+			rpctls_verbose_out("rpctls_setupcl_ssl: "
+			    "Can't load verify locations\n");
+			SSL_CTX_free(ctx);
+			return (NULL);
+		}
+		/*
+		 * The man page says that the
+		 * SSL_CTX_set0_CA_list() call is not normally
+		 * needed, but I believe it is harmless.
+		 */
+		if (rpctls_verify_cafile != NULL)
+			SSL_CTX_set0_CA_list(ctx,
+			    SSL_load_client_CA_file(rpctls_verify_cafile));
+	}
+
+	/* RPC-over-TLS must use TLSv1.3, according to the IETF draft.*/
+#ifdef notyet
+	flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 |
+	    SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2;
+#else
+	flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1_3;
+#endif
+	SSL_CTX_set_options(ctx, flags);
+	SSL_CTX_clear_mode(ctx, SSL_MODE_NO_KTLS_TX | SSL_MODE_NO_KTLS_RX);
+	return (ctx);
+}
+
+static SSL *
+rpctls_connect(SSL_CTX *ctx, int s, char *certname, u_int certlen, X509 **certp)
+{
+	SSL *ssl;
+	X509 *cert;
+	struct sockaddr_storage ad;
+	struct sockaddr *sad;
+	char hostnam[NI_MAXHOST], path[PATH_MAX];
+	int gethostret, ret;
+	char *cp, *cp2;
+	size_t len, rlen;
+	long verfret;
+
+	*certp = NULL;
+	sad = (struct sockaddr *)&ad;
+	ssl = SSL_new(ctx);
+	if (ssl == NULL) {
+		rpctls_verbose_out("rpctls_connect: "
+		    "SSL_new failed\n");
+		return (NULL);
+	}
+	if (SSL_set_fd(ssl, s) != 1) {
+		rpctls_verbose_out("rpctls_connect: "
+		    "SSL_set_fd failed\n");
+		SSL_free(ssl);
+		return (NULL);
+	}
+
+	/*
+	 * If rpctls_cert is true and certname is set, a alternate certificate
+	 * and key exists in files named <certname>.pem and <certname>key.pem
+	 * in rpctls_certdir that is to be used for mutual authentication.
+	 */
+	if (rpctls_cert && certlen > 0) {
+		len = strlcpy(path, rpctls_certdir, sizeof(path));
+		rlen = sizeof(path) - len;
+		if (rlen <= certlen) {
+			SSL_free(ssl);
+			return (NULL);
+		}
+		memcpy(&path[len], certname, certlen);
+		rlen -= certlen;
+		len += certlen;
+		path[len] = '\0';
+		if (strlcpy(&path[len], ".pem", rlen) != 4) {
+			SSL_free(ssl);
+			return (NULL);
+		}
+		ret = SSL_use_certificate_file(ssl, path, SSL_FILETYPE_PEM);
+		if (ret != 1) {
+			rpctls_verbose_out("rpctls_connect: can't use "
+			    "certificate file path=%s ret=%d\n", path, ret);
+			SSL_free(ssl);
+			return (NULL);
+		}
+		if (strlcpy(&path[len], "key.pem", rlen) != 7) {
+			SSL_free(ssl);
+			return (NULL);
+		}
+		ret = SSL_use_PrivateKey_file(ssl, path, SSL_FILETYPE_PEM);
+		if (ret != 1) {
+			rpctls_verbose_out("rpctls_connect: Can't use "
+			    "private key path=%s ret=%d\n", path, ret);
+			SSL_free(ssl);
+			return (NULL);
+		}
+	}
+
+	ret = SSL_connect(ssl);
+	if (ret != 1) {
+		rpctls_verbose_out("rpctls_connect: "
+		    "SSL_connect failed %d\n",
+		    ret);
+		SSL_free(ssl);
+		return (NULL);
+	}
+
+	cert = SSL_get_peer_certificate(ssl);
+	if (cert == NULL) {
+		rpctls_verbose_out("rpctls_connect: get peer"
+		    " certificate failed\n");
+		SSL_free(ssl);
+		return (NULL);
+	}
+	gethostret = rpctls_gethost(s, sad, hostnam, sizeof(hostnam));
+	if (gethostret == 0)
+		hostnam[0] = '\0';
+	verfret = SSL_get_verify_result(ssl);
+	if (verfret == X509_V_OK && (rpctls_verify_cafile != NULL ||
+	    rpctls_verify_capath != NULL) && (gethostret == 0 ||
+	    rpctls_checkhost(sad, cert, X509_CHECK_FLAG_NO_WILDCARDS) != 1))
+		verfret = X509_V_ERR_HOSTNAME_MISMATCH;
+	if (verfret != X509_V_OK && (rpctls_verify_cafile != NULL ||
+	    rpctls_verify_capath != NULL)) {
+		if (verfret != X509_V_OK) {
+			cp = X509_NAME_oneline(X509_get_issuer_name(cert),
+			    NULL, 0);
+			cp2 = X509_NAME_oneline(X509_get_subject_name(cert),
+			    NULL, 0);
+			if (rpctls_debug_level == 0)
+				syslog(LOG_INFO | LOG_DAEMON,
+				    "rpctls_connect: client IP %s "
+				    "issuerName=%s subjectName=%s verify "
+				    "failed %s\n", hostnam, cp, cp2,
+				    X509_verify_cert_error_string(verfret));
*** 1700 LINES SKIPPED ***


More information about the dev-commits-src-main mailing list