---
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