From ad784282b6b1d89db2381e2c69b5bdd3ab572eb6 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 12 May 2022 18:38:30 +0100 Subject: [PATCH] Rebase to new stable branch version 1.30.5 resolves: rhbz#2059289 Suppress excess messages from nbdkit-nbd-plugin resolves: rhbz#2083498 Suppress incorrect VDDK error when converting guests from vCenter resolves: rhbz#2083617 Backport new LUKS filter from 1.32. Add new Python binding for nbdkit_parse_size from 1.32 Cherry-picked from Fedora: Add new luks filter. (Fedora commit 9588e5cbc7997eed12d57c8282bd2e463ee38820) --- ...-Allow-the-remote-file-to-be-created.patch | 2 +- ...e-this-filter-so-it-prefetches-using.patch | 2 +- 0003-readahead-Fix-test.patch | 2 +- 0004-New-filter-luks.patch | 1817 ++++++++++++++ ...-filter-with-old-GnuTLS-in-Debian-10.patch | 96 + 0006-luks-Various-fixes-for-Clang.patch | 71 + ...-luks-Link-with-libcompat-on-Windows.patch | 43 + 0008-luks-Refactor-the-filter.patch | 2096 +++++++++++++++++ ...Reduce-time-taken-to-run-these-tests.patch | 101 + ...dd-nbdkit.parse_size-Python-function.patch | 112 + nbdkit.spec | 26 +- sources | 4 +- 12 files changed, 4363 insertions(+), 9 deletions(-) create mode 100644 0004-New-filter-luks.patch create mode 100644 0005-luks-Disable-filter-with-old-GnuTLS-in-Debian-10.patch create mode 100644 0006-luks-Various-fixes-for-Clang.patch create mode 100644 0007-luks-Link-with-libcompat-on-Windows.patch create mode 100644 0008-luks-Refactor-the-filter.patch create mode 100644 0009-tests-luks-Reduce-time-taken-to-run-these-tests.patch create mode 100644 0010-Add-nbdkit.parse_size-Python-function.patch diff --git a/0001-ssh-Allow-the-remote-file-to-be-created.patch b/0001-ssh-Allow-the-remote-file-to-be-created.patch index aa28047..a549937 100644 --- a/0001-ssh-Allow-the-remote-file-to-be-created.patch +++ b/0001-ssh-Allow-the-remote-file-to-be-created.patch @@ -1,4 +1,4 @@ -From 04cf7c2aa8aa8ddc446f1571d2f98661ba8a8ca7 Mon Sep 17 00:00:00 2001 +From 90f0cb8f0495ccf71dd3bed89ccb95c0120b5ef7 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Fri, 15 Apr 2022 12:08:37 +0100 Subject: [PATCH] ssh: Allow the remote file to be created diff --git a/0002-readahead-Rewrite-this-filter-so-it-prefetches-using.patch b/0002-readahead-Rewrite-this-filter-so-it-prefetches-using.patch index 50039d5..d050715 100644 --- a/0002-readahead-Rewrite-this-filter-so-it-prefetches-using.patch +++ b/0002-readahead-Rewrite-this-filter-so-it-prefetches-using.patch @@ -1,4 +1,4 @@ -From 160601397d307f70586f1bc70111141855ff68ea Mon Sep 17 00:00:00 2001 +From af145808cecd18f6f80b672b5988ec1064f9b4a7 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Sat, 16 Apr 2022 18:39:13 +0100 Subject: [PATCH] readahead: Rewrite this filter so it prefetches using .cache diff --git a/0003-readahead-Fix-test.patch b/0003-readahead-Fix-test.patch index 7f3a8b9..6750e53 100644 --- a/0003-readahead-Fix-test.patch +++ b/0003-readahead-Fix-test.patch @@ -1,4 +1,4 @@ -From a9708db324eb1989680132813da20d5a4c5496a9 Mon Sep 17 00:00:00 2001 +From 5d679d01417a81a3a981520d2a0332e2370a2536 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 21 Apr 2022 16:14:46 +0100 Subject: [PATCH] readahead: Fix test diff --git a/0004-New-filter-luks.patch b/0004-New-filter-luks.patch new file mode 100644 index 0000000..6084b5c --- /dev/null +++ b/0004-New-filter-luks.patch @@ -0,0 +1,1817 @@ +From 71755e96c423874e39a2ed85eb5d2e1c12c643f2 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +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 | 7 +- + 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, 1669 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 e391f110..2349b49e 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], +@@ -1379,6 +1381,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 +@@ -1495,6 +1498,8 @@ 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" + +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. + =head1 SEE ALSO + + L, ++L, + L, + L, + L, +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 ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++#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 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 is a filter for L 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 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. 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 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 disk data over the NBD connection (if this is a ++problem see L, 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. Note you must force LUKSv1 ++(eg. using cryptsetup I<--type luks1>). L 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 BSECRET ++ ++Use the secret passphrase when decrypting the disk. ++ ++Note that passing this on the command line is not secure on shared ++machines. ++ ++=item B ++ ++Ask for the passphrase (interactively) when nbdkit starts up. ++ ++=item BFILENAME ++ ++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 BFD ++ ++Read the passphrase from file descriptor number C, 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 to find the location of C<$filterdir>. ++ ++=back ++ ++=head1 VERSION ++ ++C first appeared in nbdkit 1.32. ++ ++=head1 SEE ALSO ++ ++L, ++L, ++L, ++L, ++L, ++L, ++L, ++L, ++L. ++ ++=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, + L, + L, + L, ++L, + L. + + =head1 AUTHORS +diff --git a/tests/Makefile.am b/tests/Makefile.am +index 0e7ce711..fa66a112 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -1590,6 +1590,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 + diff --git a/0005-luks-Disable-filter-with-old-GnuTLS-in-Debian-10.patch b/0005-luks-Disable-filter-with-old-GnuTLS-in-Debian-10.patch new file mode 100644 index 0000000..25eccbc --- /dev/null +++ b/0005-luks-Disable-filter-with-old-GnuTLS-in-Debian-10.patch @@ -0,0 +1,96 @@ +From ddda62b9faef8cb2cdf4bc4b60bead34f0f143d5 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sun, 8 May 2022 12:13:39 +0100 +Subject: [PATCH] luks: Disable filter with old GnuTLS in Debian 10 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +On Debian 10: + +luks.c: In function ‘parse_cipher_strings’: +luks.c:574:26: error: ‘GNUTLS_CIPHER_AES_128_XTS’ undeclared (first use in this function); did you mean ‘GNUTLS_CIPHER_AES_128_CCM’? + h->gnutls_cipher = GNUTLS_CIPHER_AES_128_XTS; + ^~~~~~~~~~~~~~~~~~~~~~~~~ + GNUTLS_CIPHER_AES_128_CCM +luks.c:574:26: note: each undeclared identifier is reported only once for each function it appears in +luks.c:577:26: error: ‘GNUTLS_CIPHER_AES_256_XTS’ undeclared (first use in this function); did you mean ‘GNUTLS_CIPHER_AES_256_CCM’? + h->gnutls_cipher = GNUTLS_CIPHER_AES_256_XTS; + ^~~~~~~~~~~~~~~~~~~~~~~~~ + GNUTLS_CIPHER_AES_256_CCM +luks.c: In function ‘try_passphrase_in_keyslot’: +luks.c:728:7: error: implicit declaration of function ‘gnutls_pbkdf2’; did you mean ‘gnutls_prf’? [-Werror=implicit-function-declaration] + r = gnutls_pbkdf2 (h->hash_alg, &key, &salt, ks->password_iterations, + ^~~~~~~~~~~~~ + gnutls_prf + +Because gnutls_pbkdf2 is missing there's no chance of making this +filter work on this platform so it's best to compile it out. + +Fixes: commit 468919dce6c5eb57503eacac0f67e5dd87c58e6c +(cherry picked from commit f9f67e483f4aad19ad6101163d32562f13504ca7) +--- + configure.ac | 7 +++++-- + filters/luks/Makefile.am | 2 +- + tests/Makefile.am | 2 +- + 3 files changed, 7 insertions(+), 4 deletions(-) + +diff --git a/configure.ac b/configure.ac +index 2349b49e..05db668e 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -636,11 +636,14 @@ AS_IF([test "x$GNUTLS_LIBS" != "x"],[ + gnutls_certificate_set_known_dh_params \ + gnutls_group_get \ + gnutls_group_get_name \ ++ gnutls_pbkdf2 \ + gnutls_session_set_verify_cert \ + gnutls_srp_server_get_username \ + ]) + LIBS="$old_LIBS" + ]) ++AM_CONDITIONAL([HAVE_GNUTLS_PBKDF2], ++ [test "x$GNUTLS_LIBS" != "x" && test "x$ac_cv_func_gnutls_pbkdf2" = xyes]) + + AC_ARG_ENABLE([linuxdisk], + [AS_HELP_STRING([--disable-linuxdisk], +@@ -1498,8 +1501,8 @@ 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 "luks ................................... " \ ++ test "x$HAVE_GNUTLS_PBKDF2_TRUE" = "x" + feature "xz ..................................... " \ + test "x$HAVE_LIBLZMA_TRUE" = "x" + +diff --git a/filters/luks/Makefile.am b/filters/luks/Makefile.am +index 30089621..622e5c3d 100644 +--- a/filters/luks/Makefile.am ++++ b/filters/luks/Makefile.am +@@ -33,7 +33,7 @@ include $(top_srcdir)/common-rules.mk + + EXTRA_DIST = nbdkit-luks-filter.pod + +-if HAVE_GNUTLS ++if HAVE_GNUTLS_PBKDF2 + + filter_LTLIBRARIES = nbdkit-luks-filter.la + +diff --git a/tests/Makefile.am b/tests/Makefile.am +index fa66a112..ffeef097 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -1591,7 +1591,7 @@ EXTRA_DIST += \ + $(NULL) + + # luks filter test. +-if HAVE_GNUTLS ++if HAVE_GNUTLS_PBKDF2 + TESTS += \ + test-luks-info.sh \ + test-luks-copy.sh \ +-- +2.31.1 + diff --git a/0006-luks-Various-fixes-for-Clang.patch b/0006-luks-Various-fixes-for-Clang.patch new file mode 100644 index 0000000..d1c9cf4 --- /dev/null +++ b/0006-luks-Various-fixes-for-Clang.patch @@ -0,0 +1,71 @@ +From 95f27197a7ea2d0fb0f19162152d0d72eeead752 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sun, 8 May 2022 12:30:09 +0100 +Subject: [PATCH] luks: Various fixes for Clang + +With Clang: + +luks.c:728:25: error: implicit conversion from enumeration type 'gnutls_digest_algorithm_t' to different enumeration type 'gnutls_mac_algorithm_t' [-Werror,-Wenum-conversion] + r = gnutls_pbkdf2 (h->hash_alg, &key, &salt, ks->password_iterations, + ~~~~~~~~~~~~~ ~~~^~~~~~~~ +luks.c:764:25: error: implicit conversion from enumeration type 'gnutls_digest_algorithm_t' to different enumeration type 'gnutls_mac_algorithm_t' [-Werror,-Wenum-conversion] + r = gnutls_pbkdf2 (h->hash_alg, &mkey, &msalt, + ~~~~~~~~~~~~~ ~~~^~~~~~~~ +luks.c:886:35: error: result of comparison of constant 18446744073709551615 with expression of type 'uint32_t' (aka 'unsigned int') is always false [-Werror,-Wtautological-constant-out-of-range-compare] + if (ks->password_iterations > ULONG_MAX) { + ~~~~~~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~ + +Fixes: commit 468919dce6c5eb57503eacac0f67e5dd87c58e6c +(cherry picked from commit 87d488ede9101a2effc71cd1851bf4a4caa521d2) +--- + filters/luks/luks.c | 13 ++++++------- + 1 file changed, 6 insertions(+), 7 deletions(-) + +diff --git a/filters/luks/luks.c b/filters/luks/luks.c +index 706a9bd2..cc619698 100644 +--- a/filters/luks/luks.c ++++ b/filters/luks/luks.c +@@ -693,6 +693,10 @@ key_material_length_in_sectors (struct handle *h, size_t i) + static int + try_passphrase_in_keyslot (nbdkit_next *next, struct handle *h, size_t i) + { ++ /* I believe this is supposed to be safe, looking at the GnuTLS ++ * header file. ++ */ ++ const gnutls_mac_algorithm_t mac = (gnutls_mac_algorithm_t) h->hash_alg; + struct luks_keyslot *ks = &h->phdr.keyslot[i]; + size_t split_key_len; + CLEANUP_FREE uint8_t *split_key = NULL; +@@ -725,7 +729,7 @@ try_passphrase_in_keyslot (nbdkit_next *next, struct handle *h, size_t i) + } + + /* Hash the passphrase to make a possible masterkey. */ +- r = gnutls_pbkdf2 (h->hash_alg, &key, &salt, ks->password_iterations, ++ r = gnutls_pbkdf2 (mac, &key, &salt, ks->password_iterations, + masterkey, h->phdr.master_key_len); + if (r != 0) { + nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r)); +@@ -761,7 +765,7 @@ try_passphrase_in_keyslot (nbdkit_next *next, struct handle *h, size_t i) + /* Check if the masterkey is correct by comparing hash of the + * masterkey with LUKS header. + */ +- r = gnutls_pbkdf2 (h->hash_alg, &mkey, &msalt, ++ r = gnutls_pbkdf2 (mac, &mkey, &msalt, + h->phdr.master_key_digest_iterations, + key_digest, LUKS_DIGESTSIZE); + if (r != 0) { +@@ -883,11 +887,6 @@ luks_prepare (nbdkit_next *next, void *handle, int readonly) + "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; +-- +2.31.1 + diff --git a/0007-luks-Link-with-libcompat-on-Windows.patch b/0007-luks-Link-with-libcompat-on-Windows.patch new file mode 100644 index 0000000..9ef0bd2 --- /dev/null +++ b/0007-luks-Link-with-libcompat-on-Windows.patch @@ -0,0 +1,43 @@ +From 6c052a340d7452feae84845965fdc99542da2404 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sun, 8 May 2022 12:38:00 +0100 +Subject: [PATCH] luks: Link with libcompat on Windows + +/usr/lib/gcc/x86_64-w64-mingw32/11.2.1/../../../../x86_64-w64-mingw32/bin/ld: ../../common/utils/.libs/libutils.a(libutils_la-full-rw.o): in function `full_pread': +/builds/nbdkit/nbdkit/common/utils/full-rw.c:53: undefined reference to `pread' +/usr/lib/gcc/x86_64-w64-mingw32/11.2.1/../../../../x86_64-w64-mingw32/bin/ld: ../../common/utils/.libs/libutils.a(libutils_la-full-rw.o): in function `full_pwrite': +/builds/nbdkit/nbdkit/common/utils/full-rw.c:76: undefined reference to `pwrite' +/usr/lib/gcc/x86_64-w64-mingw32/11.2.1/../../../../x86_64-w64-mingw32/bin/ld: ../../common/utils/.libs/libutils.a(libutils_la-vector.o): in function `generic_vector_reserve_page_aligned': +/builds/nbdkit/nbdkit/common/utils/vector.c:112: undefined reference to `sysconf' +/usr/lib/gcc/x86_64-w64-mingw32/11.2.1/../../../../x86_64-w64-mingw32/bin/ld: /builds/nbdkit/nbdkit/common/utils/vector.c:134: undefined reference to `posix_memalign' +collect2: error: ld returned 1 exit status + +Fixes: commit 468919dce6c5eb57503eacac0f67e5dd87c58e6c +(cherry picked from commit 4a28c4c46aedf270929a62a1c5ecf2c1129cd456) +--- + filters/luks/Makefile.am | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/filters/luks/Makefile.am b/filters/luks/Makefile.am +index 622e5c3d..2688f696 100644 +--- a/filters/luks/Makefile.am ++++ b/filters/luks/Makefile.am +@@ -45,6 +45,7 @@ nbdkit_luks_filter_la_SOURCES = \ + nbdkit_luks_filter_la_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/common/include \ ++ -I$(top_srcdir)/common/replacements \ + -I$(top_srcdir)/common/utils \ + $(NULL) + nbdkit_luks_filter_la_CFLAGS = \ +@@ -53,6 +54,7 @@ nbdkit_luks_filter_la_CFLAGS = \ + $(NULL) + nbdkit_luks_filter_la_LIBADD = \ + $(top_builddir)/common/utils/libutils.la \ ++ $(top_builddir)/common/replacements/libcompat.la \ + $(IMPORT_LIBRARY_ON_WINDOWS) \ + $(GNUTLS_LIBS) \ + $(NULL) +-- +2.31.1 + diff --git a/0008-luks-Refactor-the-filter.patch b/0008-luks-Refactor-the-filter.patch new file mode 100644 index 0000000..40e3c78 --- /dev/null +++ b/0008-luks-Refactor-the-filter.patch @@ -0,0 +1,2096 @@ +From c34ec6b17c25f94020c83a18482e8eac8e5fa8c8 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sun, 8 May 2022 16:13:13 +0100 +Subject: [PATCH] luks: Refactor the filter + +Move the LUKS-specific code out into a separate file. This is mostly +pure refactoring to tidy things up. + +(cherry picked from commit 2159d85e0bed1943542da58b43c91f2caa096d6c) +--- + filters/luks/Makefile.am | 2 + + filters/luks/luks-encryption.c | 926 +++++++++++++++++++++++++++++++++ + filters/luks/luks-encryption.h | 78 +++ + filters/luks/luks.c | 874 ++----------------------------- + 4 files changed, 1037 insertions(+), 843 deletions(-) + create mode 100644 filters/luks/luks-encryption.c + create mode 100644 filters/luks/luks-encryption.h + +diff --git a/filters/luks/Makefile.am b/filters/luks/Makefile.am +index 2688f696..0894ac8b 100644 +--- a/filters/luks/Makefile.am ++++ b/filters/luks/Makefile.am +@@ -38,6 +38,8 @@ if HAVE_GNUTLS_PBKDF2 + filter_LTLIBRARIES = nbdkit-luks-filter.la + + nbdkit_luks_filter_la_SOURCES = \ ++ luks-encryption.c \ ++ luks-encryption.h \ + luks.c \ + $(top_srcdir)/include/nbdkit-filter.h \ + $(NULL) +diff --git a/filters/luks/luks-encryption.c b/filters/luks/luks-encryption.c +new file mode 100644 +index 00000000..8ee0eb35 +--- /dev/null ++++ b/filters/luks/luks-encryption.c +@@ -0,0 +1,926 @@ ++/* 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 ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++#include "luks-encryption.h" ++ ++#include "byte-swapping.h" ++#include "cleanup.h" ++#include "isaligned.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 ++ ++/* 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__)); ++ ++/* Block cipher mode of operation. ++ * https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation ++ */ ++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 (); ++ } ++} ++ ++/* Methods used by LUKS to generate initial vectors. ++ * ++ * ESSIV is a bit more complicated to implement. It is supported by ++ * qemu but not by us. ++ */ ++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 (); ++ } ++} ++ ++/* Cipher algorithm. ++ * ++ * qemu in theory supports many more, but with the GnuTLS backend only ++ * AES is supported. The kernel seems to only support AES for LUKSv1. ++ */ ++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 (); ++ } ++} ++ ++/* Hash, eg.MD5, SHA1 etc. ++ * ++ * We reuse the GnuTLS digest algorithm enum here since it supports at ++ * least all the ones that LUKSv1 does. ++ */ ++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. ++ */ ++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 data. */ ++struct luks_data { ++ /* 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; ++}; ++ ++/* Parse the header fields containing cipher algorithm, mode, etc. */ ++static int ++parse_cipher_strings (struct luks_data *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 luks_data *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 luks_data *h, ++ size_t i, const char *passphrase) ++{ ++ /* I believe this is supposed to be safe, looking at the GnuTLS ++ * header file. ++ */ ++ const gnutls_mac_algorithm_t mac = (gnutls_mac_algorithm_t) h->hash_alg; ++ 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 (mac, &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 / LUKS_SECTOR_SIZE); ++ 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 (mac, &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; ++} ++ ++struct luks_data * ++load_header (nbdkit_next *next, const char *passphrase) ++{ ++ static const char expected_magic[] = LUKS_MAGIC; ++ struct luks_data *h; ++ int64_t size; ++ int err = 0, r; ++ size_t i; ++ struct luks_keyslot *ks; ++ char uuid[41]; ++ ++ h = calloc (1, sizeof *h); ++ if (h == NULL) { ++ nbdkit_error ("calloc: %m"); ++ return NULL; ++ } ++ ++ /* 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) { ++ free (h); ++ return NULL; ++ } ++ if (size < 16384) { ++ nbdkit_error ("disk is too small to be LUKS-encrypted"); ++ free (h); ++ return NULL; ++ } ++ ++ /* Read the phdr. */ ++ if (next->pread (next, &h->phdr, sizeof h->phdr, 0, 0, &err) == -1) { ++ free (h); ++ errno = err; ++ return NULL; ++ } ++ ++ if (memcmp (h->phdr.magic, expected_magic, LUKS_MAGIC_LEN) != 0) { ++ nbdkit_error ("this disk does not contain a LUKS header"); ++ return NULL; ++ } ++ 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); ++ free (h); ++ return NULL; ++ } ++ ++ /* 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"); ++ free (h); ++ return NULL; ++ } ++ ++ /* 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"); ++ free (h); ++ return NULL; ++ } ++ ++ 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); ++ free (h); ++ return NULL; ++ } ++ if (ks->stripes >= 10000) { ++ nbdkit_error ("bad LUKSv1 header: key slot %zu stripes too large", i); ++ return NULL; ++ } ++ 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); ++ free (h); ++ return NULL; ++ } ++ 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); ++ free (h); ++ return NULL; ++ } ++ /*FALLTHROUGH*/ ++ case LUKS_KEY_DISABLED: ++ break; ++ ++ default: ++ nbdkit_error ("bad LUKSv1 header: key slot %zu has " ++ "an invalid active flag", i); ++ return NULL; ++ } ++ } ++ ++ /* Decode the ciphers. */ ++ if (parse_cipher_strings (h) == -1) { ++ free (h); ++ return NULL; ++ } ++ ++ /* 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, passphrase); ++ if (r == -1) { ++ free (h); ++ return NULL; ++ } ++ if (r > 0) ++ goto unlocked; ++ } ++ nbdkit_error ("LUKS passphrase is not correct, " ++ "no key slot could be unlocked"); ++ free (h); ++ return NULL; ++ ++ unlocked: ++ assert (h->masterkey != NULL); ++ nbdkit_debug ("LUKS unlocked block device with passphrase"); ++ ++ return h; ++} ++ ++/* Free fields in the handle that might have been set by load_header. */ ++void ++free_luks_data (struct luks_data *h) ++{ ++ if (h->masterkey) { ++ memset (h->masterkey, 0, h->phdr.master_key_len); ++ free (h->masterkey); ++ } ++ free (h); ++} ++ ++uint64_t ++get_payload_offset (struct luks_data *h) ++{ ++ return h->phdr.payload_offset; ++} ++ ++gnutls_cipher_hd_t ++create_cipher (struct luks_data *h) ++{ ++ gnutls_datum_t mkey; ++ gnutls_cipher_hd_t cipher; ++ int r; ++ ++ assert (h->masterkey != NULL); ++ ++ mkey.data = (unsigned char *) h->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 NULL; ++ } ++ return cipher; ++} ++ ++/* Perform decryption of a block of data in memory. */ ++int ++do_decrypt (struct luks_data *h, gnutls_cipher_hd_t cipher, ++ uint64_t sector, uint8_t *buf, size_t nr_sectors) ++{ ++ const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode); ++ CLEANUP_FREE uint8_t *iv = malloc (ivlen); ++ int r; ++ ++ while (nr_sectors) { ++ 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; ++ nr_sectors--; ++ sector++; ++ } ++ ++ return 0; ++} ++ ++/* Perform encryption of a block of data in memory. */ ++int ++do_encrypt (struct luks_data *h, gnutls_cipher_hd_t cipher, ++ uint64_t sector, uint8_t *buf, size_t nr_sectors) ++{ ++ const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode); ++ CLEANUP_FREE uint8_t *iv = malloc (ivlen); ++ int r; ++ ++ while (nr_sectors) { ++ 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; ++ nr_sectors--; ++ sector++; ++ } ++ ++ return 0; ++} +diff --git a/filters/luks/luks-encryption.h b/filters/luks/luks-encryption.h +new file mode 100644 +index 00000000..3f8b9c9e +--- /dev/null ++++ b/filters/luks/luks-encryption.h +@@ -0,0 +1,78 @@ ++/* nbdkit ++ * Copyright (C) 2013-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. ++ */ ++ ++/* This header file defines the file format used by LUKSv1. See also: ++ * https://gitlab.com/cryptsetup/cryptsetup/-/wikis/LUKS-standard/on-disk-format.pdf ++ * Note we do not yet support LUKSv2. ++ */ ++ ++#ifndef NBDKIT_LUKS_ENCRYPTION_H ++#define NBDKIT_LUKS_ENCRYPTION_H ++ ++#include ++#include ++ ++#define LUKS_SECTOR_SIZE 512 ++ ++/* Per-connection data. */ ++struct luks_data; ++ ++/* Load the LUKS header, parse the algorithms, unlock the masterkey ++ * using the passphrase, initialize all the fields in the handle. ++ * ++ * This function may call next->pread (many times). ++ */ ++extern struct luks_data *load_header (nbdkit_next *next, ++ const char *passphrase); ++ ++/* Free the handle and all fields inside it. */ ++extern void free_luks_data (struct luks_data *h); ++ ++/* Get the offset where the encrypted data starts (in sectors). */ ++extern uint64_t get_payload_offset (struct luks_data *h); ++ ++/* Create an GnuTLS cipher, initialized with the master key. Must be ++ * freed by the caller using gnutls_cipher_deinit. ++ */ ++extern gnutls_cipher_hd_t create_cipher (struct luks_data *h); ++ ++/* Perform decryption/encryption of a block of memory in-place. ++ * ++ * 'sector' is the sector number on disk, used to calculate IVs. (The ++ * keyslots also use these functions, but sector must be 0). ++ */ ++extern int do_decrypt (struct luks_data *h, gnutls_cipher_hd_t cipher, ++ uint64_t sector, uint8_t *buf, size_t nr_sectors); ++extern int do_encrypt (struct luks_data *h, gnutls_cipher_hd_t cipher, ++ uint64_t sector, uint8_t *buf, size_t nr_sectors); ++ ++#endif /* NBDKIT_LUKS_ENCRYPTION_H */ +diff --git a/filters/luks/luks.c b/filters/luks/luks.c +index cc619698..8ad3f4ec 100644 +--- a/filters/luks/luks.c ++++ b/filters/luks/luks.c +@@ -45,49 +45,11 @@ + + #include + +-#include "byte-swapping.h" ++#include "luks-encryption.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; + +@@ -135,251 +97,9 @@ luks_config_complete (nbdkit_next_config_complete *next, nbdkit_backend *nxdata) + #define luks_config_help \ + "passphrase= 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; ++ struct luks_data *h; + }; + + static void * +@@ -405,536 +125,21 @@ luks_close (void *handle) + { + struct handle *h = handle; + +- if (h->masterkey) { +- memset (h->masterkey, 0, h->phdr.master_key_len); +- free (h->masterkey); +- } ++ free_luks_data (h->h); + 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) +-{ +- /* I believe this is supposed to be safe, looking at the GnuTLS +- * header file. +- */ +- const gnutls_mac_algorithm_t mac = (gnutls_mac_algorithm_t) h->hash_alg; +- 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 (mac, &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 (mac, &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); ++ assert (h->h == NULL); + +- /* 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"); ++ h->h = load_header (next, passphrase); ++ if (h->h == NULL) + 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; +- } +- /*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; + } +@@ -946,19 +151,20 @@ luks_get_size (nbdkit_next *next, void *handle) + int64_t size; + + /* Check that prepare has been called already. */ +- assert (h->phdr.version > 0); ++ assert (h->h != NULL); ++ ++ const uint64_t payload_offset = get_payload_offset (h->h) * LUKS_SECTOR_SIZE; + + size = next->get_size (next); + if (size == -1) + return -1; + +- if (size < h->phdr.payload_offset * LUKS_SECTOR_SIZE) { ++ if (size < payload_offset) { + nbdkit_error ("disk too small, or contains an incomplete LUKS partition"); + return -1; + } + +- size -= h->phdr.payload_offset * LUKS_SECTOR_SIZE; +- return size; ++ return size - payload_offset; + } + + /* Whatever the plugin says, several operations are not supported by +@@ -1031,15 +237,12 @@ luks_pread (nbdkit_next *next, void *handle, + uint32_t flags, int *err) + { + struct handle *h = handle; +- const uint64_t payload_offset = h->phdr.payload_offset * LUKS_SECTOR_SIZE; ++ const uint64_t payload_offset = get_payload_offset (h->h) * 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) { ++ if (!h->h) { + *err = EIO; + return -1; + } +@@ -1053,16 +256,13 @@ luks_pread (nbdkit_next *next, void *handle, + } + } + +- 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 */ + ++ cipher = create_cipher (h->h); ++ if (!cipher) ++ return -1; ++ + /* Unaligned head */ + if (sectoffs) { + uint64_t n = MIN (LUKS_SECTOR_SIZE - sectoffs, count); +@@ -1073,15 +273,13 @@ luks_pread (nbdkit_next *next, void *handle, + flags, err) == -1) + goto err; + +- if (do_decrypt (h, cipher, offset & ~LUKS_SECTOR_SIZE, +- sector, LUKS_SECTOR_SIZE) == -1) ++ if (do_decrypt (h->h, cipher, sectnum, sector, 1) == -1) + goto err; + + memcpy (buf, §or[sectoffs], n); + + buf += n; + count -= n; +- offset += n; + sectnum++; + } + +@@ -1092,12 +290,11 @@ luks_pread (nbdkit_next *next, void *handle, + flags, err) == -1) + goto err; + +- if (do_decrypt (h, cipher, offset, buf, LUKS_SECTOR_SIZE) == -1) ++ if (do_decrypt (h->h, cipher, sectnum, buf, 1) == -1) + goto err; + + buf += LUKS_SECTOR_SIZE; + count -= LUKS_SECTOR_SIZE; +- offset += LUKS_SECTOR_SIZE; + sectnum++; + } + +@@ -1109,7 +306,7 @@ luks_pread (nbdkit_next *next, void *handle, + flags, err) == -1) + goto err; + +- if (do_decrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1) ++ if (do_decrypt (h->h, cipher, sectnum, sector, 1) == -1) + goto err; + + memcpy (buf, sector, count); +@@ -1120,7 +317,7 @@ luks_pread (nbdkit_next *next, void *handle, + + err: + gnutls_cipher_deinit (cipher); +- return -1; ++ goto err; + } + + /* Lock preventing read-modify-write cycles from overlapping. */ +@@ -1133,15 +330,12 @@ luks_pwrite (nbdkit_next *next, void *handle, + uint32_t flags, int *err) + { + struct handle *h = handle; +- const uint64_t payload_offset = h->phdr.payload_offset * LUKS_SECTOR_SIZE; ++ const uint64_t payload_offset = get_payload_offset (h->h) * 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) { ++ if (!h->h) { + *err = EIO; + return -1; + } +@@ -1153,16 +347,13 @@ luks_pwrite (nbdkit_next *next, void *handle, + 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 */ + ++ cipher = create_cipher (h->h); ++ if (!cipher) ++ return -1; ++ + /* Unaligned head */ + if (sectoffs) { + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&read_modify_write_lock); +@@ -1176,8 +367,7 @@ luks_pwrite (nbdkit_next *next, void *handle, + + memcpy (§or[sectoffs], buf, n); + +- if (do_encrypt (h, cipher, offset & ~LUKS_SECTOR_SIZE, +- sector, LUKS_SECTOR_SIZE) == -1) ++ if (do_encrypt (h->h, cipher, sectnum, sector, 1) == -1) + goto err; + + if (next->pwrite (next, sector, LUKS_SECTOR_SIZE, +@@ -1187,7 +377,6 @@ luks_pwrite (nbdkit_next *next, void *handle, + + buf += n; + count -= n; +- offset += n; + sectnum++; + } + +@@ -1195,7 +384,7 @@ luks_pwrite (nbdkit_next *next, void *handle, + while (count >= LUKS_SECTOR_SIZE) { + memcpy (sector, buf, LUKS_SECTOR_SIZE); + +- if (do_encrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1) ++ if (do_encrypt (h->h, cipher, sectnum, sector, 1) == -1) + goto err; + + if (next->pwrite (next, sector, LUKS_SECTOR_SIZE, +@@ -1205,7 +394,6 @@ luks_pwrite (nbdkit_next *next, void *handle, + + buf += LUKS_SECTOR_SIZE; + count -= LUKS_SECTOR_SIZE; +- offset += LUKS_SECTOR_SIZE; + sectnum++; + } + +@@ -1220,7 +408,7 @@ luks_pwrite (nbdkit_next *next, void *handle, + + memcpy (sector, buf, count); + +- if (do_encrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1) ++ if (do_encrypt (h->h, cipher, sectnum, sector, 1) == -1) + goto err; + + if (next->pwrite (next, sector, LUKS_SECTOR_SIZE, +-- +2.31.1 + diff --git a/0009-tests-luks-Reduce-time-taken-to-run-these-tests.patch b/0009-tests-luks-Reduce-time-taken-to-run-these-tests.patch new file mode 100644 index 0000000..f30b011 --- /dev/null +++ b/0009-tests-luks-Reduce-time-taken-to-run-these-tests.patch @@ -0,0 +1,101 @@ +From 64ce47cc59c062cf64cb7bf7a9861f4c5d767514 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sun, 8 May 2022 18:05:45 +0100 +Subject: [PATCH] tests: luks: Reduce time taken to run these tests + +Under valgrind they ran very slowly. Turns out valgrinding over +GnuTLS hashing code is not pretty. About half the time seems to be +taken opening the keyslot, and the rest copying the data. + +This change reduces the time (under valgrind) from 15 minutes 45 seconds +to about 6 mins 30 seconds. + +(cherry picked from commit 7320ae5dba476171a024ca44b889b3474302dc40) +--- + tests/test-luks-copy.sh | 18 +++++++++--------- + tests/test-luks-info.sh | 6 +++--- + 2 files changed, 12 insertions(+), 12 deletions(-) + +diff --git a/tests/test-luks-copy.sh b/tests/test-luks-copy.sh +index 99f300d0..01801811 100755 +--- a/tests/test-luks-copy.sh ++++ b/tests/test-luks-copy.sh +@@ -60,8 +60,8 @@ rm -f $encrypt_disk $plain_disk $pid $sock + qemu-img create -f luks \ + --object secret,data=123456,id=sec0 \ + -o key-secret=sec0 \ +- $encrypt_disk 10M +-truncate -s 10M $plain_disk ++ $encrypt_disk 1M ++truncate -s 1M $plain_disk + qemu-img convert --target-image-opts -n \ + --object secret,data=123456,id=sec0 \ + $plain_disk \ +@@ -74,11 +74,11 @@ start_nbdkit -P $pid -U $sock \ + uri="nbd+unix:///?socket=$sock" + + # Copy the whole disk out. It should be empty. +-nbdcopy "$uri" $plain_disk ++nbdcopy -C 1 "$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 ++00100000' ]; then + echo "$0: expected plaintext disk to be empty" + exit 1 + fi +@@ -88,14 +88,14 @@ fi + 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 'h.pwrite(b"3"*65536, 900*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 'buf = h.pread(65536, 900*1024)' \ + -c 'assert buf == b"3"*65536' \ + -c 'h.flush()' + +@@ -115,11 +115,11 @@ if [ "$(hexdump -C $plain_disk)" != '00000000 31 31 31 31 31 31 31 31 31 31 31 + * + 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| ++000e1000 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 |................| ++000f1000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| + * +-00a00000' ]; then ++00100000' ]; then + echo "$0: unexpected content" + exit 1 + fi +diff --git a/tests/test-luks-info.sh b/tests/test-luks-info.sh +index 3eff657b..ef141ecd 100755 +--- a/tests/test-luks-info.sh ++++ b/tests/test-luks-info.sh +@@ -46,11 +46,11 @@ rm -f $disk $info + qemu-img create -f luks \ + --object secret,data=123456,id=sec0 \ + -o key-secret=sec0 \ +- $disk 10M ++ $disk 1M + + 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 ++# Check the size is 1M (so it doesn't include the LUKS header). ++grep "1048576" $info +-- +2.31.1 + diff --git a/0010-Add-nbdkit.parse_size-Python-function.patch b/0010-Add-nbdkit.parse_size-Python-function.patch new file mode 100644 index 0000000..48e4561 --- /dev/null +++ b/0010-Add-nbdkit.parse_size-Python-function.patch @@ -0,0 +1,112 @@ +From e19ef7726379acf90dcff248e90813898266d2b4 Mon Sep 17 00:00:00 2001 +From: Nikolaus Rath +Date: Mon, 9 May 2022 10:04:30 +0100 +Subject: [PATCH] Add nbdkit.parse_size() Python function. + +This enables Python plugins to parse sizes the same way as C plugins. + +I'm not sure about the best way to test this - input is appreciated. + +I'm not too happy with the way this code is tested. It workes, but putting the tests into +test-python-plugin.py feels misplaced: this file is intended to support the unit tests in +test_python.py, not run its own unit tests. + +(cherry picked from commit 1b7d72542be68e254c1ef86ecb1a82b05c78ff63) +--- + plugins/python/modfunctions.c | 21 +++++++++++++++++++++ + plugins/python/nbdkit-python-plugin.pod | 5 +++++ + tests/test-python-plugin.py | 19 +++++++++++++++++++ + 3 files changed, 45 insertions(+) + +diff --git a/plugins/python/modfunctions.c b/plugins/python/modfunctions.c +index fffbaab2..46b0c904 100644 +--- a/plugins/python/modfunctions.c ++++ b/plugins/python/modfunctions.c +@@ -93,11 +93,32 @@ do_shutdown (PyObject *self, PyObject *args) + Py_RETURN_NONE; + } + ++/* nbdkit.parse_size */ ++static PyObject * ++parse_size (PyObject *self, PyObject *args) ++{ ++ const char *s; ++ if (!PyArg_ParseTuple (args, "s", &s)) { ++ PyErr_SetString (PyExc_TypeError, "Expected string, got something else"); ++ return NULL; ++ } ++ ++ int64_t size = nbdkit_parse_size(s); ++ if (size == -1) { ++ PyErr_SetString (PyExc_ValueError, "Unable to parse string as size"); ++ return NULL; ++ } ++ ++ return PyLong_FromSize_t((size_t)size); ++} ++ + static PyMethodDef NbdkitMethods[] = { + { "debug", debug, METH_VARARGS, + "Print a debug message" }, + { "export_name", export_name, METH_VARARGS, + "Return the optional export name negotiated with the client" }, ++ { "parse_size", parse_size, METH_VARARGS, ++ "Parse human-readable size strings into bytes" }, + { "set_error", set_error, METH_VARARGS, + "Store an errno value prior to throwing an exception" }, + { "shutdown", do_shutdown, METH_VARARGS, +diff --git a/plugins/python/nbdkit-python-plugin.pod b/plugins/python/nbdkit-python-plugin.pod +index 051b0237..ccc9406f 100644 +--- a/plugins/python/nbdkit-python-plugin.pod ++++ b/plugins/python/nbdkit-python-plugin.pod +@@ -131,6 +131,11 @@ Record C as the reason you are about to throw an exception. C + should correspond to usual errno values, where it may help to + C. + ++=head3 C ++ ++Parse a string (such as "100M") into a size in bytes. Wraps the ++C C function. ++ + =head3 C + + Request asynchronous server shutdown. +diff --git a/tests/test-python-plugin.py b/tests/test-python-plugin.py +index 0b34d532..d4f379fc 100644 +--- a/tests/test-python-plugin.py ++++ b/tests/test-python-plugin.py +@@ -34,12 +34,31 @@ + import nbdkit + import pickle + import base64 ++import unittest + + API_VERSION = 2 + + cfg = {} + + ++# Not nice, but there doesn't seem to be a better way of putting this ++class TestAPI(unittest.TestCase): ++ ++ def test_parse_size(self): ++ self.assertEqual(nbdkit.parse_size('511'), 511) ++ self.assertEqual(nbdkit.parse_size('7k'), 7*1024) ++ self.assertEqual(nbdkit.parse_size('17M'), 17*1024*1024) ++ ++ with self.assertRaises(TypeError): ++ nbdkit.parse_size(17) ++ ++ with self.assertRaises(ValueError): ++ nbdkit.parse_size('foo') ++ ++ ++TestAPI().test_parse_size() ++ ++ + def config(k, v): + global cfg + if k == "cfg": +-- +2.31.1 + diff --git a/nbdkit.spec b/nbdkit.spec index 3cb9740..1199e9e 100644 --- a/nbdkit.spec +++ b/nbdkit.spec @@ -52,7 +52,7 @@ ExclusiveArch: x86_64 %global source_directory 1.30-stable Name: nbdkit -Version: 1.30.4 +Version: 1.30.5 Release: 1%{?dist} Summary: NBD server @@ -81,6 +81,13 @@ Source3: copy-patches.sh Patch0001: 0001-ssh-Allow-the-remote-file-to-be-created.patch Patch0002: 0002-readahead-Rewrite-this-filter-so-it-prefetches-using.patch Patch0003: 0003-readahead-Fix-test.patch +Patch0004: 0004-New-filter-luks.patch +Patch0005: 0005-luks-Disable-filter-with-old-GnuTLS-in-Debian-10.patch +Patch0006: 0006-luks-Various-fixes-for-Clang.patch +Patch0007: 0007-luks-Link-with-libcompat-on-Windows.patch +Patch0008: 0008-luks-Refactor-the-filter.patch +Patch0009: 0009-tests-luks-Reduce-time-taken-to-run-these-tests.patch +Patch0010: 0010-Add-nbdkit.parse_size-Python-function.patch # For automatic RPM Provides generation. # See: https://rpm-software-management.github.io/rpm/manual/dependency_generators.html @@ -550,6 +557,8 @@ nbdkit-limit-filter Limit nr clients that can connect concurrently. nbdkit-log-filter Log all transactions to a file. +nbdkit-luks-filter Read and write LUKS-encrypted disks. + nbdkit-multi-conn-filter Enable, emulate or disable multi-conn. nbdkit-nocache-filter Disable cache requests in the underlying plugin. @@ -1048,6 +1057,7 @@ export LIBGUESTFS_TRACE=1 %{_libdir}/%{name}/filters/nbdkit-ip-filter.so %{_libdir}/%{name}/filters/nbdkit-limit-filter.so %{_libdir}/%{name}/filters/nbdkit-log-filter.so +%{_libdir}/%{name}/filters/nbdkit-luks-filter.so %{_libdir}/%{name}/filters/nbdkit-multi-conn-filter.so %{_libdir}/%{name}/filters/nbdkit-nocache-filter.so %{_libdir}/%{name}/filters/nbdkit-noextents-filter.so @@ -1083,6 +1093,7 @@ export LIBGUESTFS_TRACE=1 %{_mandir}/man1/nbdkit-ip-filter.1* %{_mandir}/man1/nbdkit-limit-filter.1* %{_mandir}/man1/nbdkit-log-filter.1* +%{_mandir}/man1/nbdkit-luks-filter.1* %{_mandir}/man1/nbdkit-multi-conn-filter.1* %{_mandir}/man1/nbdkit-nocache-filter.1* %{_mandir}/man1/nbdkit-noextents-filter.1* @@ -1180,8 +1191,8 @@ export LIBGUESTFS_TRACE=1 %changelog -* Tue Apr 26 2022 Richard W.M. Jones - 1.30.4-1 -- Rebase to new stable branch version 1.30.4 +* Tue Apr 26 2022 Richard W.M. Jones - 1.30.5-1 +- Rebase to new stable branch version 1.30.5 resolves: rhbz#2059289 - Add automatic provides generator and subpackage nbdkit-srpm-macros resolves: rhbz#2059291 @@ -1189,9 +1200,16 @@ export LIBGUESTFS_TRACE=1 - Fix license of bash-completion subpackage - vddk: Fix use of uninitialized memory when computing block size resolves: rhbz#2066655 -- Skip vsock tests unless the vsock_loopback module is loaded (2069558) +- Skip vsock tests unless the vsock_loopback module is loaded + resolves: rhbz#2069558 - Add support for ssh create remote file. +- Suppress excess messages from nbdkit-nbd-plugin + resolves: rhbz#2083498 +- Suppress incorrect VDDK error when converting guests from vCenter + resolves: rhbz#2083617 - Backport new readahead filter from 1.32. +- Backport new LUKS filter from 1.32. +- Add new Python binding for nbdkit_parse_size from 1.32 * Mon Jan 24 2022 Richard W.M. Jones - 1.28.5-1 - Rebase to new stable branch version 1.28.5 diff --git a/sources b/sources index b012ddd..ceac80d 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ -SHA512 (nbdkit-1.30.4.tar.gz) = b2db6cde29b04fc73831a5f062b7b5d0a4ed2c3785ccefe81add4bc89cd62d5705b904922f53ea1fd4b0987c9aa2a0ef34fc58eefa804b37d424a44112fbf1b1 -SHA512 (nbdkit-1.30.4.tar.gz.sig) = ed8a60214274d88f418656b1b50d9eb5b73aa0ff18e36446cf21a6fe2c07ca72311fd745c5d99f94cb3676dba2ee3a768ab08a05cf9d0d24d8156c43118d57d6 +SHA512 (nbdkit-1.30.5.tar.gz) = 033f06a23f7f227acb8516994582c94033a35d11ff8d7db8ca310b73cf591ea1a4833dbfbaf58e653556a1bcf30cbca21494b7f9e61a1a8cafd566993b24a799 +SHA512 (nbdkit-1.30.5.tar.gz.sig) = c8e342ff168b3308b888658f2958a2ccfd2e2b37664f65d3a71feb495085bd60834b4142103c6cf51e83b5d08f2815ab392b5afe5077b9a8fbfee9218db9bdf1