Blame SOURCES/0004-New-filter-luks.patch

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