Index: projects/nfs-over-tls/usr.sbin/rpctlscd/Makefile =================================================================== --- projects/nfs-over-tls/usr.sbin/rpctlscd/Makefile (revision 359224) +++ projects/nfs-over-tls/usr.sbin/rpctlscd/Makefile (revision 359225) @@ -1,30 +1,29 @@ # $FreeBSD$ .include PROG= rpctlscd MAN= rpctlscd.8 SRCS= rpctlscd.c rpctlscd.h rpctlscd_svc.c rpctlscd_xdr.c CFLAGS+= -I. -WARNS?= 1 LIBADD= ssl crypto 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 .include Index: projects/nfs-over-tls/usr.sbin/rpctlscd/rpctlscd.8 =================================================================== --- projects/nfs-over-tls/usr.sbin/rpctlscd/rpctlscd.8 (revision 359224) +++ projects/nfs-over-tls/usr.sbin/rpctlscd/rpctlscd.8 (revision 359225) @@ -1,72 +1,149 @@ .\" Copyright (c) 2008 Isilon Inc http://www.isilon.com/ .\" Authors: Doug Rabson .\" Developed with Red Inc: Alfred Perlstein .\" .\" 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 rpctlscd.8 by Rick Macklem. -.Dd January 21, 2020 +.Dd March 11, 2020 .Dt RPCTLSCD 8 .Os .Sh NAME .Nm rpctlscd .Nd "Sun RPC over TLS Client 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 p Ar CApath +.Op Fl r Ar CRLfile +.Op Fl V .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. .Pp The options are as follows: .Bl -tag -width indent +.It Fl D Ar certdir +Use ``certdir'' instead of /etc/rpctlscd for the +.Fl c +option. .It Fl d Run in debug mode. In this mode, .Nm will not fork when it starts. +.It Fl h +This option specifies that the certificate provided by the server during +TLS handshake must have the Fully Qualified Domain Name for the server's +IP address in either the subjectAltName or commonName field of the +certificate. +This option is meaningless unless the +.FL V +option is also specified. +.It Fl l 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)) +calls. +Note that this is a path name for the file and is not assumed to be +in ``certdir''. +This option may need to be specified when the +.Fl V +option is specified. +.It Fl m +Enable support for mutual authentication. +A certificate must be found in /etc/rpctlscd (or the directory specified by +.Fl D ) +in case a server requests a peer certificate. +The certificate needs to be in a file named ``cert.pem'' and a key in +a file named ``key.pem'' in the directory for this option to work. +.It Fl p 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 might not be passed +to the server during the TLS handshake. +The openssl documentation indicates this call is rarely needed. +(However, I was not able to determine if/when this matters, so +if in doubt, use the +.Fl l +option instead of this option.) +.It Fl r 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 meaningless unless either the +.Fl l +or +.Fl p +have been specified. +.It Fl V +This option specifies that the certificate provided by the server +during the TLS handshake must verify. +If this option is specified, the +.Fl l +option may also need to be specified. .It Fl v 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 3 , +.Xr openssl 1 , .Xr syslog 3 , .Xr mount_nfs 8 , .Xr rpctlssd 8 +.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. .Sh HISTORY The .Nm manual page first appeared in .Fx 13.0 . Index: projects/nfs-over-tls/usr.sbin/rpctlscd/rpctlscd.c =================================================================== --- projects/nfs-over-tls/usr.sbin/rpctlscd/rpctlscd.c (revision 359224) +++ projects/nfs-over-tls/usr.sbin/rpctlscd/rpctlscd.c (revision 359225) @@ -1,483 +1,657 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ * Authors: Doug Rabson * Developed with Red Inc: Alfred Perlstein * * 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. */ /* Modified from gssd.c for the client side of kernel RPC-over-TLS. */ #include __FBSDID("$FreeBSD$"); #include #include -#include +#include +#include +#include #include #include +#include #include #include #include #include #include #include #include #include #include -#include - #include #include #include #include #include #include #include #include "rpctlscd.h" #ifndef _PATH_RPCTLSCDSOCK #define _PATH_RPCTLSCDSOCK "/var/run/rpctlscd.sock" #endif #ifndef _PATH_CERTANDKEY #define _PATH_CERTANDKEY "/etc/rpctlscd/" #endif +#ifndef _PREFERRED_CIPHERS +#define _PREFERRED_CIPHERS "SHA384:SHA256:!CAMELLIA" +#endif static int rpctls_debug_level; static bool rpctls_verbose; static int testnossl; static SSL_CTX *rpctls_ctx = NULL; static const char *rpctls_verify_cafile = NULL; +static const char *rpctls_verify_capath = NULL; +static const char *rpctls_crlfile = NULL; static const char *rpctls_certdir = _PATH_CERTANDKEY; static bool rpctls_verify = false; static bool rpctls_comparehost = false; +static uint64_t rpctls_ssl_refno = 0; +static uint64_t rpctls_ssl_sec = 0; +static uint64_t rpctls_ssl_usec = 0; +static bool rpctls_gothup = false; +/* + * 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; + SSL *ssl; +}; +static struct ssl_list rpctls_ssllist; + static void rpctlscd_terminate(int); static SSL_CTX *rpctls_setupcl_ssl(bool cert); static SSL *rpctls_connect(SSL_CTX *ctx, int s); static int rpctls_checkhost(int s, X509 *cert); +static int rpctls_loadfiles(SSL_CTX *ctx); +static void rpctls_huphandler(int sig __unused); extern void rpctlscd_1(struct svc_req *rqstp, SVCXPRT *transp); extern int gssd_syscall(const char *path); 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 fd, oldmask, ch; SVCXPRT *xprt; bool cert; + struct timeval tm; + struct timezone tz; + /* 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; testnossl = 0; cert = false; - while ((ch = getopt(argc, argv, "cD:dhl:tVv")) != -1) { + while ((ch = getopt(argc, argv, "D:dhl:mp:rtVv")) != -1) { switch (ch) { - case 'c': - cert = true; - break; 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': + cert = true; + break; + case 'p': + rpctls_verify_capath = optarg; + break; + case 'r': + rpctls_crlfile = optarg; + break; case 't': testnossl = 1; break; case 'V': rpctls_verify = true; break; case 'v': rpctls_verbose = true; break; default: - fprintf(stderr, "usage: %s [-c] " + fprintf(stderr, "usage: %s " "[-D certdir] [-d] [-h] " - "[-l verify_locations_file] " + "[-l CAfile] [-m] " + "[-p CApath] [-r CRLfile] " "[-V] [-v]\n", argv[0]); exit(1); break; } } + 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) { 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, rpctlscd_terminate); + signal(SIGHUP, rpctls_huphandler); 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"); } /* Set up the OpenSSL TSL stuff. */ rpctls_ctx = rpctls_setupcl_ssl(cert); if (rpctls_ctx == NULL) { if (rpctls_debug_level == 0) { syslog(LOG_ERR, "Can't set up TSL context"); exit(1); } err(1, "Can't set up TSL context"); } + LIST_INIT(&rpctls_ssllist); gssd_syscall(_PATH_RPCTLSCDSOCK); svc_run(); gssd_syscall(""); SSL_CTX_free(rpctls_ctx); EVP_cleanup(); return (0); } static void rpctlscd_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); } } bool_t rpctlscd_null_1_svc(void *argp, void *result, struct svc_req *rqstp) { rpctlscd_verbose_out("rpctlscd_null: done\n"); return (TRUE); } bool_t -rpctlscd_connect_1_svc(void *argp, void *result, struct svc_req *rqstp) +rpctlscd_connect_1_svc(void *argp, + struct rpctlscd_connect_res *result, struct svc_req *rqstp) { int s; bool_t res; SSL *ssl; char buf[1024]; ssize_t siz, ret; + struct ssl_entry *newslp; rpctlscd_verbose_out("rpctlsd_connect: started\n"); /* Get the socket fd from the kernel. */ s = gssd_syscall("C"); rpctlscd_verbose_out("rpctlsd_connect s=%d\n", s); if (s < 0) return (FALSE); /* Do a TLS connect handshake. */ ssl = rpctls_connect(rpctls_ctx, s); if (ssl == NULL) rpctlscd_verbose_out("rpctlsd_connect: can't do TLS " "handshake\n"); + else { + 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 (testnossl != 0 && ssl != NULL) { /* Read the 478 bytes of junk off the socket. */ siz = 478; ret = 1; while (siz > 0 && ret > 0) { ret = recv(s, &buf[478 - siz], siz, 0); siz -= ret; } } - /* Done with socket fd, so let the kernel know. */ - gssd_syscall("D"); - if (ssl == NULL) + if (ssl == NULL) { + /* + * For RPC-over-TLS, this upcall is expected + * to close off the socket. + */ + shutdown(s, SHUT_WR); + close(s); return (FALSE); + } + + /* Maintain list of all current SSL *'s */ + newslp = malloc(sizeof(*newslp)); + newslp->refno = rpctls_ssl_refno; + newslp->s = s; + newslp->ssl = ssl; + LIST_INSERT_HEAD(&rpctls_ssllist, newslp, next); return (TRUE); } +bool_t +rpctlscd_disconnect_1_svc(struct rpctlscd_disconnect_arg *argp, + void *result, struct svc_req *rqstp) +{ + struct ssl_entry *slp; + + 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) { + rpctlscd_verbose_out("rpctlscd_disconnect: fd=%d closed\n", + slp->s); + LIST_REMOVE(slp, next); + SSL_shutdown(slp->ssl); + SSL_free(slp->ssl); + /* + * For RPC-over-TLS, this upcall is expected + * to close off the socket. + */ + shutdown(slp->s, SHUT_WR); + close(slp->s); + free(slp); + } else + return (FALSE); + return (TRUE); +} + int rpctlscd_1_freeresult(SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result) { return (TRUE); } static void rpctlscd_terminate(int sig __unused) { gssd_syscall(""); exit(0); } static SSL_CTX * rpctls_setupcl_ssl(bool cert) { 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) { rpctlscd_verbose_out("rpctls_setupcl_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) { + rpctlscd_verbose_out("rpctls_setupcl_ssl: " + "SSL_CTX_set_cipher_list failed to set any ciphers\n"); + SSL_CTX_free(ctx); + return (NULL); + } + + /* * If cert is true, a certificate and key exists in * rpctls_certdir, so that it can do mutual authentication. */ if (cert) { /* Get the cert.pem and key.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) { rpctlscd_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], "key.pem", rlen) != 7) { SSL_CTX_free(ctx); return (NULL); } ret = SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM); if (ret != 1) { rpctlscd_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) { + if (rpctls_verify_cafile != NULL || rpctls_verify_capath != NULL) { + if (rpctls_crlfile != NULL) { + ret = rpctls_loadfiles(ctx); + if (ret == 0) { + rpctlscd_verbose_out("rpctls_setup_ssl: " + "Load CAfile, CRLfile failed\n"); + SSL_CTX_free(ctx); + return (NULL); + } + } ret = SSL_CTX_load_verify_locations(ctx, - rpctls_verify_cafile, NULL); + rpctls_verify_cafile, rpctls_verify_capath); if (ret != 1) { rpctlscd_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. */ #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); return (ctx); } static SSL * rpctls_connect(SSL_CTX *ctx, int s) { SSL *ssl; X509 *cert; int ret; char *cp; + if (rpctls_gothup) { + rpctls_gothup = false; + ret = rpctls_loadfiles(ctx); + if (ret == 0) + rpctlscd_verbose_out("rpctls_connect: Can't " + "load CAfile, CRLfile\n"); + } ssl = SSL_new(ctx); if (ssl == NULL) { rpctlscd_verbose_out("rpctls_connect: " "SSL_new failed\n"); return (NULL); } if (SSL_set_fd(ssl, s) != 1) { rpctlscd_verbose_out("rpctls_connect: " "SSL_set_fd failed\n"); SSL_free(ssl); return (NULL); } ret = SSL_connect(ssl); if (ret != 1) { rpctlscd_verbose_out("rpctls_connect: " "SSL_connect failed %d\n", ret); SSL_free(ssl); return (NULL); } cert = SSL_get_peer_certificate(ssl); if (cert == NULL) { rpctlscd_verbose_out("rpctls_connect: get peer" " certificate failed\n"); SSL_shutdown(ssl); SSL_free(ssl); return (NULL); } + cp = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0); + rpctlscd_verbose_out("rpctls_connect: cert issuerName=%s\n", cp); cp = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); rpctlscd_verbose_out("rpctls_connect: cert subjectName=%s\n", cp); ret = SSL_get_verify_result(ssl); rpctlscd_verbose_out("rpctls_connect: get " "verify result=%d\n", ret); if (ret == X509_V_OK && rpctls_comparehost && rpctls_checkhost(s, cert) != 1) ret = X509_V_ERR_HOSTNAME_MISMATCH; X509_free(cert); if (rpctls_verify && ret != X509_V_OK) { SSL_shutdown(ssl); SSL_free(ssl); return (NULL); } #ifdef notnow ret = BIO_get_ktls_send(SSL_get_wbio(ssl)); fprintf(stderr, "ktls_send=%d\n", ret); ret = BIO_get_ktls_recv(SSL_get_rbio(ssl)); fprintf(stderr, "ktls_recv=%d\n", ret); #endif return (ssl); } /* * Check a client IP address against any host address in the * certificate. Basically getpeername(2), getnameinfo(3) and * X509_check_host(). */ static int rpctls_checkhost(int s, X509 *cert) { struct sockaddr *sad; - struct sockaddr_in *sin; - struct sockaddr_in6 *sin6; struct sockaddr_storage ad; - char hostnam[NI_MAXHOST + 1], addrstr[INET6_ADDRSTRLEN + 1]; - const char *cp; + char hostnam[NI_MAXHOST]; socklen_t slen; int ret; sad = (struct sockaddr *)&ad; slen = sizeof(ad); if (getpeername(s, sad, &slen) < 0) return (0); - switch (sad->sa_family) { - case AF_INET: - sin = (struct sockaddr_in *)sad; - cp = inet_ntop(sad->sa_family, &sin->sin_addr.s_addr, - addrstr, sizeof(addrstr)); - if (cp != NULL) - rpctlscd_verbose_out("rpctls_checkhost: " - "peer ip %s\n", cp); - if (getnameinfo((const struct sockaddr *)sad, - sizeof(struct sockaddr_in), hostnam, - sizeof(hostnam), NULL, 0, NI_NAMEREQD) != 0) - return (0); - break; - case AF_INET6: - sin6 = (struct sockaddr_in6 *)sad; - cp = inet_ntop(sad->sa_family, &sin6->sin6_addr, - addrstr, sizeof(addrstr)); - if (cp != NULL) - rpctlscd_verbose_out("rpctls_checkhost: " - "peer ip %s\n", cp); - if (getnameinfo((const struct sockaddr *)sad, - sizeof(struct sockaddr_in6), hostnam, - sizeof(hostnam), NULL, 0, NI_NAMEREQD) != 0) - return (0); - break; - default: + if (getnameinfo((const struct sockaddr *)sad, + sad->sa_len, hostnam, sizeof(hostnam), + NULL, 0, NI_NUMERICHOST) == 0) + rpctlscd_verbose_out("rpctls_checkhost: %s\n", + hostnam); + if (getnameinfo((const struct sockaddr *)sad, + sad->sa_len, hostnam, sizeof(hostnam), + NULL, 0, NI_NAMEREQD) != 0) return (0); - } - rpctlscd_verbose_out("rpctls_checkhost: hostname %s\n", - hostnam); + rpctlscd_verbose_out("rpctls_checkhost: DNS %s\n", hostnam); ret = X509_check_host(cert, hostnam, strlen(hostnam), 0, NULL); - rpctlscd_verbose_out("rpctls_checkhost: X509_check_host ret=%d\n", - ret); return (ret); +} + +/* + * Load the CAfile (and optionally CRLfile) into the certificate + * verification store. + */ +static int +rpctls_loadfiles(SSL_CTX *ctx) +{ + X509_STORE *certstore; + X509_LOOKUP *certlookup; + int ret; + + if (rpctls_verify_cafile != NULL || + rpctls_verify_capath != NULL) { + if (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) { + rpctlscd_verbose_out( + "rpctls_loadfiles: Can't" + " load CRLfile=%s\n", + rpctls_crlfile); + return (ret); + } + } + ret = SSL_CTX_load_verify_locations(ctx, + rpctls_verify_cafile, rpctls_verify_capath); + if (ret == 0) { + rpctlscd_verbose_out("rpctls_loadfiles: " + "Can't load verify locations\n"); + return (ret); + } + if (rpctls_verify_cafile != NULL) + SSL_CTX_set_client_CA_list(ctx, + SSL_load_client_CA_file( + rpctls_verify_cafile)); + } + return (1); +} + +static void +rpctls_huphandler(int sig __unused) +{ + + rpctls_gothup = true; } Index: projects/nfs-over-tls/usr.sbin/rpctlssd/rpctlssd.8 =================================================================== --- projects/nfs-over-tls/usr.sbin/rpctlssd/rpctlssd.8 (revision 359224) +++ projects/nfs-over-tls/usr.sbin/rpctlssd/rpctlssd.8 (revision 359225) @@ -1,76 +1,189 @@ .\" Copyright (c) 2008 Isilon Inc http://www.isilon.com/ .\" Authors: Doug Rabson .\" Developed with Red Inc: Alfred Perlstein .\" .\" 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 rpctlssd.8 by Rick Macklem. -.Dd January 21, 2020 +.Dd March 11, 2020 .Dt RPCTLSSD 8 .Os .Sh NAME .Nm rpctlssd .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 p Ar CApath +.Op Fl r Ar CRLfile .Op Fl v .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 is needed to support clients doing NFS over TLS. +Note that the +.Fl tls +option in the +.Xr exports 5 +file specifies that the client must use RPC over TLS and the +.Fl tlscert +option in the +.Xr exports 5 +file specifies that the client must provide a certificate +that verifies. +For this latter case, the +.Fl m +and +.Fl l +options must 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 +.Fl l +options to require that the client certificate have the correct +Fully Qualified Domain Name in it. +.Pp +A certificate and associated key must exist in /etc/rpctlssd +(or the ``certdir'' specified by the +.Fl D +option) +in files named ``cert.pem'' and ``key.pem''. +.Pp The options are as follows: .Bl -tag -width indent +.It Fl D Ar certdir +Use ``certdir'' instead of /etc/rpctlssd as the location for the +certificate in a file called ``cert.pem'' and key in ``key.pem''. .It Fl d Run in debug mode. In this mode, .Nm will not fork when it starts. +.It Fl h +This option specifies that the client must provide a certificate +that both verifies and has the Fully Qualified Domain Name (FQDN) for +the IP address that the client uses to connect to the server +in either the subjectAltName or commonName field of the +certificate. +With this option, a failure to verify the client certificate +or find the FQDN in the certificate will result in the +server sending AUTH_REJECTEDCRED replies to all client RPCs. +This option requires the +.Fl m +and +.Fl l +options. +.It Fl l 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 calls. +Note that this is a path name for the file and is not assumed to be +in ``certdir''. +This option should be specified when the +.Fl m +option is specified so that the daemon can verify the client's +certificate. +.It Fl m +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, the export option +.Fl tlscert +will be used to require a client to provide a certificate +that verifies. +.It Fl p 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. +(I was not able to determine if/when this matters, but +if in doubt, use the +.Fl l +option instead of this option.) +.It Fl r 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 meaningless unless either the +.Fl l +or +.Fl p +have been specified. .It Fl v 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 3 , +.Xr openssl 1 , .Xr syslog 3 , +.Xr exports 5 , .Xr mount_nfs 8 , .Xr rpctlscd 8 +.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. .Sh HISTORY The .Nm manual page first appeared in .Fx 13.0 . -.Sh AUTHORS -This -manual page was adapted from a manual page for the gssd daemon written by -.An Doug Rabson Aq Mt dfr@FreeBSD.org .