nbdkit/0008-luks-Refactor-the-filter.patch

2097 lines
60 KiB
Diff

From e8279107801bb93303b22e1b927929ce18279dc5 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
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 <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <limits.h>
+#include <assert.h>
+
+#include <gnutls/crypto.h>
+
+#include <nbdkit-filter.h>
+
+#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, &sector32, 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, &sector, 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 <stdint.h>
+#include <gnutls/crypto.h>
+
+#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 <nbdkit-filter.h>
-#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> 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, &sector32, 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, &sector, 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, &sector[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 (&sector[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