1817 lines
53 KiB
Diff
1817 lines
53 KiB
Diff
From c19936170cf8b385687cf40f5a9507d87ae08267 Mon Sep 17 00:00:00 2001
|
|
From: "Richard W.M. Jones" <rjones@redhat.com>
|
|
Date: Sat, 30 Apr 2022 12:35:07 +0100
|
|
Subject: [PATCH] New filter: luks
|
|
|
|
This filter allows you to open, read and write LUKSv1 disk images,
|
|
compatible with the ones used by dm-crypt and qemu.
|
|
|
|
(cherry picked from commit 468919dce6c5eb57503eacac0f67e5dd87c58e6c)
|
|
---
|
|
TODO | 11 +-
|
|
configure.ac | 6 +-
|
|
docs/nbdkit-tls.pod | 1 +
|
|
filters/luks/Makefile.am | 77 ++
|
|
filters/luks/luks.c | 1263 +++++++++++++++++++++++++++
|
|
filters/luks/nbdkit-luks-filter.pod | 120 +++
|
|
plugins/file/nbdkit-file-plugin.pod | 1 +
|
|
tests/Makefile.am | 12 +
|
|
tests/test-luks-copy.sh | 125 +++
|
|
tests/test-luks-info.sh | 56 ++
|
|
10 files changed, 1668 insertions(+), 4 deletions(-)
|
|
create mode 100644 filters/luks/Makefile.am
|
|
create mode 100644 filters/luks/luks.c
|
|
create mode 100644 filters/luks/nbdkit-luks-filter.pod
|
|
create mode 100755 tests/test-luks-copy.sh
|
|
create mode 100755 tests/test-luks-info.sh
|
|
|
|
diff --git a/TODO b/TODO
|
|
index 4d2a9796..0f5dc41d 100644
|
|
--- a/TODO
|
|
+++ b/TODO
|
|
@@ -195,9 +195,6 @@ Suggestions for filters
|
|
connections. This may even allow a filter to offer a more parallel
|
|
threading model than the underlying plugin.
|
|
|
|
-* LUKS encrypt/decrypt filter, bonus points if compatible with qemu
|
|
- LUKS-encrypted disk images
|
|
-
|
|
* CBT filter to track dirty blocks. See these links for inspiration:
|
|
https://www.cloudandheat.com/block-level-data-tracking-using-davice-mappers-dm-era/
|
|
https://github.com/qemu/qemu/blob/master/docs/interop/bitmaps.rst
|
|
@@ -232,6 +229,14 @@ Suggestions for filters
|
|
could inject a flush after pausing. However this requires that
|
|
filter background threads have access to the plugin (see above).
|
|
|
|
+nbdkit-luks-filter:
|
|
+
|
|
+* This filter should also support LUKSv2 (and so should qemu).
|
|
+
|
|
+* There are some missing features: ESSIV, more ciphers.
|
|
+
|
|
+* Implement trim and zero if possible.
|
|
+
|
|
nbdkit-readahead-filter:
|
|
|
|
* The filter should open a new connection to the plugin per background
|
|
diff --git a/configure.ac b/configure.ac
|
|
index a402921b..de85b4da 100644
|
|
--- a/configure.ac
|
|
+++ b/configure.ac
|
|
@@ -127,6 +127,7 @@ filters="\
|
|
ip \
|
|
limit \
|
|
log \
|
|
+ luks \
|
|
multi-conn \
|
|
nocache \
|
|
noextents \
|
|
@@ -614,8 +615,9 @@ PKG_CHECK_MODULES([GNUTLS], [gnutls >= 3.3.0], [
|
|
], [
|
|
AC_MSG_WARN([gnutls not found or < 3.3.0, TLS support will be disabled.])
|
|
])
|
|
+AM_CONDITIONAL([HAVE_GNUTLS], [test "x$GNUTLS_LIBS" != "x"])
|
|
|
|
-AS_IF([test "$GNUTLS_LIBS" != ""],[
|
|
+AS_IF([test "x$GNUTLS_LIBS" != "x"],[
|
|
AC_MSG_CHECKING([for default TLS session priority string])
|
|
AC_ARG_WITH([tls-priority],
|
|
[AS_HELP_STRING([--with-tls-priority=...],
|
|
@@ -1383,6 +1385,7 @@ AC_CONFIG_FILES([Makefile
|
|
filters/ip/Makefile
|
|
filters/limit/Makefile
|
|
filters/log/Makefile
|
|
+ filters/luks/Makefile
|
|
filters/multi-conn/Makefile
|
|
filters/nocache/Makefile
|
|
filters/noextents/Makefile
|
|
@@ -1481,6 +1484,7 @@ echo "Optional filters:"
|
|
echo
|
|
feature "ext2" test "x$HAVE_EXT2_TRUE" = "x"
|
|
feature "gzip" test "x$HAVE_ZLIB_TRUE" = "x"
|
|
+feature "LUKS" test "x$HAVE_GNUTLS_TRUE" != "x"
|
|
feature "xz" test "x$HAVE_LIBLZMA_TRUE" = "x"
|
|
|
|
echo
|
|
diff --git a/docs/nbdkit-tls.pod b/docs/nbdkit-tls.pod
|
|
index 86f5f984..4d0dc14c 100644
|
|
--- a/docs/nbdkit-tls.pod
|
|
+++ b/docs/nbdkit-tls.pod
|
|
@@ -364,6 +364,7 @@ More information can be found in L<gnutls_priority_init(3)>.
|
|
=head1 SEE ALSO
|
|
|
|
L<nbdkit(1)>,
|
|
+L<nbdkit-luks-filter(1)>,
|
|
L<nbdkit-tls-fallback-filter(1)>,
|
|
L<nbdcopy(1)>,
|
|
L<nbdfuse(1)>,
|
|
diff --git a/filters/luks/Makefile.am b/filters/luks/Makefile.am
|
|
new file mode 100644
|
|
index 00000000..30089621
|
|
--- /dev/null
|
|
+++ b/filters/luks/Makefile.am
|
|
@@ -0,0 +1,77 @@
|
|
+# nbdkit
|
|
+# Copyright (C) 2019-2022 Red Hat Inc.
|
|
+#
|
|
+# 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.
|
|
+
|
|
+include $(top_srcdir)/common-rules.mk
|
|
+
|
|
+EXTRA_DIST = nbdkit-luks-filter.pod
|
|
+
|
|
+if HAVE_GNUTLS
|
|
+
|
|
+filter_LTLIBRARIES = nbdkit-luks-filter.la
|
|
+
|
|
+nbdkit_luks_filter_la_SOURCES = \
|
|
+ luks.c \
|
|
+ $(top_srcdir)/include/nbdkit-filter.h \
|
|
+ $(NULL)
|
|
+
|
|
+nbdkit_luks_filter_la_CPPFLAGS = \
|
|
+ -I$(top_srcdir)/include \
|
|
+ -I$(top_srcdir)/common/include \
|
|
+ -I$(top_srcdir)/common/utils \
|
|
+ $(NULL)
|
|
+nbdkit_luks_filter_la_CFLAGS = \
|
|
+ $(WARNINGS_CFLAGS) \
|
|
+ $(GNUTLS_CFLAGS) \
|
|
+ $(NULL)
|
|
+nbdkit_luks_filter_la_LIBADD = \
|
|
+ $(top_builddir)/common/utils/libutils.la \
|
|
+ $(IMPORT_LIBRARY_ON_WINDOWS) \
|
|
+ $(GNUTLS_LIBS) \
|
|
+ $(NULL)
|
|
+nbdkit_luks_filter_la_LDFLAGS = \
|
|
+ -module -avoid-version -shared $(NO_UNDEFINED_ON_WINDOWS) \
|
|
+ -Wl,--version-script=$(top_srcdir)/filters/filters.syms \
|
|
+ $(NULL)
|
|
+
|
|
+if HAVE_POD
|
|
+
|
|
+man_MANS = nbdkit-luks-filter.1
|
|
+CLEANFILES += $(man_MANS)
|
|
+
|
|
+nbdkit-luks-filter.1: nbdkit-luks-filter.pod \
|
|
+ $(top_builddir)/podwrapper.pl
|
|
+ $(PODWRAPPER) --section=1 --man $@ \
|
|
+ --html $(top_builddir)/html/$@.html \
|
|
+ $<
|
|
+
|
|
+endif HAVE_POD
|
|
+
|
|
+endif
|
|
diff --git a/filters/luks/luks.c b/filters/luks/luks.c
|
|
new file mode 100644
|
|
index 00000000..706a9bd2
|
|
--- /dev/null
|
|
+++ b/filters/luks/luks.c
|
|
@@ -0,0 +1,1263 @@
|
|
+/* nbdkit
|
|
+ * Copyright (C) 2018-2022 Red Hat Inc.
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#include <config.h>
|
|
+
|
|
+#include <stdio.h>
|
|
+#include <stdlib.h>
|
|
+#include <stdint.h>
|
|
+#include <inttypes.h>
|
|
+#include <string.h>
|
|
+#include <limits.h>
|
|
+#include <assert.h>
|
|
+#include <pthread.h>
|
|
+
|
|
+#include <gnutls/crypto.h>
|
|
+
|
|
+#include <nbdkit-filter.h>
|
|
+
|
|
+#include "byte-swapping.h"
|
|
+#include "cleanup.h"
|
|
+#include "isaligned.h"
|
|
+#include "minmax.h"
|
|
+#include "rounding.h"
|
|
+
|
|
+/* LUKSv1 constants. */
|
|
+#define LUKS_MAGIC { 'L', 'U', 'K', 'S', 0xBA, 0xBE }
|
|
+#define LUKS_MAGIC_LEN 6
|
|
+#define LUKS_DIGESTSIZE 20
|
|
+#define LUKS_SALTSIZE 32
|
|
+#define LUKS_NUMKEYS 8
|
|
+#define LUKS_KEY_DISABLED 0x0000DEAD
|
|
+#define LUKS_KEY_ENABLED 0x00AC71F3
|
|
+#define LUKS_STRIPES 4000
|
|
+#define LUKS_ALIGN_KEYSLOTS 4096
|
|
+#define LUKS_SECTOR_SIZE 512
|
|
+
|
|
+/* Key slot. */
|
|
+struct luks_keyslot {
|
|
+ uint32_t active; /* LUKS_KEY_DISABLED|LUKS_KEY_ENABLED */
|
|
+ uint32_t password_iterations;
|
|
+ char password_salt[LUKS_SALTSIZE];
|
|
+ uint32_t key_material_offset;
|
|
+ uint32_t stripes;
|
|
+} __attribute__((__packed__));
|
|
+
|
|
+/* LUKS superblock. */
|
|
+struct luks_phdr {
|
|
+ char magic[LUKS_MAGIC_LEN]; /* LUKS_MAGIC */
|
|
+ uint16_t version; /* Only 1 is supported. */
|
|
+ char cipher_name[32];
|
|
+ char cipher_mode[32];
|
|
+ char hash_spec[32];
|
|
+ uint32_t payload_offset;
|
|
+ uint32_t master_key_len;
|
|
+ uint8_t master_key_digest[LUKS_DIGESTSIZE];
|
|
+ uint8_t master_key_salt[LUKS_SALTSIZE];
|
|
+ uint32_t master_key_digest_iterations;
|
|
+ uint8_t uuid[40];
|
|
+
|
|
+ struct luks_keyslot keyslot[LUKS_NUMKEYS]; /* Key slots. */
|
|
+} __attribute__((__packed__));
|
|
+
|
|
+static char *passphrase = NULL;
|
|
+
|
|
+static void
|
|
+luks_unload (void)
|
|
+{
|
|
+ /* XXX We should really store the passphrase (and master key)
|
|
+ * in mlock-ed memory.
|
|
+ */
|
|
+ if (passphrase) {
|
|
+ memset (passphrase, 0, strlen (passphrase));
|
|
+ free (passphrase);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int
|
|
+luks_thread_model (void)
|
|
+{
|
|
+ return NBDKIT_THREAD_MODEL_PARALLEL;
|
|
+}
|
|
+
|
|
+static int
|
|
+luks_config (nbdkit_next_config *next, nbdkit_backend *nxdata,
|
|
+ const char *key, const char *value)
|
|
+{
|
|
+ if (strcmp (key, "passphrase") == 0) {
|
|
+ if (nbdkit_read_password (value, &passphrase) == -1)
|
|
+ return -1;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return next (nxdata, key, value);
|
|
+}
|
|
+
|
|
+static int
|
|
+luks_config_complete (nbdkit_next_config_complete *next, nbdkit_backend *nxdata)
|
|
+{
|
|
+ if (passphrase == NULL) {
|
|
+ nbdkit_error ("LUKS \"passphrase\" parameter is missing");
|
|
+ return -1;
|
|
+ }
|
|
+ return next (nxdata);
|
|
+}
|
|
+
|
|
+#define luks_config_help \
|
|
+ "passphrase=<SECRET> Secret passphrase."
|
|
+
|
|
+enum cipher_mode {
|
|
+ CIPHER_MODE_ECB, CIPHER_MODE_CBC, CIPHER_MODE_XTS, CIPHER_MODE_CTR,
|
|
+};
|
|
+
|
|
+static enum cipher_mode
|
|
+lookup_cipher_mode (const char *str)
|
|
+{
|
|
+ if (strcmp (str, "ecb") == 0)
|
|
+ return CIPHER_MODE_ECB;
|
|
+ if (strcmp (str, "cbc") == 0)
|
|
+ return CIPHER_MODE_CBC;
|
|
+ if (strcmp (str, "xts") == 0)
|
|
+ return CIPHER_MODE_XTS;
|
|
+ if (strcmp (str, "ctr") == 0)
|
|
+ return CIPHER_MODE_CTR;
|
|
+ nbdkit_error ("unknown cipher mode: %s "
|
|
+ "(expecting \"ecb\", \"cbc\", \"xts\" or \"ctr\")", str);
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static const char *
|
|
+cipher_mode_to_string (enum cipher_mode v)
|
|
+{
|
|
+ switch (v) {
|
|
+ case CIPHER_MODE_ECB: return "ecb";
|
|
+ case CIPHER_MODE_CBC: return "cbc";
|
|
+ case CIPHER_MODE_XTS: return "xts";
|
|
+ case CIPHER_MODE_CTR: return "ctr";
|
|
+ default: abort ();
|
|
+ }
|
|
+}
|
|
+
|
|
+enum ivgen {
|
|
+ IVGEN_PLAIN, IVGEN_PLAIN64, /* IVGEN_ESSIV, */
|
|
+};
|
|
+
|
|
+static enum ivgen
|
|
+lookup_ivgen (const char *str)
|
|
+{
|
|
+ if (strcmp (str, "plain") == 0)
|
|
+ return IVGEN_PLAIN;
|
|
+ if (strcmp (str, "plain64") == 0)
|
|
+ return IVGEN_PLAIN64;
|
|
+/*
|
|
+ if (strcmp (str, "essiv") == 0)
|
|
+ return IVGEN_ESSIV;
|
|
+*/
|
|
+ nbdkit_error ("unknown IV generation algorithm: %s "
|
|
+ "(expecting \"plain\", \"plain64\" etc)", str);
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static const char *
|
|
+ivgen_to_string (enum ivgen v)
|
|
+{
|
|
+ switch (v) {
|
|
+ case IVGEN_PLAIN: return "plain";
|
|
+ case IVGEN_PLAIN64: return "plain64";
|
|
+ /*case IVGEN_ESSIV: return "essiv";*/
|
|
+ default: abort ();
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+calculate_iv (enum ivgen v, uint8_t *iv, size_t ivlen, uint64_t sector)
|
|
+{
|
|
+ size_t prefixlen;
|
|
+ uint32_t sector32;
|
|
+
|
|
+ switch (v) {
|
|
+ case IVGEN_PLAIN:
|
|
+ prefixlen = 4; /* 32 bits */
|
|
+ if (prefixlen > ivlen)
|
|
+ prefixlen = ivlen;
|
|
+ sector32 = (uint32_t) sector; /* truncate to only lower bits */
|
|
+ sector32 = htole32 (sector32);
|
|
+ memcpy (iv, §or32, prefixlen);
|
|
+ memset (iv + prefixlen, 0, ivlen - prefixlen);
|
|
+ break;
|
|
+
|
|
+ case IVGEN_PLAIN64:
|
|
+ prefixlen = 8; /* 64 bits */
|
|
+ if (prefixlen > ivlen)
|
|
+ prefixlen = ivlen;
|
|
+ sector = htole64 (sector);
|
|
+ memcpy (iv, §or, prefixlen);
|
|
+ memset (iv + prefixlen, 0, ivlen - prefixlen);
|
|
+ break;
|
|
+
|
|
+ /*case IVGEN_ESSIV:*/
|
|
+ default: abort ();
|
|
+ }
|
|
+}
|
|
+
|
|
+enum cipher_alg {
|
|
+ CIPHER_ALG_AES_128, CIPHER_ALG_AES_192, CIPHER_ALG_AES_256,
|
|
+};
|
|
+
|
|
+static enum cipher_alg
|
|
+lookup_cipher_alg (const char *str, enum cipher_mode mode, int key_bytes)
|
|
+{
|
|
+ if (mode == CIPHER_MODE_XTS)
|
|
+ key_bytes /= 2;
|
|
+
|
|
+ if (strcmp (str, "aes") == 0) {
|
|
+ if (key_bytes == 16)
|
|
+ return CIPHER_ALG_AES_128;
|
|
+ if (key_bytes == 24)
|
|
+ return CIPHER_ALG_AES_192;
|
|
+ if (key_bytes == 32)
|
|
+ return CIPHER_ALG_AES_256;
|
|
+ }
|
|
+ nbdkit_error ("unknown cipher algorithm: %s (expecting \"aes\", etc)", str);
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static const char *
|
|
+cipher_alg_to_string (enum cipher_alg v)
|
|
+{
|
|
+ switch (v) {
|
|
+ case CIPHER_ALG_AES_128: return "aes-128";
|
|
+ case CIPHER_ALG_AES_192: return "aes-192";
|
|
+ case CIPHER_ALG_AES_256: return "aes-256";
|
|
+ default: abort ();
|
|
+ }
|
|
+}
|
|
+
|
|
+#if 0
|
|
+static int
|
|
+cipher_alg_key_bytes (enum cipher_alg v)
|
|
+{
|
|
+ switch (v) {
|
|
+ case CIPHER_ALG_AES_128: return 16;
|
|
+ case CIPHER_ALG_AES_192: return 24;
|
|
+ case CIPHER_ALG_AES_256: return 32;
|
|
+ default: abort ();
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int
|
|
+cipher_alg_iv_len (enum cipher_alg v, enum cipher_mode mode)
|
|
+{
|
|
+ if (CIPHER_MODE_ECB)
|
|
+ return 0; /* Don't need an IV in this mode. */
|
|
+
|
|
+ switch (v) {
|
|
+ case CIPHER_ALG_AES_128:
|
|
+ case CIPHER_ALG_AES_192:
|
|
+ case CIPHER_ALG_AES_256:
|
|
+ return 16;
|
|
+ default: abort ();
|
|
+ }
|
|
+}
|
|
+
|
|
+static gnutls_digest_algorithm_t
|
|
+lookup_hash (const char *str)
|
|
+{
|
|
+ if (strcmp (str, "md5") == 0)
|
|
+ return GNUTLS_DIG_MD5;
|
|
+ if (strcmp (str, "sha1") == 0)
|
|
+ return GNUTLS_DIG_SHA1;
|
|
+ if (strcmp (str, "sha224") == 0)
|
|
+ return GNUTLS_DIG_SHA224;
|
|
+ if (strcmp (str, "sha256") == 0)
|
|
+ return GNUTLS_DIG_SHA256;
|
|
+ if (strcmp (str, "sha384") == 0)
|
|
+ return GNUTLS_DIG_SHA384;
|
|
+ if (strcmp (str, "sha512") == 0)
|
|
+ return GNUTLS_DIG_SHA512;
|
|
+ if (strcmp (str, "ripemd160") == 0)
|
|
+ return GNUTLS_DIG_RMD160;
|
|
+ nbdkit_error ("unknown hash algorithm: %s "
|
|
+ "(expecting \"md5\", \"sha1\", \"sha224\", etc)", str);
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static const char *
|
|
+hash_to_string (gnutls_digest_algorithm_t v)
|
|
+{
|
|
+ switch (v) {
|
|
+ case GNUTLS_DIG_UNKNOWN: return "unknown";
|
|
+ case GNUTLS_DIG_MD5: return "md5";
|
|
+ case GNUTLS_DIG_SHA1: return "sha1";
|
|
+ case GNUTLS_DIG_SHA224: return "sha224";
|
|
+ case GNUTLS_DIG_SHA256: return "sha256";
|
|
+ case GNUTLS_DIG_SHA384: return "sha384";
|
|
+ case GNUTLS_DIG_SHA512: return "sha512";
|
|
+ case GNUTLS_DIG_RMD160: return "ripemd160";
|
|
+ default: abort ();
|
|
+ }
|
|
+}
|
|
+
|
|
+#if 0
|
|
+/* See qemu & dm-crypt implementations for an explanation of what's
|
|
+ * going on here.
|
|
+ */
|
|
+static enum cipher_alg
|
|
+lookup_essiv_cipher (enum cipher_alg cipher_alg,
|
|
+ gnutls_digest_algorithm_t ivgen_hash_alg)
|
|
+{
|
|
+ int digest_bytes = gnutls_hash_get_len (ivgen_hash_alg);
|
|
+ int key_bytes = cipher_alg_key_bytes (cipher_alg);
|
|
+
|
|
+ if (digest_bytes == key_bytes)
|
|
+ return cipher_alg;
|
|
+
|
|
+ switch (cipher_alg) {
|
|
+ case CIPHER_ALG_AES_128:
|
|
+ case CIPHER_ALG_AES_192:
|
|
+ case CIPHER_ALG_AES_256:
|
|
+ if (digest_bytes == 16) return CIPHER_ALG_AES_128;
|
|
+ if (digest_bytes == 24) return CIPHER_ALG_AES_192;
|
|
+ if (digest_bytes == 32) return CIPHER_ALG_AES_256;
|
|
+ nbdkit_error ("no %s cipher available with key size %d",
|
|
+ "AES", digest_bytes);
|
|
+ return -1;
|
|
+ default:
|
|
+ nbdkit_error ("ESSIV does not support cipher %s",
|
|
+ cipher_alg_to_string (cipher_alg));
|
|
+ return -1;
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
+/* Per-connection handle. */
|
|
+struct handle {
|
|
+ /* LUKS header, if necessary byte-swapped into host order. */
|
|
+ struct luks_phdr phdr;
|
|
+
|
|
+ /* Decoded algorithm etc. */
|
|
+ enum cipher_alg cipher_alg;
|
|
+ enum cipher_mode cipher_mode;
|
|
+ gnutls_digest_algorithm_t hash_alg;
|
|
+ enum ivgen ivgen_alg;
|
|
+ gnutls_digest_algorithm_t ivgen_hash_alg;
|
|
+ enum cipher_alg ivgen_cipher_alg;
|
|
+
|
|
+ /* GnuTLS algorithm. */
|
|
+ gnutls_cipher_algorithm_t gnutls_cipher;
|
|
+
|
|
+ /* If we managed to decrypt one of the keyslots using the passphrase
|
|
+ * then this contains the master key, otherwise NULL.
|
|
+ */
|
|
+ uint8_t *masterkey;
|
|
+};
|
|
+
|
|
+static void *
|
|
+luks_open (nbdkit_next_open *next, nbdkit_context *nxdata,
|
|
+ int readonly, const char *exportname, int is_tls)
|
|
+{
|
|
+ struct handle *h;
|
|
+
|
|
+ if (next (nxdata, readonly, exportname) == -1)
|
|
+ return NULL;
|
|
+
|
|
+ h = calloc (1, sizeof *h);
|
|
+ if (h == NULL) {
|
|
+ nbdkit_error ("calloc: %m");
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ return h;
|
|
+}
|
|
+
|
|
+static void
|
|
+luks_close (void *handle)
|
|
+{
|
|
+ struct handle *h = handle;
|
|
+
|
|
+ if (h->masterkey) {
|
|
+ memset (h->masterkey, 0, h->phdr.master_key_len);
|
|
+ free (h->masterkey);
|
|
+ }
|
|
+ free (h);
|
|
+}
|
|
+
|
|
+/* Perform decryption of a block of data in memory. */
|
|
+static int
|
|
+do_decrypt (struct handle *h, gnutls_cipher_hd_t cipher,
|
|
+ uint64_t offset, uint8_t *buf, size_t len)
|
|
+{
|
|
+ const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode);
|
|
+ uint64_t sector = offset / LUKS_SECTOR_SIZE;
|
|
+ CLEANUP_FREE uint8_t *iv = malloc (ivlen);
|
|
+ int r;
|
|
+
|
|
+ assert (IS_ALIGNED (offset, LUKS_SECTOR_SIZE));
|
|
+ assert (IS_ALIGNED (len, LUKS_SECTOR_SIZE));
|
|
+
|
|
+ while (len) {
|
|
+ calculate_iv (h->ivgen_alg, iv, ivlen, sector);
|
|
+ gnutls_cipher_set_iv (cipher, iv, ivlen);
|
|
+ r = gnutls_cipher_decrypt2 (cipher,
|
|
+ buf, LUKS_SECTOR_SIZE, /* ciphertext */
|
|
+ buf, LUKS_SECTOR_SIZE /* plaintext */);
|
|
+ if (r != 0) {
|
|
+ nbdkit_error ("gnutls_cipher_decrypt2: %s", gnutls_strerror (r));
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ buf += LUKS_SECTOR_SIZE;
|
|
+ offset += LUKS_SECTOR_SIZE;
|
|
+ len -= LUKS_SECTOR_SIZE;
|
|
+ sector++;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Perform encryption of a block of data in memory. */
|
|
+static int
|
|
+do_encrypt (struct handle *h, gnutls_cipher_hd_t cipher,
|
|
+ uint64_t offset, uint8_t *buf, size_t len)
|
|
+{
|
|
+ const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode);
|
|
+ uint64_t sector = offset / LUKS_SECTOR_SIZE;
|
|
+ CLEANUP_FREE uint8_t *iv = malloc (ivlen);
|
|
+ int r;
|
|
+
|
|
+ assert (IS_ALIGNED (offset, LUKS_SECTOR_SIZE));
|
|
+ assert (IS_ALIGNED (len, LUKS_SECTOR_SIZE));
|
|
+
|
|
+ while (len) {
|
|
+ calculate_iv (h->ivgen_alg, iv, ivlen, sector);
|
|
+ gnutls_cipher_set_iv (cipher, iv, ivlen);
|
|
+ r = gnutls_cipher_encrypt2 (cipher,
|
|
+ buf, LUKS_SECTOR_SIZE, /* plaintext */
|
|
+ buf, LUKS_SECTOR_SIZE /* ciphertext */);
|
|
+ if (r != 0) {
|
|
+ nbdkit_error ("gnutls_cipher_decrypt2: %s", gnutls_strerror (r));
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ buf += LUKS_SECTOR_SIZE;
|
|
+ offset += LUKS_SECTOR_SIZE;
|
|
+ len -= LUKS_SECTOR_SIZE;
|
|
+ sector++;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Parse the header fields containing cipher algorithm, mode, etc. */
|
|
+static int
|
|
+parse_cipher_strings (struct handle *h)
|
|
+{
|
|
+ char cipher_name[33], cipher_mode[33], hash_spec[33];
|
|
+ char *ivgen, *ivhash;
|
|
+
|
|
+ /* Copy the header fields locally and ensure they are \0 terminated. */
|
|
+ memcpy (cipher_name, h->phdr.cipher_name, 32);
|
|
+ cipher_name[32] = 0;
|
|
+ memcpy (cipher_mode, h->phdr.cipher_mode, 32);
|
|
+ cipher_mode[32] = 0;
|
|
+ memcpy (hash_spec, h->phdr.hash_spec, 32);
|
|
+ hash_spec[32] = 0;
|
|
+
|
|
+ nbdkit_debug ("LUKS v%" PRIu16 " cipher: %s mode: %s hash: %s "
|
|
+ "master key: %" PRIu32 " bits",
|
|
+ h->phdr.version, cipher_name, cipher_mode, hash_spec,
|
|
+ h->phdr.master_key_len * 8);
|
|
+
|
|
+ /* The cipher_mode header has the form: "ciphermode-ivgen[:ivhash]"
|
|
+ * QEmu writes: "xts-plain64"
|
|
+ */
|
|
+ ivgen = strchr (cipher_mode, '-');
|
|
+ if (!ivgen) {
|
|
+ nbdkit_error ("incorrect cipher_mode header, "
|
|
+ "expecting mode-ivgenerator but got \"%s\"", cipher_mode);
|
|
+ return -1;
|
|
+ }
|
|
+ *ivgen = '\0';
|
|
+ ivgen++;
|
|
+
|
|
+ ivhash = strchr (ivgen, ':');
|
|
+ if (!ivhash)
|
|
+ h->ivgen_hash_alg = GNUTLS_DIG_UNKNOWN;
|
|
+ else {
|
|
+ *ivhash = '\0';
|
|
+ ivhash++;
|
|
+
|
|
+ h->ivgen_hash_alg = lookup_hash (ivhash);
|
|
+ if (h->ivgen_hash_alg == -1)
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ h->cipher_mode = lookup_cipher_mode (cipher_mode);
|
|
+ if (h->cipher_mode == -1)
|
|
+ return -1;
|
|
+
|
|
+ h->cipher_alg = lookup_cipher_alg (cipher_name, h->cipher_mode,
|
|
+ h->phdr.master_key_len);
|
|
+ if (h->cipher_alg == -1)
|
|
+ return -1;
|
|
+
|
|
+ h->hash_alg = lookup_hash (hash_spec);
|
|
+ if (h->hash_alg == -1)
|
|
+ return -1;
|
|
+
|
|
+ h->ivgen_alg = lookup_ivgen (ivgen);
|
|
+ if (h->ivgen_alg == -1)
|
|
+ return -1;
|
|
+
|
|
+#if 0
|
|
+ if (h->ivgen_alg == IVGEN_ESSIV) {
|
|
+ if (!ivhash) {
|
|
+ nbdkit_error ("incorrect IV generator hash specification");
|
|
+ return -1;
|
|
+ }
|
|
+ h->ivgen_cipher_alg = lookup_essiv_cipher (h->cipher_alg,
|
|
+ h->ivgen_hash_alg);
|
|
+ if (h->ivgen_cipher_alg == -1)
|
|
+ return -1;
|
|
+ }
|
|
+ else
|
|
+#endif
|
|
+ h->ivgen_cipher_alg = h->cipher_alg;
|
|
+
|
|
+ nbdkit_debug ("LUKS parsed ciphers: %s %s %s %s %s %s",
|
|
+ cipher_alg_to_string (h->cipher_alg),
|
|
+ cipher_mode_to_string (h->cipher_mode),
|
|
+ hash_to_string (h->hash_alg),
|
|
+ ivgen_to_string (h->ivgen_alg),
|
|
+ hash_to_string (h->ivgen_hash_alg),
|
|
+ cipher_alg_to_string (h->ivgen_cipher_alg));
|
|
+
|
|
+ /* GnuTLS combines cipher and block mode into a single value. Not
|
|
+ * all possible combinations are available in GnuTLS. See:
|
|
+ * https://www.gnutls.org/manual/html_node/Supported-ciphersuites.html
|
|
+ */
|
|
+ h->gnutls_cipher = GNUTLS_CIPHER_NULL;
|
|
+ switch (h->cipher_mode) {
|
|
+ case CIPHER_MODE_XTS:
|
|
+ switch (h->cipher_alg) {
|
|
+ case CIPHER_ALG_AES_128:
|
|
+ h->gnutls_cipher = GNUTLS_CIPHER_AES_128_XTS;
|
|
+ break;
|
|
+ case CIPHER_ALG_AES_256:
|
|
+ h->gnutls_cipher = GNUTLS_CIPHER_AES_256_XTS;
|
|
+ break;
|
|
+ default: break;
|
|
+ }
|
|
+ break;
|
|
+ case CIPHER_MODE_CBC:
|
|
+ switch (h->cipher_alg) {
|
|
+ case CIPHER_ALG_AES_128:
|
|
+ h->gnutls_cipher = GNUTLS_CIPHER_AES_128_CBC;
|
|
+ break;
|
|
+ case CIPHER_ALG_AES_192:
|
|
+ h->gnutls_cipher = GNUTLS_CIPHER_AES_192_CBC;
|
|
+ break;
|
|
+ case CIPHER_ALG_AES_256:
|
|
+ h->gnutls_cipher = GNUTLS_CIPHER_AES_256_CBC;
|
|
+ break;
|
|
+ default: break;
|
|
+ }
|
|
+ default: break;
|
|
+ }
|
|
+ if (h->gnutls_cipher == GNUTLS_CIPHER_NULL) {
|
|
+ nbdkit_error ("cipher algorithm %s in mode %s is not supported by GnuTLS",
|
|
+ cipher_alg_to_string (h->cipher_alg),
|
|
+ cipher_mode_to_string (h->cipher_mode));
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Anti-Forensic merge operation. */
|
|
+static void
|
|
+xor (const uint8_t *in1, const uint8_t *in2, uint8_t *out, size_t len)
|
|
+{
|
|
+ size_t i;
|
|
+
|
|
+ for (i = 0; i < len; ++i)
|
|
+ out[i] = in1[i] ^ in2[i];
|
|
+}
|
|
+
|
|
+static int
|
|
+af_hash (gnutls_digest_algorithm_t hash_alg, uint8_t *block, size_t len)
|
|
+{
|
|
+ size_t digest_bytes = gnutls_hash_get_len (hash_alg);
|
|
+ size_t nr_blocks, last_block_len;
|
|
+ size_t i;
|
|
+ CLEANUP_FREE uint8_t *temp = malloc (digest_bytes);
|
|
+ int r;
|
|
+ gnutls_hash_hd_t hash;
|
|
+
|
|
+ nr_blocks = len / digest_bytes;
|
|
+ last_block_len = len % digest_bytes;
|
|
+ if (last_block_len != 0)
|
|
+ nr_blocks++;
|
|
+ else
|
|
+ last_block_len = digest_bytes;
|
|
+
|
|
+ for (i = 0; i < nr_blocks; ++i) {
|
|
+ const uint32_t iv = htobe32 (i);
|
|
+ const size_t blen = i < nr_blocks - 1 ? digest_bytes : last_block_len;
|
|
+
|
|
+ /* Hash iv + i'th block into temp. */
|
|
+ r = gnutls_hash_init (&hash, hash_alg);
|
|
+ if (r != 0) {
|
|
+ nbdkit_error ("gnutls_hash_init: %s", gnutls_strerror (r));
|
|
+ return -1;
|
|
+ }
|
|
+ gnutls_hash (hash, &iv, sizeof iv);
|
|
+ gnutls_hash (hash, &block[i*digest_bytes], blen);
|
|
+ gnutls_hash_deinit (hash, temp);
|
|
+
|
|
+ memcpy (&block[i*digest_bytes], temp, blen);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+afmerge (gnutls_digest_algorithm_t hash_alg, uint32_t stripes,
|
|
+ const uint8_t *in, uint8_t *out, size_t outlen)
|
|
+{
|
|
+ CLEANUP_FREE uint8_t *block = calloc (1, outlen);
|
|
+ size_t i;
|
|
+
|
|
+ /* NB: input size is stripes * master_key_len where
|
|
+ * master_key_len == outlen
|
|
+ */
|
|
+ for (i = 0; i < stripes-1; ++i) {
|
|
+ xor (&in[i*outlen], block, block, outlen);
|
|
+ if (af_hash (hash_alg, block, outlen) == -1)
|
|
+ return -1;
|
|
+ }
|
|
+ xor (&in[i*outlen], block, out, outlen);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Length of key material in key slot i (sectors).
|
|
+ *
|
|
+ * This is basically copied from qemu because the spec description is
|
|
+ * unintelligible and apparently doesn't match reality.
|
|
+ */
|
|
+static uint64_t
|
|
+key_material_length_in_sectors (struct handle *h, size_t i)
|
|
+{
|
|
+ uint64_t len, r;
|
|
+
|
|
+ len = h->phdr.master_key_len * h->phdr.keyslot[i].stripes;
|
|
+ r = DIV_ROUND_UP (len, LUKS_SECTOR_SIZE);
|
|
+ r = ROUND_UP (r, LUKS_ALIGN_KEYSLOTS / LUKS_SECTOR_SIZE);
|
|
+ return r;
|
|
+}
|
|
+
|
|
+/* Try the passphrase in key slot i. If this returns true then the
|
|
+ * passphrase was able to decrypt the master key, and the master key
|
|
+ * has been stored in h->masterkey.
|
|
+ */
|
|
+static int
|
|
+try_passphrase_in_keyslot (nbdkit_next *next, struct handle *h, size_t i)
|
|
+{
|
|
+ struct luks_keyslot *ks = &h->phdr.keyslot[i];
|
|
+ size_t split_key_len;
|
|
+ CLEANUP_FREE uint8_t *split_key = NULL;
|
|
+ CLEANUP_FREE uint8_t *masterkey = NULL;
|
|
+ const gnutls_datum_t key =
|
|
+ { (unsigned char *) passphrase, strlen (passphrase) };
|
|
+ const gnutls_datum_t salt =
|
|
+ { (unsigned char *) ks->password_salt, LUKS_SALTSIZE };
|
|
+ const gnutls_datum_t msalt =
|
|
+ { (unsigned char *) h->phdr.master_key_salt, LUKS_SALTSIZE };
|
|
+ gnutls_datum_t mkey;
|
|
+ gnutls_cipher_hd_t cipher;
|
|
+ int r, err = 0;
|
|
+ uint64_t start;
|
|
+ uint8_t key_digest[LUKS_DIGESTSIZE];
|
|
+
|
|
+ if (ks->active != LUKS_KEY_ENABLED)
|
|
+ return 0;
|
|
+
|
|
+ split_key_len = h->phdr.master_key_len * ks->stripes;
|
|
+ split_key = malloc (split_key_len);
|
|
+ if (split_key == NULL) {
|
|
+ nbdkit_error ("malloc: %m");
|
|
+ return -1;
|
|
+ }
|
|
+ masterkey = malloc (h->phdr.master_key_len);
|
|
+ if (masterkey == NULL) {
|
|
+ nbdkit_error ("malloc: %m");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ /* Hash the passphrase to make a possible masterkey. */
|
|
+ r = gnutls_pbkdf2 (h->hash_alg, &key, &salt, ks->password_iterations,
|
|
+ masterkey, h->phdr.master_key_len);
|
|
+ if (r != 0) {
|
|
+ nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r));
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ /* Read master key material from plugin. */
|
|
+ start = ks->key_material_offset * LUKS_SECTOR_SIZE;
|
|
+ if (next->pread (next, split_key, split_key_len, start, 0, &err) == -1) {
|
|
+ errno = err;
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ /* Decrypt the (still AFsplit) master key material. */
|
|
+ mkey.data = (unsigned char *) masterkey;
|
|
+ mkey.size = h->phdr.master_key_len;
|
|
+ r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL);
|
|
+ if (r != 0) {
|
|
+ nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r));
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ r = do_decrypt (h, cipher, 0, split_key, split_key_len);
|
|
+ gnutls_cipher_deinit (cipher);
|
|
+ if (r == -1)
|
|
+ return -1;
|
|
+
|
|
+ /* Decode AFsplit key to a possible masterkey. */
|
|
+ if (afmerge (h->hash_alg, ks->stripes, split_key,
|
|
+ masterkey, h->phdr.master_key_len) == -1)
|
|
+ return -1;
|
|
+
|
|
+ /* Check if the masterkey is correct by comparing hash of the
|
|
+ * masterkey with LUKS header.
|
|
+ */
|
|
+ r = gnutls_pbkdf2 (h->hash_alg, &mkey, &msalt,
|
|
+ h->phdr.master_key_digest_iterations,
|
|
+ key_digest, LUKS_DIGESTSIZE);
|
|
+ if (r != 0) {
|
|
+ nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r));
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ if (memcmp (key_digest, h->phdr.master_key_digest, LUKS_DIGESTSIZE) == 0) {
|
|
+ /* The passphrase is correct so save the master key in the handle. */
|
|
+ h->masterkey = malloc (h->phdr.master_key_len);
|
|
+ if (h->masterkey == NULL) {
|
|
+ nbdkit_error ("malloc: %m");
|
|
+ return -1;
|
|
+ }
|
|
+ memcpy (h->masterkey, masterkey, h->phdr.master_key_len);
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+luks_prepare (nbdkit_next *next, void *handle, int readonly)
|
|
+{
|
|
+ static const char expected_magic[] = LUKS_MAGIC;
|
|
+ struct handle *h = handle;
|
|
+ int64_t size;
|
|
+ int err = 0, r;
|
|
+ size_t i;
|
|
+ struct luks_keyslot *ks;
|
|
+ char uuid[41];
|
|
+
|
|
+ /* Check we haven't been called before, this should never happen. */
|
|
+ assert (h->phdr.version == 0);
|
|
+
|
|
+ /* Check the struct size matches the documentation. */
|
|
+ assert (sizeof (struct luks_phdr) == 592);
|
|
+
|
|
+ /* Check this is a LUKSv1 disk. */
|
|
+ size = next->get_size (next);
|
|
+ if (size == -1)
|
|
+ return -1;
|
|
+ if (size < 16384) {
|
|
+ nbdkit_error ("disk is too small to be LUKS-encrypted");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ /* Read the phdr. */
|
|
+ if (next->pread (next, &h->phdr, sizeof h->phdr, 0, 0, &err) == -1) {
|
|
+ errno = err;
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ if (memcmp (h->phdr.magic, expected_magic, LUKS_MAGIC_LEN) != 0) {
|
|
+ nbdkit_error ("this disk does not contain a LUKS header");
|
|
+ return -1;
|
|
+ }
|
|
+ h->phdr.version = be16toh (h->phdr.version);
|
|
+ if (h->phdr.version != 1) {
|
|
+ nbdkit_error ("this disk contains a LUKS version %" PRIu16 " header, "
|
|
+ "but this filter only supports LUKSv1",
|
|
+ h->phdr.version);
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ /* Byte-swap the rest of the header. */
|
|
+ h->phdr.payload_offset = be32toh (h->phdr.payload_offset);
|
|
+ h->phdr.master_key_len = be32toh (h->phdr.master_key_len);
|
|
+ h->phdr.master_key_digest_iterations =
|
|
+ be32toh (h->phdr.master_key_digest_iterations);
|
|
+
|
|
+ for (i = 0; i < LUKS_NUMKEYS; ++i) {
|
|
+ ks = &h->phdr.keyslot[i];
|
|
+ ks->active = be32toh (ks->active);
|
|
+ ks->password_iterations = be32toh (ks->password_iterations);
|
|
+ ks->key_material_offset = be32toh (ks->key_material_offset);
|
|
+ ks->stripes = be32toh (ks->stripes);
|
|
+ }
|
|
+
|
|
+ /* Sanity check some fields. */
|
|
+ if (h->phdr.payload_offset >= size / LUKS_SECTOR_SIZE) {
|
|
+ nbdkit_error ("bad LUKSv1 header: payload offset points beyond "
|
|
+ "the end of the disk");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ /* We derive several allocations from master_key_len so make sure
|
|
+ * it's not insane.
|
|
+ */
|
|
+ if (h->phdr.master_key_len > 1024) {
|
|
+ nbdkit_error ("bad LUKSv1 header: master key is too long");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < LUKS_NUMKEYS; ++i) {
|
|
+ uint64_t start, len;
|
|
+
|
|
+ ks = &h->phdr.keyslot[i];
|
|
+ switch (ks->active) {
|
|
+ case LUKS_KEY_ENABLED:
|
|
+ if (!ks->stripes) {
|
|
+ nbdkit_error ("bad LUKSv1 header: key slot %zu is corrupted", i);
|
|
+ return -1;
|
|
+ }
|
|
+ if (ks->stripes >= 10000) {
|
|
+ nbdkit_error ("bad LUKSv1 header: key slot %zu stripes too large", i);
|
|
+ return -1;
|
|
+ }
|
|
+ start = ks->key_material_offset;
|
|
+ len = key_material_length_in_sectors (h, i);
|
|
+ if (len > 4096) /* bound it at something reasonable */ {
|
|
+ nbdkit_error ("bad LUKSv1 header: key slot %zu key material length "
|
|
+ "is too large", i);
|
|
+ return -1;
|
|
+ }
|
|
+ if (start * LUKS_SECTOR_SIZE >= size ||
|
|
+ (start + len) * LUKS_SECTOR_SIZE >= size) {
|
|
+ nbdkit_error ("bad LUKSv1 header: key slot %zu key material offset "
|
|
+ "points beyond the end of the disk", i);
|
|
+ return -1;
|
|
+ }
|
|
+ if (ks->password_iterations > ULONG_MAX) {
|
|
+ nbdkit_error ("bad LUKSv1 header: key slot %zu "
|
|
+ "iterations too large", i);
|
|
+ return -1;
|
|
+ }
|
|
+ /*FALLTHROUGH*/
|
|
+ case LUKS_KEY_DISABLED:
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ nbdkit_error ("bad LUKSv1 header: key slot %zu has "
|
|
+ "an invalid active flag", i);
|
|
+ return -1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Decode the ciphers. */
|
|
+ if (parse_cipher_strings (h) == -1)
|
|
+ return -1;
|
|
+
|
|
+ /* Dump some information about the header. */
|
|
+ memcpy (uuid, h->phdr.uuid, 40);
|
|
+ uuid[40] = 0;
|
|
+ nbdkit_debug ("LUKS UUID: %s", uuid);
|
|
+
|
|
+ for (i = 0; i < LUKS_NUMKEYS; ++i) {
|
|
+ uint64_t start, len;
|
|
+
|
|
+ ks = &h->phdr.keyslot[i];
|
|
+ if (ks->active == LUKS_KEY_ENABLED) {
|
|
+ start = ks->key_material_offset;
|
|
+ len = key_material_length_in_sectors (h, i);
|
|
+ nbdkit_debug ("LUKS key slot %zu: key material in sectors %" PRIu64
|
|
+ "..%" PRIu64,
|
|
+ i, start, start+len-1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Now try to unlock the master key. */
|
|
+ for (i = 0; i < LUKS_NUMKEYS; ++i) {
|
|
+ r = try_passphrase_in_keyslot (next, h, i);
|
|
+ if (r == -1)
|
|
+ return -1;
|
|
+ if (r > 0)
|
|
+ goto unlocked;
|
|
+ }
|
|
+ nbdkit_error ("LUKS passphrase is not correct, "
|
|
+ "no key slot could be unlocked");
|
|
+ return -1;
|
|
+
|
|
+ unlocked:
|
|
+ assert (h->masterkey != NULL);
|
|
+ nbdkit_debug ("LUKS unlocked block device with passphrase");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int64_t
|
|
+luks_get_size (nbdkit_next *next, void *handle)
|
|
+{
|
|
+ struct handle *h = handle;
|
|
+ int64_t size;
|
|
+
|
|
+ /* Check that prepare has been called already. */
|
|
+ assert (h->phdr.version > 0);
|
|
+
|
|
+ size = next->get_size (next);
|
|
+ if (size == -1)
|
|
+ return -1;
|
|
+
|
|
+ if (size < h->phdr.payload_offset * LUKS_SECTOR_SIZE) {
|
|
+ nbdkit_error ("disk too small, or contains an incomplete LUKS partition");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ size -= h->phdr.payload_offset * LUKS_SECTOR_SIZE;
|
|
+ return size;
|
|
+}
|
|
+
|
|
+/* Whatever the plugin says, several operations are not supported by
|
|
+ * this filter:
|
|
+ *
|
|
+ * - extents
|
|
+ * - trim
|
|
+ * - zero
|
|
+ */
|
|
+static int
|
|
+luks_can_extents (nbdkit_next *next, void *handle)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+luks_can_trim (nbdkit_next *next, void *handle)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+luks_can_zero (nbdkit_next *next, void *handle)
|
|
+{
|
|
+ return NBDKIT_ZERO_EMULATE;
|
|
+}
|
|
+
|
|
+static int
|
|
+luks_can_fast_zero (nbdkit_next *next, void *handle)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Rely on nbdkit to call .pread to emulate .cache calls. We will
|
|
+ * respond by decrypting the block which could be stored by the cache
|
|
+ * filter or similar on top.
|
|
+ */
|
|
+static int
|
|
+luks_can_cache (nbdkit_next *next, void *handle)
|
|
+{
|
|
+ return NBDKIT_CACHE_EMULATE;
|
|
+}
|
|
+
|
|
+/* Advertise minimum/preferred sector-sized blocks, although we can in
|
|
+ * fact handle any read or write.
|
|
+ */
|
|
+static int
|
|
+luks_block_size (nbdkit_next *next, void *handle,
|
|
+ uint32_t *minimum, uint32_t *preferred, uint32_t *maximum)
|
|
+{
|
|
+ if (next->block_size (next, minimum, preferred, maximum) == -1)
|
|
+ return -1;
|
|
+
|
|
+ if (*minimum == 0) { /* No constraints set by the plugin. */
|
|
+ *minimum = LUKS_SECTOR_SIZE;
|
|
+ *preferred = LUKS_SECTOR_SIZE;
|
|
+ *maximum = 0xffffffff;
|
|
+ }
|
|
+ else {
|
|
+ *minimum = MAX (*minimum, LUKS_SECTOR_SIZE);
|
|
+ *preferred = MAX (*minimum, MAX (*preferred, LUKS_SECTOR_SIZE));
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Decrypt data. */
|
|
+static int
|
|
+luks_pread (nbdkit_next *next, void *handle,
|
|
+ void *buf, uint32_t count, uint64_t offset,
|
|
+ uint32_t flags, int *err)
|
|
+{
|
|
+ struct handle *h = handle;
|
|
+ const uint64_t payload_offset = h->phdr.payload_offset * LUKS_SECTOR_SIZE;
|
|
+ CLEANUP_FREE uint8_t *sector = NULL;
|
|
+ uint64_t sectnum, sectoffs;
|
|
+ const gnutls_datum_t mkey =
|
|
+ { (unsigned char *) h->masterkey, h->phdr.master_key_len };
|
|
+ gnutls_cipher_hd_t cipher;
|
|
+ int r;
|
|
+
|
|
+ if (!h->masterkey) {
|
|
+ *err = EIO;
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ if (!IS_ALIGNED (count | offset, LUKS_SECTOR_SIZE)) {
|
|
+ sector = malloc (LUKS_SECTOR_SIZE);
|
|
+ if (sector == NULL) {
|
|
+ *err = errno;
|
|
+ nbdkit_error ("malloc: %m");
|
|
+ return -1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL);
|
|
+ if (r != 0) {
|
|
+ nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r));
|
|
+ *err = EIO;
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ sectnum = offset / LUKS_SECTOR_SIZE; /* sector number */
|
|
+ sectoffs = offset % LUKS_SECTOR_SIZE; /* offset within the sector */
|
|
+
|
|
+ /* Unaligned head */
|
|
+ if (sectoffs) {
|
|
+ uint64_t n = MIN (LUKS_SECTOR_SIZE - sectoffs, count);
|
|
+
|
|
+ assert (sector);
|
|
+ if (next->pread (next, sector, LUKS_SECTOR_SIZE,
|
|
+ sectnum * LUKS_SECTOR_SIZE + payload_offset,
|
|
+ flags, err) == -1)
|
|
+ goto err;
|
|
+
|
|
+ if (do_decrypt (h, cipher, offset & ~LUKS_SECTOR_SIZE,
|
|
+ sector, LUKS_SECTOR_SIZE) == -1)
|
|
+ goto err;
|
|
+
|
|
+ memcpy (buf, §or[sectoffs], n);
|
|
+
|
|
+ buf += n;
|
|
+ count -= n;
|
|
+ offset += n;
|
|
+ sectnum++;
|
|
+ }
|
|
+
|
|
+ /* Aligned body */
|
|
+ while (count >= LUKS_SECTOR_SIZE) {
|
|
+ if (next->pread (next, buf, LUKS_SECTOR_SIZE,
|
|
+ sectnum * LUKS_SECTOR_SIZE + payload_offset,
|
|
+ flags, err) == -1)
|
|
+ goto err;
|
|
+
|
|
+ if (do_decrypt (h, cipher, offset, buf, LUKS_SECTOR_SIZE) == -1)
|
|
+ goto err;
|
|
+
|
|
+ buf += LUKS_SECTOR_SIZE;
|
|
+ count -= LUKS_SECTOR_SIZE;
|
|
+ offset += LUKS_SECTOR_SIZE;
|
|
+ sectnum++;
|
|
+ }
|
|
+
|
|
+ /* Unaligned tail */
|
|
+ if (count) {
|
|
+ assert (sector);
|
|
+ if (next->pread (next, sector, LUKS_SECTOR_SIZE,
|
|
+ sectnum * LUKS_SECTOR_SIZE + payload_offset,
|
|
+ flags, err) == -1)
|
|
+ goto err;
|
|
+
|
|
+ if (do_decrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1)
|
|
+ goto err;
|
|
+
|
|
+ memcpy (buf, sector, count);
|
|
+ }
|
|
+
|
|
+ gnutls_cipher_deinit (cipher);
|
|
+ return 0;
|
|
+
|
|
+ err:
|
|
+ gnutls_cipher_deinit (cipher);
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+/* Lock preventing read-modify-write cycles from overlapping. */
|
|
+static pthread_mutex_t read_modify_write_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
+
|
|
+/* Encrypt data. */
|
|
+static int
|
|
+luks_pwrite (nbdkit_next *next, void *handle,
|
|
+ const void *buf, uint32_t count, uint64_t offset,
|
|
+ uint32_t flags, int *err)
|
|
+{
|
|
+ struct handle *h = handle;
|
|
+ const uint64_t payload_offset = h->phdr.payload_offset * LUKS_SECTOR_SIZE;
|
|
+ CLEANUP_FREE uint8_t *sector = NULL;
|
|
+ uint64_t sectnum, sectoffs;
|
|
+ const gnutls_datum_t mkey =
|
|
+ { (unsigned char *) h->masterkey, h->phdr.master_key_len };
|
|
+ gnutls_cipher_hd_t cipher;
|
|
+ int r;
|
|
+
|
|
+ if (!h->masterkey) {
|
|
+ *err = EIO;
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ sector = malloc (LUKS_SECTOR_SIZE);
|
|
+ if (sector == NULL) {
|
|
+ *err = errno;
|
|
+ nbdkit_error ("malloc: %m");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL);
|
|
+ if (r != 0) {
|
|
+ nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r));
|
|
+ *err = EIO;
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ sectnum = offset / LUKS_SECTOR_SIZE; /* sector number */
|
|
+ sectoffs = offset % LUKS_SECTOR_SIZE; /* offset within the sector */
|
|
+
|
|
+ /* Unaligned head */
|
|
+ if (sectoffs) {
|
|
+ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&read_modify_write_lock);
|
|
+
|
|
+ uint64_t n = MIN (LUKS_SECTOR_SIZE - sectoffs, count);
|
|
+
|
|
+ if (next->pread (next, sector, LUKS_SECTOR_SIZE,
|
|
+ sectnum * LUKS_SECTOR_SIZE + payload_offset,
|
|
+ flags, err) == -1)
|
|
+ goto err;
|
|
+
|
|
+ memcpy (§or[sectoffs], buf, n);
|
|
+
|
|
+ if (do_encrypt (h, cipher, offset & ~LUKS_SECTOR_SIZE,
|
|
+ sector, LUKS_SECTOR_SIZE) == -1)
|
|
+ goto err;
|
|
+
|
|
+ if (next->pwrite (next, sector, LUKS_SECTOR_SIZE,
|
|
+ sectnum * LUKS_SECTOR_SIZE + payload_offset,
|
|
+ flags, err) == -1)
|
|
+ goto err;
|
|
+
|
|
+ buf += n;
|
|
+ count -= n;
|
|
+ offset += n;
|
|
+ sectnum++;
|
|
+ }
|
|
+
|
|
+ /* Aligned body */
|
|
+ while (count >= LUKS_SECTOR_SIZE) {
|
|
+ memcpy (sector, buf, LUKS_SECTOR_SIZE);
|
|
+
|
|
+ if (do_encrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1)
|
|
+ goto err;
|
|
+
|
|
+ if (next->pwrite (next, sector, LUKS_SECTOR_SIZE,
|
|
+ sectnum * LUKS_SECTOR_SIZE + payload_offset,
|
|
+ flags, err) == -1)
|
|
+ goto err;
|
|
+
|
|
+ buf += LUKS_SECTOR_SIZE;
|
|
+ count -= LUKS_SECTOR_SIZE;
|
|
+ offset += LUKS_SECTOR_SIZE;
|
|
+ sectnum++;
|
|
+ }
|
|
+
|
|
+ /* Unaligned tail */
|
|
+ if (count) {
|
|
+ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&read_modify_write_lock);
|
|
+
|
|
+ if (next->pread (next, sector, LUKS_SECTOR_SIZE,
|
|
+ sectnum * LUKS_SECTOR_SIZE + payload_offset,
|
|
+ flags, err) == -1)
|
|
+ goto err;
|
|
+
|
|
+ memcpy (sector, buf, count);
|
|
+
|
|
+ if (do_encrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1)
|
|
+ goto err;
|
|
+
|
|
+ if (next->pwrite (next, sector, LUKS_SECTOR_SIZE,
|
|
+ sectnum * LUKS_SECTOR_SIZE + payload_offset,
|
|
+ flags, err) == -1)
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ gnutls_cipher_deinit (cipher);
|
|
+ return 0;
|
|
+
|
|
+ err:
|
|
+ gnutls_cipher_deinit (cipher);
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static struct nbdkit_filter filter = {
|
|
+ .name = "luks",
|
|
+ .longname = "nbdkit luks filter",
|
|
+ .unload = luks_unload,
|
|
+ .thread_model = luks_thread_model,
|
|
+ .config = luks_config,
|
|
+ .config_complete = luks_config_complete,
|
|
+ .config_help = luks_config_help,
|
|
+ .open = luks_open,
|
|
+ .close = luks_close,
|
|
+ .prepare = luks_prepare,
|
|
+ .get_size = luks_get_size,
|
|
+ .can_extents = luks_can_extents,
|
|
+ .can_trim = luks_can_trim,
|
|
+ .can_zero = luks_can_zero,
|
|
+ .can_fast_zero = luks_can_fast_zero,
|
|
+ .can_cache = luks_can_cache,
|
|
+ .block_size = luks_block_size,
|
|
+ .pread = luks_pread,
|
|
+ .pwrite = luks_pwrite,
|
|
+};
|
|
+
|
|
+NBDKIT_REGISTER_FILTER(filter)
|
|
diff --git a/filters/luks/nbdkit-luks-filter.pod b/filters/luks/nbdkit-luks-filter.pod
|
|
new file mode 100644
|
|
index 00000000..56e51561
|
|
--- /dev/null
|
|
+++ b/filters/luks/nbdkit-luks-filter.pod
|
|
@@ -0,0 +1,120 @@
|
|
+=head1 NAME
|
|
+
|
|
+nbdkit-luks-filter - read and write LUKS-encrypted disks and partitions
|
|
+
|
|
+=head1 SYNOPSIS
|
|
+
|
|
+ nbdkit file encrypted-disk.img --filter=luks passphrase=+/tmp/secret
|
|
+
|
|
+=head1 DESCRIPTION
|
|
+
|
|
+C<nbdkit-luks-filter> is a filter for L<nbdkit(1)> which transparently
|
|
+opens a LUKS-encrypted disk image. LUKS ("Linux Unified Key Setup")
|
|
+is the Full Disk Encryption (FDE) system commonly used by Linux
|
|
+systems. This filter is compatible with LUKSv1 as implemented by the
|
|
+Linux kernel (dm_crypt), and by qemu.
|
|
+
|
|
+You can place this filter on top of L<nbdkit-file-plugin(1)> to
|
|
+decrypt a local file:
|
|
+
|
|
+ nbdkit file encrypted-disk.img --filter=luks passphrase=+/tmp/secret
|
|
+
|
|
+If LUKS is present inside a partition in the disk image then you will
|
|
+have to combine this filter with L<nbdkit-partition-filter(1)>. The
|
|
+order of the filters is important:
|
|
+
|
|
+ nbdkit file encrypted-disk.img \
|
|
+ --filter=luks passphrase=+/tmp/secret \
|
|
+ --filter=partition partition=1
|
|
+
|
|
+This filter also works on top of other plugins such as
|
|
+L<nbdkit-curl-plugin(1)>:
|
|
+
|
|
+ nbdkit curl https://example.com/encrypted-disk.img \
|
|
+ --filter=luks passphrase=+/tmp/secret
|
|
+
|
|
+The web server sees only the encrypted data. Without knowing the
|
|
+passphrase, the web server cannot access the decrypted disk. Only
|
|
+encrypted data is sent over the HTTP connection. nbdkit itself will
|
|
+serve I<unencrypted> disk data over the NBD connection (if this is a
|
|
+problem see L<nbdkit-tls(1)>, or use a Unix domain socket I<-U>).
|
|
+
|
|
+The passphrase can be stored in a file (as shown), passed directly on
|
|
+the command line (insecure), entered interactively, or passed to
|
|
+nbdkit over a file descriptor.
|
|
+
|
|
+This filter can read and write LUKSv1. It cannot create disks, change
|
|
+passphrases, add keyslots, etc. To do that, you can use ordinary
|
|
+Linux tools like L<cryptsetup(8)>. Note you must force LUKSv1
|
|
+(eg. using cryptsetup I<--type luks1>). L<qemu-img(1)> can also
|
|
+create compatible disk images:
|
|
+
|
|
+ qemu-img create -f luks \
|
|
+ --object secret,data=SECRET,id=sec0 \
|
|
+ -o key-secret=sec0 \
|
|
+ encrypted-disk.img 1G
|
|
+
|
|
+=head1 PARAMETERS
|
|
+
|
|
+=over 4
|
|
+
|
|
+=item B<passphrase=>SECRET
|
|
+
|
|
+Use the secret passphrase when decrypting the disk.
|
|
+
|
|
+Note that passing this on the command line is not secure on shared
|
|
+machines.
|
|
+
|
|
+=item B<passphrase=->
|
|
+
|
|
+Ask for the passphrase (interactively) when nbdkit starts up.
|
|
+
|
|
+=item B<passphrase=+>FILENAME
|
|
+
|
|
+Read the passphrase from the named file. This is a secure method to
|
|
+supply a passphrase, as long as you set the permissions on the file
|
|
+appropriately.
|
|
+
|
|
+=item B<passphrase=->FD
|
|
+
|
|
+Read the passphrase from file descriptor number C<FD>, inherited from
|
|
+the parent process when nbdkit starts up. This is also a secure
|
|
+method to supply a passphrase.
|
|
+
|
|
+=back
|
|
+
|
|
+=head1 FILES
|
|
+
|
|
+=over 4
|
|
+
|
|
+=item F<$filterdir/nbdkit-luks-filter.so>
|
|
+
|
|
+The plugin.
|
|
+
|
|
+Use C<nbdkit --dump-config> to find the location of C<$filterdir>.
|
|
+
|
|
+=back
|
|
+
|
|
+=head1 VERSION
|
|
+
|
|
+C<nbdkit-luks-filter> first appeared in nbdkit 1.32.
|
|
+
|
|
+=head1 SEE ALSO
|
|
+
|
|
+L<nbdkit-curl-plugin(1)>,
|
|
+L<nbdkit-file-plugin(1)>,
|
|
+L<nbdkit-ip-filter(1)>,
|
|
+L<nbdkit-partition-filter(1)>,
|
|
+L<nbdkit(1)>,
|
|
+L<nbdkit-tls(1)>,
|
|
+L<nbdkit-plugin(3)>,
|
|
+L<cryptsetup(8)>,
|
|
+L<qemu-img(1)>.
|
|
+
|
|
+=head1 AUTHORS
|
|
+
|
|
+Richard W.M. Jones
|
|
+
|
|
+=head1 COPYRIGHT
|
|
+
|
|
+Copyright (C) 2013-2022 Red Hat Inc.
|
|
diff --git a/plugins/file/nbdkit-file-plugin.pod b/plugins/file/nbdkit-file-plugin.pod
|
|
index f8f0e198..b95e7349 100644
|
|
--- a/plugins/file/nbdkit-file-plugin.pod
|
|
+++ b/plugins/file/nbdkit-file-plugin.pod
|
|
@@ -223,6 +223,7 @@ L<nbdkit-partitioning-plugin(1)>,
|
|
L<nbdkit-tmpdisk-plugin(1)>,
|
|
L<nbdkit-exportname-filter(1)>,
|
|
L<nbdkit-fua-filter(1)>,
|
|
+L<nbdkit-luks-filter(1)>,
|
|
L<nbdkit-noextents-filter(1)>.
|
|
|
|
=head1 AUTHORS
|
|
diff --git a/tests/Makefile.am b/tests/Makefile.am
|
|
index b310e8a2..c29453ba 100644
|
|
--- a/tests/Makefile.am
|
|
+++ b/tests/Makefile.am
|
|
@@ -1596,6 +1596,18 @@ EXTRA_DIST += \
|
|
test-log-script-info.sh \
|
|
$(NULL)
|
|
|
|
+# luks filter test.
|
|
+if HAVE_GNUTLS
|
|
+TESTS += \
|
|
+ test-luks-info.sh \
|
|
+ test-luks-copy.sh \
|
|
+ $(NULL)
|
|
+endif
|
|
+EXTRA_DIST += \
|
|
+ test-luks-info.sh \
|
|
+ test-luks-copy.sh \
|
|
+ $(NULL)
|
|
+
|
|
# multi-conn filter test.
|
|
TESTS += \
|
|
test-multi-conn.sh \
|
|
diff --git a/tests/test-luks-copy.sh b/tests/test-luks-copy.sh
|
|
new file mode 100755
|
|
index 00000000..99f300d0
|
|
--- /dev/null
|
|
+++ b/tests/test-luks-copy.sh
|
|
@@ -0,0 +1,125 @@
|
|
+#!/usr/bin/env bash
|
|
+# nbdkit
|
|
+# Copyright (C) 2018-2022 Red Hat Inc.
|
|
+#
|
|
+# 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.
|
|
+
|
|
+source ./functions.sh
|
|
+set -e
|
|
+set -x
|
|
+
|
|
+requires nbdcopy --version
|
|
+requires nbdsh --version
|
|
+requires_nbdsh_uri
|
|
+requires qemu-img --version
|
|
+requires bash -c 'qemu-img --help | grep -- --target-image-opts'
|
|
+requires hexdump --version
|
|
+requires truncate --version
|
|
+requires_filter luks
|
|
+
|
|
+encrypt_disk=luks-copy1.img
|
|
+plain_disk=luks-copy2.img
|
|
+pid=luks-copy.pid
|
|
+sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX)
|
|
+cleanup_fn rm -f $encrypt_disk $plain_disk $pid $sock
|
|
+rm -f $encrypt_disk $plain_disk $pid $sock
|
|
+
|
|
+# Create an empty encrypted disk container.
|
|
+#
|
|
+# NB: This is complicated because qemu doesn't create an all-zeroes
|
|
+# plaintext disk for some reason when you use create -f luks. It
|
|
+# starts with random plaintext.
|
|
+#
|
|
+# https://stackoverflow.com/a/44669936
|
|
+qemu-img create -f luks \
|
|
+ --object secret,data=123456,id=sec0 \
|
|
+ -o key-secret=sec0 \
|
|
+ $encrypt_disk 10M
|
|
+truncate -s 10M $plain_disk
|
|
+qemu-img convert --target-image-opts -n \
|
|
+ --object secret,data=123456,id=sec0 \
|
|
+ $plain_disk \
|
|
+ driver=luks,file.filename=$encrypt_disk,key-secret=sec0
|
|
+rm $plain_disk
|
|
+
|
|
+# Start nbdkit on the encrypted disk.
|
|
+start_nbdkit -P $pid -U $sock \
|
|
+ file $encrypt_disk --filter=luks passphrase=123456
|
|
+uri="nbd+unix:///?socket=$sock"
|
|
+
|
|
+# Copy the whole disk out. It should be empty.
|
|
+nbdcopy "$uri" $plain_disk
|
|
+
|
|
+if [ "$(hexdump -C $plain_disk)" != '00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
|
+*
|
|
+00a00000' ]; then
|
|
+ echo "$0: expected plaintext disk to be empty"
|
|
+ exit 1
|
|
+fi
|
|
+
|
|
+# Use nbdsh to overwrite with some known data and check we can read
|
|
+# back what we wrote.
|
|
+nbdsh -u "$uri" \
|
|
+ -c 'h.pwrite(b"1"*65536, 0)' \
|
|
+ -c 'h.pwrite(b"2"*65536, 128*1024)' \
|
|
+ -c 'h.pwrite(b"3"*65536, 9*1024*1024)' \
|
|
+ -c 'buf = h.pread(65536, 0)' \
|
|
+ -c 'assert buf == b"1"*65536' \
|
|
+ -c 'buf = h.pread(65536, 65536)' \
|
|
+ -c 'assert buf == bytearray(65536)' \
|
|
+ -c 'buf = h.pread(65536, 128*1024)' \
|
|
+ -c 'assert buf == b"2"*65536' \
|
|
+ -c 'buf = h.pread(65536, 9*1024*1024)' \
|
|
+ -c 'assert buf == b"3"*65536' \
|
|
+ -c 'h.flush()'
|
|
+
|
|
+# Use qemu to copy out the whole disk. Note we called flush() above
|
|
+# so the disk should be synchronised.
|
|
+qemu-img convert --image-opts \
|
|
+ --object secret,data=123456,id=sec0 \
|
|
+ driver=luks,file.filename=$encrypt_disk,key-secret=sec0 \
|
|
+ $plain_disk
|
|
+
|
|
+# Check the contents are expected.
|
|
+if [ "$(hexdump -C $plain_disk)" != '00000000 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|
|
+*
|
|
+00010000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
|
+*
|
|
+00020000 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|
|
+*
|
|
+00030000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
|
+*
|
|
+00900000 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|
|
+*
|
|
+00910000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
|
+*
|
|
+00a00000' ]; then
|
|
+ echo "$0: unexpected content"
|
|
+ exit 1
|
|
+fi
|
|
diff --git a/tests/test-luks-info.sh b/tests/test-luks-info.sh
|
|
new file mode 100755
|
|
index 00000000..3eff657b
|
|
--- /dev/null
|
|
+++ b/tests/test-luks-info.sh
|
|
@@ -0,0 +1,56 @@
|
|
+#!/usr/bin/env bash
|
|
+# nbdkit
|
|
+# Copyright (C) 2018-2022 Red Hat Inc.
|
|
+#
|
|
+# 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.
|
|
+
|
|
+source ./functions.sh
|
|
+set -e
|
|
+set -x
|
|
+
|
|
+requires nbdinfo --version
|
|
+requires qemu-img --version
|
|
+requires_filter luks
|
|
+
|
|
+disk=luks-info.img
|
|
+info=luks-info.log
|
|
+cleanup_fn rm -f $disk $info
|
|
+rm -f $disk $info
|
|
+
|
|
+qemu-img create -f luks \
|
|
+ --object secret,data=123456,id=sec0 \
|
|
+ -o key-secret=sec0 \
|
|
+ $disk 10M
|
|
+
|
|
+nbdkit -U - file $disk --filter=luks passphrase=123456 \
|
|
+ --run 'nbdinfo $uri' > $info
|
|
+cat $info
|
|
+
|
|
+# Check the size is 10M (so it doesn't include the LUKS header).
|
|
+grep "10485760" $info
|
|
--
|
|
2.31.1
|
|
|