Index: projects/nfs-over-tls/nfs-over-tls-setup.txt =================================================================== --- projects/nfs-over-tls/nfs-over-tls-setup.txt (revision 364970) +++ projects/nfs-over-tls/nfs-over-tls-setup.txt (revision 364971) @@ -1,362 +1,362 @@ The first part of this document covers how to set up a test environment. This will keep changing as things get merged into FreeBSD's head. Note that, for now, the NFS over TLS uses TLS1.2 and not TLS1.3. This will change once the kernel tls knows how to do 1.3. Setup for testing. For now, setting up test machine(s) is a bit awkward, but hopefully this lists all the steps. - Install a recent FreeBSD current system on amd64 system(s). There are many ways to do this, but downloading and installing a snapshot is probably the easiest. Go anonymous ftp onto ftp.freebsd.org cd pub/FreeBSD/snapshots/ISO-IMAGES/13.0 - You want to get an install image with "amd64" in it (thats 64bit x86). The higher the 6digit number, the newer the snapshot. An iso with "disc1" in the name is a full install image that can be burned onto a DVD. (I haven't done other types of installs, but there are several others.) - Burn the iso onto a DVD (or whatever). - Install it on a 64bit x86 system. If the 6digit number is 364898 or higher, the kernel sources and /usr/include should be sufficiently up to date. If not, you will need to get newer sources via "svn" and symbolically link the directories into /usr/include. (For here on, you might as well login as root.) Now, you will need a subversion client on some machine. If you do not already have one, log into the newly installed FreeBSD system and # pkg install subversion - It will probably ask you to install "pkg" first and then it should work. If your version is less than 364898 or if you do not already have them, you will need to get up-to-date sources via "svn": # cd /usr/src # svn checkout http://svn.freebsd.org/base/head . Use "svn" to download the modified sources into /usr/nfs-over-tls. # cd /usr # mkdir nfs-over-tls # cd nfs-over-tls # svn checkout http://svn.freebsd.org/base/projects/nfs-over-tls . Now, you can build/install the modified kernel. If the src.*.mk files are missing from /usr/share/mk... # cd /usr/share/mk # tar xf /usr/nfs-over-tls/sharemk.tar Set up the kernel source tree. # cd /usr/src/sys/amd64/conf - edit GENERIC and add a line options KERN_TLS to it. # cd /usr/src # make buildkernel # make installkernel Fix the include files. Here's my "cheat" way of doing this. (This is needed if your installed system is prior to 364898.) # cd /usr/include # mv sys sys.old # ln -s /usr/src/sys/sys sys # mv fs fs.old # ln -s /usr/src/sys/fs fs # mv netinet netinet.old # ln -s /usr/src/sys/netinet netinet - This should be enough to allow userspace building. Now, you need jhb@'s patched openssl3 source tree, so you can build it. - If you don't already have one, get a github account. (If you don't have git anywhere, I think "pkg install git" will get it installed.) - You will need perl5. # pkg install perl5 # cd /usr # mkdir openssl # cd openssl # git clone https://github.com/bsdjhb/openssl.git # cd openssl (or not, I can't remember if you end up with another openssl dir?) # git checkout ktls_rx # mkdir obj # cd obj # ../config --prefix=/usr/ktls --openssldir=/usr/ktls enable-ktls # make # make install - This installs the patched openssl3 under /usr/ktls. I only use this stuff for linking the daemons and use the regular openssl1.1.1 otherwise. Now, you need to patch the include files in /usr/ktls/include/openssl. (clang doesn't like the DEFINE_OR_DECLARE_STACK_OF(XX) before the typedef for XX.) # cd /usr/ktls/include/openssl # patch -p0 < /usr/nfs-over-tls/openssl3.patch And now you should be able to build/install the utilities. (You'll get warnings about SSL_CTX_load_verify_locations() deprecated. Thats ok for now.) # cd /usr/nfs-over-tls/usr.sbin/rpctlssd # make SRCTOP=/usr/nfs-over-tls # cp rpctlssd /usr/sbin # cp rpctlssd.8.gz /usr/share/man/man8 # cd ../rpctlscd # make SRCTOP=/usr/nfs-over-tls # cp rpctlscd /usr/sbin # cp rpctlscd.8.gz /usr/share/man/man8 # cd /usr/nfs-over-tls # mkdir sbin # cd sbin # ln -s /usr/src/sbin/mount mount # cd /usr/nfs-over-tls/usr.sbin/mountd # make SRCTOP=/usr/nfs-over-tls # cp mountd /usr/sbin # cp exports.5.gz /usr/share/man/man5 You can copy the rc.d scripts as follows: # cd /usr/nfs-over-tls/rc.d # cp rpctlscd rpctlssd /etc/rc.d Almost done. Here's a few more things you need to do: # cd /etc - edit sysctl.conf and add these two lines kern.ipc.tls.enable=1 kern.ipc.mb_use_ext_pgs=1 Then reboot the system. You should now be finally ready to configure and run a TLS mount. (If you have problems or I forgot any steps, feel free to email me at rmacklem@uoguelph.ca.) First, a bit of background. NFS-over-TLS uses the KERNEL_TLS and will only work on architectures that support a direct map, such as amd64 (not i386). Then daemons must be running on the NFS server(s) and NFS client(s) for NFS-over-TLS to work. rpctlssd(8) for the server(s) and rpctlscd(8) for the client(s). Then you will have to create x509 certificate for at least the NFS server(s) and, optionally, some or all of the NFS clients. There are many ways to create signed certificates for TLS, but here is the simple method I've used for a site local CA, using only the openssl command that is in FreeBSD. I am far from an expert in this area, so there are almost certainly better ways to do this. (Some have mentioned easyrsa in the openVPN software package, but I haven't tried it.) I have only applied this one simple patch to /etc/ssl/openssl.cnf: --- openssl.cnf.sav 2020-03-31 07:41:12.545812000 -0700 +++ openssl.cnf 2020-04-01 10:55:31.682616000 -0700 @@ -66,7 +66,7 @@ name_opt = ca_default # Subject Name options cert_opt = ca_default # Certificate field options # Extension copying option: use with caution. -# copy_extensions = copy +copy_extensions = copy # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs # so this is commented out by default to leave a V1 CRL. You will probably want to make additional changes to /etc/ssl/openssl.cnf. First, I create a simple subtree of files under a top level directory (I use /root) with the following commands: # mkdir demoCA # cd demoCA # cat /dev/null > index.txt # echo 01 > serial # echo 01 > crlnumber # mkdir private # chmod 700 private # mkdir newcerts Now, I use the following commands to create certifcates and a Certificate Revocation List (CRL). (In the directory above "demoCA".) 1 - Create the Certificate Authority (CA) root key. # openssl genrsa -out demoCA/private/cakey.pem 2 - Create a CA root certificate. # openssl req -new -x509 -key demoCA/private/cakey.pem -out demoCA/cacert.pem --> There will be several questions to answer. As far as I know, you can put just about anything in these fields. Now, you should be ready to create/sign certificates for the NFS server/client(s). 3 - Create a key for the certificate. # openssl genrsa -out key.pem (If this certificate is for a client laptop, you might want to use the "-aes256" option, so the key.pem file is encrypted using a passphrase. This implies that the passphrase will need to be entered when the rpctlscd(8) daemon is started on the client, but that the key cannot be used without the passphrase, if it is compromised.) 4 - Create a Certificate Signing Request (CSR). # openssl req -new -key key.pem -addext "subjectAltName=" -out req.pem --> Similar to #2, you will need to answer the questions. The CN must be something different than used for #2. Typically it would be the FQDN of the machine, if it has one. The other fields can be the same as #2 and some (maybe all?) must be the same. is the messy part. The subjectAltName should have all the FQDN name(s) for the machine, if it has any. These are specified via DNS: and are separated by ','s if there are multiple entries. If you are using the "-u" option on the server and this certificate is for a client where you wish all RPCs to be done as a specific "user" on the NFS server, you also need to put an entry in of the form otherName:1.X;UTF8:. Here are a few examples: For a server with a FQDN of nfsv4-server.uoguelph.ca: # openssl req -new -key key.pem -addext "subjectAltName=DNS:nfsv4-server.uoguelph.ca" -out req.pem For a server with two network interfaces, where one reverse DNS resolves to nfsv4-server.uoguelph.ca and the other to nfsv4-server2.uoguelph.ca # openssl req -new -key key.pem -addext "subjectAltName=DNS:nfsv4-server.uoguelph.ca,DNS:nfsv4-server2.uoguelph.ca" -out req.pem For a client where you wish all RPCs to be done as the user rmacklem on the above server: -# openssl req -new -key key.pem -addext "subjectAltName=otherName:1.2.3.4.6.9;UTF8:rmacklem@uoguelph.ca" -out req.pem +# openssl req -new -key key.pem -addext "subjectAltName=otherName:1.3.6.1.4.1.2238.1.1.1;UTF8:rmacklem@uoguelph.ca" -out req.pem For a client similar to the above, but has a FQDN of nfsv4-client.uoguelph.ca: -# openssl req -new -key key.pem -addext "subjectAltName=DNS:nfsv4-client.uoguelph.ca,othername:1.2.3.4.6.9;UTF8:rmacklem@uoguelph.ca" -out req.pem +# openssl req -new -key key.pem -addext "subjectAltName=DNS:nfsv4-client.uoguelph.ca,othername:1.3.6.1.4.1.2238.1.1.1;UTF8:rmacklem@uoguelph.ca" -out req.pem If you want to look at the CSR: # openssl req -in req.pem -noout -text 5 - Sign the CSR with the CA. # openssl ca -in req.pem -out cert.pem (A copy of cert.pem will be kept in demoCA/newcerts with the name .pem.) You can now copy key.pem and cert.pem to the directory /etc/rpctlssd on the server(s) or /etc/rpctlscd on the client(s). If you want to look at any certificate, you can use the command... # openssl x509 -in cert.pem -noout -text For a minimal setup where you do not want certificates in the client(s), all you need to do is #3-5 for each of the NFS servers. If you want certificates for clients, just repeat #3-5 for each of them. If you have created certificates for any of your NFS client(s), you probably want to create a Certificate Revocation List (CRL) as well. The initial file will not have any revocations in it, but can be provided to either/both of the rpctlssd(8) and rpctlscd(8) daemons, then it can be reloaded by posting a SIGHUP to the daemon(s) when updated. This avoids restarting the daemon(s), which is not a good thing to do while there are NFS-over-TLS mount(s) to the NFS server. 6 - Create a new/empty CRL. # openssl ca -gencrl -out demoCA/crl.pem You can look at this CRL with the command... # openssl crl -in crl.pem -noout -text If you need to revoke a certifcate at some time. # openssl ca -revoke where is the certificate being revoked Then do #6 again and copy it to the NFS server(s) and NFS client(s) that are using it. (More on where it is used further down it this doc.) Now, when will you want certificates for some/all of the client(s)? One case might be laptops. They do not have fixed IP addresses/DNS names, so exports(5) cannot be applied to them easily. You might find that the client having a certificate signed by your CA is sufficient assurance of its identity to allow it to do an NFS mount from any IP address. For this case, you can also set the otherName field of the subjectAltName to "user@dns_domain" so that all RPCs will be performed on the server as "user", if you specify the "-u" command line option for the rpctlssd(8) daemon on the NFS server. (If you do not want this feature simply do not set the otherName field of subjectAltName or do not set "-u" on the rpctlssd(8) daemon.) Another case might be where you do not trust the client to use the correct IP address when mounting the NFS server, although the client has a well known fixed IP address/DNS name. For this case, you can also set the DNS field of subjectAltName to the FQDN of the client and enable the server to check this via the "-h" option. The FQDN in the client's certificate may have a wildcard "*" in it, depending on what command line options are specified for the server's rpctlssd. For client(s) where you find controlling mount access via the client's IP address using the exports(5) file is sufficient and you are not using the "-u" command line option on the server's rpctlssd, the client does not need to have a certificate. You can still allow/require the client to use TLS so that the RPC traffic is encrypted on the wire. Once you have key(s) and certificate(s) in the /etc/rpctlssd directory on the NFS server(s) and in the /etc/rpctlscd directory on the NFS client(s), you need to set the appropriate command line option(s) for the daemons. The man pages for rpctlscd(8) and rpctlssd(8) cover the command line options, but here are a few examples. For an NFS server: - An NFS server where no clients have certificates. # rpctlssd - An NFS server where some/all clients have certificates and you wish to verify them against your site local CA created above. # rpctlssd -m -l /root/demoCA/cacert.pem -r /root/demoCA/crl.pem - An NFS server where all clients have certificates and FQDN names that are in the certificates (in either the subjectAltName DNS field or subjectName CN field) and you want to check the client's IP address reverse DNS maps to the FQDN. The FQDN in the client's certificate cannot have a wildcard "*" in it. # rpctlssd -m -h -l /root/demoCA/cacert.pem -r /root/demoCA/crl.pem - Similar to the above, but the FQDN in the client's certificate may have a wildcard "*" in it, which will only match a single component of the client's reverse DNS name. For example, an FQDN set to "*.uoguelph.ca" will match "laptop21.uoguelph.ca", but not "laptop3.cis.uoguelph.ca". # rpctlssd -m -h -w -l /root/demoCA/cacert.pem -r /root/demoCA/crl.pem - Similar to the above, except that a wildcard "*" in the FQDN can match multiple fields. For example, if the FQDN is set to "*.uoguelph.ca", it would match "laptop3.cis.uoguelph.ca" as well as "laptop21.uoguelph.ca". # rpctlssd -m -h -W -l /root/demoCA/cacert.pem -r /root/demoCA/crl.pem - An NFS server where some client(s) have certificates with the otherName field of the subjectAltName set to "user@dns_domain" and you want those clients to use the for "user" in the password database for all RPCs on the connection, ignoring the credentials in the RPC header. # rpctlssd -m -u -l /root/demoCA/cacert.pem -r /root/demoCA/crl.pem For an NFS client: - An NFS client without a certificate. # rpctlscd - An NFS client with a certificate and key in /etc/rpctlscd on the client created by the site local CA above that the server can use for verification. # rpctlscd -m - An NFS client which wants to verify the NFS server's certificate. (This requires that the cacert.pem and crl.pem be copied onto the client from the CA site.) The FQDN in the server's certificate must match the reverse DNS name for the server's IP address and there cannot be a wildcard in the FQDN. # rpctlscd -l -r - An NFS client that has a certificate and key in /etc/rpctlscd and also wishes to verify the NFS server's certificate as above. # rpctlscd -m -l -r If you use either the "-m" and/or "-v" options, you probably want to modify your /etc/syslog.conf so that "LOG_INFO | LOG_DAEMON" goes somewhere. For "-m" it will log failed certificate verifications there and for "-v" it will log a lot of other stuff, as well. Once you have set things up, you can add line(s) to your /etc/rc.conf for the daemon(s): For the client: rpctlscd_enable="YES" For the server: rpctlssd_enable="YES" - plus rpctlscd_flags and/or rpctlssd_flags if you are using command line options for these (see below). Index: projects/nfs-over-tls/usr.sbin/rpctlssd/rpctlssd.8 =================================================================== --- projects/nfs-over-tls/usr.sbin/rpctlssd/rpctlssd.8 (revision 364970) +++ projects/nfs-over-tls/usr.sbin/rpctlssd/rpctlssd.8 (revision 364971) @@ -1,332 +1,332 @@ .\" 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 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 n Ar domain_name .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). 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.2.3.4.6.9;UTF8: field of +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@dns_domain that maps to a . 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/rpctlssd (or the .Dq certdir specified by the .Fl D option) in files named .Dq cert.pem and .Dq key.pem . .Pp If a SIGHUP signal is sent to the daemon it will reload the .Dq CRLfile . 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 Use .Dq certdir instead of /etc/rpctlssd as the location for the certificate in a file called .Dq cert.pem and key in .Dq 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 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 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 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 export 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_name This option specifies what the .Dq domain_name is for use with the .Fl u option, overriding the domain_name 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_name that was specified for .Xr nfsuserd 8 . .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 u This option specifies that if the client provides a certificate that both verifies and has a subjectAltName with an otherName of the form -.Dq otherName:1.2.3.4.6.9;UTF8:user@dns_domain +.Dq otherName:1.3.6.1.4.1.2238.1.1.1;UTF8:user@dns_domain the daemon will attempt to map .Dq user@dns_domain in the above to a . The mapping of .Dq user@dns_domain is done in the same manner as the .Xr nfsuserd 8 daemon, where .Dq dns_domain is the domain of the NFS server (or the one set via the .Fl n option) and .Dq user is a valid username in the password database. If this mapping is successful, then the 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 does not conform to RFC-X, which does not allow certificates to be used for user authentication. .It Fl v 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 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 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 exports 5 , .Xr mount_nfs 8 , .Xr nfsuserd 8 , .Xr rpctlscd 8 , .Xr syslogd 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/rpctlssd/rpctlssd.c =================================================================== --- projects/nfs-over-tls/usr.sbin/rpctlssd/rpctlssd.c (revision 364970) +++ projects/nfs-over-tls/usr.sbin/rpctlssd/rpctlssd.c (revision 364971) @@ -1,973 +1,973 @@ /*- * 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 server 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 #include "rpctlssd.h" #ifndef _PATH_RPCTLSSDSOCK #define _PATH_RPCTLSSDSOCK "/var/run/rpctlssd.sock" #endif #ifndef _PATH_CERTANDKEY #define _PATH_CERTANDKEY "/etc/rpctlssd/" #endif #ifndef _PATH_RPCTLSSDPID #define _PATH_RPCTLSSDPID "/var/run/rpctlssd.pid" #endif #ifndef _PREFERRED_CIPHERS #define _PREFERRED_CIPHERS "AES128-GCM-SHA256" #endif static struct pidfh *rpctls_pfh = NULL; static int rpctls_debug_level; static bool rpctls_verbose; static SSL_CTX *rpctls_ctx = NULL; static bool rpctls_do_mutual = false; 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_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_gothup = false; static bool rpctls_cnuser = false; static char *rpctls_dnsname; -static const char *rpctls_cnuseroid = "1.2.3.4.6.9"; +static const char *rpctls_cnuseroid = "1.3.6.1.4.1.2238.1.1.1"; /* * 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 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); static int rpctls_gethost(int s, struct sockaddr *sad, char *hostip, size_t hostlen); static int rpctls_checkhost(struct sockaddr *sad, X509 *cert); static int rpctls_loadcrlfile(SSL_CTX *ctx); 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); 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, debug; SVCXPRT *xprt; struct timeval tm; struct timezone tz; char hostname[MAXHOSTNAMELEN + 2]; pid_t otherpid; /* 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"); } if (modfind("ktls_ocf") < 0) { /* Not present in kernel, try loading it */ if (kldload("ktls_ocf") < 0 || modfind("ktls_ocf") < 0) errx(1, "Cannot load ktls_ocf"); } if (modfind("aesni") < 0) { /* Not present in kernel, try loading it */ kldload("aesni"); } /* 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; } fprintf(stderr, "dnsname=%s\n", rpctls_dnsname); debug = 0; rpctls_verbose = false; while ((ch = getopt(argc, argv, "D:dhl:n:mp:r:uvWw")) != -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] [-d] [-h] " "[-l CAfile] [-m] " "[-n domain_name] " "[-p CApath] [-r CRLfile] " "[-u] [-v] [-W] [-w]\n", argv[0]); exit(1); } } if (rpctls_do_mutual && rpctls_verify_cafile == NULL && rpctls_verify_capath == NULL) errx(1, "-m requires the -l and/or " "-p options"); if (rpctls_comparehost && (!rpctls_do_mutual || (rpctls_verify_cafile == NULL && rpctls_verify_capath == NULL))) errx(1, "-h requires the -m plus the " "-l and/or -p 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 and/or -p 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); svc_run(); rpctls_syscall(RPCTLS_SYSC_SRVSHUTDOWN, ""); SSL_CTX_free(rpctls_ctx); EVP_cleanup(); return (0); } static void rpctlssd_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 rpctlssd_null_1_svc(void *argp, void *result, struct svc_req *rqstp) { rpctlssd_verbose_out("rpctlssd_null_svc: done\n"); return (TRUE); } bool_t rpctlssd_connect_1_svc(void *argp, struct rpctlssd_connect_res *result, struct svc_req *rqstp) { int ngrps, s; SSL *ssl; uint32_t flags; struct ssl_entry *newslp; uint32_t uid; uint32_t *gidp; rpctlssd_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, ""); rpctlssd_verbose_out("rpctlsd_connect_svc s=%d\n", s); 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); if (ssl == NULL) { free(gidp); rpctlssd_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 { rpctlssd_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->refno = rpctls_ssl_refno; 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, 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) { rpctlssd_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); rpctlssd_verbose_out("get_shutdown=%d\n", ret); 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, 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) { rpctlssd_verbose_out("rpctlssd_disconnect fd=%d closed\n", slp->s); LIST_REMOVE(slp, next); ret = SSL_get_shutdown(slp->ssl); rpctlssd_verbose_out("get_shutdown1=%d\n", ret); /* * 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); /* * For RPC-over-TLS, this upcall is expected * to close off the socket. */ 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(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 *)result; if (res->gid.gid_val != NULL) 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); /* * Shut down all TCP connections, so that any compromised TLS * connection is no longer usable. */ LIST_FOREACH(slp, &rpctls_ssllist, next) shutdown(slp->s, SHUT_RD); exit(0); } /* Allow the handshake to proceed. */ static int rpctls_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) { return (1); } static SSL_CTX * rpctls_setup_ssl(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) { rpctlssd_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) { rpctlssd_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 key.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) { rpctlssd_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], "key.pem", rlen) != 7) { SSL_CTX_free(ctx); return (NULL); } ret = SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM); if (ret != 1) { rpctlssd_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) { rpctlssd_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) { rpctlssd_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); } return (ctx); } static SSL * rpctls_server(SSL_CTX *ctx, int s, uint32_t *flags, uint32_t *uidp, int *ngrps, uint32_t *gidp) { SSL *ssl; X509 *cert; struct sockaddr *sad; struct sockaddr_storage ad; char hostnam[NI_MAXHOST]; int gethostret, ret; char *cp, *cp2; *flags = 0; sad = (struct sockaddr *)&ad; if (rpctls_gothup) { rpctls_gothup = false; ret = rpctls_loadcrlfile(ctx); if (ret == 0) rpctlssd_verbose_out("rpctls_server: Can't " "reload CRLfile\n"); } ssl = SSL_new(ctx); if (ssl == NULL) { rpctlssd_verbose_out("rpctls_server: SSL_new failed\n"); return (NULL); } if (SSL_set_fd(ssl, s) != 1) { rpctlssd_verbose_out("rpctls_server: SSL_set_fd failed\n"); SSL_free(ssl); return (NULL); } ret = SSL_accept(ssl); if (ret != 1) { rpctlssd_verbose_out("rpctls_server: SSL_accept " "failed ret=%d\n", ret); SSL_free(ssl); return (NULL); } *flags |= RPCTLS_FLAGS_HANDSHAKE; if (rpctls_do_mutual) { cert = SSL_get_peer_certificate(ssl); if (cert != NULL) { 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); rpctlssd_verbose_out("%s\n", cp2); *flags |= RPCTLS_FLAGS_GOTCERT; ret = SSL_get_verify_result(ssl); if (ret != 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(ret)); else fprintf(stderr, "rpctls_server: client IP %s " "issuerName=%s subjectName=%s" " verify failed %s\n", hostnam, cp, cp2, X509_verify_cert_error_string(ret)); } if (ret == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || ret == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) *flags |= RPCTLS_FLAGS_SELFSIGNED; else if (ret == X509_V_OK) { if (rpctls_comparehost) { ret = 0; if (gethostret != 0) ret = rpctls_checkhost(sad, cert); if (ret != 1) { *flags |= RPCTLS_FLAGS_DISABLED; rpctlssd_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; } X509_free(cert); } else rpctlssd_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)); rpctlssd_verbose_out("rpctls_server: BIO_get_ktls_send=%d\n", ret); if (ret != 0) { ret = BIO_get_ktls_recv(SSL_get_rbio(ssl)); rpctlssd_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); } /* * Get the client's IP address. */ static 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) { rpctlssd_verbose_out("rpctls_gethost: %s\n", hostip); ret = 1; } return (ret); } /* * Check a client IP address against any host address in the * certificate. Basically getnameinfo(3) and * X509_check_host(). */ static int rpctls_checkhost(struct sockaddr *sad, X509 *cert) { 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); rpctlssd_verbose_out("rpctls_checkhost: DNS %s\n", hostnam); ret = X509_check_host(cert, hostnam, strlen(hostnam), rpctls_wildcard, NULL); return (ret); } /* * 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 a commonName to see if it maps to "user@domain" and * acquire a for it if it does. */ 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; GENERAL_NAMES *genlist; GENERAL_NAME *genname; OTHERNAME *val; /* First, find the otherName in the subjectAltName. */ genlist = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); rpctlssd_verbose_out("genlist=%p\n", genlist); if (genlist == NULL) return (0); val = 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; break; } if (val == NULL) return (0); rpctlssd_verbose_out("fnd type=0x%x len=%d anstyp=0x%x data=%s\n", val->value->type, val->value->value.utf8string->length, val->value->value.utf8string->type, val->value->value.utf8string->data); /* Check to see that it is the correct OID. */ i = i2t_ASN1_OBJECT(usern, sizeof(usern), val->type_id); rpctlssd_verbose_out("obj=%d str=%s\n", i, usern); if (i != strlen(rpctls_cnuseroid) || memcmp(usern, rpctls_cnuseroid, i) != 0) { rpctlssd_verbose_out("rpctls_cnname: invalid cnuser " "oid len=%d val=%s\n", i, usern); return (0); } /* Sanity check the otherName. */ if (val->value->type != V_ASN1_UTF8STRING || val->value->value.utf8string->length < 3 || val->value->value.utf8string->length > sizeof(usern) - 1) { rpctlssd_verbose_out("rpctls_cnname: invalid cnuser " "type=%d\n", val->value->type); return (0); } /* 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'; rpctlssd_verbose_out("rpctls_cnname: userstr %s\n", usern); /* Now, look for the @dnsname suffix in the commonName. */ cp = strcasestr(usern, rpctls_dnsname); if (cp == NULL) return (0); rpctlssd_verbose_out("dns=%s\n", cp); if (*(cp + strlen(rpctls_dnsname)) != '\0') return (0); *cp = '\0'; /* See if the "user" is in the passwd database. */ rpctlssd_verbose_out("user=%s\n", usern); pwd = getpwnam(usern); if (pwd == NULL) return (0); rpctlssd_verbose_out("pwname=%s\n", pwd->pw_name); *uidp = pwd->pw_uid; *ngrps = NGROUPS; if (getgrouplist(pwd->pw_name, pwd->pw_gid, gids, ngrps) < 0) return (0); for (i = 0; i < *ngrps; i++) gidp[i] = gids[i]; return (1); } /* * (re)load the CRLfile into the certificate verification store. */ static 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) { rpctlssd_verbose_out( "rpctls_loadcrlfile: Can't" " load CRLfile=%s\n", rpctls_crlfile); return (ret); } } return (1); } static void rpctls_huphandler(int sig __unused) { rpctls_gothup = true; }