[Libguestfs] [PATCH nbdkit] tls: Implement Pre-Shared Keys (PSK) authentication.

Richard W.M. Jones rjones at redhat.com
Mon Jun 25 17:01:50 UTC 2018


---
 docs/nbdkit.pod.in |  45 +++++++++--
 src/crypto.c       | 234 +++++++++++++++++++++++++++++++++++++----------------
 src/internal.h     |   1 +
 src/main.c         |   8 +-
 4 files changed, 210 insertions(+), 78 deletions(-)

diff --git a/docs/nbdkit.pod.in b/docs/nbdkit.pod.in
index 42e6e6b..80d1ecd 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,29 @@ 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.  Currently PSK support in NBD clients is
+not widespread.
+
+Create a PSK file containing one or more C<username:key> pairs.  It is
+easiest to use L<psktool(1)> for this:
+
+ psktool -u rich -p /tmp/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-psk=/tmp/psk 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.
+
 =head2 Default TLS behaviour
 
 If nbdkit was compiled without GnuTLS support, then TLS is disabled
@@ -765,11 +794,14 @@ on the command line).  Also it is impossible to turn on TLS in this
 scenario.  You can tell if nbdkit was compiled without GnuTLS support
 because C<nbdkit --dump-config> will contain C<tls=no>.
 
-If TLS certificates cannot be loaded either from the built-in path or
-from the directory specified by I<--tls-certificates>, then TLS
-defaults to disabled.  Turning TLS on will give a warning
-(I<--tls=on>) or error (I<--tls=require>) about the missing
-certificates.
+If the I<--tls-psk> option is used then TLS is enabled with pre-shared
+keys.
+
+If the I<--tls-psk> option is I<not> used: If TLS certificates cannot
+be loaded either from the built-in path or from the directory
+specified by I<--tls-certificates>, then TLS defaults to disabled.
+Turning TLS on will give a warning (I<--tls=on>) or error
+(I<--tls=require>) about the missing certificates.
 
 If TLS certificates can be loaded from the built-in path or from the
 I<--tls-certificates> directory, then TLS will by default be enabled
@@ -968,6 +1000,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/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;
-- 
2.16.2




More information about the Libguestfs mailing list