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
This commit is contained in:
parent
c67a2909a6
commit
b9cbc85d72
@ -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
|
||||
|
29
usr.sbin/rpc.tlsclntd/Makefile
Normal file
29
usr.sbin/rpc.tlsclntd/Makefile
Normal file
@ -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>
|
201
usr.sbin/rpc.tlsclntd/rpc.tlsclntd.8
Normal file
201
usr.sbin/rpc.tlsclntd/rpc.tlsclntd.8
Normal file
@ -0,0 +1,201 @@
|
||||
.\" Copyright (c) 2008 Isilon Inc http://www.isilon.com/
|
||||
.\" Authors: Doug Rabson <dfr@rabson.org>
|
||||
.\" Developed with Red Inc: Alfred Perlstein <alfred@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.
|
730
usr.sbin/rpc.tlsclntd/rpc.tlsclntd.c
Normal file
730
usr.sbin/rpc.tlsclntd/rpc.tlsclntd.c
Normal file
@ -0,0 +1,730 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2008 Isilon Inc http://www.isilon.com/
|
||||
* Authors: Doug Rabson <dfr@rabson.org>
|
||||
* Developed with Red Inc: Alfred Perlstein <alfred@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));
|
||||
else
|
||||
fprintf(stderr,
|
||||
"rpctls_connect: client IP %s "
|
||||
"issuerName=%s subjectName=%s verify "
|
||||
"failed %s\n", hostnam, cp, cp2,
|
||||
X509_verify_cert_error_string(verfret));
|
||||
}
|
||||
X509_free(cert);
|
||||
SSL_free(ssl);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Check to see if ktls is enabled on the connection. */
|
||||
ret = BIO_get_ktls_send(SSL_get_wbio(ssl));
|
||||
rpctls_verbose_out("rpctls_connect: BIO_get_ktls_send=%d\n", ret);
|
||||
if (ret != 0) {
|
||||
ret = BIO_get_ktls_recv(SSL_get_rbio(ssl));
|
||||
rpctls_verbose_out("rpctls_connect: BIO_get_ktls_recv=%d\n",
|
||||
ret);
|
||||
}
|
||||
if (ret == 0) {
|
||||
if (rpctls_debug_level == 0)
|
||||
syslog(LOG_ERR, "ktls not working\n");
|
||||
else
|
||||
fprintf(stderr, "ktls not working\n");
|
||||
X509_free(cert);
|
||||
SSL_free(ssl);
|
||||
return (NULL);
|
||||
}
|
||||
if (ret == X509_V_OK && (rpctls_verify_cafile != NULL ||
|
||||
rpctls_verify_capath != NULL) && rpctls_crlfile != NULL)
|
||||
*certp = cert;
|
||||
else
|
||||
X509_free(cert);
|
||||
|
||||
return (ssl);
|
||||
}
|
||||
|
||||
static void
|
||||
rpctls_huphandler(int sig __unused)
|
||||
{
|
||||
|
||||
rpctls_gothup = true;
|
||||
}
|
29
usr.sbin/rpc.tlsservd/Makefile
Normal file
29
usr.sbin/rpc.tlsservd/Makefile
Normal file
@ -0,0 +1,29 @@
|
||||
# $FreeBSD$
|
||||
|
||||
.include <src.opts.mk>
|
||||
|
||||
PROG= rpc.tlsservd
|
||||
MAN= rpc.tlsservd.8
|
||||
SRCS= rpc.tlsservd.c rpc.tlscommon.c rpctlssd.h rpctlssd_svc.c rpctlssd_xdr.c
|
||||
|
||||
CFLAGS+= -I.
|
||||
|
||||
LIBADD= ssl crypto util
|
||||
|
||||
CLEANFILES= rpctlssd_svc.c rpctlssd_xdr.c rpctlssd.h
|
||||
|
||||
RPCSRC= ${SRCTOP}/sys/rpc/rpcsec_tls/rpctlssd.x
|
||||
RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -L -C -M
|
||||
|
||||
rpctlssd_svc.c: ${RPCSRC} rpctlssd.h
|
||||
${RPCGEN} -m -o ${.TARGET} ${RPCSRC}
|
||||
|
||||
rpctlssd_xdr.c: ${RPCSRC} rpctlssd.h
|
||||
${RPCGEN} -c -o ${.TARGET} ${RPCSRC}
|
||||
|
||||
rpctlssd.h: ${RPCSRC}
|
||||
${RPCGEN} -h -o ${.TARGET} ${RPCSRC}
|
||||
|
||||
.PATH: ${SRCTOP}/sys/rpc/rpcsec_tls
|
||||
|
||||
.include <bsd.prog.mk>
|
295
usr.sbin/rpc.tlsservd/rpc.tlscommon.c
Normal file
295
usr.sbin/rpc.tlsservd/rpc.tlscommon.c
Normal file
@ -0,0 +1,295 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2021 Rick Macklem
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/queue.h>
|
||||
#include <sys/syslog.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <netdb.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <rpc/rpc.h>
|
||||
|
||||
#include <openssl/opensslconf.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#include "rpc.tlscommon.h"
|
||||
|
||||
/*
|
||||
* How long to delay a reload of the CRL when there are RPC request(s)
|
||||
* to process, in usec. Must be less than 1second.
|
||||
*/
|
||||
#define RELOADDELAY 250000
|
||||
|
||||
void
|
||||
rpctls_svc_run(void)
|
||||
{
|
||||
int ret;
|
||||
struct timeval tv;
|
||||
fd_set readfds;
|
||||
uint64_t curtime, nexttime;
|
||||
struct timespec tp;
|
||||
sigset_t sighup_mask;
|
||||
|
||||
/* Expand svc_run() here so that we can call rpctls_loadcrlfile(). */
|
||||
curtime = nexttime = 0;
|
||||
sigemptyset(&sighup_mask);
|
||||
sigaddset(&sighup_mask, SIGHUP);
|
||||
for (;;) {
|
||||
clock_gettime(CLOCK_MONOTONIC, &tp);
|
||||
curtime = tp.tv_sec;
|
||||
curtime = curtime * 1000000 + tp.tv_nsec / 1000;
|
||||
sigprocmask(SIG_BLOCK, &sighup_mask, NULL);
|
||||
if (rpctls_gothup && curtime >= nexttime) {
|
||||
rpctls_gothup = false;
|
||||
sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
|
||||
ret = rpctls_loadcrlfile(rpctls_ctx);
|
||||
if (ret != 0)
|
||||
rpctls_checkcrl();
|
||||
else
|
||||
rpctls_verbose_out("rpc.tlsservd: Can't "
|
||||
"reload CRLfile\n");
|
||||
clock_gettime(CLOCK_MONOTONIC, &tp);
|
||||
nexttime = tp.tv_sec;
|
||||
nexttime = nexttime * 1000000 + tp.tv_nsec / 1000 +
|
||||
RELOADDELAY;
|
||||
} else
|
||||
sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
|
||||
|
||||
/*
|
||||
* If a reload is pending, poll for received request(s),
|
||||
* otherwise set a RELOADDELAY timeout, since a SIGHUP
|
||||
* could be processed between the got_sighup test and
|
||||
* the select() system call.
|
||||
*/
|
||||
tv.tv_sec = 0;
|
||||
if (rpctls_gothup)
|
||||
tv.tv_usec = 0;
|
||||
else
|
||||
tv.tv_usec = RELOADDELAY;
|
||||
readfds = svc_fdset;
|
||||
switch (select(svc_maxfd + 1, &readfds, NULL, NULL, &tv)) {
|
||||
case -1:
|
||||
if (errno == EINTR) {
|
||||
/* Allow a reload now. */
|
||||
nexttime = 0;
|
||||
continue;
|
||||
}
|
||||
syslog(LOG_ERR, "rpc.tls daemon died: select: %m");
|
||||
exit(1);
|
||||
case 0:
|
||||
/* Allow a reload now. */
|
||||
nexttime = 0;
|
||||
continue;
|
||||
default:
|
||||
svc_getreqset(&readfds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (re)load the CRLfile into the certificate verification store.
|
||||
*/
|
||||
int
|
||||
rpctls_loadcrlfile(SSL_CTX *ctx)
|
||||
{
|
||||
X509_STORE *certstore;
|
||||
X509_LOOKUP *certlookup;
|
||||
int ret;
|
||||
|
||||
if ((rpctls_verify_cafile != NULL ||
|
||||
rpctls_verify_capath != NULL) &&
|
||||
rpctls_crlfile != NULL) {
|
||||
certstore = SSL_CTX_get_cert_store(ctx);
|
||||
certlookup = X509_STORE_add_lookup(
|
||||
certstore, X509_LOOKUP_file());
|
||||
ret = 0;
|
||||
if (certlookup != NULL)
|
||||
ret = X509_load_crl_file(certlookup,
|
||||
rpctls_crlfile, X509_FILETYPE_PEM);
|
||||
if (ret != 0)
|
||||
ret = X509_STORE_set_flags(certstore,
|
||||
X509_V_FLAG_CRL_CHECK |
|
||||
X509_V_FLAG_CRL_CHECK_ALL);
|
||||
if (ret == 0) {
|
||||
rpctls_verbose_out(
|
||||
"rpctls_loadcrlfile: Can't"
|
||||
" load CRLfile=%s\n",
|
||||
rpctls_crlfile);
|
||||
return (ret);
|
||||
}
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the CRL file and check for any extant connections
|
||||
* that might now be revoked.
|
||||
*/
|
||||
void
|
||||
rpctls_checkcrl(void)
|
||||
{
|
||||
struct ssl_entry *slp;
|
||||
BIO *infile;
|
||||
X509_CRL *crl;
|
||||
X509_REVOKED *revoked;
|
||||
char *cp, *cp2, nullstr[1];
|
||||
int ret;
|
||||
|
||||
if (rpctls_crlfile == NULL || (rpctls_verify_cafile == NULL &&
|
||||
rpctls_verify_capath == NULL))
|
||||
return;
|
||||
infile = BIO_new(BIO_s_file());
|
||||
if (infile == NULL) {
|
||||
rpctls_verbose_out("rpctls_checkcrl: Cannot BIO_new\n");
|
||||
return;
|
||||
}
|
||||
ret = BIO_read_filename(infile, rpctls_crlfile);
|
||||
if (ret != 1) {
|
||||
rpctls_verbose_out("rpctls_checkcrl: Cannot read CRL file\n");
|
||||
BIO_free(infile);
|
||||
return;
|
||||
}
|
||||
|
||||
nullstr[0] = '\0';
|
||||
for (crl = PEM_read_bio_X509_CRL(infile, NULL, NULL, nullstr);
|
||||
crl != NULL; crl = PEM_read_bio_X509_CRL(infile, NULL, NULL,
|
||||
nullstr)) {
|
||||
LIST_FOREACH(slp, &rpctls_ssllist, next) {
|
||||
if (slp->cert != NULL) {
|
||||
ret = X509_CRL_get0_by_cert(crl, &revoked,
|
||||
slp->cert);
|
||||
/*
|
||||
* Do a shutdown on the socket, so that it
|
||||
* can no longer be used. The kernel RPC
|
||||
* code will notice the socket is disabled
|
||||
* and will do a disconnect upcall, which will
|
||||
* close the socket.
|
||||
*/
|
||||
if (ret == 1) {
|
||||
cp2 = X509_NAME_oneline(
|
||||
X509_get_subject_name(slp->cert),
|
||||
NULL, 0);
|
||||
cp = X509_NAME_oneline(
|
||||
X509_get_issuer_name(slp->cert),
|
||||
NULL, 0);
|
||||
if (rpctls_debug_level == 0)
|
||||
syslog(LOG_INFO | LOG_DAEMON,
|
||||
"rpctls_daemon: Certificate"
|
||||
" Revoked "
|
||||
"issuerName=%s "
|
||||
"subjectName=%s: "
|
||||
"TCP connection closed",
|
||||
cp, cp2);
|
||||
else
|
||||
fprintf(stderr,
|
||||
"rpctls_daemon: Certificate"
|
||||
" Revoked "
|
||||
"issuerName=%s "
|
||||
"subjectName=%s: "
|
||||
"TCP connection closed",
|
||||
cp, cp2);
|
||||
shutdown(slp->s, SHUT_WR);
|
||||
slp->shutoff = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
X509_CRL_free(crl);
|
||||
}
|
||||
BIO_free(infile);
|
||||
}
|
||||
|
||||
void
|
||||
rpctls_verbose_out(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
if (rpctls_verbose) {
|
||||
va_start(ap, fmt);
|
||||
if (rpctls_debug_level == 0)
|
||||
vsyslog(LOG_INFO | LOG_DAEMON, fmt, ap);
|
||||
else
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check a IP address against any host address in the
|
||||
* certificate. Basically getnameinfo(3) and
|
||||
* X509_check_host().
|
||||
*/
|
||||
int
|
||||
rpctls_checkhost(struct sockaddr *sad, X509 *cert, unsigned int wildcard)
|
||||
{
|
||||
char hostnam[NI_MAXHOST];
|
||||
int ret;
|
||||
|
||||
if (getnameinfo((const struct sockaddr *)sad,
|
||||
sad->sa_len, hostnam, sizeof(hostnam),
|
||||
NULL, 0, NI_NAMEREQD) != 0)
|
||||
return (0);
|
||||
rpctls_verbose_out("rpctls_checkhost: DNS %s\n",
|
||||
hostnam);
|
||||
ret = X509_check_host(cert, hostnam, strlen(hostnam),
|
||||
wildcard, NULL);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the peer's IP address.
|
||||
*/
|
||||
int
|
||||
rpctls_gethost(int s, struct sockaddr *sad, char *hostip, size_t hostlen)
|
||||
{
|
||||
socklen_t slen;
|
||||
int ret;
|
||||
|
||||
slen = sizeof(struct sockaddr_storage);
|
||||
if (getpeername(s, sad, &slen) < 0)
|
||||
return (0);
|
||||
ret = 0;
|
||||
if (getnameinfo((const struct sockaddr *)sad,
|
||||
sad->sa_len, hostip, hostlen,
|
||||
NULL, 0, NI_NUMERICHOST) == 0) {
|
||||
rpctls_verbose_out("rpctls_gethost: %s\n",
|
||||
hostip);
|
||||
ret = 1;
|
||||
}
|
||||
return (ret);
|
||||
}
|
68
usr.sbin/rpc.tlsservd/rpc.tlscommon.h
Normal file
68
usr.sbin/rpc.tlsservd/rpc.tlscommon.h
Normal file
@ -0,0 +1,68 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2021 Rick Macklem
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
/*
|
||||
* Functions in rpc.tlscommon.c used by both rpc.tlsservd.c and rpc.tlsclntd.c.
|
||||
*/
|
||||
int rpctls_gethost(int s, struct sockaddr *sad,
|
||||
char *hostip, size_t hostlen);
|
||||
int rpctls_checkhost(struct sockaddr *sad, X509 *cert,
|
||||
unsigned int wildcard);
|
||||
int rpctls_loadcrlfile(SSL_CTX *ctx);
|
||||
void rpctls_checkcrl(void);
|
||||
void rpctls_verbose_out(const char *fmt, ...);
|
||||
void rpctls_svc_run(void);
|
||||
|
||||
/*
|
||||
* A linked list of all current "SSL *"s and socket "fd"s
|
||||
* for kernel RPC TLS connections is maintained.
|
||||
* The "refno" field is a unique 64bit value used to
|
||||
* identify which entry a kernel RPC upcall refers to.
|
||||
*/
|
||||
LIST_HEAD(ssl_list, ssl_entry);
|
||||
struct ssl_entry {
|
||||
LIST_ENTRY(ssl_entry) next;
|
||||
uint64_t refno;
|
||||
int s;
|
||||
bool shutoff;
|
||||
SSL *ssl;
|
||||
X509 *cert;
|
||||
};
|
||||
|
||||
/* Global variables shared between rpc.tlscommon.c and the daemons. */
|
||||
extern int rpctls_debug_level;
|
||||
extern bool rpctls_verbose;
|
||||
extern SSL_CTX *rpctls_ctx;
|
||||
extern const char *rpctls_verify_cafile;
|
||||
extern const char *rpctls_verify_capath;
|
||||
extern char *rpctls_crlfile;
|
||||
extern bool rpctls_cert;
|
||||
extern bool rpctls_gothup;
|
||||
extern struct ssl_list rpctls_ssllist;
|
||||
|
348
usr.sbin/rpc.tlsservd/rpc.tlsservd.8
Normal file
348
usr.sbin/rpc.tlsservd/rpc.tlsservd.8
Normal file
@ -0,0 +1,348 @@
|
||||
.\" Copyright (c) 2008 Isilon Inc http://www.isilon.com/
|
||||
.\" Authors: Doug Rabson <dfr@rabson.org>
|
||||
.\" Developed with Red Inc: Alfred Perlstein <alfred@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.tlsservd.8 by Rick Macklem.
|
||||
.Dd January 29, 2021
|
||||
.Dt RPC.TLSSERVD 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm rpc.tlsservd
|
||||
.Nd "Sun RPC over TLS Server Daemon"
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl D Ar certdir
|
||||
.Op Fl d
|
||||
.Op Fl h
|
||||
.Op Fl l Ar CAfile
|
||||
.Op Fl m
|
||||
.Op Fl n Ar domain
|
||||
.Op Fl p Ar CApath
|
||||
.Op Fl r Ar CRLfile
|
||||
.Op Fl u
|
||||
.Op Fl v
|
||||
.Op Fl W
|
||||
.Op Fl w
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
program provides support for the server side of the kernel Sun RPC over TLS
|
||||
implementation.
|
||||
This daemon must be running to allow the kernel RPC to perform the TLS
|
||||
handshake after a TCP client has sent the STARTTLS Null RPC request to
|
||||
the server.
|
||||
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.
|
||||
Note that the
|
||||
.Fl tls
|
||||
option in the
|
||||
.Xr exports 5
|
||||
file specifies that the client must use RPC over TLS.
|
||||
The
|
||||
.Fl tlscert
|
||||
option in the
|
||||
.Xr exports 5
|
||||
file specifies that the client must provide a certificate
|
||||
that verifies.
|
||||
The
|
||||
.Fl tlscertuser
|
||||
option in the
|
||||
.Xr exports 5
|
||||
file specifies that the client must provide a certificate
|
||||
that verifies and has a otherName:1.3.6.1.4.1.2238.1.1.1;UTF8: field of
|
||||
subjectAltName of the form
|
||||
.Dq user@domain
|
||||
where
|
||||
.Dq domain
|
||||
matches the one for this server and
|
||||
.Dq user
|
||||
is a valid user name that maps to a <uid, gid_list>.
|
||||
For the latter two cases, the
|
||||
.Fl m
|
||||
and either the
|
||||
.Fl l
|
||||
or
|
||||
.Fl p
|
||||
options must be specified.
|
||||
The
|
||||
.Fl tlscertuser
|
||||
option also requires that the
|
||||
.Fl u
|
||||
option on this daemon be specified.
|
||||
.Pp
|
||||
Also, if the IP address used by the client cannot be trusted,
|
||||
the rules in
|
||||
.Xr exports 5
|
||||
cannot be applied safely.
|
||||
As such, the
|
||||
.Fl h
|
||||
option can be used along with
|
||||
.Fl m
|
||||
and either the
|
||||
.Fl l
|
||||
or
|
||||
.Fl p
|
||||
options to require that the client certificate have the correct
|
||||
Fully Qualified Domain Name (FQDN) in it.
|
||||
.Pp
|
||||
A certificate and associated key must exist in /etc/rpc.tlsservd
|
||||
(or the
|
||||
.Dq certdir
|
||||
specified by the
|
||||
.Fl D
|
||||
option)
|
||||
in files named
|
||||
.Dq cert.pem
|
||||
and
|
||||
.Dq certkey.pem .
|
||||
.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 m
|
||||
option has been specified.
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width indent
|
||||
.It Fl D Ar certdir , Fl Fl certdir= Ns Ar certdir
|
||||
Use
|
||||
.Dq certdir
|
||||
instead of /etc/rpc.tlsservd as the location for the
|
||||
certificate in a file called
|
||||
.Dq cert.pem
|
||||
and associated key in
|
||||
.Dq certkey.pem .
|
||||
.It Fl d , Fl Fl debuglevel
|
||||
Run in debug mode.
|
||||
In this mode,
|
||||
.Nm
|
||||
will not fork when it starts.
|
||||
.It Fl h , Fl Fl checkhost
|
||||
This option specifies that the client must provide a certificate
|
||||
that both verifies and has a FQDN that matches the reverse
|
||||
DNS name for the IP address that
|
||||
the client uses to connect to the server.
|
||||
The FQDN should be
|
||||
in the DNS field of the subjectAltName, but is also allowed
|
||||
to be in the CN field of the
|
||||
subjectName in the certificate.
|
||||
By default, a wildcard "*" in the FQDN is not allowed.
|
||||
With this option, a failure to verify the client certificate
|
||||
or match the FQDN will result in the
|
||||
server sending AUTH_REJECTEDCRED replies to all client RPCs.
|
||||
This option requires the
|
||||
.Fl m
|
||||
and either the
|
||||
.Fl l
|
||||
or
|
||||
.Fl p
|
||||
options.
|
||||
.It Fl l Ar CAfile , Fl Fl verifylocs= Ns Ar CAfile
|
||||
This option specifies the path name of a CA certificate(s) file
|
||||
in pem format, which is used to verify client certificates and to
|
||||
set the list of CA(s) sent to the client so that it knows which
|
||||
certificate to send to the server during the TLS handshake.
|
||||
This path name is used in
|
||||
.Dq SSL_CTX_load_verify_locations(ctx,CAfile,NULL)
|
||||
and
|
||||
.Dq SSL_CTX_set_client_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 .
|
||||
Either this option or the
|
||||
.Fl p
|
||||
option must be specified when the
|
||||
.Fl m
|
||||
option is specified so that the daemon can verify the client's
|
||||
certificate.
|
||||
.It Fl m , Fl Fl mutualverf
|
||||
This option specifies that the server is to request a certificate
|
||||
from the client during the TLS handshake.
|
||||
It does not require that the client provide a certificate.
|
||||
It should be specified unless no client doing RPC over TLS is
|
||||
required to have a certificate.
|
||||
For NFS, either the
|
||||
.Xr exports 5
|
||||
option
|
||||
.Fl tlscert
|
||||
or
|
||||
.Fl tlscertuser
|
||||
may be used to require a client to provide a certificate
|
||||
that verifies.
|
||||
See
|
||||
.Xr exports 5 .
|
||||
.It Fl n Ar domain , Fl Fl domain= Ns Ar domain
|
||||
This option specifies what the
|
||||
.Dq domain
|
||||
is for use with the
|
||||
.Fl u
|
||||
option, overriding the domain taken from the
|
||||
.Xr gethostname 2
|
||||
of the server this daemon is running on.
|
||||
If you have specified the
|
||||
.Fl domain
|
||||
command line option for
|
||||
.Xr nfsuserd 8
|
||||
then you should specify this option with the same
|
||||
.Dq domain
|
||||
that was specified for
|
||||
.Xr nfsuserd 8 .
|
||||
This option is only meaningful when used with the
|
||||
.Fl u
|
||||
option.
|
||||
.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_set_client_CA_list(ctx,SSL_load_client_CA_file())
|
||||
is not called, so a list of CA names might not be passed
|
||||
to the client during the TLS handshake.
|
||||
.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.
|
||||
This option is only meaningful when either the
|
||||
.Fl l
|
||||
or
|
||||
.Fl p
|
||||
have been specified.
|
||||
.It Fl u , Fl Fl certuser
|
||||
This option specifies that if the client provides a certificate
|
||||
that both verifies and has a subjectAltName with an otherName
|
||||
component of the form
|
||||
.Dq otherName:1.3.6.1.4.1.2238.1.1.1;UTF8:user@domain
|
||||
where
|
||||
.Dq domain
|
||||
matches the one for this server,
|
||||
then the daemon will attempt to map
|
||||
.Dq user
|
||||
in the above
|
||||
to a user credential <uid, gid_list>.
|
||||
There should only be one of these otherName components for each
|
||||
.Dq domain .
|
||||
If
|
||||
.Dq user
|
||||
is a valid username in the password database,
|
||||
then the <uid, gid_list> for
|
||||
.Dq user
|
||||
will be used for all
|
||||
RPCs on the mount instead of the credentials in the RPC request
|
||||
header.
|
||||
This option requires the
|
||||
.Fl m
|
||||
and either the
|
||||
.Fl l
|
||||
or
|
||||
.Fl p
|
||||
options.
|
||||
Use of this option might not conform to RFC-NNNN, which does
|
||||
not allow certificates to be used for user authentication.
|
||||
.It Fl v , Fl Fl verbose
|
||||
Run in verbose mode.
|
||||
In this mode,
|
||||
.Nm
|
||||
will log activity messages to
|
||||
.Xr syslogd 8
|
||||
using LOG_INFO | LOG_DAEMON or to
|
||||
stderr, if the
|
||||
.Fl d
|
||||
option has also been specified.
|
||||
.It Fl W , Fl Fl multiwild
|
||||
This option is used with the
|
||||
.Fl h
|
||||
option to allow use of a wildcard
|
||||
.Dq *
|
||||
that matches multiple
|
||||
components of the reverse DNS name for the client's IP
|
||||
address.
|
||||
For example, the FQDN
|
||||
.Dq *.uoguelph.ca
|
||||
would match both
|
||||
.Dq laptop21.uoguelph.ca
|
||||
and
|
||||
.Dq laptop3.cis.uoguelph.ca .
|
||||
.It Fl w , Fl Fl singlewild
|
||||
Similar to
|
||||
.Fl W
|
||||
but allows the wildcard
|
||||
.Dq *
|
||||
to match a single component of the reverse DNS name.
|
||||
For example, the FQDN
|
||||
.Dq *.uoguelph.ca
|
||||
would match
|
||||
.Dq laptop21.uoguelph.ca
|
||||
but not
|
||||
.Dq laptop3.cis.uoguelph.ca .
|
||||
Only one of the
|
||||
.Fl W
|
||||
and
|
||||
.Fl w
|
||||
options is allowed.
|
||||
.El
|
||||
.Sh EXIT STATUS
|
||||
.Ex -std
|
||||
.Sh SEE ALSO
|
||||
.Xr openssl 1 ,
|
||||
.Xr ktls 4 ,
|
||||
.Xr exports 5 ,
|
||||
.Xr mount_nfs 8 ,
|
||||
.Xr nfsuserd 8 ,
|
||||
.Xr rpc.tlsclntd 8 ,
|
||||
.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.
|
886
usr.sbin/rpc.tlsservd/rpc.tlsservd.c
Normal file
886
usr.sbin/rpc.tlsservd/rpc.tlsservd.c
Normal file
@ -0,0 +1,886 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2008 Isilon Inc http://www.isilon.com/
|
||||
* Authors: Doug Rabson <dfr@rabson.org>
|
||||
* Developed with Red Inc: Alfred Perlstein <alfred@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 server 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/linker.h>
|
||||
#include <sys/module.h>
|
||||
#include <sys/queue.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 <pwd.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.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 "rpctlssd.h"
|
||||
#include "rpc.tlscommon.h"
|
||||
|
||||
#ifndef _PATH_RPCTLSSDSOCK
|
||||
#define _PATH_RPCTLSSDSOCK "/var/run/rpc.tlsservd.sock"
|
||||
#endif
|
||||
#ifndef _PATH_CERTANDKEY
|
||||
#define _PATH_CERTANDKEY "/etc/rpc.tlsservd/"
|
||||
#endif
|
||||
#ifndef _PATH_RPCTLSSDPID
|
||||
#define _PATH_RPCTLSSDPID "/var/run/rpc.tlsservd.pid"
|
||||
#endif
|
||||
#ifndef _PREFERRED_CIPHERS
|
||||
#define _PREFERRED_CIPHERS "AES128-GCM-SHA256"
|
||||
#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_gothup = false;
|
||||
struct ssl_list rpctls_ssllist;
|
||||
|
||||
static struct pidfh *rpctls_pfh = NULL;
|
||||
static bool rpctls_do_mutual = false;
|
||||
static const char *rpctls_certdir = _PATH_CERTANDKEY;
|
||||
static bool rpctls_comparehost = false;
|
||||
static unsigned int rpctls_wildcard = X509_CHECK_FLAG_NO_WILDCARDS;
|
||||
static uint64_t rpctls_ssl_refno = 0;
|
||||
static uint64_t rpctls_ssl_sec = 0;
|
||||
static uint64_t rpctls_ssl_usec = 0;
|
||||
static bool rpctls_cnuser = false;
|
||||
static char *rpctls_dnsname;
|
||||
static const char *rpctls_cnuseroid = "1.3.6.1.4.1.2238.1.1.1";
|
||||
|
||||
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, uint32_t *uidp,
|
||||
int *ngrps, uint32_t *gidp, X509 **certp);
|
||||
static int rpctls_cnname(X509 *cert, uint32_t *uidp,
|
||||
int *ngrps, uint32_t *gidp);
|
||||
static char *rpctls_getdnsname(char *dnsname);
|
||||
static void rpctls_huphandler(int sig __unused);
|
||||
|
||||
extern void rpctlssd_1(struct svc_req *rqstp, SVCXPRT *transp);
|
||||
|
||||
static struct option longopts[] = {
|
||||
{ "certdir", required_argument, NULL, 'D' },
|
||||
{ "debuglevel", no_argument, NULL, 'd' },
|
||||
{ "checkhost", no_argument, NULL, 'h' },
|
||||
{ "verifylocs", required_argument, NULL, 'l' },
|
||||
{ "mutualverf", no_argument, NULL, 'm' },
|
||||
{ "domain", required_argument, NULL, 'n' },
|
||||
{ "verifydir", required_argument, NULL, 'p' },
|
||||
{ "crl", required_argument, NULL, 'r' },
|
||||
{ "certuser", no_argument, NULL, 'u' },
|
||||
{ "verbose", no_argument, NULL, 'v' },
|
||||
{ "multiwild", no_argument, NULL, 'W' },
|
||||
{ "singlewild", no_argument, NULL, 'w' },
|
||||
{ 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, debug, fd, oldmask;
|
||||
SVCXPRT *xprt;
|
||||
struct timeval tm;
|
||||
struct timezone tz;
|
||||
char hostname[MAXHOSTNAMELEN + 2];
|
||||
pid_t otherpid;
|
||||
bool tls_enable;
|
||||
size_t tls_enable_len;
|
||||
|
||||
/* Check that another rpctlssd isn't already running. */
|
||||
rpctls_pfh = pidfile_open(_PATH_RPCTLSSDPID, 0600, &otherpid);
|
||||
if (rpctls_pfh == NULL) {
|
||||
if (errno == EEXIST)
|
||||
errx(1, "rpctlssd 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;
|
||||
|
||||
/* Set the dns name for the server. */
|
||||
rpctls_dnsname = rpctls_getdnsname(hostname);
|
||||
if (rpctls_dnsname == NULL) {
|
||||
strcpy(hostname, "@default.domain");
|
||||
rpctls_dnsname = hostname;
|
||||
}
|
||||
|
||||
debug = 0;
|
||||
rpctls_verbose = false;
|
||||
while ((ch = getopt_long(argc, argv, "D:dhl:n:mp:r:uvWw", longopts,
|
||||
NULL)) != -1) {
|
||||
switch (ch) {
|
||||
case 'D':
|
||||
rpctls_certdir = optarg;
|
||||
break;
|
||||
case 'd':
|
||||
rpctls_debug_level++;
|
||||
break;
|
||||
case 'h':
|
||||
rpctls_comparehost = true;
|
||||
break;
|
||||
case 'l':
|
||||
rpctls_verify_cafile = optarg;
|
||||
break;
|
||||
case 'm':
|
||||
rpctls_do_mutual = true;
|
||||
break;
|
||||
case 'n':
|
||||
hostname[0] = '@';
|
||||
strlcpy(&hostname[1], optarg, MAXHOSTNAMELEN + 1);
|
||||
rpctls_dnsname = hostname;
|
||||
break;
|
||||
case 'p':
|
||||
rpctls_verify_capath = optarg;
|
||||
break;
|
||||
case 'r':
|
||||
rpctls_crlfile = optarg;
|
||||
break;
|
||||
case 'u':
|
||||
rpctls_cnuser = true;
|
||||
break;
|
||||
case 'v':
|
||||
rpctls_verbose = true;
|
||||
break;
|
||||
case 'W':
|
||||
if (rpctls_wildcard != X509_CHECK_FLAG_NO_WILDCARDS)
|
||||
errx(1, "options -w and -W are mutually "
|
||||
"exclusive");
|
||||
rpctls_wildcard = X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS;
|
||||
break;
|
||||
case 'w':
|
||||
if (rpctls_wildcard != X509_CHECK_FLAG_NO_WILDCARDS)
|
||||
errx(1, "options -w and -W are mutually "
|
||||
"exclusive");
|
||||
rpctls_wildcard = 0;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "usage: %s "
|
||||
"[-D/--certdir certdir] [-d/--debuglevel] "
|
||||
"[-h/--checkhost] "
|
||||
"[-l/--verifylocs CAfile] [-m/--mutualverf] "
|
||||
"[-n/--domain domain_name] "
|
||||
"[-p/--verifydir CApath] [-r/--crl CRLfile] "
|
||||
"[-u/--certuser] [-v/--verbose] [-W/--multiwild] "
|
||||
"[-w/--singlewild]\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (rpctls_do_mutual && rpctls_verify_cafile == NULL &&
|
||||
rpctls_verify_capath == NULL)
|
||||
errx(1, "-m requires the -l <CAfile> and/or "
|
||||
"-p <CApath> options");
|
||||
if (rpctls_comparehost && (!rpctls_do_mutual ||
|
||||
(rpctls_verify_cafile == NULL && rpctls_verify_capath == NULL)))
|
||||
errx(1, "-h requires the -m plus the "
|
||||
"-l <CAfile> and/or -p <CApath> options");
|
||||
if (!rpctls_comparehost && rpctls_wildcard !=
|
||||
X509_CHECK_FLAG_NO_WILDCARDS)
|
||||
errx(1, "The -w or -W options require the -h option");
|
||||
if (rpctls_cnuser && (!rpctls_do_mutual ||
|
||||
(rpctls_verify_cafile == NULL && rpctls_verify_capath == NULL)))
|
||||
errx(1, "-u requires the -m plus 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");
|
||||
}
|
||||
|
||||
if (rpctls_debug_level == 0) {
|
||||
if (daemon(0, 0) != 0)
|
||||
err(1, "Can't daemonize");
|
||||
signal(SIGINT, SIG_IGN);
|
||||
signal(SIGQUIT, SIG_IGN);
|
||||
signal(SIGHUP, SIG_IGN);
|
||||
}
|
||||
signal(SIGTERM, rpctlssd_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_RPCTLSSDSOCK);
|
||||
strcpy(sun.sun_path, _PATH_RPCTLSSDSOCK);
|
||||
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 rpctlssd socket");
|
||||
exit(1);
|
||||
}
|
||||
err(1, "Can't create local rpctlssd 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 rpctlssd socket");
|
||||
exit(1);
|
||||
}
|
||||
err(1, "Can't bind local rpctlssd socket");
|
||||
}
|
||||
umask(oldmask);
|
||||
if (listen(fd, SOMAXCONN) < 0) {
|
||||
if (rpctls_debug_level == 0) {
|
||||
syslog(LOG_ERR,
|
||||
"Can't listen on local rpctlssd socket");
|
||||
exit(1);
|
||||
}
|
||||
err(1, "Can't listen on local rpctlssd 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 rpctlssd socket");
|
||||
exit(1);
|
||||
}
|
||||
err(1, "Can't create transport for local rpctlssd socket");
|
||||
}
|
||||
if (!svc_reg(xprt, RPCTLSSD, RPCTLSSDVERS, rpctlssd_1, NULL)) {
|
||||
if (rpctls_debug_level == 0) {
|
||||
syslog(LOG_ERR,
|
||||
"Can't register service for local rpctlssd socket");
|
||||
exit(1);
|
||||
}
|
||||
err(1, "Can't register service for local rpctlssd socket");
|
||||
}
|
||||
|
||||
rpctls_ctx = rpctls_setup_ssl(rpctls_certdir);
|
||||
if (rpctls_ctx == NULL) {
|
||||
if (rpctls_debug_level == 0) {
|
||||
syslog(LOG_ERR, "Can't create SSL context");
|
||||
exit(1);
|
||||
}
|
||||
err(1, "Can't create SSL context");
|
||||
}
|
||||
rpctls_gothup = false;
|
||||
LIST_INIT(&rpctls_ssllist);
|
||||
|
||||
rpctls_syscall(RPCTLS_SYSC_SRVSETPATH, _PATH_RPCTLSSDSOCK);
|
||||
|
||||
rpctls_svc_run();
|
||||
|
||||
rpctls_syscall(RPCTLS_SYSC_SRVSHUTDOWN, "");
|
||||
|
||||
SSL_CTX_free(rpctls_ctx);
|
||||
EVP_cleanup();
|
||||
return (0);
|
||||
}
|
||||
|
||||
bool_t
|
||||
rpctlssd_null_1_svc(__unused void *argp, __unused void *result,
|
||||
__unused struct svc_req *rqstp)
|
||||
{
|
||||
|
||||
rpctls_verbose_out("rpctlssd_null_svc: done\n");
|
||||
return (TRUE);
|
||||
}
|
||||
|
||||
bool_t
|
||||
rpctlssd_connect_1_svc(__unused void *argp,
|
||||
struct rpctlssd_connect_res *result, __unused struct svc_req *rqstp)
|
||||
{
|
||||
int ngrps, s;
|
||||
SSL *ssl;
|
||||
uint32_t flags;
|
||||
struct ssl_entry *newslp;
|
||||
uint32_t uid;
|
||||
uint32_t *gidp;
|
||||
X509 *cert;
|
||||
|
||||
rpctls_verbose_out("rpctlsd_connect_svc: started\n");
|
||||
memset(result, 0, sizeof(*result));
|
||||
/* Get the socket fd from the kernel. */
|
||||
s = rpctls_syscall(RPCTLS_SYSC_SRVSOCKET, "");
|
||||
if (s < 0)
|
||||
return (FALSE);
|
||||
|
||||
/* Do the server side of a TLS handshake. */
|
||||
gidp = calloc(NGROUPS, sizeof(*gidp));
|
||||
ssl = rpctls_server(rpctls_ctx, s, &flags, &uid, &ngrps, gidp, &cert);
|
||||
if (ssl == NULL) {
|
||||
free(gidp);
|
||||
rpctls_verbose_out("rpctlssd_connect_svc: ssl "
|
||||
"accept failed\n");
|
||||
/*
|
||||
* For RPC-over-TLS, this upcall is expected
|
||||
* to close off the socket upon handshake failure.
|
||||
*/
|
||||
close(s);
|
||||
return (FALSE);
|
||||
} else {
|
||||
rpctls_verbose_out("rpctlssd_connect_svc: "
|
||||
"succeeded flags=0x%x\n", flags);
|
||||
result->flags = flags;
|
||||
result->sec = rpctls_ssl_sec;
|
||||
result->usec = rpctls_ssl_usec;
|
||||
result->ssl = ++rpctls_ssl_refno;
|
||||
/* Hard to believe this could ever wrap around.. */
|
||||
if (rpctls_ssl_refno == 0)
|
||||
result->ssl = ++rpctls_ssl_refno;
|
||||
if ((flags & RPCTLS_FLAGS_CERTUSER) != 0) {
|
||||
result->uid = uid;
|
||||
result->gid.gid_len = ngrps;
|
||||
result->gid.gid_val = gidp;
|
||||
} else {
|
||||
result->uid = 0;
|
||||
result->gid.gid_len = 0;
|
||||
result->gid.gid_val = gidp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Maintain list of all current SSL *'s */
|
||||
newslp = malloc(sizeof(*newslp));
|
||||
newslp->ssl = ssl;
|
||||
newslp->s = s;
|
||||
newslp->shutoff = false;
|
||||
newslp->refno = rpctls_ssl_refno;
|
||||
newslp->cert = cert;
|
||||
LIST_INSERT_HEAD(&rpctls_ssllist, newslp, next);
|
||||
return (TRUE);
|
||||
}
|
||||
|
||||
bool_t
|
||||
rpctlssd_handlerecord_1_svc(struct rpctlssd_handlerecord_arg *argp,
|
||||
struct rpctlssd_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("rpctlssd_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
|
||||
rpctlssd_disconnect_1_svc(struct rpctlssd_disconnect_arg *argp,
|
||||
struct rpctlssd_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("rpctlssd_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
|
||||
rpctlssd_1_freeresult(__unused SVCXPRT *transp, xdrproc_t xdr_result,
|
||||
caddr_t result)
|
||||
{
|
||||
rpctlssd_connect_res *res;
|
||||
|
||||
if (xdr_result == (xdrproc_t)xdr_rpctlssd_connect_res) {
|
||||
res = (rpctlssd_connect_res *)(void *)result;
|
||||
free(res->gid.gid_val);
|
||||
}
|
||||
return (TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
rpctlssd_terminate(int sig __unused)
|
||||
{
|
||||
struct ssl_entry *slp;
|
||||
|
||||
rpctls_syscall(RPCTLS_SYSC_SRVSHUTDOWN, "");
|
||||
pidfile_remove(rpctls_pfh);
|
||||
|
||||
LIST_FOREACH(slp, &rpctls_ssllist, next)
|
||||
shutdown(slp->s, SHUT_RD);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* Allow the handshake to proceed. */
|
||||
static int
|
||||
rpctls_verify_callback(__unused int preverify_ok,
|
||||
__unused X509_STORE_CTX *x509_ctx)
|
||||
{
|
||||
|
||||
return (1);
|
||||
}
|
||||
|
||||
static SSL_CTX *
|
||||
rpctls_setup_ssl(const char *certdir)
|
||||
{
|
||||
SSL_CTX *ctx;
|
||||
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_server_method());
|
||||
if (ctx == NULL) {
|
||||
rpctls_verbose_out("rpctls_setup_ssl: SSL_CTX_new failed\n");
|
||||
return (NULL);
|
||||
}
|
||||
SSL_CTX_set_ecdh_auto(ctx, 1);
|
||||
|
||||
/*
|
||||
* Set preferred ciphers, since KERN_TLS only supports a
|
||||
* few of them.
|
||||
*/
|
||||
ret = SSL_CTX_set_cipher_list(ctx, _PREFERRED_CIPHERS);
|
||||
if (ret == 0) {
|
||||
rpctls_verbose_out("rpctls_setup_ssl: "
|
||||
"SSL_CTX_set_cipher_list failed to set any ciphers\n");
|
||||
SSL_CTX_free(ctx);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Get the cert.pem and certkey.pem files from the directory certdir. */
|
||||
len = strlcpy(path, 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_setup_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_setup_ssl: Can't use private "
|
||||
"key path=%s ret=%d\n", path, ret);
|
||||
SSL_CTX_free(ctx);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Set Mutual authentication, as required. */
|
||||
if (rpctls_do_mutual) {
|
||||
if (rpctls_verify_cafile != NULL ||
|
||||
rpctls_verify_capath != NULL) {
|
||||
if (rpctls_crlfile != NULL) {
|
||||
ret = rpctls_loadcrlfile(ctx);
|
||||
if (ret == 0) {
|
||||
rpctls_verbose_out("rpctls_setup_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_setup_ssl: "
|
||||
"Can't load verify locations\n");
|
||||
SSL_CTX_free(ctx);
|
||||
return (NULL);
|
||||
}
|
||||
if (rpctls_verify_cafile != NULL)
|
||||
SSL_CTX_set_client_CA_list(ctx,
|
||||
SSL_load_client_CA_file(
|
||||
rpctls_verify_cafile));
|
||||
}
|
||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER,
|
||||
rpctls_verify_callback);
|
||||
}
|
||||
SSL_CTX_clear_mode(ctx, SSL_MODE_NO_KTLS_TX | SSL_MODE_NO_KTLS_RX);
|
||||
return (ctx);
|
||||
}
|
||||
|
||||
static SSL *
|
||||
rpctls_server(SSL_CTX *ctx, int s, uint32_t *flags, uint32_t *uidp,
|
||||
int *ngrps, uint32_t *gidp, X509 **certp)
|
||||
{
|
||||
SSL *ssl;
|
||||
X509 *cert;
|
||||
struct sockaddr *sad;
|
||||
struct sockaddr_storage ad;
|
||||
char hostnam[NI_MAXHOST];
|
||||
int gethostret, ret;
|
||||
char *cp, *cp2;
|
||||
long verfret;
|
||||
|
||||
*flags = 0;
|
||||
*certp = NULL;
|
||||
sad = (struct sockaddr *)&ad;
|
||||
ssl = SSL_new(ctx);
|
||||
if (ssl == NULL) {
|
||||
rpctls_verbose_out("rpctls_server: SSL_new failed\n");
|
||||
return (NULL);
|
||||
}
|
||||
if (SSL_set_fd(ssl, s) != 1) {
|
||||
rpctls_verbose_out("rpctls_server: SSL_set_fd failed\n");
|
||||
SSL_free(ssl);
|
||||
return (NULL);
|
||||
}
|
||||
ret = SSL_accept(ssl);
|
||||
if (ret != 1) {
|
||||
rpctls_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) {
|
||||
gethostret = rpctls_gethost(s, sad, hostnam,
|
||||
sizeof(hostnam));
|
||||
if (gethostret == 0)
|
||||
hostnam[0] = '\0';
|
||||
cp2 = X509_NAME_oneline(
|
||||
X509_get_subject_name(cert), NULL, 0);
|
||||
*flags |= RPCTLS_FLAGS_GOTCERT;
|
||||
verfret = SSL_get_verify_result(ssl);
|
||||
if (verfret != X509_V_OK) {
|
||||
cp = X509_NAME_oneline(
|
||||
X509_get_issuer_name(cert), NULL, 0);
|
||||
if (rpctls_debug_level == 0)
|
||||
syslog(LOG_INFO | LOG_DAEMON,
|
||||
"rpctls_server: client IP %s "
|
||||
"issuerName=%s subjectName=%s"
|
||||
" verify failed %s\n", hostnam,
|
||||
cp, cp2,
|
||||
X509_verify_cert_error_string(
|
||||
verfret));
|
||||
else
|
||||
fprintf(stderr,
|
||||
"rpctls_server: client IP %s "
|
||||
"issuerName=%s subjectName=%s"
|
||||
" verify failed %s\n", hostnam,
|
||||
cp, cp2,
|
||||
X509_verify_cert_error_string(
|
||||
verfret));
|
||||
}
|
||||
if (verfret ==
|
||||
X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
|
||||
verfret == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN)
|
||||
*flags |= RPCTLS_FLAGS_SELFSIGNED;
|
||||
else if (verfret == X509_V_OK) {
|
||||
if (rpctls_comparehost) {
|
||||
ret = 0;
|
||||
if (gethostret != 0)
|
||||
ret = rpctls_checkhost(sad,
|
||||
cert, rpctls_wildcard);
|
||||
if (ret != 1) {
|
||||
*flags |=
|
||||
RPCTLS_FLAGS_DISABLED;
|
||||
rpctls_verbose_out(
|
||||
"rpctls_server: "
|
||||
"checkhost "
|
||||
"failed\n");
|
||||
}
|
||||
}
|
||||
if (rpctls_cnuser) {
|
||||
ret = rpctls_cnname(cert, uidp,
|
||||
ngrps, gidp);
|
||||
if (ret != 0)
|
||||
*flags |= RPCTLS_FLAGS_CERTUSER;
|
||||
}
|
||||
*flags |= RPCTLS_FLAGS_VERIFIED;
|
||||
*certp = cert;
|
||||
cert = NULL;
|
||||
}
|
||||
if (cert != NULL)
|
||||
X509_free(cert);
|
||||
} else
|
||||
rpctls_verbose_out("rpctls_server: "
|
||||
"No peer certificate\n");
|
||||
}
|
||||
|
||||
/* Check to see that ktls is working for the connection. */
|
||||
ret = BIO_get_ktls_send(SSL_get_wbio(ssl));
|
||||
rpctls_verbose_out("rpctls_server: BIO_get_ktls_send=%d\n", ret);
|
||||
if (ret != 0) {
|
||||
ret = BIO_get_ktls_recv(SSL_get_rbio(ssl));
|
||||
rpctls_verbose_out("rpctls_server: BIO_get_ktls_recv=%d\n",
|
||||
ret);
|
||||
}
|
||||
if (ret == 0) {
|
||||
if (rpctls_debug_level == 0)
|
||||
syslog(LOG_ERR, "ktls not working");
|
||||
else
|
||||
fprintf(stderr, "ktls not working\n");
|
||||
/*
|
||||
* The handshake has completed, so all that can be
|
||||
* done is disable the connection.
|
||||
*/
|
||||
*flags |= RPCTLS_FLAGS_DISABLED;
|
||||
}
|
||||
|
||||
return (ssl);
|
||||
}
|
||||
|
||||
/*
|
||||
* Acquire the dnsname for this server.
|
||||
*/
|
||||
static char *
|
||||
rpctls_getdnsname(char *hostname)
|
||||
{
|
||||
char *cp, *dnsname;
|
||||
struct addrinfo *aip, hints;
|
||||
int error;
|
||||
|
||||
dnsname = NULL;
|
||||
if (gethostname(hostname, MAXHOSTNAMELEN) == 0) {
|
||||
if ((cp = strchr(hostname, '.')) != NULL &&
|
||||
*(cp + 1) != '\0') {
|
||||
*cp = '@';
|
||||
dnsname = cp;
|
||||
} else {
|
||||
memset((void *)&hints, 0, sizeof (hints));
|
||||
hints.ai_flags = AI_CANONNAME;
|
||||
error = getaddrinfo(hostname, NULL, &hints, &aip);
|
||||
if (error == 0) {
|
||||
if (aip->ai_canonname != NULL &&
|
||||
(cp = strchr(aip->ai_canonname, '.')) !=
|
||||
NULL && *(cp + 1) != '\0') {
|
||||
hostname[0] = '@';
|
||||
strlcpy(&hostname[1], cp + 1,
|
||||
MAXHOSTNAMELEN + 1);
|
||||
dnsname = hostname;
|
||||
}
|
||||
freeaddrinfo(aip);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (dnsname);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for an otherName component of subjectAltName where the OID
|
||||
* matches and the "domain" matches that of this server.
|
||||
* If found, map "user" to a <uid, gidlist> for it.
|
||||
*/
|
||||
static int
|
||||
rpctls_cnname(X509 *cert, uint32_t *uidp, int *ngrps, uint32_t *gidp)
|
||||
{
|
||||
char *cp, usern[1024 + 1];
|
||||
struct passwd *pwd;
|
||||
gid_t gids[NGROUPS];
|
||||
int i, j;
|
||||
GENERAL_NAMES *genlist;
|
||||
GENERAL_NAME *genname;
|
||||
OTHERNAME *val;
|
||||
size_t slen;
|
||||
|
||||
/* First, find the otherName in the subjectAltName. */
|
||||
genlist = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
|
||||
if (genlist == NULL)
|
||||
return (0);
|
||||
cp = NULL;
|
||||
for (i = 0; i < sk_GENERAL_NAME_num(genlist); i++) {
|
||||
genname = sk_GENERAL_NAME_value(genlist, i);
|
||||
if (genname->type != GEN_OTHERNAME)
|
||||
continue;
|
||||
val = genname->d.otherName;
|
||||
|
||||
/* Check to see that it is the correct OID. */
|
||||
slen = i2t_ASN1_OBJECT(usern, sizeof(usern), val->type_id);
|
||||
if (slen != strlen(rpctls_cnuseroid) || memcmp(usern,
|
||||
rpctls_cnuseroid, slen) != 0)
|
||||
continue;
|
||||
|
||||
/* Sanity check the otherName. */
|
||||
if (val->value->type != V_ASN1_UTF8STRING ||
|
||||
val->value->value.utf8string->length < 3 ||
|
||||
(size_t)val->value->value.utf8string->length > sizeof(usern)
|
||||
- 1) {
|
||||
rpctls_verbose_out("rpctls_cnname: invalid cnuser "
|
||||
"type=%d\n", val->value->type);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Look for a "user" in the otherName */
|
||||
memcpy(usern, val->value->value.utf8string->data,
|
||||
val->value->value.utf8string->length);
|
||||
usern[val->value->value.utf8string->length] = '\0';
|
||||
|
||||
/* Now, look for the @dnsname suffix in the commonName. */
|
||||
cp = strcasestr(usern, rpctls_dnsname);
|
||||
if (cp == NULL)
|
||||
continue;
|
||||
if (*(cp + strlen(rpctls_dnsname)) != '\0') {
|
||||
cp = NULL;
|
||||
continue;
|
||||
}
|
||||
*cp = '\0';
|
||||
break;
|
||||
}
|
||||
if (cp == NULL)
|
||||
return (0);
|
||||
|
||||
/* See if the "user" is in the passwd database. */
|
||||
pwd = getpwnam(usern);
|
||||
if (pwd == NULL)
|
||||
return (0);
|
||||
*uidp = pwd->pw_uid;
|
||||
*ngrps = NGROUPS;
|
||||
if (getgrouplist(pwd->pw_name, pwd->pw_gid, gids, ngrps) < 0)
|
||||
return (0);
|
||||
rpctls_verbose_out("mapped user=%s ngrps=%d uid=%d\n", pwd->pw_name,
|
||||
*ngrps, pwd->pw_uid);
|
||||
for (j = 0; j < *ngrps; j++)
|
||||
gidp[j] = gids[j];
|
||||
return (1);
|
||||
}
|
||||
|
||||
static void
|
||||
rpctls_huphandler(int sig __unused)
|
||||
{
|
||||
|
||||
rpctls_gothup = true;
|
||||
}
|
Loading…
Reference in New Issue
Block a user