Richard W.M. Jones
2018-Jun-25 21:33 UTC
[Libguestfs] [PATCH v2 nbdkit] tls: Implement Pre-Shared Keys (PSK)
v2: * Improved documentation. * Added a test (interop with qemu client).
Richard W.M. Jones
2018-Jun-25 21:33 UTC
[Libguestfs] [PATCH v2 nbdkit] tls: Implement Pre-Shared Keys (PSK) authentication.
--- .gitignore | 1 + docs/nbdkit.pod.in | 42 ++++++++- nbdkit.in | 2 +- src/crypto.c | 234 +++++++++++++++++++++++++++++++++++--------------- src/internal.h | 1 + src/main.c | 8 +- tests/Makefile.am | 8 ++ tests/make-psk.sh | 54 ++++++++++++ tests/test-tls-psk.sh | 118 +++++++++++++++++++++++++ 9 files changed, 394 insertions(+), 74 deletions(-) diff --git a/.gitignore b/.gitignore index 5c3d822..c986d76 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ Makefile.in /tests/disk.xz /tests/ext2.img /tests/file-data +/tests/keys.psk /tests/offset-data /tests/partition-disk /tests/pki diff --git a/docs/nbdkit.pod.in b/docs/nbdkit.pod.in index 42e6e6b..115db3c 100644 --- a/docs/nbdkit.pod.in +++ b/docs/nbdkit.pod.in @@ -11,7 +11,7 @@ nbdkit - A toolkit for creating NBD servers [--newstyle] [--oldstyle] [-P PIDFILE] [-p PORT] [-r] [--run CMD] [-s] [--selinux-label LABEL] [-t THREADS] [--tls=off|on|require] [--tls-certificates /path/to/certificates] - [--tls-verify-peer] + [--tls-psk /path/to/pskfile] [--tls-verify-peer] [-U SOCKET] [-u USER] [-v] [-V] PLUGIN [key=value [key=value [...]]] @@ -288,6 +288,12 @@ support). See L</TLS> below. Set the path to the TLS certificates directory. If not specified, some built-in paths are checked. See L</TLS> below for more details. +=item B<--tls-psk> /path/to/pskfile + +Set the path to the pre-shared keys (PSK) file. If used, this +overrides certificate authentication. There is no built-in path. See +L</TLS> below for more details. + =item B<--tls-verify-peer> Enables TLS client certificate verification. The default is I<not> to @@ -757,6 +763,35 @@ denied. Also denied are clients which present a valid certificate signed by another CA. Also denied are clients with certificates added to the certificate revocation list (F<ca-crl.pem>). +=head2 TLS with Pre-Shared Keys (PSK) + +As a simpler alternative to TLS certificates, you may used pre-shared +keys to authenticate clients. + +Create a PSK file containing one or more C<username:key> pairs. It is +easiest to use L<psktool(1)> for this: + + mkdir -m 0700 /tmp/keys + psktool -u rich -p /tmp/keys/keys.psk + +The PSK file contains the hex-encoded random keys in plaintext. Any +client which can read this file will be able to connect to the server. + +Use the nbdkit I<--tls-psk> option to start the server: + + nbdkit --tls=require --tls-psk=/tmp/keys/keys.psk -e / file file=disk.img + +This option overrides X.509 certificate authentication. + +Clients must supply one of the usernames in the PSK file and the +corresponding key in order to connect. An example of connecting using +L<qemu-img(1)> is: + + qemu-img info \ + --object tls-creds-psk,id=tls0,dir=/tmp/keys,username=rich,endpoint=client \ + --image-opts \ + file.driver=nbd,file.host=localhost,file.port=10809,file.tls-creds=tls0,file.export=/ + =head2 Default TLS behaviour If nbdkit was compiled without GnuTLS support, then TLS is disabled @@ -779,6 +814,10 @@ whether or not to use TLS and whether or not to present certificates. TLS client certificates are I<not> checked by default unless you specify I<--tls-verify-peer>. +If the I<--tls-psk> option is used then TLS is enabled (but I<not> +required). To ensure that all clients are authorized you must use +I<--tls=require>. + Each of these defaults is insecure to some extent (including I<--tls=on> which could be subject to a downgrade attack), so if you expect TLS then it is best to specify the I<--tls> option that you @@ -968,6 +1007,7 @@ L<https://en.wikipedia.org/wiki/Fibre_Channel_over_Ethernet>. L<gnutls_priority_init(3)>, L<qemu-img(1)>, +L<psktool(1)>, L<systemd.socket(5)>. =head1 AUTHORS diff --git a/nbdkit.in b/nbdkit.in index e1cb941..51abcbf 100644 --- a/nbdkit.in +++ b/nbdkit.in @@ -70,7 +70,7 @@ while [ $# -gt 0 ]; do args[$i]="$1" shift ;; - -e | --export* | -g | --group | -i | --ip* | -P | --pid* | -p | --port | --run | --selinux-label | -t | --threads | --tls | --tls-certificates | -U | --unix | -u | --user) + -e | --export* | -g | --group | -i | --ip* | -P | --pid* | -p | --port | --run | --selinux-label | -t | --threads | --tls | --tls-certificates | --tls-psk | -U | --unix | -u | --user) args[$i]="$1" ((++i)) args[$i]="$2" diff --git a/src/crypto.c b/src/crypto.c index 23c5c8f..6af3977 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -37,10 +37,12 @@ #include <stdlib.h> #include <stdarg.h> #include <stdint.h> +#include <stdbool.h> #include <inttypes.h> #include <string.h> #include <unistd.h> #include <fcntl.h> +#include <limits.h> #include <errno.h> #include <sys/types.h> #include <assert.h> @@ -51,7 +53,12 @@ #include <gnutls/gnutls.h> +static int crypto_auth = 0; +#define CRYPTO_AUTH_CERTIFICATES 1 +#define CRYPTO_AUTH_PSK 2 + static gnutls_certificate_credentials_t x509_creds; +static gnutls_psk_server_credentials_t psk_creds; static void print_gnutls_error (int err, const char *fs, ...) __attribute__((format (printf, 2, 3))); @@ -147,23 +154,9 @@ load_certificates (const char *path) return 1; } -/* Initialize crypto. This also handles the command line parameters - * and loading the server certificate. - */ -void -crypto_init (int tls_set_on_cli) +static int +start_certificates (void) { - int err; - - err = gnutls_global_init (); - if (err < 0) { - print_gnutls_error (err, "initializing GnuTLS"); - exit (EXIT_FAILURE); - } - - if (tls == 0) /* --tls=off */ - return; - /* Try to locate the certificates directory and load them. */ if (tls_certificates_dir == NULL) { const char *home; @@ -196,49 +189,119 @@ crypto_init (int tls_set_on_cli) if (load_certificates (tls_certificates_dir)) goto found_certificates; } - - /* If we get here, we didn't manage to load the certificates. If - * --tls=require was given on the command line then that's a - * problem. - */ - if (tls == 2) { /* --tls=require */ - fprintf (stderr, - "%s: --tls=require but could not load TLS certificates.\n" - "Try setting ‘--tls-certificates=/path/to/certificates’ or read\n" - "the \"TLS\" section in nbdkit(1).\n", - program_name); - exit (EXIT_FAILURE); - } - - /* If --tls=on was given on the command line, warn before we turn - * TLS off. - */ - if (tls == 1 && tls_set_on_cli) { /* explicit --tls=on */ - fprintf (stderr, - "%s: warning: --tls=on but could not load TLS certificates.\n" - "TLS will be disabled and TLS connections will be rejected.\n" - "Try setting ‘--tls-certificates=/path/to/certificates’ or read\n" - "the \"TLS\" section in nbdkit(1).\n", - program_name); - } - - tls = 0; - debug ("TLS disabled: could not load TLS certificates"); - return; + return -1; found_certificates: #ifdef HAVE_GNUTLS_CERTIFICATE_SET_KNOWN_DH_PARAMS gnutls_certificate_set_known_dh_params (x509_creds, GNUTLS_SEC_PARAM_MEDIUM); #endif + return 0; +} - debug ("TLS enabled"); +static int +start_psk (void) +{ + int err; + CLEANUP_FREE char *abs_psk_file = NULL; + + /* Make sure the path to the PSK file is absolute. */ + abs_psk_file = realpath (tls_psk, NULL); + if (abs_psk_file == NULL) { + perror (tls_psk); + exit (EXIT_FAILURE); + } + + err = gnutls_psk_allocate_server_credentials (&psk_creds); + if (err < 0) { + print_gnutls_error (err, "allocating PSK credentials"); + exit (EXIT_FAILURE); + } + + /* Note that this function makes a copy of the string so it + * is safe to free it afterwards. + */ + gnutls_psk_set_server_credentials_file (psk_creds, abs_psk_file); + + return 0; +} + +/* Initialize crypto. This also handles the command line parameters + * and loading the server certificate. + */ +void +crypto_init (int tls_set_on_cli) +{ + int err, r; + const char *what; + + err = gnutls_global_init (); + if (err < 0) { + print_gnutls_error (err, "initializing GnuTLS"); + exit (EXIT_FAILURE); + } + + if (tls == 0) /* --tls=off */ + return; + + /* --tls-psk overrides certificates. */ + if (tls_psk != NULL) { + r = start_psk (); + what = "Pre-Shared Keys (PSK)"; + if (r == 0) crypto_auth = CRYPTO_AUTH_PSK; + } + else { + r = start_certificates (); + what = "X.509 certificates"; + if (r == 0) crypto_auth = CRYPTO_AUTH_CERTIFICATES; + } + + if (r == 0) { + debug ("TLS enabled using: %s", what); + } + else { + /* If we get here, we didn't manage to load the PSK file / + * certificates. If --tls=require was given on the command line + * then that's a problem. + */ + if (tls == 2) { /* --tls=require */ + fprintf (stderr, + "%s: --tls=require but could not load TLS certificates.\n" + "Try setting ‘--tls-certificates=/path/to/certificates’ or read\n" + "the \"TLS\" section in nbdkit(1).\n", + program_name); + exit (EXIT_FAILURE); + } + + /* If --tls=on was given on the command line, warn before we turn + * TLS off. + */ + if (tls == 1 && tls_set_on_cli) { /* explicit --tls=on */ + fprintf (stderr, + "%s: warning: --tls=on but could not load TLS certificates.\n" + "TLS will be disabled and TLS connections will be rejected.\n" + "Try setting ‘--tls-certificates=/path/to/certificates’ or read\n" + "the \"TLS\" section in nbdkit(1).\n", + program_name); + } + + tls = 0; + debug ("TLS disabled: could not load TLS certificates"); + } } void crypto_free (void) { - if (tls > 0) - gnutls_certificate_free_credentials (x509_creds); + if (tls > 0) { + switch (crypto_auth) { + case CRYPTO_AUTH_CERTIFICATES: + gnutls_certificate_free_credentials (x509_creds); + break; + case CRYPTO_AUTH_PSK: + gnutls_psk_free_server_credentials (psk_creds); + break; + } + } gnutls_global_deinit (); } @@ -335,6 +398,7 @@ int crypto_negotiate_tls (struct connection *conn, int sockin, int sockout) { gnutls_session_t *session; + CLEANUP_FREE char *priority = NULL; int err; /* Create the GnuTLS session. */ @@ -351,33 +415,61 @@ crypto_negotiate_tls (struct connection *conn, int sockin, int sockout) return -1; } - err = gnutls_priority_set_direct (*session, TLS_PRIORITY, NULL); - if (err < 0) { - nbdkit_error ("failed to set TLS session priority to %s: %s", - TLS_PRIORITY, gnutls_strerror (err)); - goto error; - } + switch (crypto_auth) { + case CRYPTO_AUTH_CERTIFICATES: + /* Associate the session with the server credentials (key, cert). */ + err = gnutls_credentials_set (*session, GNUTLS_CRD_CERTIFICATE, + x509_creds); + if (err < 0) { + nbdkit_error ("gnutls_credentials_set: %s", gnutls_strerror (err)); + goto error; + } - /* Associate the session with the server credentials (key, cert). */ - err = gnutls_credentials_set (*session, GNUTLS_CRD_CERTIFICATE, - x509_creds); - if (err < 0) { - nbdkit_error ("gnutls_credentials_set: %s", gnutls_strerror (err)); - goto error; - } - - /* If verify peer is enabled, tell GnuTLS to request the client - * certificates. (Note the default is to not request or verify - * certificates). - */ - if (tls_verify_peer) { + /* If verify peer is enabled, tell GnuTLS to request the client + * certificates. (Note the default is to not request or verify + * certificates). + */ + if (tls_verify_peer) { #ifdef HAVE_GNUTLS_SESSION_SET_VERIFY_CERT - gnutls_certificate_server_set_request (*session, GNUTLS_CERT_REQUEST); - gnutls_session_set_verify_cert (*session, NULL, 0); + gnutls_certificate_server_set_request (*session, GNUTLS_CERT_REQUEST); + gnutls_session_set_verify_cert (*session, NULL, 0); #else - nbdkit_error ("--tls-verify-peer: GnuTLS >= 3.4.6 is required for this feature"); - goto error; + nbdkit_error ("--tls-verify-peer: GnuTLS >= 3.4.6 is required for this feature"); + goto error; #endif + } + + priority = strdup (TLS_PRIORITY); + if (priority == NULL) { + nbdkit_error ("strdup: %m"); + goto error; + } + break; + + case CRYPTO_AUTH_PSK: + /* Associate the session with the server PSK credentials. */ + err = gnutls_credentials_set (*session, GNUTLS_CRD_PSK, psk_creds); + if (err < 0) { + nbdkit_error ("gnutls_credentials_set: %s", gnutls_strerror (err)); + goto error; + } + + if (asprintf (&priority, "%s:+PSK:+DHE-PSK", TLS_PRIORITY) == -1) { + nbdkit_error ("asprintf: %m"); + goto error; + } + break; + + default: + abort (); + } + + assert (priority != NULL); + err = gnutls_priority_set_direct (*session, priority, NULL); + if (err < 0) { + nbdkit_error ("failed to set TLS session priority to %s: %s", + priority, gnutls_strerror (err)); + goto error; } /* Set up GnuTLS so it reads and writes on the raw sockets, and set diff --git a/src/internal.h b/src/internal.h index ea3155c..ec19841 100644 --- a/src/internal.h +++ b/src/internal.h @@ -108,6 +108,7 @@ extern int readonly; extern const char *selinux_label; extern int tls; extern const char *tls_certificates_dir; +extern const char *tls_psk; extern int tls_verify_peer; extern char *unixsocket; extern int verbose; diff --git a/src/main.c b/src/main.c index 8d901cf..6524b85 100644 --- a/src/main.c +++ b/src/main.c @@ -89,6 +89,7 @@ const char *selinux_label; /* --selinux-label */ int threads; /* -t */ int tls; /* --tls : 0=off 1=on 2=require */ const char *tls_certificates_dir; /* --tls-certificates */ +const char *tls_psk; /* --tls-psk */ int tls_verify_peer; /* --tls-verify-peer */ char *unixsocket; /* -U */ const char *user, *group; /* -u & -g */ @@ -144,6 +145,7 @@ static const struct option long_options[] = { { "threads", 1, NULL, 't' }, { "tls", 1, NULL, 0 }, { "tls-certificates", 1, NULL, 0 }, + { "tls-psk", 1, NULL, 0 }, { "tls-verify-peer", 0, NULL, 0 }, { "unix", 1, NULL, 'U' }, { "user", 1, NULL, 'u' }, @@ -161,7 +163,7 @@ usage (void) " [--newstyle] [--oldstyle] [-P PIDFILE] [-p PORT] [-r]\n" " [--run CMD] [-s] [--selinux-label LABEL] [-t THREADS]\n" " [--tls=off|on|require] [--tls-certificates /path/to/certificates]\n" - " [--tls-verify-peer]\n" + " [--tls-psk /path/to/pskfile] [--tls-verify-peer]\n" " [-U SOCKET] [-u USER] [-v] [-V]\n" " PLUGIN [key=value [key=value [...]]]\n" "\n" @@ -314,6 +316,10 @@ main (int argc, char *argv[]) tls_certificates_dir = optarg; break; } + else if (strcmp (long_options[option_index].name, "tls-psk") == 0) { + tls_psk = optarg; + break; + } else if (strcmp (long_options[option_index].name, "tls-verify-peer") == 0) { tls_verify_peer = 1; break; diff --git a/tests/Makefile.am b/tests/Makefile.am index 755fe51..a7a31b9 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -37,6 +37,7 @@ MAINTAINERCLEANFILES EXTRA_DIST = \ functions.sh \ make-pki.sh \ + make-psk.sh \ python-exception.py \ README.tests \ shebang.pl \ @@ -72,6 +73,7 @@ EXTRA_DIST = \ test-start.sh \ test-random-sock.sh \ test-tls.sh \ + test-tls-psk.sh \ test-version.sh \ test-version-filter.sh \ test-version-plugin.sh \ @@ -112,6 +114,7 @@ TESTS += \ test-captive.sh \ test-random-sock.sh \ test-tls.sh \ + test-tls-psk.sh \ test-ip.sh \ test-socket-activation \ test-foreground.sh @@ -171,6 +174,11 @@ check_DATA += pki/.stamp pki/.stamp: $(srcdir)/make-pki.sh $(srcdir)/make-pki.sh +# PSK keys for the TLS-PSK tests. +check_DATA += keys.psk +keys.psk: $(srcdir)/make-psk.sh + $(srcdir)/make-psk.sh + #---------------------------------------------------------------------- # Tests of C plugins or tests which require plugins. diff --git a/tests/make-psk.sh b/tests/make-psk.sh new file mode 100755 index 0000000..5e27ea5 --- /dev/null +++ b/tests/make-psk.sh @@ -0,0 +1,54 @@ +#!/bin/bash - +# nbdkit +# Copyright (C) 2018 Red Hat Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * 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. +# +# * Neither the name of Red Hat nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED HAT 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 RED HAT 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. + +set -e + +# This creates the PSK keys for the TLS-PSK tests. However if pkstool +# doesn't exist, just create an empty directory instead. + +if [ ! -f test-tls-psk.sh ]; then + echo "$0: script is being run from the wrong directory." + echo "Don't try to run this script by hand." + exit 1 +fi + +rm -f keys.psk + +if ! psktool --help >/dev/null 2>&1; then + echo "$0: psktool not found, TLS-PSK tests will be skipped." + touch keys.psk + exit 0 +fi + +# Create the keys file. +psktool -u qemu -p keys.psk diff --git a/tests/test-tls-psk.sh b/tests/test-tls-psk.sh new file mode 100755 index 0000000..4f8111a --- /dev/null +++ b/tests/test-tls-psk.sh @@ -0,0 +1,118 @@ +#!/bin/bash - +# nbdkit +# Copyright (C) 2018 Red Hat Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * 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. +# +# * Neither the name of Red Hat nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED HAT 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 RED HAT 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. + +set -e +set -x +source ./functions.sh + +# Don't fail if certain commands aren't available. +if ! ss --version; then + echo "$0: 'ss' command not available" + exit 77 +fi +if ! command -v qemu-img > /dev/null; then + echo "$0: 'qemu-img' command not available" + exit 77 +fi +if ! qemu-img --help | grep -- --object; then + echo "$0: 'qemu-img' command does not have the --object option" + exit 77 +fi + +# Does the qemu-img binary support PSK? +if LANG=C qemu-img info --object tls-creds-psk,id=id disk |& + grep -sq "invalid object type: tls-creds-psk" +then + echo "$0: 'qemu-img' command does not support TLS-PSK" + exit 77 +fi + +# Does the nbdkit binary support TLS? +if ! nbdkit --dump-config | grep -sq tls=yes; then + echo "$0: nbdkit built without TLS support" + exit 77 +fi + +# Did we create the PSK keys file? +# Probably 'certtool' is missing. +if [ ! -s keys.psk ]; then + echo "$0: PSK keys file was not created by the test harness" + exit 77 +fi + +# Unfortunately qemu cannot do TLS over a Unix domain socket (nbdkit +# probably can, although it is not tested). Find an unused port to +# listen on. +for port in `seq 50000 65535`; do + if ! ss -ltn | grep -sqE ":$port\b"; then break; fi +done +echo picked unused port $port + +nbdkit -P tls-psk.pid -p $port -n --tls=require --tls-psk=keys.psk example1 + +# We may have to wait a short time for the pid file to appear. +for i in `seq 1 10`; do + if test -f tls-psk.pid; then + break + fi + sleep 1 +done +if ! test -f tls-psk.pid; then + echo "$0: PID file was not created" + exit 1 +fi + +pid="$(cat tls-psk.pid)" + +# Kill the process on exit. +cleanup () +{ + status=$? + + kill $pid + rm -f tls-psk.pid tls-psk.out + + exit $status +} +trap cleanup INT QUIT TERM EXIT ERR + +# Run qemu-img against the server. +LANG=C \ +qemu-img info \ + --object "tls-creds-psk,id=tls0,endpoint=client,dir=$PWD" \ + --image-opts "file.driver=nbd,file.host=localhost,file.port=$port,file.tls-creds=tls0" > tls-psk.out + +cat tls-psk.out + +grep -sq "^file format: raw" tls-psk.out +grep -sq "^virtual size: 100M" tls-psk.out -- 2.16.2
Possibly Parallel Threads
- [PATCH nbdkit] tls: Implement Pre-Shared Keys (PSK) authentication.
- Re: [PATCH nbdkit] tls: Implement Pre-Shared Keys (PSK) authentication.
- [PATCH nbdkit] tls: Implement Pre-Shared Keys (PSK) authentication.
- Re: [PATCH nbdkit] tls: Implement Pre-Shared Keys (PSK) authentication.
- Re: [PATCH nbdkit] tls: Implement Pre-Shared Keys (PSK) authentication.