2097 lines
60 KiB
Diff
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, §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 <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, §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
|
|
|