[dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup

Carl-Daniel Hailfinger c-d.hailfinger.devel.2006 at gmx.net
Thu Jan 5 09:53:46 CET 2017


On 01.01.2017 23:23, Milan Broz wrote:
> On 12/30/2016 07:31 AM, Carl-Daniel Hailfinger wrote:
>> Followup to myself:
>>
>> A patch for cryptsetup follows. It does not talk to the kernel interface
>> yet, but it can decode v7 of the GELI header.
>> Keyfile support is next to decrypt the encrypted metadata in the header.
> I think we can implement it as just another supported on-disk format but
> it will need some testing images and full integration in libcryptsetup
> (similar to TrueCrypt/VeraCrypt formats).

I'm using the TrueCrypt/VeraCrypt code in libcryptsetup as template for
the GELI code.


> I can do that myself but I would like to have full documentation and/or
> implementation of parsing of GELI on-disk format.

That's actually a problem. The available documentation about the on-disk
format is essentially just the FreeBSD code, and various bits and pieces
scattered all over the net.

GELI code in FreeBSD:
https://svnweb.freebsd.org/base/head/sys/geom/eli/

Example GPLv3 code in GRUB (for password-based containers only):
http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/disk/geli.c
http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/disk/cryptodisk.c

Example GPLv3 code as a NBD implementation:
https://github.com/jepler/ungeli

Bits and pieces of documentation (not all of them refer to the current
version of GELI):
https://www.freebsd.org/cgi/man.cgi?geli(8)
https://www.freebsd.org/doc/handbook/disks-encrypting.html
https://lists.freebsd.org/pipermail/freebsd-geom/2012-June/005284.html
http://www.derkeiler.com/Newsgroups/sci.crypt/2005-07/0959.html

The way keys are generated, stored and used is not entirely
straightforward. The various pieces of documentation and analysis
contradict each other and I have not verified which one is correct, so
please take this with a large heap of salt, it might be totally bogus:
For some cases, a 64-byte key file (generated from random data) is
hashed with SHA512, the last 256 bits of the hash are thrown away, and
the remaining 256 bits are divided into an IV key and an AES-128 key.
For older versions of GELI, the IV key is used for AES encryption as
well (a bug which was corrected in newer versions of GELI). The metadata
has two key store slots. Each slot hosts an encrypted version of the
master key (which itself is divided into IV key and encryption "data"
key) and the SHA512 hash of the master key. The "data" key part of the
master key is not directly used for AES-XTS encryption, but there are
more derivation mechanisms in there which are relevant for storage media
larger than 2^20 blocks.

A quick note about the on-disk format: Ciphertext starts directly at the
beginning of the container. The metadata is stored in the last 512-byte
sector of the container. Soft sectors are usually 4096 bytes, optionally
amended with a 512-byte authentication data sector per 4096-byte soft
sector. AFAIK no third-party code supports authentication data.


> And it will take some time (maybe create issue for it in project tracker).

I noticed that I had forgotten to attach the current state of the
implementation. I think patch 3 compiles and does something useful,
patch 4 is the current state (and probably doesn't compile). The code is
full of #if 0 and similar crud, and you can see it's just messing with
the TCRYPT code to replace some of the code with GELI code. The code
does NOT reflect my coding standards, it's just a crufty hack while I
was figuring out how to add code to cryptsetup.

What the code does right now is parse a container and output the
encrypted keyslots as well as check the MD5 checksum of the metadata.


> Anyway, thanks for working on this!

You're welcome. I hope to revisit this in a few weeks when I have more time.

Regards,
Carl-Daniel
-------------- next part --------------
From 8c0057315fe8ac8022120888c401ae843538289b Mon Sep 17 00:00:00 2001
From: Carl-Daniel Hailfinger <c-d.hailfinger.devel.2006 at gmx.net>
Date: Fri, 30 Dec 2016 05:40:44 +0100
Subject: [PATCH] Initial version of cryptsetup with GELI support. Only dumping
 is supported right now.

---
 configure.ac         |    1 +
 lib/Makefile.am      |    6 +-
 lib/geli/Makefile.am |   14 +
 lib/geli/geli.c      | 1073 ++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/geli/geli.h      |   96 +++++
 lib/libcryptsetup.h  |   13 +
 lib/setup.c          |   67 ++++
 src/cryptsetup.c     |  106 ++++-
 8 files changed, 1373 insertions(+), 3 deletions(-)
 create mode 100644 lib/geli/Makefile.am
 create mode 100644 lib/geli/geli.c
 create mode 100644 lib/geli/geli.h

diff --git a/configure.ac b/configure.ac
index d1f029a..1c46a7d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -467,6 +467,7 @@ lib/luks1/Makefile
 lib/loopaes/Makefile
 lib/verity/Makefile
 lib/tcrypt/Makefile
+lib/geli/Makefile
 src/Makefile
 po/Makefile.in
 man/Makefile
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 6662568..5b99a7c 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = crypto_backend luks1 loopaes verity tcrypt
+SUBDIRS = crypto_backend luks1 loopaes verity tcrypt geli
 
 moduledir = $(libdir)/cryptsetup
 
@@ -12,6 +12,7 @@ AM_CPPFLAGS = -include config.h \
 	-I$(top_srcdir)/lib/loopaes		\
 	-I$(top_srcdir)/lib/verity		\
 	-I$(top_srcdir)/lib/tcrypt		\
+	-I$(top_srcdir)/lib/geli		\
 	-DDATADIR=\""$(datadir)"\"		\
 	-DLIBDIR=\""$(libdir)"\"		\
 	-DPREFIX=\""$(prefix)"\"		\
@@ -25,7 +26,8 @@ common_ldadd = \
 	luks1/libluks1.la			\
 	loopaes/libloopaes.la			\
 	verity/libverity.la			\
-	tcrypt/libtcrypt.la
+	tcrypt/libtcrypt.la			\
+	geli/libgeli.la
 
 libcryptsetup_la_DEPENDENCIES = $(common_ldadd) libcryptsetup.sym
 
diff --git a/lib/geli/Makefile.am b/lib/geli/Makefile.am
new file mode 100644
index 0000000..96aef4e
--- /dev/null
+++ b/lib/geli/Makefile.am
@@ -0,0 +1,14 @@
+moduledir = $(libdir)/cryptsetup
+
+noinst_LTLIBRARIES = libgeli.la
+
+libgeli_la_CFLAGS = -Wall $(AM_CFLAGS) @CRYPTO_CFLAGS@
+
+libgeli_la_SOURCES = \
+	geli.c \
+	geli.h
+
+AM_CPPFLAGS = -include config.h \
+        -I$(top_srcdir)/lib			\
+        -I$(top_srcdir)/lib/crypto_backend
+
diff --git a/lib/geli/geli.c b/lib/geli/geli.c
new file mode 100644
index 0000000..43790ef
--- /dev/null
+++ b/lib/geli/geli.c
@@ -0,0 +1,1073 @@
+/*
+ * TCRYPT (TrueCrypt-compatible) and VeraCrypt volume handling
+ *
+ * Copyright (C) 2012, Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2012-2015, Milan Broz
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "libcryptsetup.h"
+#include "geli.h"
+#include "internal.h"
+
+#if 0
+/* TCRYPT PBKDF variants */
+static struct {
+	unsigned int legacy:1;
+	unsigned int veracrypt:1;
+	const char *name;
+	const char *hash;
+	unsigned int iterations;
+} tcrypt_kdf[] = {
+	{ 0, 0, "pbkdf2", "ripemd160", 2000 },
+	{ 0, 0, "pbkdf2", "ripemd160", 1000 },
+	{ 0, 0, "pbkdf2", "sha512",    1000 },
+	{ 0, 0, "pbkdf2", "whirlpool", 1000 },
+	{ 1, 0, "pbkdf2", "sha1",      2000 },
+	{ 0, 1, "pbkdf2", "sha512",    500000 },
+	{ 0, 1, "pbkdf2", "ripemd160", 655331 },
+	{ 0, 1, "pbkdf2", "ripemd160", 327661 }, // boot only
+	{ 0, 1, "pbkdf2", "whirlpool", 500000 },
+	{ 0, 1, "pbkdf2", "sha256",    500000 }, // VeraCrypt 1.0f
+	{ 0, 1, "pbkdf2", "sha256",    200000 }, // boot only
+	{ 0, 0, NULL,     NULL,        0 }
+};
+
+struct tcrypt_alg {
+		const char *name;
+		unsigned int key_size;
+		unsigned int iv_size;
+		unsigned int key_offset;
+		unsigned int iv_offset; /* or tweak key offset */
+		unsigned int key_extra_size;
+};
+
+struct tcrypt_algs {
+	unsigned int legacy:1;
+	unsigned int chain_count;
+	unsigned int chain_key_size;
+	const char *long_name;
+	const char *mode;
+	struct tcrypt_alg cipher[3];
+};
+
+/* TCRYPT cipher variants */
+static struct tcrypt_algs tcrypt_cipher[] = {
+/* XTS mode */
+{0,1,64,"aes","xts-plain64",
+	{{"aes",    64,16,0,32,0}}},
+{0,1,64,"serpent","xts-plain64",
+	{{"serpent",64,16,0,32,0}}},
+{0,1,64,"twofish","xts-plain64",
+	{{"twofish",64,16,0,32,0}}},
+{0,2,128,"twofish-aes","xts-plain64",
+	{{"twofish",64,16, 0,64,0},
+	 {"aes",    64,16,32,96,0}}},
+{0,3,192,"serpent-twofish-aes","xts-plain64",
+	{{"serpent",64,16, 0, 96,0},
+	 {"twofish",64,16,32,128,0},
+	 {"aes",    64,16,64,160,0}}},
+{0,2,128,"aes-serpent","xts-plain64",
+	{{"aes",    64,16, 0,64,0},
+	 {"serpent",64,16,32,96,0}}},
+{0,3,192,"aes-twofish-serpent","xts-plain64",
+	{{"aes",    64,16, 0, 96,0},
+	 {"twofish",64,16,32,128,0},
+	 {"serpent",64,16,64,160,0}}},
+{0,2,128,"serpent-twofish","xts-plain64",
+	{{"serpent",64,16, 0,64,0},
+	 {"twofish",64,16,32,96,0}}},
+
+/* LRW mode */
+{0,1,48,"aes","lrw-benbi",
+	{{"aes",    48,16,32,0,0}}},
+{0,1,48,"serpent","lrw-benbi",
+	{{"serpent",48,16,32,0,0}}},
+{0,1,48,"twofish","lrw-benbi",
+	{{"twofish",48,16,32,0,0}}},
+{0,2,96,"twofish-aes","lrw-benbi",
+	{{"twofish",48,16,32,0,0},
+	 {"aes",    48,16,64,0,0}}},
+{0,3,144,"serpent-twofish-aes","lrw-benbi",
+	{{"serpent",48,16,32,0,0},
+	 {"twofish",48,16,64,0,0},
+	 {"aes",    48,16,96,0,0}}},
+{0,2,96,"aes-serpent","lrw-benbi",
+	{{"aes",    48,16,32,0,0},
+	 {"serpent",48,16,64,0,0}}},
+{0,3,144,"aes-twofish-serpent","lrw-benbi",
+	{{"aes",    48,16,32,0,0},
+	 {"twofish",48,16,64,0,0},
+	 {"serpent",48,16,96,0,0}}},
+{0,2,96,"serpent-twofish", "lrw-benbi",
+	{{"serpent",48,16,32,0,0},
+	 {"twofish",48,16,64,0,0}}},
+
+/* Kernel LRW block size is fixed to 16 bytes for GF(2^128)
+ * thus cannot be used with blowfish where block is 8 bytes.
+ * There also no GF(2^64) support.
+{1,1,64,"blowfish_le","lrw-benbi",
+	 {{"blowfish_le",64,8,32,0,0}}},
+{1,2,112,"blowfish_le-aes","lrw-benbi",
+	 {{"blowfish_le",64, 8,32,0,0},
+	  {"aes",        48,16,88,0,0}}},
+{1,3,160,"serpent-blowfish_le-aes","lrw-benbi",
+	  {{"serpent",    48,16, 32,0,0},
+	   {"blowfish_le",64, 8, 64,0,0},
+	   {"aes",        48,16,120,0,0}}},*/
+
+/*
+ * CBC + "outer" CBC (both with whitening)
+ * chain_key_size: alg_keys_bytes + IV_seed_bytes + whitening_bytes
+ */
+{1,1,32+16+16,"aes","cbc-tcw",
+	{{"aes",    32,16,32,0,32}}},
+{1,1,32+16+16,"serpent","cbc-tcw",
+	{{"serpent",32,16,32,0,32}}},
+{1,1,32+16+16,"twofish","cbc-tcw",
+	{{"twofish",32,16,32,0,32}}},
+{1,2,64+16+16,"twofish-aes","cbci-tcrypt",
+	{{"twofish",32,16,32,0,0},
+	 {"aes",    32,16,64,0,32}}},
+{1,3,96+16+16,"serpent-twofish-aes","cbci-tcrypt",
+	{{"serpent",32,16,32,0,0},
+	 {"twofish",32,16,64,0,0},
+	 {"aes",    32,16,96,0,32}}},
+{1,2,64+16+16,"aes-serpent","cbci-tcrypt",
+	{{"aes",    32,16,32,0,0},
+	 {"serpent",32,16,64,0,32}}},
+{1,3,96+16+16,"aes-twofish-serpent", "cbci-tcrypt",
+	{{"aes",    32,16,32,0,0},
+	 {"twofish",32,16,64,0,0},
+	 {"serpent",32,16,96,0,32}}},
+{1,2,64+16+16,"serpent-twofish", "cbci-tcrypt",
+	{{"serpent",32,16,32,0,0},
+	 {"twofish",32,16,64,0,32}}},
+{1,1,16+8+16,"cast5","cbc-tcw",
+	{{"cast5",   16,8,32,0,24}}},
+{1,1,24+8+16,"des3_ede","cbc-tcw",
+	{{"des3_ede",24,8,32,0,24}}},
+{1,1,56+8+16,"blowfish_le","cbc-tcrypt",
+	{{"blowfish_le",56,8,32,0,24}}},
+{1,2,88+16+16,"blowfish_le-aes","cbc-tcrypt",
+	{{"blowfish_le",56, 8,32,0,0},
+	 {"aes",        32,16,88,0,32}}},
+{1,3,120+16+16,"serpent-blowfish_le-aes","cbc-tcrypt",
+	{{"serpent",    32,16, 32,0,0},
+	 {"blowfish_le",56, 8, 64,0,0},
+	 {"aes",        32,16,120,0,32}}},
+{}
+};
+#endif
+
+static int GELI_hdr_from_disk(struct geli_phdr *hdr,
+				struct crypt_params_geli *params,
+				int cipher_index)
+{
+	log_dbg("GELI tracking: 100\n");
+	/* Check sanity of header */
+	if (strcmp(G_ELI_MAGIC, hdr->md_magic)) {
+		log_dbg("Missing GELI magic.\n");
+		return -EINVAL;
+	}
+
+	/* FIXME: Check MD5 */
+
+	/* Convert header to cpu format */
+	hdr->md_version    = le32_to_cpu(hdr->md_version);
+	hdr->md_flags      = le32_to_cpu(hdr->md_flags);
+	hdr->md_ealgo      = le16_to_cpu(hdr->md_ealgo);
+	hdr->md_keylen     = le16_to_cpu(hdr->md_keylen);
+	hdr->md_aalgo      = le16_to_cpu(hdr->md_aalgo);
+	hdr->md_provsize   = le64_to_cpu(hdr->md_provsize);
+	hdr->md_sectorsize = le32_to_cpu(hdr->md_sectorsize);
+	// md_keys is 8 bit, no conversion needed
+	hdr->md_iterations = le32_to_cpu(hdr->md_iterations);
+
+	/* Set params */
+
+	return 0;
+}
+
+#if 0
+/*
+ * Kernel implements just big-endian version of blowfish, hack it here
+ */
+static void TCRYPT_swab_le(char *buf)
+{
+	uint32_t *l = (uint32_t*)&buf[0];
+	uint32_t *r = (uint32_t*)&buf[4];
+	*l = swab32(*l);
+	*r = swab32(*r);
+}
+
+static int decrypt_blowfish_le_cbc(struct tcrypt_alg *alg,
+				   const char *key, char *buf)
+{
+	int bs = alg->iv_size;
+	char iv[bs], iv_old[bs];
+	struct crypt_cipher *cipher = NULL;
+	int i, j, r;
+
+	assert(bs == 2*sizeof(uint32_t));
+
+	r = crypt_cipher_init(&cipher, "blowfish", "ecb",
+			      &key[alg->key_offset], alg->key_size);
+	if (r < 0)
+		return r;
+
+	memcpy(iv, &key[alg->iv_offset], alg->iv_size);
+	for (i = 0; i < TCRYPT_HDR_LEN; i += bs) {
+		memcpy(iv_old, &buf[i], bs);
+		TCRYPT_swab_le(&buf[i]);
+		r = crypt_cipher_decrypt(cipher, &buf[i], &buf[i],
+					  bs, NULL, 0);
+		TCRYPT_swab_le(&buf[i]);
+		if (r < 0)
+			break;
+		for (j = 0; j < bs; j++)
+			buf[i + j] ^= iv[j];
+		memcpy(iv, iv_old, bs);
+	}
+
+	crypt_cipher_destroy(cipher);
+	crypt_memzero(iv, bs);
+	crypt_memzero(iv_old, bs);
+	return r;
+}
+
+static void TCRYPT_remove_whitening(char *buf, const char *key)
+{
+	int j;
+
+	for (j = 0; j < TCRYPT_HDR_LEN; j++)
+		buf[j] ^= key[j % 8];
+}
+
+static void TCRYPT_copy_key(struct tcrypt_alg *alg, const char *mode,
+			     char *out_key, const char *key)
+{
+	int ks2;
+	if (!strncmp(mode, "xts", 3)) {
+		ks2 = alg->key_size / 2;
+		memcpy(out_key, &key[alg->key_offset], ks2);
+		memcpy(&out_key[ks2], &key[alg->iv_offset], ks2);
+	} else if (!strncmp(mode, "lrw", 3)) {
+		ks2 = alg->key_size - TCRYPT_LRW_IKEY_LEN;
+		memcpy(out_key, &key[alg->key_offset], ks2);
+		memcpy(&out_key[ks2], key, TCRYPT_LRW_IKEY_LEN);
+	} else if (!strncmp(mode, "cbc", 3)) {
+		memcpy(out_key, &key[alg->key_offset], alg->key_size);
+		/* IV + whitening */
+		memcpy(&out_key[alg->key_size], &key[alg->iv_offset],
+		       alg->key_extra_size);
+	}
+}
+
+static int TCRYPT_decrypt_hdr_one(struct tcrypt_alg *alg, const char *mode,
+				   const char *key,struct tcrypt_phdr *hdr)
+{
+	char backend_key[TCRYPT_HDR_KEY_LEN];
+	char iv[TCRYPT_HDR_IV_LEN] = {};
+	char mode_name[MAX_CIPHER_LEN + 1];
+	struct crypt_cipher *cipher;
+	char *c, *buf = (char*)&hdr->e;
+	int r;
+
+	/* Remove IV if present */
+	mode_name[MAX_CIPHER_LEN] = '\0';
+	strncpy(mode_name, mode, MAX_CIPHER_LEN);
+	c = strchr(mode_name, '-');
+	if (c)
+		*c = '\0';
+
+	if (!strncmp(mode, "lrw", 3))
+		iv[alg->iv_size - 1] = 1;
+	else if (!strncmp(mode, "cbc", 3)) {
+		TCRYPT_remove_whitening(buf, &key[8]);
+		if (!strcmp(alg->name, "blowfish_le"))
+			return decrypt_blowfish_le_cbc(alg, key, buf);
+		memcpy(iv, &key[alg->iv_offset], alg->iv_size);
+	}
+
+	TCRYPT_copy_key(alg, mode, backend_key, key);
+	r = crypt_cipher_init(&cipher, alg->name, mode_name,
+			      backend_key, alg->key_size);
+	if (!r) {
+		r = crypt_cipher_decrypt(cipher, buf, buf, TCRYPT_HDR_LEN,
+					 iv, alg->iv_size);
+		crypt_cipher_destroy(cipher);
+	}
+
+	crypt_memzero(backend_key, sizeof(backend_key));
+	crypt_memzero(iv, TCRYPT_HDR_IV_LEN);
+	return r;
+}
+
+/*
+ * For chanined ciphers and CBC mode we need "outer" decryption.
+ * Backend doesn't provide this, so implement it here directly using ECB.
+ */
+static int TCRYPT_decrypt_cbci(struct tcrypt_algs *ciphers,
+				const char *key, struct tcrypt_phdr *hdr)
+{
+	struct crypt_cipher *cipher[ciphers->chain_count];
+	unsigned int bs = ciphers->cipher[0].iv_size;
+	char *buf = (char*)&hdr->e, iv[bs], iv_old[bs];
+	unsigned int i, j;
+	int r = -EINVAL;
+
+	TCRYPT_remove_whitening(buf, &key[8]);
+
+	memcpy(iv, &key[ciphers->cipher[0].iv_offset], bs);
+
+	/* Initialize all ciphers in chain in ECB mode */
+	for (j = 0; j < ciphers->chain_count; j++)
+		cipher[j] = NULL;
+	for (j = 0; j < ciphers->chain_count; j++) {
+		r = crypt_cipher_init(&cipher[j], ciphers->cipher[j].name, "ecb",
+				      &key[ciphers->cipher[j].key_offset],
+				      ciphers->cipher[j].key_size);
+		if (r < 0)
+			goto out;
+	}
+
+	/* Implements CBC with chained ciphers in loop inside */
+	for (i = 0; i < TCRYPT_HDR_LEN; i += bs) {
+		memcpy(iv_old, &buf[i], bs);
+		for (j = ciphers->chain_count; j > 0; j--) {
+			r = crypt_cipher_decrypt(cipher[j - 1], &buf[i], &buf[i],
+						  bs, NULL, 0);
+			if (r < 0)
+				goto out;
+		}
+		for (j = 0; j < bs; j++)
+			buf[i + j] ^= iv[j];
+		memcpy(iv, iv_old, bs);
+	}
+out:
+	for (j = 0; j < ciphers->chain_count; j++)
+		if (cipher[j])
+			crypt_cipher_destroy(cipher[j]);
+
+	crypt_memzero(iv, bs);
+	crypt_memzero(iv_old, bs);
+	return r;
+}
+
+static int TCRYPT_decrypt_hdr(struct crypt_device *cd, struct tcrypt_phdr *hdr,
+			       const char *key, uint32_t flags)
+{
+	struct tcrypt_phdr hdr2;
+	int i, j, r = -EINVAL;
+
+	for (i = 0; tcrypt_cipher[i].chain_count; i++) {
+		if (!(flags & CRYPT_TCRYPT_LEGACY_MODES) && tcrypt_cipher[i].legacy)
+			continue;
+		log_dbg("TCRYPT:  trying cipher %s-%s",
+			tcrypt_cipher[i].long_name, tcrypt_cipher[i].mode);
+
+		memcpy(&hdr2.e, &hdr->e, TCRYPT_HDR_LEN);
+
+		if (!strncmp(tcrypt_cipher[i].mode, "cbci", 4))
+			r = TCRYPT_decrypt_cbci(&tcrypt_cipher[i], key, &hdr2);
+		else for (j = tcrypt_cipher[i].chain_count - 1; j >= 0 ; j--) {
+			if (!tcrypt_cipher[i].cipher[j].name)
+				continue;
+			r = TCRYPT_decrypt_hdr_one(&tcrypt_cipher[i].cipher[j],
+					    tcrypt_cipher[i].mode, key, &hdr2);
+			if (r < 0)
+				break;
+		}
+
+		if (r < 0) {
+			log_dbg("TCRYPT:   returned error %d, skipped.", r);
+			if (r == -ENOTSUP)
+				break;
+			r = -ENOENT;
+			continue;
+		}
+
+		if (!strncmp(hdr2.d.magic, TCRYPT_HDR_MAGIC, TCRYPT_HDR_MAGIC_LEN)) {
+			log_dbg("TCRYPT: Signature magic detected.");
+			memcpy(&hdr->e, &hdr2.e, TCRYPT_HDR_LEN);
+			r = i;
+			break;
+		}
+		if ((flags & CRYPT_TCRYPT_VERA_MODES) &&
+		     !strncmp(hdr2.d.magic, VCRYPT_HDR_MAGIC, TCRYPT_HDR_MAGIC_LEN)) {
+			log_dbg("TCRYPT: Signature magic detected (Veracrypt).");
+			memcpy(&hdr->e, &hdr2.e, TCRYPT_HDR_LEN);
+			r = i;
+			break;
+		}
+		r = -EPERM;
+	}
+
+	crypt_memzero(&hdr2, sizeof(hdr2));
+	return r;
+}
+
+static int TCRYPT_pool_keyfile(struct crypt_device *cd,
+				unsigned char pool[TCRYPT_KEY_POOL_LEN],
+				const char *keyfile)
+{
+	unsigned char data[TCRYPT_KEYFILE_LEN];
+	int i, j, fd, data_size;
+	uint32_t crc;
+
+	log_dbg("TCRYPT: using keyfile %s.", keyfile);
+
+	fd = open(keyfile, O_RDONLY);
+	if (fd < 0) {
+		log_err(cd, _("Failed to open key file.\n"));
+		return -EIO;
+	}
+
+	data_size = read_buffer(fd, data, TCRYPT_KEYFILE_LEN);
+	close(fd);
+	if (data_size < 0) {
+		log_err(cd, _("Error reading keyfile %s.\n"), keyfile);
+		return -EIO;
+	}
+
+	for (i = 0, j = 0, crc = ~0U; i < data_size; i++) {
+		crc = crypt_crc32(crc, &data[i], 1);
+		pool[j++] += (unsigned char)(crc >> 24);
+		pool[j++] += (unsigned char)(crc >> 16);
+		pool[j++] += (unsigned char)(crc >>  8);
+		pool[j++] += (unsigned char)(crc);
+		j %= TCRYPT_KEY_POOL_LEN;
+	}
+
+	crypt_memzero(&crc, sizeof(crc));
+	crypt_memzero(data, TCRYPT_KEYFILE_LEN);
+
+	return 0;
+}
+#endif
+
+static int GELI_init_hdr(struct crypt_device *cd,
+			   struct geli_phdr *hdr,
+			   struct crypt_params_geli *params)
+{
+	int r = -EPERM;
+#if 0
+	unsigned char pwd[TCRYPT_KEY_POOL_LEN] = {};
+	size_t passphrase_size;
+	char *key;
+	unsigned int i, skipped = 0;
+
+	if (posix_memalign((void*)&key, crypt_getpagesize(), TCRYPT_HDR_KEY_LEN))
+		return -ENOMEM;
+
+	if (params->keyfiles_count)
+		passphrase_size = TCRYPT_KEY_POOL_LEN;
+	else
+		passphrase_size = params->passphrase_size;
+
+	if (params->passphrase_size > TCRYPT_KEY_POOL_LEN) {
+		log_err(cd, _("Maximum TCRYPT passphrase length (%d) exceeded.\n"),
+			      TCRYPT_KEY_POOL_LEN);
+		goto out;
+	}
+
+	/* Calculate pool content from keyfiles */
+	for (i = 0; i < params->keyfiles_count; i++) {
+		r = TCRYPT_pool_keyfile(cd, pwd, params->keyfiles[i]);
+		if (r < 0)
+			goto out;
+	}
+
+	/* If provided password, combine it with pool */
+	for (i = 0; i < params->passphrase_size; i++)
+		pwd[i] += params->passphrase[i];
+
+	for (i = 0; tcrypt_kdf[i].name; i++) {
+		if (!(params->flags & CRYPT_TCRYPT_LEGACY_MODES) && tcrypt_kdf[i].legacy)
+			continue;
+		if (!(params->flags & CRYPT_TCRYPT_VERA_MODES) && tcrypt_kdf[i].veracrypt)
+			continue;
+		/* Derive header key */
+		log_dbg("TCRYPT: trying KDF: %s-%s-%d.",
+			tcrypt_kdf[i].name, tcrypt_kdf[i].hash, tcrypt_kdf[i].iterations);
+		r = crypt_pbkdf(tcrypt_kdf[i].name, tcrypt_kdf[i].hash,
+				(char*)pwd, passphrase_size,
+				hdr->salt, TCRYPT_HDR_SALT_LEN,
+				key, TCRYPT_HDR_KEY_LEN,
+				tcrypt_kdf[i].iterations);
+		if (r < 0 && crypt_hash_size(tcrypt_kdf[i].hash) < 0) {
+			log_verbose(cd, _("PBKDF2 hash algorithm %s not available, skipping.\n"),
+				      tcrypt_kdf[i].hash);
+			continue;
+		}
+		if (r < 0)
+			break;
+
+		/* Decrypt header */
+		r = TCRYPT_decrypt_hdr(cd, hdr, key, params->flags);
+		if (r == -ENOENT) {
+			skipped++;
+			r = -EPERM;
+		}
+		if (r != -EPERM)
+			break;
+	}
+
+	if ((r < 0 && r != -EPERM && skipped && skipped == i) || r == -ENOTSUP) {
+		log_err(cd, _("Required kernel crypto interface not available.\n"));
+#ifdef ENABLE_AF_ALG
+		log_err(cd, _("Ensure you have algif_skcipher kernel module loaded.\n"));
+#endif
+	}
+	if (r < 0)
+		goto out;
+#endif
+
+	log_dbg("GELI tracking: 101\n");
+	r = GELI_hdr_from_disk(hdr, params, r);
+	if (!r) {
+		log_dbg("GELI tracking: 102 magic OK\n");
+#if 0
+		log_dbg("TCRYPT: Magic: %s, Header version: %d, req. %d, sector %d"
+			", mk_offset %" PRIu64 ", hidden_size %" PRIu64
+			", volume size %" PRIu64, tcrypt_kdf[i].veracrypt ?
+			VCRYPT_HDR_MAGIC : TCRYPT_HDR_MAGIC,
+			(int)hdr->d.version, (int)hdr->d.version_tc, (int)hdr->d.sector_size,
+			hdr->d.mk_offset, hdr->d.hidden_volume_size, hdr->d.volume_size);
+		log_dbg("TCRYPT: Header cipher %s-%s, key size %zu",
+			params->cipher, params->mode, params->key_size);
+#endif
+	}
+out:
+#if 0
+	crypt_memzero(pwd, TCRYPT_KEY_POOL_LEN);
+	if (key)
+		crypt_memzero(key, TCRYPT_HDR_KEY_LEN);
+	free(key);
+#endif
+	return r;
+}
+
+int GELI_read_phdr(struct crypt_device *cd,
+		     struct geli_phdr *hdr,
+		     struct crypt_params_geli *params)
+{
+	struct device *device = crypt_metadata_device(cd);
+	ssize_t hdr_size = sizeof(struct geli_phdr);
+	//char *base_device_path;
+	int devfd = 0, r, bs;
+
+	assert(sizeof(struct geli_phdr) == 511);
+
+	log_dbg("Reading GELI header of size %zu bytes from device %s.",
+		hdr_size, device_path(device));
+
+	bs = device_block_size(device);
+	if (bs < 0)
+		return bs;
+
+#if 0
+	if (params->flags & CRYPT_TCRYPT_SYSTEM_HEADER &&
+	    crypt_dev_is_partition(device_path(device))) {
+		base_device_path = crypt_get_base_device(device_path(device));
+
+		log_dbg("Reading TCRYPT system header from device %s.", base_device_path ?: "?");
+		if (!base_device_path)
+			return -EINVAL;
+
+		r = device_alloc(&base_device, base_device_path);
+		free(base_device_path);
+		if (r < 0)
+			return r;
+		devfd = device_open(base_device, O_RDONLY);
+		device_free(base_device);
+	} else
+#endif
+		devfd = device_open(device, O_RDONLY);
+
+	if (devfd < 0) {
+		log_err(cd, _("Cannot open device %s.\n"), device_path(device));
+		return -EINVAL;
+	}
+
+	r = -EIO;
+#define GELI_HDR_OFFSET -512
+	if (lseek(devfd, GELI_HDR_OFFSET, SEEK_END) >= 0 &&
+	    read_blockwise(devfd, bs, hdr, hdr_size) == hdr_size) {
+		r = GELI_init_hdr(cd, hdr, params);
+	}
+
+	close(devfd);
+	if (r < 0)
+		memset(hdr, 0, sizeof (*hdr));
+	return r;
+}
+
+#if 0
+static struct tcrypt_algs *TCRYPT_get_algs(const char *cipher, const char *mode)
+{
+	int i;
+
+	if (!cipher || !mode)
+		return NULL;
+
+	for (i = 0; tcrypt_cipher[i].chain_count; i++)
+		if (!strcmp(tcrypt_cipher[i].long_name, cipher) &&
+		    !strcmp(tcrypt_cipher[i].mode, mode))
+		    return &tcrypt_cipher[i];
+
+	return NULL;
+}
+
+int TCRYPT_activate(struct crypt_device *cd,
+		     const char *name,
+		     struct tcrypt_phdr *hdr,
+		     struct crypt_params_tcrypt *params,
+		     uint32_t flags)
+{
+	char cipher[MAX_CIPHER_LEN], dm_name[PATH_MAX], dm_dev_name[PATH_MAX];
+	char *part_path;
+	struct device *device = NULL, *part_device = NULL;
+	unsigned int i;
+	int r;
+	uint32_t req_flags;
+	struct tcrypt_algs *algs;
+	enum devcheck device_check;
+	struct crypt_dm_active_device dmd = {
+		.target = DM_CRYPT,
+		.size   = 0,
+		.data_device = crypt_data_device(cd),
+		.u.crypt  = {
+			.cipher = cipher,
+			.offset = crypt_get_data_offset(cd),
+			.iv_offset = crypt_get_iv_offset(cd),
+		}
+	};
+
+	if (!hdr->d.version) {
+		log_dbg("TCRYPT: this function is not supported without encrypted header load.");
+		return -ENOTSUP;
+	}
+
+	if (hdr->d.sector_size && hdr->d.sector_size != SECTOR_SIZE) {
+		log_err(cd, _("Activation is not supported for %d sector size.\n"),
+			hdr->d.sector_size);
+		return -ENOTSUP;
+	}
+
+	if (strstr(params->mode, "-tcrypt")) {
+		log_err(cd, _("Kernel doesn't support activation for this TCRYPT legacy mode.\n"));
+		return -ENOTSUP;
+	}
+
+	if (strstr(params->mode, "-tcw"))
+		req_flags = DM_TCW_SUPPORTED;
+	else
+		req_flags = DM_PLAIN64_SUPPORTED;
+
+	algs = TCRYPT_get_algs(params->cipher, params->mode);
+	if (!algs)
+		return -EINVAL;
+
+	if (hdr->d.sector_size == 0)
+		return -EINVAL;
+
+	if (params->flags & CRYPT_TCRYPT_SYSTEM_HEADER)
+		dmd.size = 0;
+	else if (params->flags & CRYPT_TCRYPT_HIDDEN_HEADER)
+		dmd.size = hdr->d.hidden_volume_size / hdr->d.sector_size;
+	else
+		dmd.size = hdr->d.volume_size / hdr->d.sector_size;
+
+	if (dmd.flags & CRYPT_ACTIVATE_SHARED)
+		device_check = DEV_SHARED;
+	else
+		device_check = DEV_EXCL;
+
+	if ((params->flags & CRYPT_TCRYPT_SYSTEM_HEADER) &&
+	     !crypt_dev_is_partition(device_path(dmd.data_device))) {
+		part_path = crypt_get_partition_device(device_path(dmd.data_device),
+						       dmd.u.crypt.offset, dmd.size);
+		if (part_path) {
+			if (!device_alloc(&part_device, part_path)) {
+				log_verbose(cd, _("Activating TCRYPT system encryption for partition %s.\n"),
+					    part_path);
+				dmd.data_device = part_device;
+				dmd.u.crypt.offset = 0;
+			}
+			free(part_path);
+		} else
+			/*
+			 * System encryption use the whole device mapping, there can
+			 * be active partitions.
+			 */
+			device_check = DEV_SHARED;
+	}
+
+	r = device_block_adjust(cd, dmd.data_device, device_check,
+				dmd.u.crypt.offset, &dmd.size, &dmd.flags);
+	if (r) {
+		device_free(part_device);
+		return r;
+	}
+
+	/* Frome here, key size for every cipher must be the same */
+	dmd.u.crypt.vk = crypt_alloc_volume_key(algs->cipher[0].key_size +
+						algs->cipher[0].key_extra_size, NULL);
+	if (!dmd.u.crypt.vk) {
+		device_free(part_device);
+		return -ENOMEM;
+	}
+
+	for (i = algs->chain_count; i > 0; i--) {
+		if (i == 1) {
+			dm_name[sizeof(dm_name)-1] = '\0';
+			strncpy(dm_name, name, sizeof(dm_name)-1);
+			dmd.flags = flags;
+		} else {
+			snprintf(dm_name, sizeof(dm_name), "%s_%d", name, i-1);
+			dmd.flags = flags | CRYPT_ACTIVATE_PRIVATE;
+		}
+
+		snprintf(cipher, sizeof(cipher), "%s-%s",
+			 algs->cipher[i-1].name, algs->mode);
+
+		TCRYPT_copy_key(&algs->cipher[i-1], algs->mode,
+				dmd.u.crypt.vk->key, hdr->d.keys);
+
+		if (algs->chain_count != i) {
+			snprintf(dm_dev_name, sizeof(dm_dev_name), "%s/%s_%d",
+				 dm_get_dir(), name, i);
+			r = device_alloc(&device, dm_dev_name);
+			if (r)
+				break;
+			dmd.data_device = device;
+			dmd.u.crypt.offset = 0;
+		}
+
+		log_dbg("Trying to activate TCRYPT device %s using cipher %s.",
+			dm_name, dmd.u.crypt.cipher);
+		r = dm_create_device(cd, dm_name, CRYPT_TCRYPT, &dmd, 0);
+
+		device_free(device);
+		device = NULL;
+
+		if (r)
+			break;
+	}
+
+	if (r < 0 && !(dm_flags() & req_flags)) {
+		log_err(cd, _("Kernel doesn't support TCRYPT compatible mapping.\n"));
+		r = -ENOTSUP;
+	}
+
+	device_free(part_device);
+	crypt_free_volume_key(dmd.u.crypt.vk);
+	return r;
+}
+
+static int TCRYPT_remove_one(struct crypt_device *cd, const char *name,
+		      const char *base_uuid, int index)
+{
+	struct crypt_dm_active_device dmd = {};
+	char dm_name[PATH_MAX];
+	int r;
+
+	if (snprintf(dm_name, sizeof(dm_name), "%s_%d", name, index) < 0)
+		return -ENOMEM;
+
+	r = dm_status_device(cd, dm_name);
+	if (r < 0)
+		return r;
+
+	r = dm_query_device(cd, dm_name, DM_ACTIVE_UUID, &dmd);
+	if (!r && !strncmp(dmd.uuid, base_uuid, strlen(base_uuid)))
+		r = dm_remove_device(cd, dm_name, 0, 0);
+
+	free(CONST_CAST(void*)dmd.uuid);
+	return r;
+}
+
+int TCRYPT_deactivate(struct crypt_device *cd, const char *name)
+{
+	struct crypt_dm_active_device dmd = {};
+	int r;
+
+	r = dm_query_device(cd, name, DM_ACTIVE_UUID, &dmd);
+	if (r < 0)
+		return r;
+	if (!dmd.uuid)
+		return -EINVAL;
+
+	r = dm_remove_device(cd, name, 0, 0);
+	if (r < 0)
+		goto out;
+
+	r = TCRYPT_remove_one(cd, name, dmd.uuid, 1);
+	if (r < 0)
+		goto out;
+
+	r = TCRYPT_remove_one(cd, name, dmd.uuid, 2);
+	if (r < 0)
+		goto out;
+out:
+	free(CONST_CAST(void*)dmd.uuid);
+	return (r == -ENODEV) ? 0 : r;
+}
+
+static int TCRYPT_status_one(struct crypt_device *cd, const char *name,
+			      const char *base_uuid, int index,
+			      size_t *key_size, char *cipher,
+			      uint64_t *data_offset, struct device **device)
+{
+	struct crypt_dm_active_device dmd = {};
+	char dm_name[PATH_MAX], *c;
+	int r;
+
+	if (snprintf(dm_name, sizeof(dm_name), "%s_%d", name, index) < 0)
+		return -ENOMEM;
+
+	r = dm_status_device(cd, dm_name);
+	if (r < 0)
+		return r;
+
+	r = dm_query_device(cd, dm_name, DM_ACTIVE_DEVICE |
+					  DM_ACTIVE_UUID |
+					  DM_ACTIVE_CRYPT_CIPHER |
+					  DM_ACTIVE_CRYPT_KEYSIZE, &dmd);
+	if (r > 0)
+		r = 0;
+	if (!r && !strncmp(dmd.uuid, base_uuid, strlen(base_uuid))) {
+		if ((c = strchr(dmd.u.crypt.cipher, '-')))
+			*c = '\0';
+		strcat(cipher, "-");
+		strncat(cipher, dmd.u.crypt.cipher, MAX_CIPHER_LEN);
+		*key_size += dmd.u.crypt.vk->keylength;
+		*data_offset = dmd.u.crypt.offset * SECTOR_SIZE;
+		device_free(*device);
+		*device = dmd.data_device;
+	} else {
+		device_free(dmd.data_device);
+		r = -ENODEV;
+	}
+
+	free(CONST_CAST(void*)dmd.uuid);
+	free(CONST_CAST(void*)dmd.u.crypt.cipher);
+	crypt_free_volume_key(dmd.u.crypt.vk);
+	return r;
+}
+
+int TCRYPT_init_by_name(struct crypt_device *cd, const char *name,
+			const struct crypt_dm_active_device *dmd,
+			struct device **device,
+			struct crypt_params_tcrypt *tcrypt_params,
+			struct tcrypt_phdr *tcrypt_hdr)
+{
+	struct tcrypt_algs *algs;
+	char cipher[MAX_CIPHER_LEN * 4], mode[MAX_CIPHER_LEN+1], *tmp;
+	size_t key_size;
+	int r;
+
+	memset(tcrypt_params, 0, sizeof(*tcrypt_params));
+	memset(tcrypt_hdr, 0, sizeof(*tcrypt_hdr));
+	tcrypt_hdr->d.sector_size = SECTOR_SIZE;
+	tcrypt_hdr->d.mk_offset = dmd->u.crypt.offset * SECTOR_SIZE;
+
+	strncpy(cipher, dmd->u.crypt.cipher, MAX_CIPHER_LEN);
+	tmp = strchr(cipher, '-');
+	if (!tmp)
+		return -EINVAL;
+	*tmp = '\0';
+	mode[MAX_CIPHER_LEN] = '\0';
+	strncpy(mode, ++tmp, MAX_CIPHER_LEN);
+
+	key_size = dmd->u.crypt.vk->keylength;
+	r = TCRYPT_status_one(cd, name, dmd->uuid, 1, &key_size,
+			      cipher, &tcrypt_hdr->d.mk_offset, device);
+	if (!r)
+		r = TCRYPT_status_one(cd, name, dmd->uuid, 2, &key_size,
+				      cipher, &tcrypt_hdr->d.mk_offset, device);
+
+	if (r < 0 && r != -ENODEV)
+		return r;
+
+	algs = TCRYPT_get_algs(cipher, mode);
+	if (!algs || key_size != algs->chain_key_size)
+		return -EINVAL;
+
+	tcrypt_params->key_size = algs->chain_key_size;
+	tcrypt_params->cipher = algs->long_name;
+	tcrypt_params->mode = algs->mode;
+	return 0;
+}
+
+uint64_t TCRYPT_get_data_offset(struct crypt_device *cd,
+				 struct tcrypt_phdr *hdr,
+				 struct crypt_params_tcrypt *params)
+{
+	uint64_t size;
+
+	/* No real header loaded, initialized by active device */
+	if (!hdr->d.version)
+		goto hdr_offset;
+
+	/* Mapping through whole device, not partition! */
+	if (params->flags & CRYPT_TCRYPT_SYSTEM_HEADER) {
+		if (crypt_dev_is_partition(device_path(crypt_metadata_device(cd))))
+			return 0;
+		goto hdr_offset;
+	}
+
+	if (params->mode && !strncmp(params->mode, "xts", 3)) {
+		if (hdr->d.version < 3)
+			return 1;
+
+		if (params->flags & CRYPT_TCRYPT_HIDDEN_HEADER) {
+			if (hdr->d.version > 3)
+				return (hdr->d.mk_offset / hdr->d.sector_size);
+			if (device_size(crypt_metadata_device(cd), &size) < 0)
+				return 0;
+			return (size - hdr->d.hidden_volume_size +
+				(TCRYPT_HDR_HIDDEN_OFFSET_OLD)) / hdr->d.sector_size;
+		}
+		goto hdr_offset;
+	}
+
+	if (params->flags & CRYPT_TCRYPT_HIDDEN_HEADER) {
+		if (device_size(crypt_metadata_device(cd), &size) < 0)
+			return 0;
+		return (size - hdr->d.hidden_volume_size +
+			(TCRYPT_HDR_HIDDEN_OFFSET_OLD)) / hdr->d.sector_size;
+	}
+
+hdr_offset:
+	return hdr->d.mk_offset / hdr->d.sector_size;
+}
+
+uint64_t TCRYPT_get_iv_offset(struct crypt_device *cd,
+			      struct tcrypt_phdr *hdr,
+			      struct crypt_params_tcrypt *params)
+{
+	uint64_t iv_offset;
+
+	if (params->mode && !strncmp(params->mode, "xts", 3))
+		iv_offset = TCRYPT_get_data_offset(cd, hdr, params);
+	else if (params->mode && !strncmp(params->mode, "lrw", 3))
+		iv_offset = 0;
+	else
+		iv_offset = hdr->d.mk_offset / hdr->d.sector_size;
+
+	if (params->flags & CRYPT_TCRYPT_SYSTEM_HEADER)
+		iv_offset += crypt_dev_partition_offset(device_path(crypt_metadata_device(cd)));
+
+	return iv_offset;
+}
+
+int TCRYPT_get_volume_key(struct crypt_device *cd,
+			  struct tcrypt_phdr *hdr,
+			  struct crypt_params_tcrypt *params,
+			  struct volume_key **vk)
+{
+	struct tcrypt_algs *algs;
+	unsigned int i, key_index;
+
+	if (!hdr->d.version) {
+		log_err(cd, _("This function is not supported without TCRYPT header load."));
+		return -ENOTSUP;
+	}
+
+	algs = TCRYPT_get_algs(params->cipher, params->mode);
+	if (!algs)
+		return -EINVAL;
+
+	*vk = crypt_alloc_volume_key(params->key_size, NULL);
+	if (!*vk)
+		return -ENOMEM;
+
+	for (i = 0, key_index = 0; i < algs->chain_count; i++) {
+		TCRYPT_copy_key(&algs->cipher[i], algs->mode,
+				&(*vk)->key[key_index], hdr->d.keys);
+		key_index += algs->cipher[i].key_size;
+	}
+
+	return 0;
+}
+#endif
+
+static int log_std_hexdump(struct crypt_device *cd, uint8_t *p, int len)
+{
+	int i;
+
+
+	for(i = 0; i < len; i++) {
+		if (i && !(i % 16))
+			log_std(cd, "\n\t\t");
+		log_std(cd, "%02hhx ", (char)p[i]);
+	}
+	log_std(cd, "\n");
+
+	return 0;
+}
+
+int GELI_dump(struct crypt_device *cd,
+		struct geli_phdr *hdr,
+		struct crypt_params_geli *params)
+{
+	log_std(cd, "GELI header information for %s\n",
+		device_path(crypt_metadata_device(cd)));
+
+	if (hdr->md_version == 7) {
+		log_std(cd, "Magic:\t\t%s\n", hdr->md_magic);
+		log_std(cd, "Version:\t%" PRIu32 "\n", hdr->md_version);
+		log_std(cd, "Flags:\t\t0x%" PRIx32 "\n", hdr->md_flags);
+		log_std(cd, "Encryption algo:\t0x%" PRIx16 "\n", hdr->md_ealgo);
+		log_std(cd, "Key length:\t%" PRIu16 "\n", hdr->md_keylen);
+		log_std(cd, "Authentication algo:\t0x%" PRIx16 "\n", hdr->md_aalgo);
+		log_std(cd, "Provider size:\t%" PRIu64 "\n", hdr->md_provsize);
+		log_std(cd, "Sector size:\t%" PRIu32 "\n", hdr->md_sectorsize);
+		log_std(cd, "Number of keys:\t%" PRIu8 "\n", hdr->md_keys);
+		log_std(cd, "Number of iterations:\t%" PRIu32 "\n", hdr->md_iterations);
+		log_std(cd, "Salt:\t\t\t");
+		log_std_hexdump(cd, hdr->md_salt, G_ELI_SALTLEN);
+		log_std(cd, "Encrypted MKeys:\t");
+		log_std_hexdump(cd, hdr->md_mkeysbuf, G_ELI_MAXMKEYS * G_ELI_MKEYLEN);
+		log_std(cd, "Encr. IV key 0:\t\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[0].md_ivkey, G_ELI_IVKEYLEN);
+		log_std(cd, "Encr. data key 0:\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[0].md_datakey, G_ELI_DATAKEYLEN);
+		log_std(cd, "Encr. SHA512 HMAC 0:\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[0].md_hmacsha512, SHA512_MDLEN);
+		log_std(cd, "Encr. IV key 1:\t\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[1].md_ivkey, G_ELI_IVKEYLEN);
+		log_std(cd, "Encr. data key 1:\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[1].md_datakey, G_ELI_DATAKEYLEN);
+		log_std(cd, "Encr. SHA512 HMAC 1:\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[1].md_hmacsha512, SHA512_MDLEN);
+		log_std(cd, "MD5:\t\t\t");
+		log_std_hexdump(cd, hdr->md_hash, 16);
+	} else {
+		log_std(cd, "Version %d not supported\n", hdr->md_version);
+	}
+	return 0;
+}
diff --git a/lib/geli/geli.h b/lib/geli/geli.h
new file mode 100644
index 0000000..6d4d248
--- /dev/null
+++ b/lib/geli/geli.h
@@ -0,0 +1,96 @@
+/*
+ * FreeBSD GEOM::ELI GELI compatible volume handling
+ *
+ * Copyright (C) 2017, Carl-Daniel Hailfinger
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _GELI_H
+#define _GELI_H
+
+#include <unistd.h>
+
+/* Relevant header parts from FreeBSD sys/geom/eli/g_eli.h
+ * TODO: Figure out if that is actually OK from a licensing perspective.
+ */
+#define	G_ELI_MAGIC		"GEOM::ELI"
+
+#define SHA512_MDLEN		64
+
+#define	G_ELI_MAXMKEYS		2
+#define	G_ELI_MAXKEYLEN		64
+#define	G_ELI_USERKEYLEN	G_ELI_MAXKEYLEN
+#define	G_ELI_DATAKEYLEN	G_ELI_MAXKEYLEN
+#define	G_ELI_AUTHKEYLEN	G_ELI_MAXKEYLEN
+#define	G_ELI_IVKEYLEN		G_ELI_MAXKEYLEN
+#define	G_ELI_SALTLEN		64
+#define	G_ELI_DATAIVKEYLEN	(G_ELI_DATAKEYLEN + G_ELI_IVKEYLEN)
+/* Data-Key, IV-Key, HMAC_SHA512(Derived-Key, Data-Key+IV-Key) */
+#define	G_ELI_MKEYLEN		(G_ELI_DATAIVKEYLEN + SHA512_MDLEN)
+
+struct geli_mkeyslot {
+	uint8_t		md_ivkey[G_ELI_IVKEYLEN];
+	uint8_t		md_datakey[G_ELI_DATAKEYLEN];
+	uint8_t		md_hmacsha512[SHA512_MDLEN];
+};
+
+struct geli_phdr {
+	char		md_magic[16];	/* Magic value. */
+	uint32_t	md_version;	/* Version number. */
+	uint32_t	md_flags;	/* Additional flags. */
+	uint16_t	md_ealgo;	/* Encryption algorithm. */
+	uint16_t	md_keylen;	/* Key length. */
+	uint16_t	md_aalgo;	/* Authentication algorithm. */
+	uint64_t	md_provsize;	/* Provider's size. */
+	uint32_t	md_sectorsize;	/* Sector size. */
+	uint8_t		md_keys;	/* Available keys. */
+	int32_t		md_iterations;	/* Number of iterations for PKCS#5v2. */
+	uint8_t		md_salt[G_ELI_SALTLEN]; /* Salt. */
+			/* Encrypted master key (IV-key, Data-key, HMAC). */
+	union {
+		uint8_t			md_mkeysbuf[G_ELI_MAXMKEYS * G_ELI_MKEYLEN];
+		struct geli_mkeyslot	md_mkeyslot[G_ELI_MAXMKEYS];
+	};
+	u_char		md_hash[16];	/* MD5 hash. */
+} __attribute__((__packed__));
+
+int GELI_read_phdr(struct crypt_device *cd,
+		     struct geli_phdr *hdr,
+		     struct crypt_params_geli *params);
+
+int GELI_dump(struct crypt_device *cd,
+		struct geli_phdr *hdr,
+		struct crypt_params_geli *params);
+
+#if 0
+struct crypt_device;
+struct volume_key;
+
+int GELI_parse_keyfile(struct crypt_device *cd,
+			  struct volume_key **vk,
+			  const char *hash,
+			  unsigned int *keys_count,
+			  char *buffer,
+			  size_t buffer_len);
+
+int GELI_activate(struct crypt_device *cd,
+		     const char *name,
+		     const char *base_cipher,
+		     unsigned int keys_count,
+		     struct volume_key *vk,
+		     uint32_t flags);
+#endif
+#endif
diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h
index 80bbf5c..8e31444 100644
--- a/lib/libcryptsetup.h
+++ b/lib/libcryptsetup.h
@@ -243,6 +243,8 @@ int crypt_memory_lock(struct crypt_device *cd, int lock);
 #define CRYPT_VERITY "VERITY"
 /** TCRYPT (TrueCrypt-compatible and VeraCrypt-compatible) mode */
 #define CRYPT_TCRYPT "TCRYPT"
+/** GELI (FreeBSD GEOM::ELI) mode */
+#define CRYPT_GELI "GELI"
 
 /**
  * Get device type
@@ -354,6 +356,17 @@ struct crypt_params_tcrypt {
  */
 #define CRYPT_TCRYPT_VERA_MODES      (1 << 4)
 
+/**
+ *
+ * Structure used as parameter for GELI device type.
+ *
+ * @see crypt_format
+ *
+ */
+struct crypt_params_geli {
+	int dummy;
+};
+
 /** @} */
 
 /**
diff --git a/lib/setup.c b/lib/setup.c
index 1dca99b..7d394ac 100644
--- a/lib/setup.c
+++ b/lib/setup.c
@@ -34,6 +34,7 @@
 #include "loopaes.h"
 #include "verity.h"
 #include "tcrypt.h"
+#include "geli.h"
 #include "internal.h"
 
 struct crypt_device {
@@ -76,6 +77,10 @@ struct crypt_device {
 		struct crypt_params_tcrypt params;
 		struct tcrypt_phdr hdr;
 	} tcrypt;
+	struct { /* used in CRYPT_GELI */
+		struct crypt_params_geli params;
+		struct geli_phdr hdr;
+	} geli;
 	struct { /* used if initialized without header by name */
 		char *active_name;
 		/* buffers, must refresh from kernel on every query */
@@ -245,6 +250,11 @@ static int isTCRYPT(const char *type)
 	return (type && !strcmp(CRYPT_TCRYPT, type));
 }
 
+static int isGELI(const char *type)
+{
+	return (type && !strcmp(CRYPT_GELI, type));
+}
+
 static int onlyLUKS(struct crypt_device *cd)
 {
 	int r = 0;
@@ -581,6 +591,30 @@ static int _crypt_load_tcrypt(struct crypt_device *cd, struct crypt_params_tcryp
 	return r;
 }
 
+static int _crypt_load_geli(struct crypt_device *cd, struct crypt_params_geli *params)
+{
+	int r;
+
+	if (!params)
+		return -EINVAL;
+
+	r = init_crypto(cd);
+	if (r < 0)
+		return r;
+
+	memcpy(&cd->u.geli.params, params, sizeof(*params));
+
+	r = GELI_read_phdr(cd, &cd->u.geli.hdr, &cd->u.geli.params);
+
+	if (r < 0)
+		return r;
+
+	if (!cd->type && !(cd->type = strdup(CRYPT_GELI)))
+		return -ENOMEM;
+
+	return r;
+}
+
 static int _crypt_load_verity(struct crypt_device *cd, struct crypt_params_verity *params)
 {
 	int r;
@@ -683,6 +717,9 @@ static int _init_by_name_crypt(struct crypt_device *cd, const char *name)
 	} else if (isTCRYPT(cd->type)) {
 		r = TCRYPT_init_by_name(cd, name, &dmd, &cd->device,
 					&cd->u.tcrypt.params, &cd->u.tcrypt.hdr);
+	} else if (isGELI(cd->type)) {
+		//FIXME r = GELI_init_by_name(cd, name, &dmd, &cd->device,
+		//FIXME			&cd->u.geli.params, &cd->u.geli.hdr);
 	}
 out:
 	crypt_free_volume_key(dmd.u.crypt.vk);
@@ -790,6 +827,8 @@ int crypt_init_by_name_and_header(struct crypt_device **cd,
 			(*cd)->type = strdup(CRYPT_VERITY);
 		else if (!strncmp(CRYPT_TCRYPT, dmd.uuid, sizeof(CRYPT_TCRYPT)-1))
 			(*cd)->type = strdup(CRYPT_TCRYPT);
+		else if (!strncmp(CRYPT_GELI, dmd.uuid, sizeof(CRYPT_GELI)-1))
+			(*cd)->type = strdup(CRYPT_GELI);
 		else
 			log_dbg("Unknown UUID set, some parameters are not set.");
 	} else
@@ -1177,6 +1216,12 @@ int crypt_load(struct crypt_device *cd,
 			return -EINVAL;
 		}
 		r = _crypt_load_tcrypt(cd, params);
+	} else if (isGELI(requested_type)) {
+		if (cd->type && !isGELI(cd->type)) {
+			log_dbg("Context is already initialised to type %s", cd->type);
+			return -EINVAL;
+		}
+		r = _crypt_load_geli(cd, params);
 	} else
 		return -EINVAL;
 
@@ -2032,6 +2077,11 @@ int crypt_activate_by_volume_key(struct crypt_device *cd,
 			return 0;
 		r = TCRYPT_activate(cd, name, &cd->u.tcrypt.hdr,
 				    &cd->u.tcrypt.params, flags);
+	} else if (isGELI(cd->type)) {
+		if (!name)
+			return 0;
+		//FIXME r = GELI_activate(cd, name, &cd->u.geli.hdr,
+		//FIXME		    &cd->u.geli.params, flags);
 	} else
 		log_err(cd, _("Device type is not properly initialised.\n"));
 
@@ -2115,6 +2165,8 @@ int crypt_volume_key_get(struct crypt_device *cd,
 					passphrase_size, &cd->u.luks1.hdr, &vk, cd);
 	} else if (isTCRYPT(cd->type)) {
 		r = TCRYPT_get_volume_key(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params, &vk);
+	} else if (isGELI(cd->type)) {
+		//FIXME r = GELI_get_volume_key(cd, &cd->u.geli.hdr, &cd->u.geli.params, &vk);
 	} else
 		log_err(cd, _("This operation is not supported for %s crypt device.\n"), cd->type ?: "(none)");
 
@@ -2289,6 +2341,8 @@ int crypt_dump(struct crypt_device *cd)
 		return _verity_dump(cd);
 	else if (isTCRYPT(cd->type))
 		return TCRYPT_dump(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params);
+	else if (isGELI(cd->type))
+		return GELI_dump(cd, &cd->u.geli.hdr, &cd->u.geli.params);
 
 	log_err(cd, _("Dump operation is not supported for this device type.\n"));
 	return -EINVAL;
@@ -2333,6 +2387,10 @@ const char *crypt_get_cipher(struct crypt_device *cd)
 	if (isTCRYPT(cd->type))
 		return cd->u.tcrypt.params.cipher;
 
+	if (isGELI(cd->type))
+		return "aes";
+		//FIXME return cd->u.geli.params.cipher;
+
 	if (!cd->type && !_init_by_name_crypt_none(cd))
 		return cd->u.none.cipher;
 
@@ -2353,6 +2411,10 @@ const char *crypt_get_cipher_mode(struct crypt_device *cd)
 	if (isTCRYPT(cd->type))
 		return cd->u.tcrypt.params.mode;
 
+	if (isGELI(cd->type))
+		return "xts-byte64";
+		//FIXME return cd->u.geli.params.mode;
+
 	if (!cd->type && !_init_by_name_crypt_none(cd))
 		return cd->u.none.cipher_mode;
 
@@ -2397,6 +2459,10 @@ int crypt_get_volume_key_size(struct crypt_device *cd)
 	if (isTCRYPT(cd->type))
 		return cd->u.tcrypt.params.key_size;
 
+	if (isGELI(cd->type))
+		return 256;
+		//FIXME return cd->u.geli.params.key_size;
+
 	if (!cd->type && !_init_by_name_crypt_none(cd))
 		return cd->u.none.key_size;
 
@@ -2434,6 +2500,7 @@ uint64_t crypt_get_iv_offset(struct crypt_device *cd)
 	if (isTCRYPT(cd->type))
 		return TCRYPT_get_iv_offset(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params);
 
+	//FIXME: GELI?
 	return 0;
 }
 
diff --git a/src/cryptsetup.c b/src/cryptsetup.c
index 2d6ddaf..a665fea 100644
--- a/src/cryptsetup.c
+++ b/src/cryptsetup.c
@@ -382,6 +382,101 @@ out:
 	return r;
 }
 
+static int geli_load(struct crypt_device *cd, struct crypt_params_geli *params)
+{
+	int r, eperm = 0;
+
+	do {
+		log_std("GELI tracking: 5\n");
+		r = crypt_load(cd, CRYPT_GELI, params);
+		log_std("GELI tracking: 6 crypt_load returned %i\n", r);
+
+		if (r == -EPERM) {
+			log_err(_("No device header detected with the given parameters.\n"));
+			eperm = 1;
+		}
+
+#if 0
+		if (r < 0) {
+			crypt_safe_free(CONST_CAST(char*)params->passphrase);
+			params->passphrase = NULL;
+			params->passphrase_size = 0;
+		}
+#endif
+		check_signal(&r);
+	} while (0);
+
+	/* Report wrong passphrase if at least one try failed */
+	if (eperm && r == -EPIPE)
+		r = -EPERM;
+
+	log_std("GELI tracking: 7\n");
+	return r;
+}
+
+static int action_open_geli(void)
+{
+	struct crypt_device *cd = NULL;
+	struct crypt_params_geli params = {
+		.dummy = 0,
+	};
+	const char *activated_name;
+	uint32_t activate_flags = 0;
+	int r;
+
+	activated_name = opt_test_passphrase ? NULL : action_argv[1];
+
+	log_std("GELI tracking: 9\n");
+	if ((r = crypt_init(&cd, action_argv[0])))
+		goto out;
+
+	log_std("GELI tracking: 10\n");
+	r = geli_load(cd, &params);
+	if (r < 0)
+		goto out;
+
+	log_std("GELI tracking: 11\n");
+	_set_activation_flags(&activate_flags);
+
+	log_std("GELI tracking: 12\n");
+	if (activated_name)
+		r = crypt_activate_by_volume_key(cd, activated_name, NULL, 0, activate_flags);
+out:
+	log_std("GELI tracking: 13\n");
+	crypt_free(cd);
+	return r;
+}
+
+static int action_geliDump(void)
+{
+	struct crypt_device *cd = NULL;
+	struct crypt_params_geli params = {
+		.dummy = 0,
+	};
+	int r;
+
+	log_std("GELI tracking: 1\n");
+	if ((r = crypt_init(&cd, action_argv[0])))
+		goto out;
+
+	log_std("GELI tracking: 2\n");
+	r = geli_load(cd, &params);
+	if (r < 0)
+		goto out;
+
+#if 0
+	if (opt_dump_master_key)
+		r = geliDump_with_volume_key(cd);
+	else
+#endif
+	log_std("GELI tracking: 3\n");
+		r = crypt_dump(cd);
+out:
+	crypt_free(cd);
+	log_std("GELI tracking: 4\n");
+	return r;
+}
+
 static int action_close(void)
 {
 	struct crypt_device *cd = NULL;
@@ -1295,6 +1390,7 @@ out:
 
 static int action_open(void)
 {
+	log_std("GELI tracking: 14\n");
 	if (!opt_type)
 		return -EINVAL;
 
@@ -1314,6 +1410,11 @@ static int action_open(void)
 		if (action_argc < 2 && !opt_test_passphrase)
 			goto args;
 		return action_open_tcrypt();
+	} else if (!strcmp(opt_type, "geli")) {
+		log_std("GELI tracking: 8\n");
+		if (action_argc < 2)
+			goto args;
+		return action_open_geli();
 	}
 
 	log_err(_("Unrecognized metadata device type %s.\n"), opt_type);
@@ -1388,6 +1489,7 @@ static struct action_type {
 	{ "isLuks",       action_isLuks,       1, 0, N_("<device>"), N_("tests <device> for LUKS partition header") },
 	{ "luksDump",     action_luksDump,     1, 1, N_("<device>"), N_("dump LUKS partition information") },
 	{ "tcryptDump",   action_tcryptDump,   1, 1, N_("<device>"), N_("dump TCRYPT device information") },
+	{ "geliDump",     action_geliDump,     1, 1, N_("<device>"), N_("dump GELI device information") },
 	{ "luksSuspend",  action_luksSuspend,  1, 1, N_("<device>"), N_("Suspend LUKS device and wipe key (all IOs are frozen).") },
 	{ "luksResume",   action_luksResume,   1, 1, N_("<device>"), N_("Resume suspended LUKS device.") },
 	{ "luksHeaderBackup", action_luksBackup,1,1, N_("<device>"), N_("Backup LUKS device header and keyslots") },
@@ -1524,7 +1626,7 @@ int main(int argc, const char **argv)
 		{ "tcrypt-system",     '\0', POPT_ARG_NONE, &opt_tcrypt_system,         0, N_("Device is system TCRYPT drive (with bootloader)."), NULL },
 		{ "tcrypt-backup",     '\0', POPT_ARG_NONE, &opt_tcrypt_backup,         0, N_("Use backup (secondary) TCRYPT header."), NULL },
 		{ "veracrypt",         '\0', POPT_ARG_NONE, &opt_veracrypt,             0, N_("Scan also for VeraCrypt compatible device."), NULL },
-		{ "type",               'M', POPT_ARG_STRING, &opt_type,                0, N_("Type of device metadata: luks, plain, loopaes, tcrypt."), NULL },
+		{ "type",               'M', POPT_ARG_STRING, &opt_type,                0, N_("Type of device metadata: luks, plain, loopaes, tcrypt, geli."), NULL },
 		{ "force-password",    '\0', POPT_ARG_NONE, &opt_force_password,        0, N_("Disable password quality check (if enabled)."), NULL },
 		{ "perf-same_cpu_crypt",'\0', POPT_ARG_NONE, &opt_perf_same_cpu_crypt,  0, N_("Use dm-crypt same_cpu_crypt performance compatibility option."), NULL },
 		{ "perf-submit_from_crypt_cpus",'\0', POPT_ARG_NONE, &opt_perf_submit_from_crypt_cpus,0,N_("Use dm-crypt submit_from_crypt_cpus performance compatibility option."), NULL },
@@ -1631,6 +1733,8 @@ int main(int argc, const char **argv)
 		opt_type = "tcrypt";
 	} else if (!strcmp(aname, "tcryptDump")) {
 		opt_type = "tcrypt";
+	} else if (!strcmp(aname, "geliDump")) {
+		opt_type = "geli";
 	} else if (!strcmp(aname, "remove") ||
 		   !strcmp(aname, "plainClose") ||
 		   !strcmp(aname, "luksClose") ||
-- 
1.9.1

-------------- next part --------------
From eaa23ad418e898ed9a395211e57e2d1d2cb598da Mon Sep 17 00:00:00 2001
From: Carl-Daniel Hailfinger <c-d.hailfinger.devel.2006 at gmx.net>
Date: Fri, 30 Dec 2016 05:40:44 +0100
Subject: [PATCH] Initial version of cryptsetup with GELI support. Only dumping
 is supported right now.

---
 configure.ac         |    1 +
 lib/Makefile.am      |    6 +-
 lib/geli/Makefile.am |   14 +
 lib/geli/README.txt  |    4 +
 lib/geli/geli.c      | 1097 ++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/geli/geli.h      |  102 +++++
 lib/libcryptsetup.h  |   13 +
 lib/setup.c          |   67 +++
 src/cryptsetup.c     |  106 ++++-
 9 files changed, 1407 insertions(+), 3 deletions(-)
 create mode 100644 lib/geli/Makefile.am
 create mode 100644 lib/geli/README.txt
 create mode 100644 lib/geli/geli.c
 create mode 100644 lib/geli/geli.h

diff --git a/configure.ac b/configure.ac
index d1f029a..1c46a7d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -467,6 +467,7 @@ lib/luks1/Makefile
 lib/loopaes/Makefile
 lib/verity/Makefile
 lib/tcrypt/Makefile
+lib/geli/Makefile
 src/Makefile
 po/Makefile.in
 man/Makefile
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 6662568..5b99a7c 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = crypto_backend luks1 loopaes verity tcrypt
+SUBDIRS = crypto_backend luks1 loopaes verity tcrypt geli
 
 moduledir = $(libdir)/cryptsetup
 
@@ -12,6 +12,7 @@ AM_CPPFLAGS = -include config.h \
 	-I$(top_srcdir)/lib/loopaes		\
 	-I$(top_srcdir)/lib/verity		\
 	-I$(top_srcdir)/lib/tcrypt		\
+	-I$(top_srcdir)/lib/geli		\
 	-DDATADIR=\""$(datadir)"\"		\
 	-DLIBDIR=\""$(libdir)"\"		\
 	-DPREFIX=\""$(prefix)"\"		\
@@ -25,7 +26,8 @@ common_ldadd = \
 	luks1/libluks1.la			\
 	loopaes/libloopaes.la			\
 	verity/libverity.la			\
-	tcrypt/libtcrypt.la
+	tcrypt/libtcrypt.la			\
+	geli/libgeli.la
 
 libcryptsetup_la_DEPENDENCIES = $(common_ldadd) libcryptsetup.sym
 
diff --git a/lib/geli/Makefile.am b/lib/geli/Makefile.am
new file mode 100644
index 0000000..96aef4e
--- /dev/null
+++ b/lib/geli/Makefile.am
@@ -0,0 +1,14 @@
+moduledir = $(libdir)/cryptsetup
+
+noinst_LTLIBRARIES = libgeli.la
+
+libgeli_la_CFLAGS = -Wall $(AM_CFLAGS) @CRYPTO_CFLAGS@
+
+libgeli_la_SOURCES = \
+	geli.c \
+	geli.h
+
+AM_CPPFLAGS = -include config.h \
+        -I$(top_srcdir)/lib			\
+        -I$(top_srcdir)/lib/crypto_backend
+
diff --git a/lib/geli/README.txt b/lib/geli/README.txt
new file mode 100644
index 0000000..f62075c
--- /dev/null
+++ b/lib/geli/README.txt
@@ -0,0 +1,4 @@
+The user key is derived from the keyfile as follows:
+openssl dgst -sha512 -mac hmac -macopt hexkey:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 user.key
+
+Use AES-XTS-128
diff --git a/lib/geli/geli.c b/lib/geli/geli.c
new file mode 100644
index 0000000..1f1289e
--- /dev/null
+++ b/lib/geli/geli.c
@@ -0,0 +1,1097 @@
+/*
+ * TCRYPT (TrueCrypt-compatible) and VeraCrypt volume handling
+ *
+ * Copyright (C) 2012, Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2012-2015, Milan Broz
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "libcryptsetup.h"
+#include "geli.h"
+#include "internal.h"
+
+#if 0
+/* TCRYPT PBKDF variants */
+static struct {
+	unsigned int legacy:1;
+	unsigned int veracrypt:1;
+	const char *name;
+	const char *hash;
+	unsigned int iterations;
+} tcrypt_kdf[] = {
+	{ 0, 0, "pbkdf2", "ripemd160", 2000 },
+	{ 0, 0, "pbkdf2", "ripemd160", 1000 },
+	{ 0, 0, "pbkdf2", "sha512",    1000 },
+	{ 0, 0, "pbkdf2", "whirlpool", 1000 },
+	{ 1, 0, "pbkdf2", "sha1",      2000 },
+	{ 0, 1, "pbkdf2", "sha512",    500000 },
+	{ 0, 1, "pbkdf2", "ripemd160", 655331 },
+	{ 0, 1, "pbkdf2", "ripemd160", 327661 }, // boot only
+	{ 0, 1, "pbkdf2", "whirlpool", 500000 },
+	{ 0, 1, "pbkdf2", "sha256",    500000 }, // VeraCrypt 1.0f
+	{ 0, 1, "pbkdf2", "sha256",    200000 }, // boot only
+	{ 0, 0, NULL,     NULL,        0 }
+};
+
+struct tcrypt_alg {
+		const char *name;
+		unsigned int key_size;
+		unsigned int iv_size;
+		unsigned int key_offset;
+		unsigned int iv_offset; /* or tweak key offset */
+		unsigned int key_extra_size;
+};
+
+struct tcrypt_algs {
+	unsigned int legacy:1;
+	unsigned int chain_count;
+	unsigned int chain_key_size;
+	const char *long_name;
+	const char *mode;
+	struct tcrypt_alg cipher[3];
+};
+
+/* TCRYPT cipher variants */
+static struct tcrypt_algs tcrypt_cipher[] = {
+/* XTS mode */
+{0,1,64,"aes","xts-plain64",
+	{{"aes",    64,16,0,32,0}}},
+{0,1,64,"serpent","xts-plain64",
+	{{"serpent",64,16,0,32,0}}},
+{0,1,64,"twofish","xts-plain64",
+	{{"twofish",64,16,0,32,0}}},
+{0,2,128,"twofish-aes","xts-plain64",
+	{{"twofish",64,16, 0,64,0},
+	 {"aes",    64,16,32,96,0}}},
+{0,3,192,"serpent-twofish-aes","xts-plain64",
+	{{"serpent",64,16, 0, 96,0},
+	 {"twofish",64,16,32,128,0},
+	 {"aes",    64,16,64,160,0}}},
+{0,2,128,"aes-serpent","xts-plain64",
+	{{"aes",    64,16, 0,64,0},
+	 {"serpent",64,16,32,96,0}}},
+{0,3,192,"aes-twofish-serpent","xts-plain64",
+	{{"aes",    64,16, 0, 96,0},
+	 {"twofish",64,16,32,128,0},
+	 {"serpent",64,16,64,160,0}}},
+{0,2,128,"serpent-twofish","xts-plain64",
+	{{"serpent",64,16, 0,64,0},
+	 {"twofish",64,16,32,96,0}}},
+
+/* LRW mode */
+{0,1,48,"aes","lrw-benbi",
+	{{"aes",    48,16,32,0,0}}},
+{0,1,48,"serpent","lrw-benbi",
+	{{"serpent",48,16,32,0,0}}},
+{0,1,48,"twofish","lrw-benbi",
+	{{"twofish",48,16,32,0,0}}},
+{0,2,96,"twofish-aes","lrw-benbi",
+	{{"twofish",48,16,32,0,0},
+	 {"aes",    48,16,64,0,0}}},
+{0,3,144,"serpent-twofish-aes","lrw-benbi",
+	{{"serpent",48,16,32,0,0},
+	 {"twofish",48,16,64,0,0},
+	 {"aes",    48,16,96,0,0}}},
+{0,2,96,"aes-serpent","lrw-benbi",
+	{{"aes",    48,16,32,0,0},
+	 {"serpent",48,16,64,0,0}}},
+{0,3,144,"aes-twofish-serpent","lrw-benbi",
+	{{"aes",    48,16,32,0,0},
+	 {"twofish",48,16,64,0,0},
+	 {"serpent",48,16,96,0,0}}},
+{0,2,96,"serpent-twofish", "lrw-benbi",
+	{{"serpent",48,16,32,0,0},
+	 {"twofish",48,16,64,0,0}}},
+
+/* Kernel LRW block size is fixed to 16 bytes for GF(2^128)
+ * thus cannot be used with blowfish where block is 8 bytes.
+ * There also no GF(2^64) support.
+{1,1,64,"blowfish_le","lrw-benbi",
+	 {{"blowfish_le",64,8,32,0,0}}},
+{1,2,112,"blowfish_le-aes","lrw-benbi",
+	 {{"blowfish_le",64, 8,32,0,0},
+	  {"aes",        48,16,88,0,0}}},
+{1,3,160,"serpent-blowfish_le-aes","lrw-benbi",
+	  {{"serpent",    48,16, 32,0,0},
+	   {"blowfish_le",64, 8, 64,0,0},
+	   {"aes",        48,16,120,0,0}}},*/
+
+/*
+ * CBC + "outer" CBC (both with whitening)
+ * chain_key_size: alg_keys_bytes + IV_seed_bytes + whitening_bytes
+ */
+{1,1,32+16+16,"aes","cbc-tcw",
+	{{"aes",    32,16,32,0,32}}},
+{1,1,32+16+16,"serpent","cbc-tcw",
+	{{"serpent",32,16,32,0,32}}},
+{1,1,32+16+16,"twofish","cbc-tcw",
+	{{"twofish",32,16,32,0,32}}},
+{1,2,64+16+16,"twofish-aes","cbci-tcrypt",
+	{{"twofish",32,16,32,0,0},
+	 {"aes",    32,16,64,0,32}}},
+{1,3,96+16+16,"serpent-twofish-aes","cbci-tcrypt",
+	{{"serpent",32,16,32,0,0},
+	 {"twofish",32,16,64,0,0},
+	 {"aes",    32,16,96,0,32}}},
+{1,2,64+16+16,"aes-serpent","cbci-tcrypt",
+	{{"aes",    32,16,32,0,0},
+	 {"serpent",32,16,64,0,32}}},
+{1,3,96+16+16,"aes-twofish-serpent", "cbci-tcrypt",
+	{{"aes",    32,16,32,0,0},
+	 {"twofish",32,16,64,0,0},
+	 {"serpent",32,16,96,0,32}}},
+{1,2,64+16+16,"serpent-twofish", "cbci-tcrypt",
+	{{"serpent",32,16,32,0,0},
+	 {"twofish",32,16,64,0,32}}},
+{1,1,16+8+16,"cast5","cbc-tcw",
+	{{"cast5",   16,8,32,0,24}}},
+{1,1,24+8+16,"des3_ede","cbc-tcw",
+	{{"des3_ede",24,8,32,0,24}}},
+{1,1,56+8+16,"blowfish_le","cbc-tcrypt",
+	{{"blowfish_le",56,8,32,0,24}}},
+{1,2,88+16+16,"blowfish_le-aes","cbc-tcrypt",
+	{{"blowfish_le",56, 8,32,0,0},
+	 {"aes",        32,16,88,0,32}}},
+{1,3,120+16+16,"serpent-blowfish_le-aes","cbc-tcrypt",
+	{{"serpent",    32,16, 32,0,0},
+	 {"blowfish_le",56, 8, 64,0,0},
+	 {"aes",        32,16,120,0,32}}},
+{}
+};
+#endif
+
+static int GELI_hdr_from_disk(struct geli_phdr *hdr,
+				struct crypt_params_geli *params,
+				int cipher_index)
+{
+	struct crypt_hash *hd = NULL;
+	char calculated_md5[MD5_MDLEN];
+	int r;
+
+	log_dbg("GELI tracking: 100\n");
+	/* Check sanity of header */
+	if (strcmp(G_ELI_MAGIC, hdr->md_magic)) {
+		log_dbg("Missing GELI magic.\n");
+		return -EINVAL;
+	} else {
+		log_dbg("Correct GELI magic.\n");
+	}
+
+	/* Check MD5 of header */
+	crypt_memzero(calculated_md5, MD5_MDLEN);
+	if (crypt_hash_init(&hd, "md5"))
+		return -EINVAL;
+
+	r = crypt_hash_write(hd, hdr->bytearray, offsetof(struct geli_phdr, md_hash));
+	if (!r)
+		r = crypt_hash_final(hd, calculated_md5, MD5_MDLEN);
+
+	crypt_hash_destroy(hd);
+	if (r)
+		return r;
+
+	if (memcmp(calculated_md5, hdr->md_hash, sizeof(calculated_md5))) {
+		log_dbg("GELI header MD5 mismatch.");
+		return -EINVAL;
+	} else {
+		log_dbg("Correct GELI header MD5.\n");
+	}
+
+	/* Convert header to cpu format */
+	hdr->md_version    = le32_to_cpu(hdr->md_version);
+	hdr->md_flags      = le32_to_cpu(hdr->md_flags);
+	hdr->md_ealgo      = le16_to_cpu(hdr->md_ealgo);
+	hdr->md_keylen     = le16_to_cpu(hdr->md_keylen);
+	hdr->md_aalgo      = le16_to_cpu(hdr->md_aalgo);
+	hdr->md_provsize   = le64_to_cpu(hdr->md_provsize);
+	hdr->md_sectorsize = le32_to_cpu(hdr->md_sectorsize);
+	// md_keys is 8 bit, no conversion needed
+	hdr->md_iterations = le32_to_cpu(hdr->md_iterations);
+
+	/* Set params */
+
+	return 0;
+}
+
+#if 0
+/*
+ * Kernel implements just big-endian version of blowfish, hack it here
+ */
+static void TCRYPT_swab_le(char *buf)
+{
+	uint32_t *l = (uint32_t*)&buf[0];
+	uint32_t *r = (uint32_t*)&buf[4];
+	*l = swab32(*l);
+	*r = swab32(*r);
+}
+
+static int decrypt_blowfish_le_cbc(struct tcrypt_alg *alg,
+				   const char *key, char *buf)
+{
+	int bs = alg->iv_size;
+	char iv[bs], iv_old[bs];
+	struct crypt_cipher *cipher = NULL;
+	int i, j, r;
+
+	assert(bs == 2*sizeof(uint32_t));
+
+	r = crypt_cipher_init(&cipher, "blowfish", "ecb",
+			      &key[alg->key_offset], alg->key_size);
+	if (r < 0)
+		return r;
+
+	memcpy(iv, &key[alg->iv_offset], alg->iv_size);
+	for (i = 0; i < TCRYPT_HDR_LEN; i += bs) {
+		memcpy(iv_old, &buf[i], bs);
+		TCRYPT_swab_le(&buf[i]);
+		r = crypt_cipher_decrypt(cipher, &buf[i], &buf[i],
+					  bs, NULL, 0);
+		TCRYPT_swab_le(&buf[i]);
+		if (r < 0)
+			break;
+		for (j = 0; j < bs; j++)
+			buf[i + j] ^= iv[j];
+		memcpy(iv, iv_old, bs);
+	}
+
+	crypt_cipher_destroy(cipher);
+	crypt_memzero(iv, bs);
+	crypt_memzero(iv_old, bs);
+	return r;
+}
+
+static void TCRYPT_remove_whitening(char *buf, const char *key)
+{
+	int j;
+
+	for (j = 0; j < TCRYPT_HDR_LEN; j++)
+		buf[j] ^= key[j % 8];
+}
+
+static void TCRYPT_copy_key(struct tcrypt_alg *alg, const char *mode,
+			     char *out_key, const char *key)
+{
+	int ks2;
+	if (!strncmp(mode, "xts", 3)) {
+		ks2 = alg->key_size / 2;
+		memcpy(out_key, &key[alg->key_offset], ks2);
+		memcpy(&out_key[ks2], &key[alg->iv_offset], ks2);
+	} else if (!strncmp(mode, "lrw", 3)) {
+		ks2 = alg->key_size - TCRYPT_LRW_IKEY_LEN;
+		memcpy(out_key, &key[alg->key_offset], ks2);
+		memcpy(&out_key[ks2], key, TCRYPT_LRW_IKEY_LEN);
+	} else if (!strncmp(mode, "cbc", 3)) {
+		memcpy(out_key, &key[alg->key_offset], alg->key_size);
+		/* IV + whitening */
+		memcpy(&out_key[alg->key_size], &key[alg->iv_offset],
+		       alg->key_extra_size);
+	}
+}
+
+static int TCRYPT_decrypt_hdr_one(struct tcrypt_alg *alg, const char *mode,
+				   const char *key,struct tcrypt_phdr *hdr)
+{
+	char backend_key[TCRYPT_HDR_KEY_LEN];
+	char iv[TCRYPT_HDR_IV_LEN] = {};
+	char mode_name[MAX_CIPHER_LEN + 1];
+	struct crypt_cipher *cipher;
+	char *c, *buf = (char*)&hdr->e;
+	int r;
+
+	/* Remove IV if present */
+	mode_name[MAX_CIPHER_LEN] = '\0';
+	strncpy(mode_name, mode, MAX_CIPHER_LEN);
+	c = strchr(mode_name, '-');
+	if (c)
+		*c = '\0';
+
+	if (!strncmp(mode, "lrw", 3))
+		iv[alg->iv_size - 1] = 1;
+	else if (!strncmp(mode, "cbc", 3)) {
+		TCRYPT_remove_whitening(buf, &key[8]);
+		if (!strcmp(alg->name, "blowfish_le"))
+			return decrypt_blowfish_le_cbc(alg, key, buf);
+		memcpy(iv, &key[alg->iv_offset], alg->iv_size);
+	}
+
+	TCRYPT_copy_key(alg, mode, backend_key, key);
+	r = crypt_cipher_init(&cipher, alg->name, mode_name,
+			      backend_key, alg->key_size);
+	if (!r) {
+		r = crypt_cipher_decrypt(cipher, buf, buf, TCRYPT_HDR_LEN,
+					 iv, alg->iv_size);
+		crypt_cipher_destroy(cipher);
+	}
+
+	crypt_memzero(backend_key, sizeof(backend_key));
+	crypt_memzero(iv, TCRYPT_HDR_IV_LEN);
+	return r;
+}
+
+/*
+ * For chanined ciphers and CBC mode we need "outer" decryption.
+ * Backend doesn't provide this, so implement it here directly using ECB.
+ */
+static int TCRYPT_decrypt_cbci(struct tcrypt_algs *ciphers,
+				const char *key, struct tcrypt_phdr *hdr)
+{
+	struct crypt_cipher *cipher[ciphers->chain_count];
+	unsigned int bs = ciphers->cipher[0].iv_size;
+	char *buf = (char*)&hdr->e, iv[bs], iv_old[bs];
+	unsigned int i, j;
+	int r = -EINVAL;
+
+	TCRYPT_remove_whitening(buf, &key[8]);
+
+	memcpy(iv, &key[ciphers->cipher[0].iv_offset], bs);
+
+	/* Initialize all ciphers in chain in ECB mode */
+	for (j = 0; j < ciphers->chain_count; j++)
+		cipher[j] = NULL;
+	for (j = 0; j < ciphers->chain_count; j++) {
+		r = crypt_cipher_init(&cipher[j], ciphers->cipher[j].name, "ecb",
+				      &key[ciphers->cipher[j].key_offset],
+				      ciphers->cipher[j].key_size);
+		if (r < 0)
+			goto out;
+	}
+
+	/* Implements CBC with chained ciphers in loop inside */
+	for (i = 0; i < TCRYPT_HDR_LEN; i += bs) {
+		memcpy(iv_old, &buf[i], bs);
+		for (j = ciphers->chain_count; j > 0; j--) {
+			r = crypt_cipher_decrypt(cipher[j - 1], &buf[i], &buf[i],
+						  bs, NULL, 0);
+			if (r < 0)
+				goto out;
+		}
+		for (j = 0; j < bs; j++)
+			buf[i + j] ^= iv[j];
+		memcpy(iv, iv_old, bs);
+	}
+out:
+	for (j = 0; j < ciphers->chain_count; j++)
+		if (cipher[j])
+			crypt_cipher_destroy(cipher[j]);
+
+	crypt_memzero(iv, bs);
+	crypt_memzero(iv_old, bs);
+	return r;
+}
+
+static int TCRYPT_decrypt_hdr(struct crypt_device *cd, struct tcrypt_phdr *hdr,
+			       const char *key, uint32_t flags)
+{
+	struct tcrypt_phdr hdr2;
+	int i, j, r = -EINVAL;
+
+	for (i = 0; tcrypt_cipher[i].chain_count; i++) {
+		if (!(flags & CRYPT_TCRYPT_LEGACY_MODES) && tcrypt_cipher[i].legacy)
+			continue;
+		log_dbg("TCRYPT:  trying cipher %s-%s",
+			tcrypt_cipher[i].long_name, tcrypt_cipher[i].mode);
+
+		memcpy(&hdr2.e, &hdr->e, TCRYPT_HDR_LEN);
+
+		if (!strncmp(tcrypt_cipher[i].mode, "cbci", 4))
+			r = TCRYPT_decrypt_cbci(&tcrypt_cipher[i], key, &hdr2);
+		else for (j = tcrypt_cipher[i].chain_count - 1; j >= 0 ; j--) {
+			if (!tcrypt_cipher[i].cipher[j].name)
+				continue;
+			r = TCRYPT_decrypt_hdr_one(&tcrypt_cipher[i].cipher[j],
+					    tcrypt_cipher[i].mode, key, &hdr2);
+			if (r < 0)
+				break;
+		}
+
+		if (r < 0) {
+			log_dbg("TCRYPT:   returned error %d, skipped.", r);
+			if (r == -ENOTSUP)
+				break;
+			r = -ENOENT;
+			continue;
+		}
+
+		if (!strncmp(hdr2.d.magic, TCRYPT_HDR_MAGIC, TCRYPT_HDR_MAGIC_LEN)) {
+			log_dbg("TCRYPT: Signature magic detected.");
+			memcpy(&hdr->e, &hdr2.e, TCRYPT_HDR_LEN);
+			r = i;
+			break;
+		}
+		if ((flags & CRYPT_TCRYPT_VERA_MODES) &&
+		     !strncmp(hdr2.d.magic, VCRYPT_HDR_MAGIC, TCRYPT_HDR_MAGIC_LEN)) {
+			log_dbg("TCRYPT: Signature magic detected (Veracrypt).");
+			memcpy(&hdr->e, &hdr2.e, TCRYPT_HDR_LEN);
+			r = i;
+			break;
+		}
+		r = -EPERM;
+	}
+
+	crypt_memzero(&hdr2, sizeof(hdr2));
+	return r;
+}
+
+static int TCRYPT_pool_keyfile(struct crypt_device *cd,
+				unsigned char pool[TCRYPT_KEY_POOL_LEN],
+				const char *keyfile)
+{
+	unsigned char data[TCRYPT_KEYFILE_LEN];
+	int i, j, fd, data_size;
+	uint32_t crc;
+
+	log_dbg("TCRYPT: using keyfile %s.", keyfile);
+
+	fd = open(keyfile, O_RDONLY);
+	if (fd < 0) {
+		log_err(cd, _("Failed to open key file.\n"));
+		return -EIO;
+	}
+
+	data_size = read_buffer(fd, data, TCRYPT_KEYFILE_LEN);
+	close(fd);
+	if (data_size < 0) {
+		log_err(cd, _("Error reading keyfile %s.\n"), keyfile);
+		return -EIO;
+	}
+
+	for (i = 0, j = 0, crc = ~0U; i < data_size; i++) {
+		crc = crypt_crc32(crc, &data[i], 1);
+		pool[j++] += (unsigned char)(crc >> 24);
+		pool[j++] += (unsigned char)(crc >> 16);
+		pool[j++] += (unsigned char)(crc >>  8);
+		pool[j++] += (unsigned char)(crc);
+		j %= TCRYPT_KEY_POOL_LEN;
+	}
+
+	crypt_memzero(&crc, sizeof(crc));
+	crypt_memzero(data, TCRYPT_KEYFILE_LEN);
+
+	return 0;
+}
+#endif
+
+static int GELI_init_hdr(struct crypt_device *cd,
+			   struct geli_phdr *hdr,
+			   struct crypt_params_geli *params)
+{
+	int r = -EPERM;
+#if 0
+	unsigned char pwd[TCRYPT_KEY_POOL_LEN] = {};
+	size_t passphrase_size;
+	char *key;
+	unsigned int i, skipped = 0;
+
+	if (posix_memalign((void*)&key, crypt_getpagesize(), TCRYPT_HDR_KEY_LEN))
+		return -ENOMEM;
+
+	if (params->keyfiles_count)
+		passphrase_size = TCRYPT_KEY_POOL_LEN;
+	else
+		passphrase_size = params->passphrase_size;
+
+	if (params->passphrase_size > TCRYPT_KEY_POOL_LEN) {
+		log_err(cd, _("Maximum TCRYPT passphrase length (%d) exceeded.\n"),
+			      TCRYPT_KEY_POOL_LEN);
+		goto out;
+	}
+
+	/* Calculate pool content from keyfiles */
+	for (i = 0; i < params->keyfiles_count; i++) {
+		r = TCRYPT_pool_keyfile(cd, pwd, params->keyfiles[i]);
+		if (r < 0)
+			goto out;
+	}
+
+	/* If provided password, combine it with pool */
+	for (i = 0; i < params->passphrase_size; i++)
+		pwd[i] += params->passphrase[i];
+
+	for (i = 0; tcrypt_kdf[i].name; i++) {
+		if (!(params->flags & CRYPT_TCRYPT_LEGACY_MODES) && tcrypt_kdf[i].legacy)
+			continue;
+		if (!(params->flags & CRYPT_TCRYPT_VERA_MODES) && tcrypt_kdf[i].veracrypt)
+			continue;
+		/* Derive header key */
+		log_dbg("TCRYPT: trying KDF: %s-%s-%d.",
+			tcrypt_kdf[i].name, tcrypt_kdf[i].hash, tcrypt_kdf[i].iterations);
+		r = crypt_pbkdf(tcrypt_kdf[i].name, tcrypt_kdf[i].hash,
+				(char*)pwd, passphrase_size,
+				hdr->salt, TCRYPT_HDR_SALT_LEN,
+				key, TCRYPT_HDR_KEY_LEN,
+				tcrypt_kdf[i].iterations);
+		if (r < 0 && crypt_hash_size(tcrypt_kdf[i].hash) < 0) {
+			log_verbose(cd, _("PBKDF2 hash algorithm %s not available, skipping.\n"),
+				      tcrypt_kdf[i].hash);
+			continue;
+		}
+		if (r < 0)
+			break;
+
+		/* Decrypt header */
+		r = TCRYPT_decrypt_hdr(cd, hdr, key, params->flags);
+		if (r == -ENOENT) {
+			skipped++;
+			r = -EPERM;
+		}
+		if (r != -EPERM)
+			break;
+	}
+
+	if ((r < 0 && r != -EPERM && skipped && skipped == i) || r == -ENOTSUP) {
+		log_err(cd, _("Required kernel crypto interface not available.\n"));
+#ifdef ENABLE_AF_ALG
+		log_err(cd, _("Ensure you have algif_skcipher kernel module loaded.\n"));
+#endif
+	}
+	if (r < 0)
+		goto out;
+#endif
+
+	log_dbg("GELI tracking: 101\n");
+	r = GELI_hdr_from_disk(hdr, params, r);
+	if (!r) {
+		log_dbg("GELI tracking: 102 magic OK\n");
+#if 0
+		log_dbg("TCRYPT: Magic: %s, Header version: %d, req. %d, sector %d"
+			", mk_offset %" PRIu64 ", hidden_size %" PRIu64
+			", volume size %" PRIu64, tcrypt_kdf[i].veracrypt ?
+			VCRYPT_HDR_MAGIC : TCRYPT_HDR_MAGIC,
+			(int)hdr->d.version, (int)hdr->d.version_tc, (int)hdr->d.sector_size,
+			hdr->d.mk_offset, hdr->d.hidden_volume_size, hdr->d.volume_size);
+		log_dbg("TCRYPT: Header cipher %s-%s, key size %zu",
+			params->cipher, params->mode, params->key_size);
+#endif
+	}
+out:
+#if 0
+	crypt_memzero(pwd, TCRYPT_KEY_POOL_LEN);
+	if (key)
+		crypt_memzero(key, TCRYPT_HDR_KEY_LEN);
+	free(key);
+#endif
+	return r;
+}
+
+int GELI_read_phdr(struct crypt_device *cd,
+		     struct geli_phdr *hdr,
+		     struct crypt_params_geli *params)
+{
+	struct device *device = crypt_metadata_device(cd);
+	ssize_t hdr_size = sizeof(struct geli_phdr);
+	//char *base_device_path;
+	int devfd = 0, r, bs;
+
+	assert(sizeof(struct geli_phdr) == 511);
+
+	log_dbg("Reading GELI header of size %zu bytes from device %s.",
+		hdr_size, device_path(device));
+
+	bs = device_block_size(device);
+	if (bs < 0)
+		return bs;
+
+#if 0
+	if (params->flags & CRYPT_TCRYPT_SYSTEM_HEADER &&
+	    crypt_dev_is_partition(device_path(device))) {
+		base_device_path = crypt_get_base_device(device_path(device));
+
+		log_dbg("Reading TCRYPT system header from device %s.", base_device_path ?: "?");
+		if (!base_device_path)
+			return -EINVAL;
+
+		r = device_alloc(&base_device, base_device_path);
+		free(base_device_path);
+		if (r < 0)
+			return r;
+		devfd = device_open(base_device, O_RDONLY);
+		device_free(base_device);
+	} else
+#endif
+		devfd = device_open(device, O_RDONLY);
+
+	if (devfd < 0) {
+		log_err(cd, _("Cannot open device %s.\n"), device_path(device));
+		return -EINVAL;
+	}
+
+	r = -EIO;
+#define GELI_HDR_OFFSET -512
+	if (lseek(devfd, GELI_HDR_OFFSET, SEEK_END) >= 0 &&
+	    read_blockwise(devfd, bs, hdr, hdr_size) == hdr_size) {
+		r = GELI_init_hdr(cd, hdr, params);
+	}
+
+	close(devfd);
+	if (r < 0)
+		memset(hdr, 0, sizeof (*hdr));
+	return r;
+}
+
+#if 0
+static struct tcrypt_algs *TCRYPT_get_algs(const char *cipher, const char *mode)
+{
+	int i;
+
+	if (!cipher || !mode)
+		return NULL;
+
+	for (i = 0; tcrypt_cipher[i].chain_count; i++)
+		if (!strcmp(tcrypt_cipher[i].long_name, cipher) &&
+		    !strcmp(tcrypt_cipher[i].mode, mode))
+		    return &tcrypt_cipher[i];
+
+	return NULL;
+}
+
+int TCRYPT_activate(struct crypt_device *cd,
+		     const char *name,
+		     struct tcrypt_phdr *hdr,
+		     struct crypt_params_tcrypt *params,
+		     uint32_t flags)
+{
+	char cipher[MAX_CIPHER_LEN], dm_name[PATH_MAX], dm_dev_name[PATH_MAX];
+	char *part_path;
+	struct device *device = NULL, *part_device = NULL;
+	unsigned int i;
+	int r;
+	uint32_t req_flags;
+	struct tcrypt_algs *algs;
+	enum devcheck device_check;
+	struct crypt_dm_active_device dmd = {
+		.target = DM_CRYPT,
+		.size   = 0,
+		.data_device = crypt_data_device(cd),
+		.u.crypt  = {
+			.cipher = cipher,
+			.offset = crypt_get_data_offset(cd),
+			.iv_offset = crypt_get_iv_offset(cd),
+		}
+	};
+
+	if (!hdr->d.version) {
+		log_dbg("TCRYPT: this function is not supported without encrypted header load.");
+		return -ENOTSUP;
+	}
+
+	if (hdr->d.sector_size && hdr->d.sector_size != SECTOR_SIZE) {
+		log_err(cd, _("Activation is not supported for %d sector size.\n"),
+			hdr->d.sector_size);
+		return -ENOTSUP;
+	}
+
+	if (strstr(params->mode, "-tcrypt")) {
+		log_err(cd, _("Kernel doesn't support activation for this TCRYPT legacy mode.\n"));
+		return -ENOTSUP;
+	}
+
+	if (strstr(params->mode, "-tcw"))
+		req_flags = DM_TCW_SUPPORTED;
+	else
+		req_flags = DM_PLAIN64_SUPPORTED;
+
+	algs = TCRYPT_get_algs(params->cipher, params->mode);
+	if (!algs)
+		return -EINVAL;
+
+	if (hdr->d.sector_size == 0)
+		return -EINVAL;
+
+	if (params->flags & CRYPT_TCRYPT_SYSTEM_HEADER)
+		dmd.size = 0;
+	else if (params->flags & CRYPT_TCRYPT_HIDDEN_HEADER)
+		dmd.size = hdr->d.hidden_volume_size / hdr->d.sector_size;
+	else
+		dmd.size = hdr->d.volume_size / hdr->d.sector_size;
+
+	if (dmd.flags & CRYPT_ACTIVATE_SHARED)
+		device_check = DEV_SHARED;
+	else
+		device_check = DEV_EXCL;
+
+	if ((params->flags & CRYPT_TCRYPT_SYSTEM_HEADER) &&
+	     !crypt_dev_is_partition(device_path(dmd.data_device))) {
+		part_path = crypt_get_partition_device(device_path(dmd.data_device),
+						       dmd.u.crypt.offset, dmd.size);
+		if (part_path) {
+			if (!device_alloc(&part_device, part_path)) {
+				log_verbose(cd, _("Activating TCRYPT system encryption for partition %s.\n"),
+					    part_path);
+				dmd.data_device = part_device;
+				dmd.u.crypt.offset = 0;
+			}
+			free(part_path);
+		} else
+			/*
+			 * System encryption use the whole device mapping, there can
+			 * be active partitions.
+			 */
+			device_check = DEV_SHARED;
+	}
+
+	r = device_block_adjust(cd, dmd.data_device, device_check,
+				dmd.u.crypt.offset, &dmd.size, &dmd.flags);
+	if (r) {
+		device_free(part_device);
+		return r;
+	}
+
+	/* Frome here, key size for every cipher must be the same */
+	dmd.u.crypt.vk = crypt_alloc_volume_key(algs->cipher[0].key_size +
+						algs->cipher[0].key_extra_size, NULL);
+	if (!dmd.u.crypt.vk) {
+		device_free(part_device);
+		return -ENOMEM;
+	}
+
+	for (i = algs->chain_count; i > 0; i--) {
+		if (i == 1) {
+			dm_name[sizeof(dm_name)-1] = '\0';
+			strncpy(dm_name, name, sizeof(dm_name)-1);
+			dmd.flags = flags;
+		} else {
+			snprintf(dm_name, sizeof(dm_name), "%s_%d", name, i-1);
+			dmd.flags = flags | CRYPT_ACTIVATE_PRIVATE;
+		}
+
+		snprintf(cipher, sizeof(cipher), "%s-%s",
+			 algs->cipher[i-1].name, algs->mode);
+
+		TCRYPT_copy_key(&algs->cipher[i-1], algs->mode,
+				dmd.u.crypt.vk->key, hdr->d.keys);
+
+		if (algs->chain_count != i) {
+			snprintf(dm_dev_name, sizeof(dm_dev_name), "%s/%s_%d",
+				 dm_get_dir(), name, i);
+			r = device_alloc(&device, dm_dev_name);
+			if (r)
+				break;
+			dmd.data_device = device;
+			dmd.u.crypt.offset = 0;
+		}
+
+		log_dbg("Trying to activate TCRYPT device %s using cipher %s.",
+			dm_name, dmd.u.crypt.cipher);
+		r = dm_create_device(cd, dm_name, CRYPT_TCRYPT, &dmd, 0);
+
+		device_free(device);
+		device = NULL;
+
+		if (r)
+			break;
+	}
+
+	if (r < 0 && !(dm_flags() & req_flags)) {
+		log_err(cd, _("Kernel doesn't support TCRYPT compatible mapping.\n"));
+		r = -ENOTSUP;
+	}
+
+	device_free(part_device);
+	crypt_free_volume_key(dmd.u.crypt.vk);
+	return r;
+}
+
+static int TCRYPT_remove_one(struct crypt_device *cd, const char *name,
+		      const char *base_uuid, int index)
+{
+	struct crypt_dm_active_device dmd = {};
+	char dm_name[PATH_MAX];
+	int r;
+
+	if (snprintf(dm_name, sizeof(dm_name), "%s_%d", name, index) < 0)
+		return -ENOMEM;
+
+	r = dm_status_device(cd, dm_name);
+	if (r < 0)
+		return r;
+
+	r = dm_query_device(cd, dm_name, DM_ACTIVE_UUID, &dmd);
+	if (!r && !strncmp(dmd.uuid, base_uuid, strlen(base_uuid)))
+		r = dm_remove_device(cd, dm_name, 0, 0);
+
+	free(CONST_CAST(void*)dmd.uuid);
+	return r;
+}
+
+int TCRYPT_deactivate(struct crypt_device *cd, const char *name)
+{
+	struct crypt_dm_active_device dmd = {};
+	int r;
+
+	r = dm_query_device(cd, name, DM_ACTIVE_UUID, &dmd);
+	if (r < 0)
+		return r;
+	if (!dmd.uuid)
+		return -EINVAL;
+
+	r = dm_remove_device(cd, name, 0, 0);
+	if (r < 0)
+		goto out;
+
+	r = TCRYPT_remove_one(cd, name, dmd.uuid, 1);
+	if (r < 0)
+		goto out;
+
+	r = TCRYPT_remove_one(cd, name, dmd.uuid, 2);
+	if (r < 0)
+		goto out;
+out:
+	free(CONST_CAST(void*)dmd.uuid);
+	return (r == -ENODEV) ? 0 : r;
+}
+
+static int TCRYPT_status_one(struct crypt_device *cd, const char *name,
+			      const char *base_uuid, int index,
+			      size_t *key_size, char *cipher,
+			      uint64_t *data_offset, struct device **device)
+{
+	struct crypt_dm_active_device dmd = {};
+	char dm_name[PATH_MAX], *c;
+	int r;
+
+	if (snprintf(dm_name, sizeof(dm_name), "%s_%d", name, index) < 0)
+		return -ENOMEM;
+
+	r = dm_status_device(cd, dm_name);
+	if (r < 0)
+		return r;
+
+	r = dm_query_device(cd, dm_name, DM_ACTIVE_DEVICE |
+					  DM_ACTIVE_UUID |
+					  DM_ACTIVE_CRYPT_CIPHER |
+					  DM_ACTIVE_CRYPT_KEYSIZE, &dmd);
+	if (r > 0)
+		r = 0;
+	if (!r && !strncmp(dmd.uuid, base_uuid, strlen(base_uuid))) {
+		if ((c = strchr(dmd.u.crypt.cipher, '-')))
+			*c = '\0';
+		strcat(cipher, "-");
+		strncat(cipher, dmd.u.crypt.cipher, MAX_CIPHER_LEN);
+		*key_size += dmd.u.crypt.vk->keylength;
+		*data_offset = dmd.u.crypt.offset * SECTOR_SIZE;
+		device_free(*device);
+		*device = dmd.data_device;
+	} else {
+		device_free(dmd.data_device);
+		r = -ENODEV;
+	}
+
+	free(CONST_CAST(void*)dmd.uuid);
+	free(CONST_CAST(void*)dmd.u.crypt.cipher);
+	crypt_free_volume_key(dmd.u.crypt.vk);
+	return r;
+}
+
+int TCRYPT_init_by_name(struct crypt_device *cd, const char *name,
+			const struct crypt_dm_active_device *dmd,
+			struct device **device,
+			struct crypt_params_tcrypt *tcrypt_params,
+			struct tcrypt_phdr *tcrypt_hdr)
+{
+	struct tcrypt_algs *algs;
+	char cipher[MAX_CIPHER_LEN * 4], mode[MAX_CIPHER_LEN+1], *tmp;
+	size_t key_size;
+	int r;
+
+	memset(tcrypt_params, 0, sizeof(*tcrypt_params));
+	memset(tcrypt_hdr, 0, sizeof(*tcrypt_hdr));
+	tcrypt_hdr->d.sector_size = SECTOR_SIZE;
+	tcrypt_hdr->d.mk_offset = dmd->u.crypt.offset * SECTOR_SIZE;
+
+	strncpy(cipher, dmd->u.crypt.cipher, MAX_CIPHER_LEN);
+	tmp = strchr(cipher, '-');
+	if (!tmp)
+		return -EINVAL;
+	*tmp = '\0';
+	mode[MAX_CIPHER_LEN] = '\0';
+	strncpy(mode, ++tmp, MAX_CIPHER_LEN);
+
+	key_size = dmd->u.crypt.vk->keylength;
+	r = TCRYPT_status_one(cd, name, dmd->uuid, 1, &key_size,
+			      cipher, &tcrypt_hdr->d.mk_offset, device);
+	if (!r)
+		r = TCRYPT_status_one(cd, name, dmd->uuid, 2, &key_size,
+				      cipher, &tcrypt_hdr->d.mk_offset, device);
+
+	if (r < 0 && r != -ENODEV)
+		return r;
+
+	algs = TCRYPT_get_algs(cipher, mode);
+	if (!algs || key_size != algs->chain_key_size)
+		return -EINVAL;
+
+	tcrypt_params->key_size = algs->chain_key_size;
+	tcrypt_params->cipher = algs->long_name;
+	tcrypt_params->mode = algs->mode;
+	return 0;
+}
+
+uint64_t TCRYPT_get_data_offset(struct crypt_device *cd,
+				 struct tcrypt_phdr *hdr,
+				 struct crypt_params_tcrypt *params)
+{
+	uint64_t size;
+
+	/* No real header loaded, initialized by active device */
+	if (!hdr->d.version)
+		goto hdr_offset;
+
+	/* Mapping through whole device, not partition! */
+	if (params->flags & CRYPT_TCRYPT_SYSTEM_HEADER) {
+		if (crypt_dev_is_partition(device_path(crypt_metadata_device(cd))))
+			return 0;
+		goto hdr_offset;
+	}
+
+	if (params->mode && !strncmp(params->mode, "xts", 3)) {
+		if (hdr->d.version < 3)
+			return 1;
+
+		if (params->flags & CRYPT_TCRYPT_HIDDEN_HEADER) {
+			if (hdr->d.version > 3)
+				return (hdr->d.mk_offset / hdr->d.sector_size);
+			if (device_size(crypt_metadata_device(cd), &size) < 0)
+				return 0;
+			return (size - hdr->d.hidden_volume_size +
+				(TCRYPT_HDR_HIDDEN_OFFSET_OLD)) / hdr->d.sector_size;
+		}
+		goto hdr_offset;
+	}
+
+	if (params->flags & CRYPT_TCRYPT_HIDDEN_HEADER) {
+		if (device_size(crypt_metadata_device(cd), &size) < 0)
+			return 0;
+		return (size - hdr->d.hidden_volume_size +
+			(TCRYPT_HDR_HIDDEN_OFFSET_OLD)) / hdr->d.sector_size;
+	}
+
+hdr_offset:
+	return hdr->d.mk_offset / hdr->d.sector_size;
+}
+
+uint64_t TCRYPT_get_iv_offset(struct crypt_device *cd,
+			      struct tcrypt_phdr *hdr,
+			      struct crypt_params_tcrypt *params)
+{
+	uint64_t iv_offset;
+
+	if (params->mode && !strncmp(params->mode, "xts", 3))
+		iv_offset = TCRYPT_get_data_offset(cd, hdr, params);
+	else if (params->mode && !strncmp(params->mode, "lrw", 3))
+		iv_offset = 0;
+	else
+		iv_offset = hdr->d.mk_offset / hdr->d.sector_size;
+
+	if (params->flags & CRYPT_TCRYPT_SYSTEM_HEADER)
+		iv_offset += crypt_dev_partition_offset(device_path(crypt_metadata_device(cd)));
+
+	return iv_offset;
+}
+
+int TCRYPT_get_volume_key(struct crypt_device *cd,
+			  struct tcrypt_phdr *hdr,
+			  struct crypt_params_tcrypt *params,
+			  struct volume_key **vk)
+{
+	struct tcrypt_algs *algs;
+	unsigned int i, key_index;
+
+	if (!hdr->d.version) {
+		log_err(cd, _("This function is not supported without TCRYPT header load."));
+		return -ENOTSUP;
+	}
+
+	algs = TCRYPT_get_algs(params->cipher, params->mode);
+	if (!algs)
+		return -EINVAL;
+
+	*vk = crypt_alloc_volume_key(params->key_size, NULL);
+	if (!*vk)
+		return -ENOMEM;
+
+	for (i = 0, key_index = 0; i < algs->chain_count; i++) {
+		TCRYPT_copy_key(&algs->cipher[i], algs->mode,
+				&(*vk)->key[key_index], hdr->d.keys);
+		key_index += algs->cipher[i].key_size;
+	}
+
+	return 0;
+}
+#endif
+
+static int log_std_hexdump(struct crypt_device *cd, uint8_t *p, int len)
+{
+	int i;
+
+
+	for(i = 0; i < len; i++) {
+		if (i && !(i % 16))
+			log_std(cd, "\n\t\t");
+		log_std(cd, "%02hhx ", (char)p[i]);
+	}
+	log_std(cd, "\n");
+
+	return 0;
+}
+
+int GELI_dump(struct crypt_device *cd,
+		struct geli_phdr *hdr,
+		struct crypt_params_geli *params)
+{
+	log_std(cd, "GELI header information for %s\n",
+		device_path(crypt_metadata_device(cd)));
+
+	if (hdr->md_version == 7) {
+		log_std(cd, "Magic:\t\t%s\n", hdr->md_magic);
+		log_std(cd, "Version:\t%" PRIu32 "\n", hdr->md_version);
+		log_std(cd, "Flags:\t\t0x%" PRIx32 "\n", hdr->md_flags);
+		log_std(cd, "Encryption algo:\t0x%" PRIx16 "\n", hdr->md_ealgo);
+		log_std(cd, "Key length:\t%" PRIu16 "\n", hdr->md_keylen);
+		log_std(cd, "Authentication algo:\t0x%" PRIx16 "\n", hdr->md_aalgo);
+		log_std(cd, "Provider size:\t%" PRIu64 "\n", hdr->md_provsize);
+		log_std(cd, "Sector size:\t%" PRIu32 "\n", hdr->md_sectorsize);
+		log_std(cd, "Number of keys:\t%" PRIu8 "\n", hdr->md_keys);
+		log_std(cd, "Number of iterations:\t%" PRIu32 "\n", hdr->md_iterations);
+		log_std(cd, "Salt:\t\t\t");
+		log_std_hexdump(cd, hdr->md_salt, G_ELI_SALTLEN);
+		log_std(cd, "Encrypted MKeys:\t");
+		log_std_hexdump(cd, hdr->md_mkeysbuf, G_ELI_MAXMKEYS * G_ELI_MKEYLEN);
+		log_std(cd, "Encr. IV key 0:\t\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[0].md_ivkey, G_ELI_IVKEYLEN);
+		log_std(cd, "Encr. data key 0:\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[0].md_datakey, G_ELI_DATAKEYLEN);
+		log_std(cd, "Encr. SHA512 HMAC 0:\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[0].md_hmacsha512, SHA512_MDLEN);
+		log_std(cd, "Encr. IV key 1:\t\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[1].md_ivkey, G_ELI_IVKEYLEN);
+		log_std(cd, "Encr. data key 1:\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[1].md_datakey, G_ELI_DATAKEYLEN);
+		log_std(cd, "Encr. SHA512 HMAC 1:\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[1].md_hmacsha512, SHA512_MDLEN);
+		log_std(cd, "MD5:\t\t\t");
+		log_std_hexdump(cd, hdr->md_hash, 16);
+	} else {
+		log_std(cd, "Version %d not supported\n", hdr->md_version);
+	}
+	return 0;
+}
diff --git a/lib/geli/geli.h b/lib/geli/geli.h
new file mode 100644
index 0000000..7e9070e
--- /dev/null
+++ b/lib/geli/geli.h
@@ -0,0 +1,102 @@
+/*
+ * FreeBSD GEOM::ELI GELI compatible volume handling
+ *
+ * Copyright (C) 2017, Carl-Daniel Hailfinger
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _GELI_H
+#define _GELI_H
+
+#include <unistd.h>
+
+/* Relevant header parts from FreeBSD sys/geom/eli/g_eli.h
+ * TODO: Figure out if that is actually OK from a licensing perspective.
+ */
+#define	G_ELI_MAGIC		"GEOM::ELI"
+
+#define SHA512_MDLEN		64
+
+#define	G_ELI_MAXMKEYS		2
+#define	G_ELI_MAXKEYLEN		64
+#define	G_ELI_USERKEYLEN	G_ELI_MAXKEYLEN
+#define	G_ELI_DATAKEYLEN	G_ELI_MAXKEYLEN
+#define	G_ELI_AUTHKEYLEN	G_ELI_MAXKEYLEN
+#define	G_ELI_IVKEYLEN		G_ELI_MAXKEYLEN
+#define	G_ELI_SALTLEN		64
+#define	G_ELI_DATAIVKEYLEN	(G_ELI_DATAKEYLEN + G_ELI_IVKEYLEN)
+/* Data-Key, IV-Key, HMAC_SHA512(Derived-Key, Data-Key+IV-Key) */
+#define	G_ELI_MKEYLEN		(G_ELI_DATAIVKEYLEN + SHA512_MDLEN)
+#define MD5_MDLEN		16
+
+struct geli_mkeyslot {
+	uint8_t		md_ivkey[G_ELI_IVKEYLEN];
+	uint8_t		md_datakey[G_ELI_DATAKEYLEN];
+	uint8_t		md_hmacsha512[SHA512_MDLEN];
+};
+
+struct geli_phdr {
+	union {
+		struct {
+			char		md_magic[16];	/* Magic value. */
+			uint32_t	md_version;	/* Version number. */
+			uint32_t	md_flags;	/* Additional flags. */
+			uint16_t	md_ealgo;	/* Encryption algorithm. */
+			uint16_t	md_keylen;	/* Key length. */
+			uint16_t	md_aalgo;	/* Authentication algorithm. */
+			uint64_t	md_provsize;	/* Provider's size. */
+			uint32_t	md_sectorsize;	/* Sector size. */
+			uint8_t		md_keys;	/* Available keys. */
+			int32_t		md_iterations;	/* Number of iterations for PKCS#5v2. */
+			uint8_t		md_salt[G_ELI_SALTLEN]; /* Salt. */
+					/* Encrypted master key (IV-key, Data-key, HMAC). */
+			union {
+				uint8_t			md_mkeysbuf[G_ELI_MAXMKEYS * G_ELI_MKEYLEN];
+				struct geli_mkeyslot	md_mkeyslot[G_ELI_MAXMKEYS];
+			} __attribute__((__packed__));
+			u_char		md_hash[MD5_MDLEN];	/* MD5 hash. */
+		} __attribute__((__packed__));
+		char			bytearray[511];
+	} __attribute__((__packed__));
+} __attribute__((__packed__));
+
+int GELI_read_phdr(struct crypt_device *cd,
+		     struct geli_phdr *hdr,
+		     struct crypt_params_geli *params);
+
+int GELI_dump(struct crypt_device *cd,
+		struct geli_phdr *hdr,
+		struct crypt_params_geli *params);
+
+#if 0
+struct crypt_device;
+struct volume_key;
+
+int GELI_parse_keyfile(struct crypt_device *cd,
+			  struct volume_key **vk,
+			  const char *hash,
+			  unsigned int *keys_count,
+			  char *buffer,
+			  size_t buffer_len);
+
+int GELI_activate(struct crypt_device *cd,
+		     const char *name,
+		     const char *base_cipher,
+		     unsigned int keys_count,
+		     struct volume_key *vk,
+		     uint32_t flags);
+#endif
+#endif
diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h
index 80bbf5c..8e31444 100644
--- a/lib/libcryptsetup.h
+++ b/lib/libcryptsetup.h
@@ -243,6 +243,8 @@ int crypt_memory_lock(struct crypt_device *cd, int lock);
 #define CRYPT_VERITY "VERITY"
 /** TCRYPT (TrueCrypt-compatible and VeraCrypt-compatible) mode */
 #define CRYPT_TCRYPT "TCRYPT"
+/** GELI (FreeBSD GEOM::ELI) mode */
+#define CRYPT_GELI "GELI"
 
 /**
  * Get device type
@@ -354,6 +356,17 @@ struct crypt_params_tcrypt {
  */
 #define CRYPT_TCRYPT_VERA_MODES      (1 << 4)
 
+/**
+ *
+ * Structure used as parameter for GELI device type.
+ *
+ * @see crypt_format
+ *
+ */
+struct crypt_params_geli {
+	int dummy;
+};
+
 /** @} */
 
 /**
diff --git a/lib/setup.c b/lib/setup.c
index 1dca99b..7d394ac 100644
--- a/lib/setup.c
+++ b/lib/setup.c
@@ -34,6 +34,7 @@
 #include "loopaes.h"
 #include "verity.h"
 #include "tcrypt.h"
+#include "geli.h"
 #include "internal.h"
 
 struct crypt_device {
@@ -76,6 +77,10 @@ struct crypt_device {
 		struct crypt_params_tcrypt params;
 		struct tcrypt_phdr hdr;
 	} tcrypt;
+	struct { /* used in CRYPT_GELI */
+		struct crypt_params_geli params;
+		struct geli_phdr hdr;
+	} geli;
 	struct { /* used if initialized without header by name */
 		char *active_name;
 		/* buffers, must refresh from kernel on every query */
@@ -245,6 +250,11 @@ static int isTCRYPT(const char *type)
 	return (type && !strcmp(CRYPT_TCRYPT, type));
 }
 
+static int isGELI(const char *type)
+{
+	return (type && !strcmp(CRYPT_GELI, type));
+}
+
 static int onlyLUKS(struct crypt_device *cd)
 {
 	int r = 0;
@@ -581,6 +591,30 @@ static int _crypt_load_tcrypt(struct crypt_device *cd, struct crypt_params_tcryp
 	return r;
 }
 
+static int _crypt_load_geli(struct crypt_device *cd, struct crypt_params_geli *params)
+{
+	int r;
+
+	if (!params)
+		return -EINVAL;
+
+	r = init_crypto(cd);
+	if (r < 0)
+		return r;
+
+	memcpy(&cd->u.geli.params, params, sizeof(*params));
+
+	r = GELI_read_phdr(cd, &cd->u.geli.hdr, &cd->u.geli.params);
+
+	if (r < 0)
+		return r;
+
+	if (!cd->type && !(cd->type = strdup(CRYPT_GELI)))
+		return -ENOMEM;
+
+	return r;
+}
+
 static int _crypt_load_verity(struct crypt_device *cd, struct crypt_params_verity *params)
 {
 	int r;
@@ -683,6 +717,9 @@ static int _init_by_name_crypt(struct crypt_device *cd, const char *name)
 	} else if (isTCRYPT(cd->type)) {
 		r = TCRYPT_init_by_name(cd, name, &dmd, &cd->device,
 					&cd->u.tcrypt.params, &cd->u.tcrypt.hdr);
+	} else if (isGELI(cd->type)) {
+		//FIXME r = GELI_init_by_name(cd, name, &dmd, &cd->device,
+		//FIXME			&cd->u.geli.params, &cd->u.geli.hdr);
 	}
 out:
 	crypt_free_volume_key(dmd.u.crypt.vk);
@@ -790,6 +827,8 @@ int crypt_init_by_name_and_header(struct crypt_device **cd,
 			(*cd)->type = strdup(CRYPT_VERITY);
 		else if (!strncmp(CRYPT_TCRYPT, dmd.uuid, sizeof(CRYPT_TCRYPT)-1))
 			(*cd)->type = strdup(CRYPT_TCRYPT);
+		else if (!strncmp(CRYPT_GELI, dmd.uuid, sizeof(CRYPT_GELI)-1))
+			(*cd)->type = strdup(CRYPT_GELI);
 		else
 			log_dbg("Unknown UUID set, some parameters are not set.");
 	} else
@@ -1177,6 +1216,12 @@ int crypt_load(struct crypt_device *cd,
 			return -EINVAL;
 		}
 		r = _crypt_load_tcrypt(cd, params);
+	} else if (isGELI(requested_type)) {
+		if (cd->type && !isGELI(cd->type)) {
+			log_dbg("Context is already initialised to type %s", cd->type);
+			return -EINVAL;
+		}
+		r = _crypt_load_geli(cd, params);
 	} else
 		return -EINVAL;
 
@@ -2032,6 +2077,11 @@ int crypt_activate_by_volume_key(struct crypt_device *cd,
 			return 0;
 		r = TCRYPT_activate(cd, name, &cd->u.tcrypt.hdr,
 				    &cd->u.tcrypt.params, flags);
+	} else if (isGELI(cd->type)) {
+		if (!name)
+			return 0;
+		//FIXME r = GELI_activate(cd, name, &cd->u.geli.hdr,
+		//FIXME		    &cd->u.geli.params, flags);
 	} else
 		log_err(cd, _("Device type is not properly initialised.\n"));
 
@@ -2115,6 +2165,8 @@ int crypt_volume_key_get(struct crypt_device *cd,
 					passphrase_size, &cd->u.luks1.hdr, &vk, cd);
 	} else if (isTCRYPT(cd->type)) {
 		r = TCRYPT_get_volume_key(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params, &vk);
+	} else if (isGELI(cd->type)) {
+		//FIXME r = GELI_get_volume_key(cd, &cd->u.geli.hdr, &cd->u.geli.params, &vk);
 	} else
 		log_err(cd, _("This operation is not supported for %s crypt device.\n"), cd->type ?: "(none)");
 
@@ -2289,6 +2341,8 @@ int crypt_dump(struct crypt_device *cd)
 		return _verity_dump(cd);
 	else if (isTCRYPT(cd->type))
 		return TCRYPT_dump(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params);
+	else if (isGELI(cd->type))
+		return GELI_dump(cd, &cd->u.geli.hdr, &cd->u.geli.params);
 
 	log_err(cd, _("Dump operation is not supported for this device type.\n"));
 	return -EINVAL;
@@ -2333,6 +2387,10 @@ const char *crypt_get_cipher(struct crypt_device *cd)
 	if (isTCRYPT(cd->type))
 		return cd->u.tcrypt.params.cipher;
 
+	if (isGELI(cd->type))
+		return "aes";
+		//FIXME return cd->u.geli.params.cipher;
+
 	if (!cd->type && !_init_by_name_crypt_none(cd))
 		return cd->u.none.cipher;
 
@@ -2353,6 +2411,10 @@ const char *crypt_get_cipher_mode(struct crypt_device *cd)
 	if (isTCRYPT(cd->type))
 		return cd->u.tcrypt.params.mode;
 
+	if (isGELI(cd->type))
+		return "xts-byte64";
+		//FIXME return cd->u.geli.params.mode;
+
 	if (!cd->type && !_init_by_name_crypt_none(cd))
 		return cd->u.none.cipher_mode;
 
@@ -2397,6 +2459,10 @@ int crypt_get_volume_key_size(struct crypt_device *cd)
 	if (isTCRYPT(cd->type))
 		return cd->u.tcrypt.params.key_size;
 
+	if (isGELI(cd->type))
+		return 256;
+		//FIXME return cd->u.geli.params.key_size;
+
 	if (!cd->type && !_init_by_name_crypt_none(cd))
 		return cd->u.none.key_size;
 
@@ -2434,6 +2500,7 @@ uint64_t crypt_get_iv_offset(struct crypt_device *cd)
 	if (isTCRYPT(cd->type))
 		return TCRYPT_get_iv_offset(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params);
 
+	//FIXME: GELI?
 	return 0;
 }
 
diff --git a/src/cryptsetup.c b/src/cryptsetup.c
index 2d6ddaf..a665fea 100644
--- a/src/cryptsetup.c
+++ b/src/cryptsetup.c
@@ -382,6 +382,101 @@ out:
 	return r;
 }
 
+static int geli_load(struct crypt_device *cd, struct crypt_params_geli *params)
+{
+	int r, eperm = 0;
+
+	do {
+		log_std("GELI tracking: 5\n");
+		r = crypt_load(cd, CRYPT_GELI, params);
+		log_std("GELI tracking: 6 crypt_load returned %i\n", r);
+
+		if (r == -EPERM) {
+			log_err(_("No device header detected with the given parameters.\n"));
+			eperm = 1;
+		}
+
+#if 0
+		if (r < 0) {
+			crypt_safe_free(CONST_CAST(char*)params->passphrase);
+			params->passphrase = NULL;
+			params->passphrase_size = 0;
+		}
+#endif
+		check_signal(&r);
+	} while (0);
+
+	/* Report wrong passphrase if at least one try failed */
+	if (eperm && r == -EPIPE)
+		r = -EPERM;
+
+	log_std("GELI tracking: 7\n");
+	return r;
+}
+
+static int action_open_geli(void)
+{
+	struct crypt_device *cd = NULL;
+	struct crypt_params_geli params = {
+		.dummy = 0,
+	};
+	const char *activated_name;
+	uint32_t activate_flags = 0;
+	int r;
+
+	activated_name = opt_test_passphrase ? NULL : action_argv[1];
+
+	log_std("GELI tracking: 9\n");
+	if ((r = crypt_init(&cd, action_argv[0])))
+		goto out;
+
+	log_std("GELI tracking: 10\n");
+	r = geli_load(cd, &params);
+	if (r < 0)
+		goto out;
+
+	log_std("GELI tracking: 11\n");
+	_set_activation_flags(&activate_flags);
+
+	log_std("GELI tracking: 12\n");
+	if (activated_name)
+		r = crypt_activate_by_volume_key(cd, activated_name, NULL, 0, activate_flags);
+out:
+	log_std("GELI tracking: 13\n");
+	crypt_free(cd);
+	return r;
+}
+
+static int action_geliDump(void)
+{
+	struct crypt_device *cd = NULL;
+	struct crypt_params_geli params = {
+		.dummy = 0,
+	};
+	int r;
+
+	log_std("GELI tracking: 1\n");
+	if ((r = crypt_init(&cd, action_argv[0])))
+		goto out;
+
+	log_std("GELI tracking: 2\n");
+	r = geli_load(cd, &params);
+	if (r < 0)
+		goto out;
+
+#if 0
+	if (opt_dump_master_key)
+		r = geliDump_with_volume_key(cd);
+	else
+#endif
+	log_std("GELI tracking: 3\n");
+		r = crypt_dump(cd);
+out:
+	crypt_free(cd);
+	log_std("GELI tracking: 4\n");
+	return r;
+}
+
 static int action_close(void)
 {
 	struct crypt_device *cd = NULL;
@@ -1295,6 +1390,7 @@ out:
 
 static int action_open(void)
 {
+	log_std("GELI tracking: 14\n");
 	if (!opt_type)
 		return -EINVAL;
 
@@ -1314,6 +1410,11 @@ static int action_open(void)
 		if (action_argc < 2 && !opt_test_passphrase)
 			goto args;
 		return action_open_tcrypt();
+	} else if (!strcmp(opt_type, "geli")) {
+		log_std("GELI tracking: 8\n");
+		if (action_argc < 2)
+			goto args;
+		return action_open_geli();
 	}
 
 	log_err(_("Unrecognized metadata device type %s.\n"), opt_type);
@@ -1388,6 +1489,7 @@ static struct action_type {
 	{ "isLuks",       action_isLuks,       1, 0, N_("<device>"), N_("tests <device> for LUKS partition header") },
 	{ "luksDump",     action_luksDump,     1, 1, N_("<device>"), N_("dump LUKS partition information") },
 	{ "tcryptDump",   action_tcryptDump,   1, 1, N_("<device>"), N_("dump TCRYPT device information") },
+	{ "geliDump",     action_geliDump,     1, 1, N_("<device>"), N_("dump GELI device information") },
 	{ "luksSuspend",  action_luksSuspend,  1, 1, N_("<device>"), N_("Suspend LUKS device and wipe key (all IOs are frozen).") },
 	{ "luksResume",   action_luksResume,   1, 1, N_("<device>"), N_("Resume suspended LUKS device.") },
 	{ "luksHeaderBackup", action_luksBackup,1,1, N_("<device>"), N_("Backup LUKS device header and keyslots") },
@@ -1524,7 +1626,7 @@ int main(int argc, const char **argv)
 		{ "tcrypt-system",     '\0', POPT_ARG_NONE, &opt_tcrypt_system,         0, N_("Device is system TCRYPT drive (with bootloader)."), NULL },
 		{ "tcrypt-backup",     '\0', POPT_ARG_NONE, &opt_tcrypt_backup,         0, N_("Use backup (secondary) TCRYPT header."), NULL },
 		{ "veracrypt",         '\0', POPT_ARG_NONE, &opt_veracrypt,             0, N_("Scan also for VeraCrypt compatible device."), NULL },
-		{ "type",               'M', POPT_ARG_STRING, &opt_type,                0, N_("Type of device metadata: luks, plain, loopaes, tcrypt."), NULL },
+		{ "type",               'M', POPT_ARG_STRING, &opt_type,                0, N_("Type of device metadata: luks, plain, loopaes, tcrypt, geli."), NULL },
 		{ "force-password",    '\0', POPT_ARG_NONE, &opt_force_password,        0, N_("Disable password quality check (if enabled)."), NULL },
 		{ "perf-same_cpu_crypt",'\0', POPT_ARG_NONE, &opt_perf_same_cpu_crypt,  0, N_("Use dm-crypt same_cpu_crypt performance compatibility option."), NULL },
 		{ "perf-submit_from_crypt_cpus",'\0', POPT_ARG_NONE, &opt_perf_submit_from_crypt_cpus,0,N_("Use dm-crypt submit_from_crypt_cpus performance compatibility option."), NULL },
@@ -1631,6 +1733,8 @@ int main(int argc, const char **argv)
 		opt_type = "tcrypt";
 	} else if (!strcmp(aname, "tcryptDump")) {
 		opt_type = "tcrypt";
+	} else if (!strcmp(aname, "geliDump")) {
+		opt_type = "geli";
 	} else if (!strcmp(aname, "remove") ||
 		   !strcmp(aname, "plainClose") ||
 		   !strcmp(aname, "luksClose") ||
-- 
1.9.1

-------------- next part --------------
From f6c84b5a1823bbfd576f99bd3c03b40e47063d7c Mon Sep 17 00:00:00 2001
From: Carl-Daniel Hailfinger <c-d.hailfinger.devel.2006 at gmx.net>
Date: Fri, 30 Dec 2016 05:40:44 +0100
Subject: [PATCH] Initial version of cryptsetup with GELI support. Only dumping
 is supported right now.

---
 configure.ac         |    1 +
 lib/Makefile.am      |    6 +-
 lib/geli/Makefile.am |   14 +
 lib/geli/README.txt  |    4 +
 lib/geli/geli.c      | 1157 ++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/geli/geli.h      |  102 +++++
 lib/libcryptsetup.h  |   13 +
 lib/setup.c          |   67 +++
 src/cryptsetup.c     |  106 ++++-
 9 files changed, 1467 insertions(+), 3 deletions(-)
 create mode 100644 lib/geli/Makefile.am
 create mode 100644 lib/geli/README.txt
 create mode 100644 lib/geli/geli.c
 create mode 100644 lib/geli/geli.h

diff --git a/configure.ac b/configure.ac
index d1f029a..1c46a7d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -467,6 +467,7 @@ lib/luks1/Makefile
 lib/loopaes/Makefile
 lib/verity/Makefile
 lib/tcrypt/Makefile
+lib/geli/Makefile
 src/Makefile
 po/Makefile.in
 man/Makefile
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 6662568..5b99a7c 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = crypto_backend luks1 loopaes verity tcrypt
+SUBDIRS = crypto_backend luks1 loopaes verity tcrypt geli
 
 moduledir = $(libdir)/cryptsetup
 
@@ -12,6 +12,7 @@ AM_CPPFLAGS = -include config.h \
 	-I$(top_srcdir)/lib/loopaes		\
 	-I$(top_srcdir)/lib/verity		\
 	-I$(top_srcdir)/lib/tcrypt		\
+	-I$(top_srcdir)/lib/geli		\
 	-DDATADIR=\""$(datadir)"\"		\
 	-DLIBDIR=\""$(libdir)"\"		\
 	-DPREFIX=\""$(prefix)"\"		\
@@ -25,7 +26,8 @@ common_ldadd = \
 	luks1/libluks1.la			\
 	loopaes/libloopaes.la			\
 	verity/libverity.la			\
-	tcrypt/libtcrypt.la
+	tcrypt/libtcrypt.la			\
+	geli/libgeli.la
 
 libcryptsetup_la_DEPENDENCIES = $(common_ldadd) libcryptsetup.sym
 
diff --git a/lib/geli/Makefile.am b/lib/geli/Makefile.am
new file mode 100644
index 0000000..96aef4e
--- /dev/null
+++ b/lib/geli/Makefile.am
@@ -0,0 +1,14 @@
+moduledir = $(libdir)/cryptsetup
+
+noinst_LTLIBRARIES = libgeli.la
+
+libgeli_la_CFLAGS = -Wall $(AM_CFLAGS) @CRYPTO_CFLAGS@
+
+libgeli_la_SOURCES = \
+	geli.c \
+	geli.h
+
+AM_CPPFLAGS = -include config.h \
+        -I$(top_srcdir)/lib			\
+        -I$(top_srcdir)/lib/crypto_backend
+
diff --git a/lib/geli/README.txt b/lib/geli/README.txt
new file mode 100644
index 0000000..f62075c
--- /dev/null
+++ b/lib/geli/README.txt
@@ -0,0 +1,4 @@
+The user key is derived from the keyfile as follows:
+openssl dgst -sha512 -mac hmac -macopt hexkey:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 user.key
+
+Use AES-XTS-128
diff --git a/lib/geli/geli.c b/lib/geli/geli.c
new file mode 100644
index 0000000..d6e1951
--- /dev/null
+++ b/lib/geli/geli.c
@@ -0,0 +1,1157 @@
+/*
+ * TCRYPT (TrueCrypt-compatible) and VeraCrypt volume handling
+ *
+ * Copyright (C) 2012, Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2012-2015, Milan Broz
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "libcryptsetup.h"
+#include "geli.h"
+#include "internal.h"
+
+#if 0
+/* TCRYPT PBKDF variants */
+static struct {
+	unsigned int legacy:1;
+	unsigned int veracrypt:1;
+	const char *name;
+	const char *hash;
+	unsigned int iterations;
+} tcrypt_kdf[] = {
+	{ 0, 0, "pbkdf2", "ripemd160", 2000 },
+	{ 0, 0, "pbkdf2", "ripemd160", 1000 },
+	{ 0, 0, "pbkdf2", "sha512",    1000 },
+	{ 0, 0, "pbkdf2", "whirlpool", 1000 },
+	{ 1, 0, "pbkdf2", "sha1",      2000 },
+	{ 0, 1, "pbkdf2", "sha512",    500000 },
+	{ 0, 1, "pbkdf2", "ripemd160", 655331 },
+	{ 0, 1, "pbkdf2", "ripemd160", 327661 }, // boot only
+	{ 0, 1, "pbkdf2", "whirlpool", 500000 },
+	{ 0, 1, "pbkdf2", "sha256",    500000 }, // VeraCrypt 1.0f
+	{ 0, 1, "pbkdf2", "sha256",    200000 }, // boot only
+	{ 0, 0, NULL,     NULL,        0 }
+};
+
+struct tcrypt_alg {
+		const char *name;
+		unsigned int key_size;
+		unsigned int iv_size;
+		unsigned int key_offset;
+		unsigned int iv_offset; /* or tweak key offset */
+		unsigned int key_extra_size;
+};
+
+struct tcrypt_algs {
+	unsigned int legacy:1;
+	unsigned int chain_count;
+	unsigned int chain_key_size;
+	const char *long_name;
+	const char *mode;
+	struct tcrypt_alg cipher[3];
+};
+
+/* TCRYPT cipher variants */
+static struct tcrypt_algs tcrypt_cipher[] = {
+/* XTS mode */
+{0,1,64,"aes","xts-plain64",
+	{{"aes",    64,16,0,32,0}}},
+{0,1,64,"serpent","xts-plain64",
+	{{"serpent",64,16,0,32,0}}},
+{0,1,64,"twofish","xts-plain64",
+	{{"twofish",64,16,0,32,0}}},
+{0,2,128,"twofish-aes","xts-plain64",
+	{{"twofish",64,16, 0,64,0},
+	 {"aes",    64,16,32,96,0}}},
+{0,3,192,"serpent-twofish-aes","xts-plain64",
+	{{"serpent",64,16, 0, 96,0},
+	 {"twofish",64,16,32,128,0},
+	 {"aes",    64,16,64,160,0}}},
+{0,2,128,"aes-serpent","xts-plain64",
+	{{"aes",    64,16, 0,64,0},
+	 {"serpent",64,16,32,96,0}}},
+{0,3,192,"aes-twofish-serpent","xts-plain64",
+	{{"aes",    64,16, 0, 96,0},
+	 {"twofish",64,16,32,128,0},
+	 {"serpent",64,16,64,160,0}}},
+{0,2,128,"serpent-twofish","xts-plain64",
+	{{"serpent",64,16, 0,64,0},
+	 {"twofish",64,16,32,96,0}}},
+
+/* LRW mode */
+{0,1,48,"aes","lrw-benbi",
+	{{"aes",    48,16,32,0,0}}},
+{0,1,48,"serpent","lrw-benbi",
+	{{"serpent",48,16,32,0,0}}},
+{0,1,48,"twofish","lrw-benbi",
+	{{"twofish",48,16,32,0,0}}},
+{0,2,96,"twofish-aes","lrw-benbi",
+	{{"twofish",48,16,32,0,0},
+	 {"aes",    48,16,64,0,0}}},
+{0,3,144,"serpent-twofish-aes","lrw-benbi",
+	{{"serpent",48,16,32,0,0},
+	 {"twofish",48,16,64,0,0},
+	 {"aes",    48,16,96,0,0}}},
+{0,2,96,"aes-serpent","lrw-benbi",
+	{{"aes",    48,16,32,0,0},
+	 {"serpent",48,16,64,0,0}}},
+{0,3,144,"aes-twofish-serpent","lrw-benbi",
+	{{"aes",    48,16,32,0,0},
+	 {"twofish",48,16,64,0,0},
+	 {"serpent",48,16,96,0,0}}},
+{0,2,96,"serpent-twofish", "lrw-benbi",
+	{{"serpent",48,16,32,0,0},
+	 {"twofish",48,16,64,0,0}}},
+
+/* Kernel LRW block size is fixed to 16 bytes for GF(2^128)
+ * thus cannot be used with blowfish where block is 8 bytes.
+ * There also no GF(2^64) support.
+{1,1,64,"blowfish_le","lrw-benbi",
+	 {{"blowfish_le",64,8,32,0,0}}},
+{1,2,112,"blowfish_le-aes","lrw-benbi",
+	 {{"blowfish_le",64, 8,32,0,0},
+	  {"aes",        48,16,88,0,0}}},
+{1,3,160,"serpent-blowfish_le-aes","lrw-benbi",
+	  {{"serpent",    48,16, 32,0,0},
+	   {"blowfish_le",64, 8, 64,0,0},
+	   {"aes",        48,16,120,0,0}}},*/
+
+/*
+ * CBC + "outer" CBC (both with whitening)
+ * chain_key_size: alg_keys_bytes + IV_seed_bytes + whitening_bytes
+ */
+{1,1,32+16+16,"aes","cbc-tcw",
+	{{"aes",    32,16,32,0,32}}},
+{1,1,32+16+16,"serpent","cbc-tcw",
+	{{"serpent",32,16,32,0,32}}},
+{1,1,32+16+16,"twofish","cbc-tcw",
+	{{"twofish",32,16,32,0,32}}},
+{1,2,64+16+16,"twofish-aes","cbci-tcrypt",
+	{{"twofish",32,16,32,0,0},
+	 {"aes",    32,16,64,0,32}}},
+{1,3,96+16+16,"serpent-twofish-aes","cbci-tcrypt",
+	{{"serpent",32,16,32,0,0},
+	 {"twofish",32,16,64,0,0},
+	 {"aes",    32,16,96,0,32}}},
+{1,2,64+16+16,"aes-serpent","cbci-tcrypt",
+	{{"aes",    32,16,32,0,0},
+	 {"serpent",32,16,64,0,32}}},
+{1,3,96+16+16,"aes-twofish-serpent", "cbci-tcrypt",
+	{{"aes",    32,16,32,0,0},
+	 {"twofish",32,16,64,0,0},
+	 {"serpent",32,16,96,0,32}}},
+{1,2,64+16+16,"serpent-twofish", "cbci-tcrypt",
+	{{"serpent",32,16,32,0,0},
+	 {"twofish",32,16,64,0,32}}},
+{1,1,16+8+16,"cast5","cbc-tcw",
+	{{"cast5",   16,8,32,0,24}}},
+{1,1,24+8+16,"des3_ede","cbc-tcw",
+	{{"des3_ede",24,8,32,0,24}}},
+{1,1,56+8+16,"blowfish_le","cbc-tcrypt",
+	{{"blowfish_le",56,8,32,0,24}}},
+{1,2,88+16+16,"blowfish_le-aes","cbc-tcrypt",
+	{{"blowfish_le",56, 8,32,0,0},
+	 {"aes",        32,16,88,0,32}}},
+{1,3,120+16+16,"serpent-blowfish_le-aes","cbc-tcrypt",
+	{{"serpent",    32,16, 32,0,0},
+	 {"blowfish_le",56, 8, 64,0,0},
+	 {"aes",        32,16,120,0,32}}},
+{}
+};
+#endif
+
+static int GELI_hdr_from_disk(struct geli_phdr *hdr,
+				struct crypt_params_geli *params,
+				int cipher_index)
+{
+	struct crypt_hash *hd = NULL;
+	char calculated_md5[MD5_MDLEN];
+	int r;
+
+	log_dbg("GELI tracking: 100\n");
+	/* Check sanity of header */
+	if (strcmp(G_ELI_MAGIC, hdr->md_magic)) {
+		log_dbg("Missing GELI magic.\n");
+		return -EINVAL;
+	} else {
+		log_dbg("Correct GELI magic.\n");
+	}
+
+	/* Check MD5 of header */
+	crypt_memzero(calculated_md5, MD5_MDLEN);
+	if (crypt_hash_init(&hd, "md5"))
+		return -EINVAL;
+
+	r = crypt_hash_write(hd, hdr->bytearray, offsetof(struct geli_phdr, md_hash));
+	if (!r)
+		r = crypt_hash_final(hd, calculated_md5, MD5_MDLEN);
+
+	crypt_hash_destroy(hd);
+	if (r)
+		return r;
+
+	if (memcmp(calculated_md5, hdr->md_hash, sizeof(calculated_md5))) {
+		log_dbg("GELI header MD5 mismatch.");
+		return -EINVAL;
+	} else {
+		log_dbg("Correct GELI header MD5.\n");
+	}
+
+	/* Convert header to cpu format */
+	hdr->md_version    = le32_to_cpu(hdr->md_version);
+	hdr->md_flags      = le32_to_cpu(hdr->md_flags);
+	hdr->md_ealgo      = le16_to_cpu(hdr->md_ealgo);
+	hdr->md_keylen     = le16_to_cpu(hdr->md_keylen);
+	hdr->md_aalgo      = le16_to_cpu(hdr->md_aalgo);
+	hdr->md_provsize   = le64_to_cpu(hdr->md_provsize);
+	hdr->md_sectorsize = le32_to_cpu(hdr->md_sectorsize);
+	// md_keys is 8 bit, no conversion needed
+	hdr->md_iterations = le32_to_cpu(hdr->md_iterations);
+
+	/* Set params */
+
+	return 0;
+}
+
+#if 0
+/*
+ * Kernel implements just big-endian version of blowfish, hack it here
+ */
+static void TCRYPT_swab_le(char *buf)
+{
+	uint32_t *l = (uint32_t*)&buf[0];
+	uint32_t *r = (uint32_t*)&buf[4];
+	*l = swab32(*l);
+	*r = swab32(*r);
+}
+
+static int decrypt_blowfish_le_cbc(struct tcrypt_alg *alg,
+				   const char *key, char *buf)
+{
+	int bs = alg->iv_size;
+	char iv[bs], iv_old[bs];
+	struct crypt_cipher *cipher = NULL;
+	int i, j, r;
+
+	assert(bs == 2*sizeof(uint32_t));
+
+	r = crypt_cipher_init(&cipher, "blowfish", "ecb",
+			      &key[alg->key_offset], alg->key_size);
+	if (r < 0)
+		return r;
+
+	memcpy(iv, &key[alg->iv_offset], alg->iv_size);
+	for (i = 0; i < TCRYPT_HDR_LEN; i += bs) {
+		memcpy(iv_old, &buf[i], bs);
+		TCRYPT_swab_le(&buf[i]);
+		r = crypt_cipher_decrypt(cipher, &buf[i], &buf[i],
+					  bs, NULL, 0);
+		TCRYPT_swab_le(&buf[i]);
+		if (r < 0)
+			break;
+		for (j = 0; j < bs; j++)
+			buf[i + j] ^= iv[j];
+		memcpy(iv, iv_old, bs);
+	}
+
+	crypt_cipher_destroy(cipher);
+	crypt_memzero(iv, bs);
+	crypt_memzero(iv_old, bs);
+	return r;
+}
+
+static void TCRYPT_remove_whitening(char *buf, const char *key)
+{
+	int j;
+
+	for (j = 0; j < TCRYPT_HDR_LEN; j++)
+		buf[j] ^= key[j % 8];
+}
+
+static void TCRYPT_copy_key(struct tcrypt_alg *alg, const char *mode,
+			     char *out_key, const char *key)
+{
+	int ks2;
+	if (!strncmp(mode, "xts", 3)) {
+		ks2 = alg->key_size / 2;
+		memcpy(out_key, &key[alg->key_offset], ks2);
+		memcpy(&out_key[ks2], &key[alg->iv_offset], ks2);
+	} else if (!strncmp(mode, "lrw", 3)) {
+		ks2 = alg->key_size - TCRYPT_LRW_IKEY_LEN;
+		memcpy(out_key, &key[alg->key_offset], ks2);
+		memcpy(&out_key[ks2], key, TCRYPT_LRW_IKEY_LEN);
+	} else if (!strncmp(mode, "cbc", 3)) {
+		memcpy(out_key, &key[alg->key_offset], alg->key_size);
+		/* IV + whitening */
+		memcpy(&out_key[alg->key_size], &key[alg->iv_offset],
+		       alg->key_extra_size);
+	}
+}
+
+static int TCRYPT_decrypt_hdr_one(struct tcrypt_alg *alg, const char *mode,
+				   const char *key,struct tcrypt_phdr *hdr)
+{
+	char backend_key[TCRYPT_HDR_KEY_LEN];
+	char iv[TCRYPT_HDR_IV_LEN] = {};
+	char mode_name[MAX_CIPHER_LEN + 1];
+	struct crypt_cipher *cipher;
+	char *c, *buf = (char*)&hdr->e;
+	int r;
+
+	/* Remove IV if present */
+	mode_name[MAX_CIPHER_LEN] = '\0';
+	strncpy(mode_name, mode, MAX_CIPHER_LEN);
+	c = strchr(mode_name, '-');
+	if (c)
+		*c = '\0';
+
+	if (!strncmp(mode, "lrw", 3))
+		iv[alg->iv_size - 1] = 1;
+	else if (!strncmp(mode, "cbc", 3)) {
+		TCRYPT_remove_whitening(buf, &key[8]);
+		if (!strcmp(alg->name, "blowfish_le"))
+			return decrypt_blowfish_le_cbc(alg, key, buf);
+		memcpy(iv, &key[alg->iv_offset], alg->iv_size);
+	}
+
+	TCRYPT_copy_key(alg, mode, backend_key, key);
+	r = crypt_cipher_init(&cipher, alg->name, mode_name,
+			      backend_key, alg->key_size);
+	if (!r) {
+		r = crypt_cipher_decrypt(cipher, buf, buf, TCRYPT_HDR_LEN,
+					 iv, alg->iv_size);
+		crypt_cipher_destroy(cipher);
+	}
+
+	crypt_memzero(backend_key, sizeof(backend_key));
+	crypt_memzero(iv, TCRYPT_HDR_IV_LEN);
+	return r;
+}
+
+/*
+ * For chanined ciphers and CBC mode we need "outer" decryption.
+ * Backend doesn't provide this, so implement it here directly using ECB.
+ */
+static int TCRYPT_decrypt_cbci(struct tcrypt_algs *ciphers,
+				const char *key, struct tcrypt_phdr *hdr)
+{
+	struct crypt_cipher *cipher[ciphers->chain_count];
+	unsigned int bs = ciphers->cipher[0].iv_size;
+	char *buf = (char*)&hdr->e, iv[bs], iv_old[bs];
+	unsigned int i, j;
+	int r = -EINVAL;
+
+	TCRYPT_remove_whitening(buf, &key[8]);
+
+	memcpy(iv, &key[ciphers->cipher[0].iv_offset], bs);
+
+	/* Initialize all ciphers in chain in ECB mode */
+	for (j = 0; j < ciphers->chain_count; j++)
+		cipher[j] = NULL;
+	for (j = 0; j < ciphers->chain_count; j++) {
+		r = crypt_cipher_init(&cipher[j], ciphers->cipher[j].name, "ecb",
+				      &key[ciphers->cipher[j].key_offset],
+				      ciphers->cipher[j].key_size);
+		if (r < 0)
+			goto out;
+	}
+
+	/* Implements CBC with chained ciphers in loop inside */
+	for (i = 0; i < TCRYPT_HDR_LEN; i += bs) {
+		memcpy(iv_old, &buf[i], bs);
+		for (j = ciphers->chain_count; j > 0; j--) {
+			r = crypt_cipher_decrypt(cipher[j - 1], &buf[i], &buf[i],
+						  bs, NULL, 0);
+			if (r < 0)
+				goto out;
+		}
+		for (j = 0; j < bs; j++)
+			buf[i + j] ^= iv[j];
+		memcpy(iv, iv_old, bs);
+	}
+out:
+	for (j = 0; j < ciphers->chain_count; j++)
+		if (cipher[j])
+			crypt_cipher_destroy(cipher[j]);
+
+	crypt_memzero(iv, bs);
+	crypt_memzero(iv_old, bs);
+	return r;
+}
+
+static int TCRYPT_decrypt_hdr(struct crypt_device *cd, struct tcrypt_phdr *hdr,
+			       const char *key, uint32_t flags)
+{
+	struct tcrypt_phdr hdr2;
+	int i, j, r = -EINVAL;
+
+	for (i = 0; tcrypt_cipher[i].chain_count; i++) {
+		if (!(flags & CRYPT_TCRYPT_LEGACY_MODES) && tcrypt_cipher[i].legacy)
+			continue;
+		log_dbg("TCRYPT:  trying cipher %s-%s",
+			tcrypt_cipher[i].long_name, tcrypt_cipher[i].mode);
+
+		memcpy(&hdr2.e, &hdr->e, TCRYPT_HDR_LEN);
+
+		if (!strncmp(tcrypt_cipher[i].mode, "cbci", 4))
+			r = TCRYPT_decrypt_cbci(&tcrypt_cipher[i], key, &hdr2);
+		else for (j = tcrypt_cipher[i].chain_count - 1; j >= 0 ; j--) {
+			if (!tcrypt_cipher[i].cipher[j].name)
+				continue;
+			r = TCRYPT_decrypt_hdr_one(&tcrypt_cipher[i].cipher[j],
+					    tcrypt_cipher[i].mode, key, &hdr2);
+			if (r < 0)
+				break;
+		}
+
+		if (r < 0) {
+			log_dbg("TCRYPT:   returned error %d, skipped.", r);
+			if (r == -ENOTSUP)
+				break;
+			r = -ENOENT;
+			continue;
+		}
+
+		if (!strncmp(hdr2.d.magic, TCRYPT_HDR_MAGIC, TCRYPT_HDR_MAGIC_LEN)) {
+			log_dbg("TCRYPT: Signature magic detected.");
+			memcpy(&hdr->e, &hdr2.e, TCRYPT_HDR_LEN);
+			r = i;
+			break;
+		}
+		if ((flags & CRYPT_TCRYPT_VERA_MODES) &&
+		     !strncmp(hdr2.d.magic, VCRYPT_HDR_MAGIC, TCRYPT_HDR_MAGIC_LEN)) {
+			log_dbg("TCRYPT: Signature magic detected (Veracrypt).");
+			memcpy(&hdr->e, &hdr2.e, TCRYPT_HDR_LEN);
+			r = i;
+			break;
+		}
+		r = -EPERM;
+	}
+
+	crypt_memzero(&hdr2, sizeof(hdr2));
+	return r;
+}
+#endif
+
+// original name: GELI_pool_keyfile
+static int GELI_genkeyencryptionkey(struct crypt_device *cd,
+				unsigned char kek[64],
+				const char *keyfile)
+{
+	struct crypt_hmac *ctx;
+	unsigned char buf[64];
+	int i, j, fd, data_size, total_size = 0;
+
+	log_dbg("GELI: using keyfile %s.", keyfile);
+
+	fd = open(keyfile, O_RDONLY);
+	if (fd < 0) {
+		log_err(cd, _("Failed to open key file.\n"));
+		return -EIO;
+	}
+
+	/* SHA512 HMAC with zero key. */
+	r = crypt_hmac_init(&ctx, "sha512", NULL, 0);
+	if (r) {
+		log_err(cd, _("Error in HMAC init.\n"));
+		close(fd);
+		return -EINVAL;
+	}
+
+	do {
+		data_size = read_buffer(fd, buf, sizeof(buf));
+		if (data_size < 0) {
+			log_err(cd, _("Error reading keyfile %s.\n"), keyfile);
+			crypt_hmac_destroy(&ctx);
+			close(fd);
+			return -EIO;
+		}
+		r = crypt_hmac_write(&ctx, buf, data_size);
+		if (r) {
+			log_err(cd, _("Error in HMAC update.\n"));
+			crypt_hmac_destroy(&ctx);
+			close(fd);
+			return -EINVAL;
+		}
+		total_size += data_size;
+	} while (data_size > 0);
+
+	close(fd);
+	r = crypt_hmac_final(&ctx, kek, sizeof(kek));
+	if (r) {
+		crypt_hmac_destroy(&ctx);
+		return -EINVAL;
+	}
+	
+	crypt_hmac_destroy(&ctx);
+
+	crypt_memzero(buf, sizeof(buf));
+
+	return 0;
+}
+
+#if 0
+int crypt_hmac_size(const char *name);
+int crypt_hmac_init(struct crypt_hmac **ctx, const char *name,
+		    const void *buffer, size_t length);
+int crypt_hmac_write(struct crypt_hmac *ctx, const char *buffer, size_t length);
+int crypt_hmac_final(struct crypt_hmac *ctx, char *buffer, size_t length);
+int crypt_hmac_destroy(struct crypt_hmac *ctx);
+
+static int GELI_genkeyencryptionkey();
+
+while ((done = read(fd, buf, sizeof(buf))) > 0)
+	g_eli_crypto_hmac_update(ctxp, buf, done);
+
+static unsigned char * eli_genkey(struct gctl_req *req, struct g_eli_metadata *md, unsigned char *key, bool new)
+{
+        struct hmac_ctx ctx;
+        bool nopassphrase;
+        int nfiles;
+
+        nopassphrase =
+            gctl_get_int(req, new ? "nonewpassphrase" : "nopassphrase");
+
+        g_eli_crypto_hmac_init(&ctx, NULL, 0);
+
+        nfiles = eli_genkey_files(req, new, "keyfile", &ctx, NULL, 0);
+        if (nfiles == -1)
+                return (NULL);
+        else if (nfiles == 0 && nopassphrase) {
+                gctl_error(req, "No key components given.");
+                return (NULL);
+        }
+
+        if (eli_genkey_passphrase(req, md, new, &ctx) == -1)
+                return (NULL);
+
+        g_eli_crypto_hmac_final(&ctx, key, 0);
+
+        return (key);
+}
+#endif
+
+static int GELI_init_hdr(struct crypt_device *cd,
+			   struct geli_phdr *hdr,
+			   struct crypt_params_geli *params)
+{
+	int r = -EPERM;
+#if 0
+	unsigned char pwd[TCRYPT_KEY_POOL_LEN] = {};
+	size_t passphrase_size;
+	char *key;
+	unsigned int i, skipped = 0;
+
+	if (posix_memalign((void*)&key, crypt_getpagesize(), TCRYPT_HDR_KEY_LEN))
+		return -ENOMEM;
+
+	if (params->keyfiles_count)
+		passphrase_size = TCRYPT_KEY_POOL_LEN;
+	else
+		passphrase_size = params->passphrase_size;
+
+	if (params->passphrase_size > TCRYPT_KEY_POOL_LEN) {
+		log_err(cd, _("Maximum TCRYPT passphrase length (%d) exceeded.\n"),
+			      TCRYPT_KEY_POOL_LEN);
+		goto out;
+	}
+
+	/* Calculate pool content from keyfiles */
+	for (i = 0; i < params->keyfiles_count; i++) {
+		r = TCRYPT_pool_keyfile(cd, pwd, params->keyfiles[i]);
+		if (r < 0)
+			goto out;
+	}
+
+	/* If provided password, combine it with pool */
+	for (i = 0; i < params->passphrase_size; i++)
+		pwd[i] += params->passphrase[i];
+
+	for (i = 0; tcrypt_kdf[i].name; i++) {
+		if (!(params->flags & CRYPT_TCRYPT_LEGACY_MODES) && tcrypt_kdf[i].legacy)
+			continue;
+		if (!(params->flags & CRYPT_TCRYPT_VERA_MODES) && tcrypt_kdf[i].veracrypt)
+			continue;
+		/* Derive header key */
+		log_dbg("TCRYPT: trying KDF: %s-%s-%d.",
+			tcrypt_kdf[i].name, tcrypt_kdf[i].hash, tcrypt_kdf[i].iterations);
+		r = crypt_pbkdf(tcrypt_kdf[i].name, tcrypt_kdf[i].hash,
+				(char*)pwd, passphrase_size,
+				hdr->salt, TCRYPT_HDR_SALT_LEN,
+				key, TCRYPT_HDR_KEY_LEN,
+				tcrypt_kdf[i].iterations);
+		if (r < 0 && crypt_hash_size(tcrypt_kdf[i].hash) < 0) {
+			log_verbose(cd, _("PBKDF2 hash algorithm %s not available, skipping.\n"),
+				      tcrypt_kdf[i].hash);
+			continue;
+		}
+		if (r < 0)
+			break;
+
+		/* Decrypt header */
+		r = TCRYPT_decrypt_hdr(cd, hdr, key, params->flags);
+		if (r == -ENOENT) {
+			skipped++;
+			r = -EPERM;
+		}
+		if (r != -EPERM)
+			break;
+	}
+
+	if ((r < 0 && r != -EPERM && skipped && skipped == i) || r == -ENOTSUP) {
+		log_err(cd, _("Required kernel crypto interface not available.\n"));
+#ifdef ENABLE_AF_ALG
+		log_err(cd, _("Ensure you have algif_skcipher kernel module loaded.\n"));
+#endif
+	}
+	if (r < 0)
+		goto out;
+#endif
+
+	log_dbg("GELI tracking: 101\n");
+	r = GELI_hdr_from_disk(hdr, params, r);
+	if (!r) {
+		log_dbg("GELI tracking: 102 magic OK\n");
+#if 0
+		log_dbg("TCRYPT: Magic: %s, Header version: %d, req. %d, sector %d"
+			", mk_offset %" PRIu64 ", hidden_size %" PRIu64
+			", volume size %" PRIu64, tcrypt_kdf[i].veracrypt ?
+			VCRYPT_HDR_MAGIC : TCRYPT_HDR_MAGIC,
+			(int)hdr->d.version, (int)hdr->d.version_tc, (int)hdr->d.sector_size,
+			hdr->d.mk_offset, hdr->d.hidden_volume_size, hdr->d.volume_size);
+		log_dbg("TCRYPT: Header cipher %s-%s, key size %zu",
+			params->cipher, params->mode, params->key_size);
+#endif
+	}
+out:
+#if 0
+	crypt_memzero(pwd, TCRYPT_KEY_POOL_LEN);
+	if (key)
+		crypt_memzero(key, TCRYPT_HDR_KEY_LEN);
+	free(key);
+#endif
+	return r;
+}
+
+int GELI_read_phdr(struct crypt_device *cd,
+		     struct geli_phdr *hdr,
+		     struct crypt_params_geli *params)
+{
+	struct device *device = crypt_metadata_device(cd);
+	ssize_t hdr_size = sizeof(struct geli_phdr);
+	//char *base_device_path;
+	int devfd = 0, r, bs;
+
+	assert(sizeof(struct geli_phdr) == 511);
+
+	log_dbg("Reading GELI header of size %zu bytes from device %s.",
+		hdr_size, device_path(device));
+
+	bs = device_block_size(device);
+	if (bs < 0)
+		return bs;
+
+#if 0
+	if (params->flags & CRYPT_TCRYPT_SYSTEM_HEADER &&
+	    crypt_dev_is_partition(device_path(device))) {
+		base_device_path = crypt_get_base_device(device_path(device));
+
+		log_dbg("Reading TCRYPT system header from device %s.", base_device_path ?: "?");
+		if (!base_device_path)
+			return -EINVAL;
+
+		r = device_alloc(&base_device, base_device_path);
+		free(base_device_path);
+		if (r < 0)
+			return r;
+		devfd = device_open(base_device, O_RDONLY);
+		device_free(base_device);
+	} else
+#endif
+		devfd = device_open(device, O_RDONLY);
+
+	if (devfd < 0) {
+		log_err(cd, _("Cannot open device %s.\n"), device_path(device));
+		return -EINVAL;
+	}
+
+	r = -EIO;
+#define GELI_HDR_OFFSET -512
+	if (lseek(devfd, GELI_HDR_OFFSET, SEEK_END) >= 0 &&
+	    read_blockwise(devfd, bs, hdr, hdr_size) == hdr_size) {
+		r = GELI_init_hdr(cd, hdr, params);
+	}
+
+	close(devfd);
+	if (r < 0)
+		memset(hdr, 0, sizeof (*hdr));
+	return r;
+}
+
+#if 0
+static struct tcrypt_algs *TCRYPT_get_algs(const char *cipher, const char *mode)
+{
+	int i;
+
+	if (!cipher || !mode)
+		return NULL;
+
+	for (i = 0; tcrypt_cipher[i].chain_count; i++)
+		if (!strcmp(tcrypt_cipher[i].long_name, cipher) &&
+		    !strcmp(tcrypt_cipher[i].mode, mode))
+		    return &tcrypt_cipher[i];
+
+	return NULL;
+}
+
+int TCRYPT_activate(struct crypt_device *cd,
+		     const char *name,
+		     struct tcrypt_phdr *hdr,
+		     struct crypt_params_tcrypt *params,
+		     uint32_t flags)
+{
+	char cipher[MAX_CIPHER_LEN], dm_name[PATH_MAX], dm_dev_name[PATH_MAX];
+	char *part_path;
+	struct device *device = NULL, *part_device = NULL;
+	unsigned int i;
+	int r;
+	uint32_t req_flags;
+	struct tcrypt_algs *algs;
+	enum devcheck device_check;
+	struct crypt_dm_active_device dmd = {
+		.target = DM_CRYPT,
+		.size   = 0,
+		.data_device = crypt_data_device(cd),
+		.u.crypt  = {
+			.cipher = cipher,
+			.offset = crypt_get_data_offset(cd),
+			.iv_offset = crypt_get_iv_offset(cd),
+		}
+	};
+
+	if (!hdr->d.version) {
+		log_dbg("TCRYPT: this function is not supported without encrypted header load.");
+		return -ENOTSUP;
+	}
+
+	if (hdr->d.sector_size && hdr->d.sector_size != SECTOR_SIZE) {
+		log_err(cd, _("Activation is not supported for %d sector size.\n"),
+			hdr->d.sector_size);
+		return -ENOTSUP;
+	}
+
+	if (strstr(params->mode, "-tcrypt")) {
+		log_err(cd, _("Kernel doesn't support activation for this TCRYPT legacy mode.\n"));
+		return -ENOTSUP;
+	}
+
+	if (strstr(params->mode, "-tcw"))
+		req_flags = DM_TCW_SUPPORTED;
+	else
+		req_flags = DM_PLAIN64_SUPPORTED;
+
+	algs = TCRYPT_get_algs(params->cipher, params->mode);
+	if (!algs)
+		return -EINVAL;
+
+	if (hdr->d.sector_size == 0)
+		return -EINVAL;
+
+	if (params->flags & CRYPT_TCRYPT_SYSTEM_HEADER)
+		dmd.size = 0;
+	else if (params->flags & CRYPT_TCRYPT_HIDDEN_HEADER)
+		dmd.size = hdr->d.hidden_volume_size / hdr->d.sector_size;
+	else
+		dmd.size = hdr->d.volume_size / hdr->d.sector_size;
+
+	if (dmd.flags & CRYPT_ACTIVATE_SHARED)
+		device_check = DEV_SHARED;
+	else
+		device_check = DEV_EXCL;
+
+	if ((params->flags & CRYPT_TCRYPT_SYSTEM_HEADER) &&
+	     !crypt_dev_is_partition(device_path(dmd.data_device))) {
+		part_path = crypt_get_partition_device(device_path(dmd.data_device),
+						       dmd.u.crypt.offset, dmd.size);
+		if (part_path) {
+			if (!device_alloc(&part_device, part_path)) {
+				log_verbose(cd, _("Activating TCRYPT system encryption for partition %s.\n"),
+					    part_path);
+				dmd.data_device = part_device;
+				dmd.u.crypt.offset = 0;
+			}
+			free(part_path);
+		} else
+			/*
+			 * System encryption use the whole device mapping, there can
+			 * be active partitions.
+			 */
+			device_check = DEV_SHARED;
+	}
+
+	r = device_block_adjust(cd, dmd.data_device, device_check,
+				dmd.u.crypt.offset, &dmd.size, &dmd.flags);
+	if (r) {
+		device_free(part_device);
+		return r;
+	}
+
+	/* Frome here, key size for every cipher must be the same */
+	dmd.u.crypt.vk = crypt_alloc_volume_key(algs->cipher[0].key_size +
+						algs->cipher[0].key_extra_size, NULL);
+	if (!dmd.u.crypt.vk) {
+		device_free(part_device);
+		return -ENOMEM;
+	}
+
+	for (i = algs->chain_count; i > 0; i--) {
+		if (i == 1) {
+			dm_name[sizeof(dm_name)-1] = '\0';
+			strncpy(dm_name, name, sizeof(dm_name)-1);
+			dmd.flags = flags;
+		} else {
+			snprintf(dm_name, sizeof(dm_name), "%s_%d", name, i-1);
+			dmd.flags = flags | CRYPT_ACTIVATE_PRIVATE;
+		}
+
+		snprintf(cipher, sizeof(cipher), "%s-%s",
+			 algs->cipher[i-1].name, algs->mode);
+
+		TCRYPT_copy_key(&algs->cipher[i-1], algs->mode,
+				dmd.u.crypt.vk->key, hdr->d.keys);
+
+		if (algs->chain_count != i) {
+			snprintf(dm_dev_name, sizeof(dm_dev_name), "%s/%s_%d",
+				 dm_get_dir(), name, i);
+			r = device_alloc(&device, dm_dev_name);
+			if (r)
+				break;
+			dmd.data_device = device;
+			dmd.u.crypt.offset = 0;
+		}
+
+		log_dbg("Trying to activate TCRYPT device %s using cipher %s.",
+			dm_name, dmd.u.crypt.cipher);
+		r = dm_create_device(cd, dm_name, CRYPT_TCRYPT, &dmd, 0);
+
+		device_free(device);
+		device = NULL;
+
+		if (r)
+			break;
+	}
+
+	if (r < 0 && !(dm_flags() & req_flags)) {
+		log_err(cd, _("Kernel doesn't support TCRYPT compatible mapping.\n"));
+		r = -ENOTSUP;
+	}
+
+	device_free(part_device);
+	crypt_free_volume_key(dmd.u.crypt.vk);
+	return r;
+}
+
+static int TCRYPT_remove_one(struct crypt_device *cd, const char *name,
+		      const char *base_uuid, int index)
+{
+	struct crypt_dm_active_device dmd = {};
+	char dm_name[PATH_MAX];
+	int r;
+
+	if (snprintf(dm_name, sizeof(dm_name), "%s_%d", name, index) < 0)
+		return -ENOMEM;
+
+	r = dm_status_device(cd, dm_name);
+	if (r < 0)
+		return r;
+
+	r = dm_query_device(cd, dm_name, DM_ACTIVE_UUID, &dmd);
+	if (!r && !strncmp(dmd.uuid, base_uuid, strlen(base_uuid)))
+		r = dm_remove_device(cd, dm_name, 0, 0);
+
+	free(CONST_CAST(void*)dmd.uuid);
+	return r;
+}
+
+int TCRYPT_deactivate(struct crypt_device *cd, const char *name)
+{
+	struct crypt_dm_active_device dmd = {};
+	int r;
+
+	r = dm_query_device(cd, name, DM_ACTIVE_UUID, &dmd);
+	if (r < 0)
+		return r;
+	if (!dmd.uuid)
+		return -EINVAL;
+
+	r = dm_remove_device(cd, name, 0, 0);
+	if (r < 0)
+		goto out;
+
+	r = TCRYPT_remove_one(cd, name, dmd.uuid, 1);
+	if (r < 0)
+		goto out;
+
+	r = TCRYPT_remove_one(cd, name, dmd.uuid, 2);
+	if (r < 0)
+		goto out;
+out:
+	free(CONST_CAST(void*)dmd.uuid);
+	return (r == -ENODEV) ? 0 : r;
+}
+
+static int TCRYPT_status_one(struct crypt_device *cd, const char *name,
+			      const char *base_uuid, int index,
+			      size_t *key_size, char *cipher,
+			      uint64_t *data_offset, struct device **device)
+{
+	struct crypt_dm_active_device dmd = {};
+	char dm_name[PATH_MAX], *c;
+	int r;
+
+	if (snprintf(dm_name, sizeof(dm_name), "%s_%d", name, index) < 0)
+		return -ENOMEM;
+
+	r = dm_status_device(cd, dm_name);
+	if (r < 0)
+		return r;
+
+	r = dm_query_device(cd, dm_name, DM_ACTIVE_DEVICE |
+					  DM_ACTIVE_UUID |
+					  DM_ACTIVE_CRYPT_CIPHER |
+					  DM_ACTIVE_CRYPT_KEYSIZE, &dmd);
+	if (r > 0)
+		r = 0;
+	if (!r && !strncmp(dmd.uuid, base_uuid, strlen(base_uuid))) {
+		if ((c = strchr(dmd.u.crypt.cipher, '-')))
+			*c = '\0';
+		strcat(cipher, "-");
+		strncat(cipher, dmd.u.crypt.cipher, MAX_CIPHER_LEN);
+		*key_size += dmd.u.crypt.vk->keylength;
+		*data_offset = dmd.u.crypt.offset * SECTOR_SIZE;
+		device_free(*device);
+		*device = dmd.data_device;
+	} else {
+		device_free(dmd.data_device);
+		r = -ENODEV;
+	}
+
+	free(CONST_CAST(void*)dmd.uuid);
+	free(CONST_CAST(void*)dmd.u.crypt.cipher);
+	crypt_free_volume_key(dmd.u.crypt.vk);
+	return r;
+}
+
+int TCRYPT_init_by_name(struct crypt_device *cd, const char *name,
+			const struct crypt_dm_active_device *dmd,
+			struct device **device,
+			struct crypt_params_tcrypt *tcrypt_params,
+			struct tcrypt_phdr *tcrypt_hdr)
+{
+	struct tcrypt_algs *algs;
+	char cipher[MAX_CIPHER_LEN * 4], mode[MAX_CIPHER_LEN+1], *tmp;
+	size_t key_size;
+	int r;
+
+	memset(tcrypt_params, 0, sizeof(*tcrypt_params));
+	memset(tcrypt_hdr, 0, sizeof(*tcrypt_hdr));
+	tcrypt_hdr->d.sector_size = SECTOR_SIZE;
+	tcrypt_hdr->d.mk_offset = dmd->u.crypt.offset * SECTOR_SIZE;
+
+	strncpy(cipher, dmd->u.crypt.cipher, MAX_CIPHER_LEN);
+	tmp = strchr(cipher, '-');
+	if (!tmp)
+		return -EINVAL;
+	*tmp = '\0';
+	mode[MAX_CIPHER_LEN] = '\0';
+	strncpy(mode, ++tmp, MAX_CIPHER_LEN);
+
+	key_size = dmd->u.crypt.vk->keylength;
+	r = TCRYPT_status_one(cd, name, dmd->uuid, 1, &key_size,
+			      cipher, &tcrypt_hdr->d.mk_offset, device);
+	if (!r)
+		r = TCRYPT_status_one(cd, name, dmd->uuid, 2, &key_size,
+				      cipher, &tcrypt_hdr->d.mk_offset, device);
+
+	if (r < 0 && r != -ENODEV)
+		return r;
+
+	algs = TCRYPT_get_algs(cipher, mode);
+	if (!algs || key_size != algs->chain_key_size)
+		return -EINVAL;
+
+	tcrypt_params->key_size = algs->chain_key_size;
+	tcrypt_params->cipher = algs->long_name;
+	tcrypt_params->mode = algs->mode;
+	return 0;
+}
+
+uint64_t TCRYPT_get_data_offset(struct crypt_device *cd,
+				 struct tcrypt_phdr *hdr,
+				 struct crypt_params_tcrypt *params)
+{
+	uint64_t size;
+
+	/* No real header loaded, initialized by active device */
+	if (!hdr->d.version)
+		goto hdr_offset;
+
+	/* Mapping through whole device, not partition! */
+	if (params->flags & CRYPT_TCRYPT_SYSTEM_HEADER) {
+		if (crypt_dev_is_partition(device_path(crypt_metadata_device(cd))))
+			return 0;
+		goto hdr_offset;
+	}
+
+	if (params->mode && !strncmp(params->mode, "xts", 3)) {
+		if (hdr->d.version < 3)
+			return 1;
+
+		if (params->flags & CRYPT_TCRYPT_HIDDEN_HEADER) {
+			if (hdr->d.version > 3)
+				return (hdr->d.mk_offset / hdr->d.sector_size);
+			if (device_size(crypt_metadata_device(cd), &size) < 0)
+				return 0;
+			return (size - hdr->d.hidden_volume_size +
+				(TCRYPT_HDR_HIDDEN_OFFSET_OLD)) / hdr->d.sector_size;
+		}
+		goto hdr_offset;
+	}
+
+	if (params->flags & CRYPT_TCRYPT_HIDDEN_HEADER) {
+		if (device_size(crypt_metadata_device(cd), &size) < 0)
+			return 0;
+		return (size - hdr->d.hidden_volume_size +
+			(TCRYPT_HDR_HIDDEN_OFFSET_OLD)) / hdr->d.sector_size;
+	}
+
+hdr_offset:
+	return hdr->d.mk_offset / hdr->d.sector_size;
+}
+
+uint64_t TCRYPT_get_iv_offset(struct crypt_device *cd,
+			      struct tcrypt_phdr *hdr,
+			      struct crypt_params_tcrypt *params)
+{
+	uint64_t iv_offset;
+
+	if (params->mode && !strncmp(params->mode, "xts", 3))
+		iv_offset = TCRYPT_get_data_offset(cd, hdr, params);
+	else if (params->mode && !strncmp(params->mode, "lrw", 3))
+		iv_offset = 0;
+	else
+		iv_offset = hdr->d.mk_offset / hdr->d.sector_size;
+
+	if (params->flags & CRYPT_TCRYPT_SYSTEM_HEADER)
+		iv_offset += crypt_dev_partition_offset(device_path(crypt_metadata_device(cd)));
+
+	return iv_offset;
+}
+
+int TCRYPT_get_volume_key(struct crypt_device *cd,
+			  struct tcrypt_phdr *hdr,
+			  struct crypt_params_tcrypt *params,
+			  struct volume_key **vk)
+{
+	struct tcrypt_algs *algs;
+	unsigned int i, key_index;
+
+	if (!hdr->d.version) {
+		log_err(cd, _("This function is not supported without TCRYPT header load."));
+		return -ENOTSUP;
+	}
+
+	algs = TCRYPT_get_algs(params->cipher, params->mode);
+	if (!algs)
+		return -EINVAL;
+
+	*vk = crypt_alloc_volume_key(params->key_size, NULL);
+	if (!*vk)
+		return -ENOMEM;
+
+	for (i = 0, key_index = 0; i < algs->chain_count; i++) {
+		TCRYPT_copy_key(&algs->cipher[i], algs->mode,
+				&(*vk)->key[key_index], hdr->d.keys);
+		key_index += algs->cipher[i].key_size;
+	}
+
+	return 0;
+}
+#endif
+
+static int log_std_hexdump(struct crypt_device *cd, uint8_t *p, int len)
+{
+	int i;
+
+
+	for(i = 0; i < len; i++) {
+		if (i && !(i % 16))
+			log_std(cd, "\n\t\t");
+		log_std(cd, "%02hhx ", (char)p[i]);
+	}
+	log_std(cd, "\n");
+
+	return 0;
+}
+
+int GELI_dump(struct crypt_device *cd,
+		struct geli_phdr *hdr,
+		struct crypt_params_geli *params)
+{
+	log_std(cd, "GELI header information for %s\n",
+		device_path(crypt_metadata_device(cd)));
+
+	if (hdr->md_version == 7) {
+		log_std(cd, "Magic:\t\t%s\n", hdr->md_magic);
+		log_std(cd, "Version:\t%" PRIu32 "\n", hdr->md_version);
+		log_std(cd, "Flags:\t\t0x%" PRIx32 "\n", hdr->md_flags);
+		log_std(cd, "Encryption algo:\t0x%" PRIx16 "\n", hdr->md_ealgo);
+		log_std(cd, "Key length:\t%" PRIu16 "\n", hdr->md_keylen);
+		log_std(cd, "Authentication algo:\t0x%" PRIx16 "\n", hdr->md_aalgo);
+		log_std(cd, "Provider size:\t%" PRIu64 "\n", hdr->md_provsize);
+		log_std(cd, "Sector size:\t%" PRIu32 "\n", hdr->md_sectorsize);
+		log_std(cd, "Number of keys:\t%" PRIu8 "\n", hdr->md_keys);
+		log_std(cd, "Number of iterations:\t%" PRIu32 "\n", hdr->md_iterations);
+		log_std(cd, "Salt:\t\t\t");
+		log_std_hexdump(cd, hdr->md_salt, G_ELI_SALTLEN);
+		log_std(cd, "Encrypted MKeys:\t");
+		log_std_hexdump(cd, hdr->md_mkeysbuf, G_ELI_MAXMKEYS * G_ELI_MKEYLEN);
+		log_std(cd, "Encr. IV key 0:\t\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[0].md_ivkey, G_ELI_IVKEYLEN);
+		log_std(cd, "Encr. data key 0:\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[0].md_datakey, G_ELI_DATAKEYLEN);
+		log_std(cd, "Encr. SHA512 HMAC 0:\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[0].md_hmacsha512, SHA512_MDLEN);
+		log_std(cd, "Encr. IV key 1:\t\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[1].md_ivkey, G_ELI_IVKEYLEN);
+		log_std(cd, "Encr. data key 1:\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[1].md_datakey, G_ELI_DATAKEYLEN);
+		log_std(cd, "Encr. SHA512 HMAC 1:\t");
+		log_std_hexdump(cd, hdr->md_mkeyslot[1].md_hmacsha512, SHA512_MDLEN);
+		log_std(cd, "MD5:\t\t\t");
+		log_std_hexdump(cd, hdr->md_hash, 16);
+	} else {
+		log_std(cd, "Version %d not supported\n", hdr->md_version);
+	}
+	return 0;
+}
diff --git a/lib/geli/geli.h b/lib/geli/geli.h
new file mode 100644
index 0000000..7e9070e
--- /dev/null
+++ b/lib/geli/geli.h
@@ -0,0 +1,102 @@
+/*
+ * FreeBSD GEOM::ELI GELI compatible volume handling
+ *
+ * Copyright (C) 2017, Carl-Daniel Hailfinger
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _GELI_H
+#define _GELI_H
+
+#include <unistd.h>
+
+/* Relevant header parts from FreeBSD sys/geom/eli/g_eli.h
+ * TODO: Figure out if that is actually OK from a licensing perspective.
+ */
+#define	G_ELI_MAGIC		"GEOM::ELI"
+
+#define SHA512_MDLEN		64
+
+#define	G_ELI_MAXMKEYS		2
+#define	G_ELI_MAXKEYLEN		64
+#define	G_ELI_USERKEYLEN	G_ELI_MAXKEYLEN
+#define	G_ELI_DATAKEYLEN	G_ELI_MAXKEYLEN
+#define	G_ELI_AUTHKEYLEN	G_ELI_MAXKEYLEN
+#define	G_ELI_IVKEYLEN		G_ELI_MAXKEYLEN
+#define	G_ELI_SALTLEN		64
+#define	G_ELI_DATAIVKEYLEN	(G_ELI_DATAKEYLEN + G_ELI_IVKEYLEN)
+/* Data-Key, IV-Key, HMAC_SHA512(Derived-Key, Data-Key+IV-Key) */
+#define	G_ELI_MKEYLEN		(G_ELI_DATAIVKEYLEN + SHA512_MDLEN)
+#define MD5_MDLEN		16
+
+struct geli_mkeyslot {
+	uint8_t		md_ivkey[G_ELI_IVKEYLEN];
+	uint8_t		md_datakey[G_ELI_DATAKEYLEN];
+	uint8_t		md_hmacsha512[SHA512_MDLEN];
+};
+
+struct geli_phdr {
+	union {
+		struct {
+			char		md_magic[16];	/* Magic value. */
+			uint32_t	md_version;	/* Version number. */
+			uint32_t	md_flags;	/* Additional flags. */
+			uint16_t	md_ealgo;	/* Encryption algorithm. */
+			uint16_t	md_keylen;	/* Key length. */
+			uint16_t	md_aalgo;	/* Authentication algorithm. */
+			uint64_t	md_provsize;	/* Provider's size. */
+			uint32_t	md_sectorsize;	/* Sector size. */
+			uint8_t		md_keys;	/* Available keys. */
+			int32_t		md_iterations;	/* Number of iterations for PKCS#5v2. */
+			uint8_t		md_salt[G_ELI_SALTLEN]; /* Salt. */
+					/* Encrypted master key (IV-key, Data-key, HMAC). */
+			union {
+				uint8_t			md_mkeysbuf[G_ELI_MAXMKEYS * G_ELI_MKEYLEN];
+				struct geli_mkeyslot	md_mkeyslot[G_ELI_MAXMKEYS];
+			} __attribute__((__packed__));
+			u_char		md_hash[MD5_MDLEN];	/* MD5 hash. */
+		} __attribute__((__packed__));
+		char			bytearray[511];
+	} __attribute__((__packed__));
+} __attribute__((__packed__));
+
+int GELI_read_phdr(struct crypt_device *cd,
+		     struct geli_phdr *hdr,
+		     struct crypt_params_geli *params);
+
+int GELI_dump(struct crypt_device *cd,
+		struct geli_phdr *hdr,
+		struct crypt_params_geli *params);
+
+#if 0
+struct crypt_device;
+struct volume_key;
+
+int GELI_parse_keyfile(struct crypt_device *cd,
+			  struct volume_key **vk,
+			  const char *hash,
+			  unsigned int *keys_count,
+			  char *buffer,
+			  size_t buffer_len);
+
+int GELI_activate(struct crypt_device *cd,
+		     const char *name,
+		     const char *base_cipher,
+		     unsigned int keys_count,
+		     struct volume_key *vk,
+		     uint32_t flags);
+#endif
+#endif
diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h
index 80bbf5c..8e31444 100644
--- a/lib/libcryptsetup.h
+++ b/lib/libcryptsetup.h
@@ -243,6 +243,8 @@ int crypt_memory_lock(struct crypt_device *cd, int lock);
 #define CRYPT_VERITY "VERITY"
 /** TCRYPT (TrueCrypt-compatible and VeraCrypt-compatible) mode */
 #define CRYPT_TCRYPT "TCRYPT"
+/** GELI (FreeBSD GEOM::ELI) mode */
+#define CRYPT_GELI "GELI"
 
 /**
  * Get device type
@@ -354,6 +356,17 @@ struct crypt_params_tcrypt {
  */
 #define CRYPT_TCRYPT_VERA_MODES      (1 << 4)
 
+/**
+ *
+ * Structure used as parameter for GELI device type.
+ *
+ * @see crypt_format
+ *
+ */
+struct crypt_params_geli {
+	int dummy;
+};
+
 /** @} */
 
 /**
diff --git a/lib/setup.c b/lib/setup.c
index 1dca99b..7d394ac 100644
--- a/lib/setup.c
+++ b/lib/setup.c
@@ -34,6 +34,7 @@
 #include "loopaes.h"
 #include "verity.h"
 #include "tcrypt.h"
+#include "geli.h"
 #include "internal.h"
 
 struct crypt_device {
@@ -76,6 +77,10 @@ struct crypt_device {
 		struct crypt_params_tcrypt params;
 		struct tcrypt_phdr hdr;
 	} tcrypt;
+	struct { /* used in CRYPT_GELI */
+		struct crypt_params_geli params;
+		struct geli_phdr hdr;
+	} geli;
 	struct { /* used if initialized without header by name */
 		char *active_name;
 		/* buffers, must refresh from kernel on every query */
@@ -245,6 +250,11 @@ static int isTCRYPT(const char *type)
 	return (type && !strcmp(CRYPT_TCRYPT, type));
 }
 
+static int isGELI(const char *type)
+{
+	return (type && !strcmp(CRYPT_GELI, type));
+}
+
 static int onlyLUKS(struct crypt_device *cd)
 {
 	int r = 0;
@@ -581,6 +591,30 @@ static int _crypt_load_tcrypt(struct crypt_device *cd, struct crypt_params_tcryp
 	return r;
 }
 
+static int _crypt_load_geli(struct crypt_device *cd, struct crypt_params_geli *params)
+{
+	int r;
+
+	if (!params)
+		return -EINVAL;
+
+	r = init_crypto(cd);
+	if (r < 0)
+		return r;
+
+	memcpy(&cd->u.geli.params, params, sizeof(*params));
+
+	r = GELI_read_phdr(cd, &cd->u.geli.hdr, &cd->u.geli.params);
+
+	if (r < 0)
+		return r;
+
+	if (!cd->type && !(cd->type = strdup(CRYPT_GELI)))
+		return -ENOMEM;
+
+	return r;
+}
+
 static int _crypt_load_verity(struct crypt_device *cd, struct crypt_params_verity *params)
 {
 	int r;
@@ -683,6 +717,9 @@ static int _init_by_name_crypt(struct crypt_device *cd, const char *name)
 	} else if (isTCRYPT(cd->type)) {
 		r = TCRYPT_init_by_name(cd, name, &dmd, &cd->device,
 					&cd->u.tcrypt.params, &cd->u.tcrypt.hdr);
+	} else if (isGELI(cd->type)) {
+		//FIXME r = GELI_init_by_name(cd, name, &dmd, &cd->device,
+		//FIXME			&cd->u.geli.params, &cd->u.geli.hdr);
 	}
 out:
 	crypt_free_volume_key(dmd.u.crypt.vk);
@@ -790,6 +827,8 @@ int crypt_init_by_name_and_header(struct crypt_device **cd,
 			(*cd)->type = strdup(CRYPT_VERITY);
 		else if (!strncmp(CRYPT_TCRYPT, dmd.uuid, sizeof(CRYPT_TCRYPT)-1))
 			(*cd)->type = strdup(CRYPT_TCRYPT);
+		else if (!strncmp(CRYPT_GELI, dmd.uuid, sizeof(CRYPT_GELI)-1))
+			(*cd)->type = strdup(CRYPT_GELI);
 		else
 			log_dbg("Unknown UUID set, some parameters are not set.");
 	} else
@@ -1177,6 +1216,12 @@ int crypt_load(struct crypt_device *cd,
 			return -EINVAL;
 		}
 		r = _crypt_load_tcrypt(cd, params);
+	} else if (isGELI(requested_type)) {
+		if (cd->type && !isGELI(cd->type)) {
+			log_dbg("Context is already initialised to type %s", cd->type);
+			return -EINVAL;
+		}
+		r = _crypt_load_geli(cd, params);
 	} else
 		return -EINVAL;
 
@@ -2032,6 +2077,11 @@ int crypt_activate_by_volume_key(struct crypt_device *cd,
 			return 0;
 		r = TCRYPT_activate(cd, name, &cd->u.tcrypt.hdr,
 				    &cd->u.tcrypt.params, flags);
+	} else if (isGELI(cd->type)) {
+		if (!name)
+			return 0;
+		//FIXME r = GELI_activate(cd, name, &cd->u.geli.hdr,
+		//FIXME		    &cd->u.geli.params, flags);
 	} else
 		log_err(cd, _("Device type is not properly initialised.\n"));
 
@@ -2115,6 +2165,8 @@ int crypt_volume_key_get(struct crypt_device *cd,
 					passphrase_size, &cd->u.luks1.hdr, &vk, cd);
 	} else if (isTCRYPT(cd->type)) {
 		r = TCRYPT_get_volume_key(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params, &vk);
+	} else if (isGELI(cd->type)) {
+		//FIXME r = GELI_get_volume_key(cd, &cd->u.geli.hdr, &cd->u.geli.params, &vk);
 	} else
 		log_err(cd, _("This operation is not supported for %s crypt device.\n"), cd->type ?: "(none)");
 
@@ -2289,6 +2341,8 @@ int crypt_dump(struct crypt_device *cd)
 		return _verity_dump(cd);
 	else if (isTCRYPT(cd->type))
 		return TCRYPT_dump(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params);
+	else if (isGELI(cd->type))
+		return GELI_dump(cd, &cd->u.geli.hdr, &cd->u.geli.params);
 
 	log_err(cd, _("Dump operation is not supported for this device type.\n"));
 	return -EINVAL;
@@ -2333,6 +2387,10 @@ const char *crypt_get_cipher(struct crypt_device *cd)
 	if (isTCRYPT(cd->type))
 		return cd->u.tcrypt.params.cipher;
 
+	if (isGELI(cd->type))
+		return "aes";
+		//FIXME return cd->u.geli.params.cipher;
+
 	if (!cd->type && !_init_by_name_crypt_none(cd))
 		return cd->u.none.cipher;
 
@@ -2353,6 +2411,10 @@ const char *crypt_get_cipher_mode(struct crypt_device *cd)
 	if (isTCRYPT(cd->type))
 		return cd->u.tcrypt.params.mode;
 
+	if (isGELI(cd->type))
+		return "xts-byte64";
+		//FIXME return cd->u.geli.params.mode;
+
 	if (!cd->type && !_init_by_name_crypt_none(cd))
 		return cd->u.none.cipher_mode;
 
@@ -2397,6 +2459,10 @@ int crypt_get_volume_key_size(struct crypt_device *cd)
 	if (isTCRYPT(cd->type))
 		return cd->u.tcrypt.params.key_size;
 
+	if (isGELI(cd->type))
+		return 256;
+		//FIXME return cd->u.geli.params.key_size;
+
 	if (!cd->type && !_init_by_name_crypt_none(cd))
 		return cd->u.none.key_size;
 
@@ -2434,6 +2500,7 @@ uint64_t crypt_get_iv_offset(struct crypt_device *cd)
 	if (isTCRYPT(cd->type))
 		return TCRYPT_get_iv_offset(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params);
 
+	//FIXME: GELI?
 	return 0;
 }
 
diff --git a/src/cryptsetup.c b/src/cryptsetup.c
index 2d6ddaf..a665fea 100644
--- a/src/cryptsetup.c
+++ b/src/cryptsetup.c
@@ -382,6 +382,101 @@ out:
 	return r;
 }
 
+static int geli_load(struct crypt_device *cd, struct crypt_params_geli *params)
+{
+	int r, eperm = 0;
+
+	do {
+		log_std("GELI tracking: 5\n");
+		r = crypt_load(cd, CRYPT_GELI, params);
+		log_std("GELI tracking: 6 crypt_load returned %i\n", r);
+
+		if (r == -EPERM) {
+			log_err(_("No device header detected with the given parameters.\n"));
+			eperm = 1;
+		}
+
+#if 0
+		if (r < 0) {
+			crypt_safe_free(CONST_CAST(char*)params->passphrase);
+			params->passphrase = NULL;
+			params->passphrase_size = 0;
+		}
+#endif
+		check_signal(&r);
+	} while (0);
+
+	/* Report wrong passphrase if at least one try failed */
+	if (eperm && r == -EPIPE)
+		r = -EPERM;
+
+	log_std("GELI tracking: 7\n");
+	return r;
+}
+
+static int action_open_geli(void)
+{
+	struct crypt_device *cd = NULL;
+	struct crypt_params_geli params = {
+		.dummy = 0,
+	};
+	const char *activated_name;
+	uint32_t activate_flags = 0;
+	int r;
+
+	activated_name = opt_test_passphrase ? NULL : action_argv[1];
+
+	log_std("GELI tracking: 9\n");
+	if ((r = crypt_init(&cd, action_argv[0])))
+		goto out;
+
+	log_std("GELI tracking: 10\n");
+	r = geli_load(cd, &params);
+	if (r < 0)
+		goto out;
+
+	log_std("GELI tracking: 11\n");
+	_set_activation_flags(&activate_flags);
+
+	log_std("GELI tracking: 12\n");
+	if (activated_name)
+		r = crypt_activate_by_volume_key(cd, activated_name, NULL, 0, activate_flags);
+out:
+	log_std("GELI tracking: 13\n");
+	crypt_free(cd);
+	return r;
+}
+
+static int action_geliDump(void)
+{
+	struct crypt_device *cd = NULL;
+	struct crypt_params_geli params = {
+		.dummy = 0,
+	};
+	int r;
+
+	log_std("GELI tracking: 1\n");
+	if ((r = crypt_init(&cd, action_argv[0])))
+		goto out;
+
+	log_std("GELI tracking: 2\n");
+	r = geli_load(cd, &params);
+	if (r < 0)
+		goto out;
+
+#if 0
+	if (opt_dump_master_key)
+		r = geliDump_with_volume_key(cd);
+	else
+#endif
+	log_std("GELI tracking: 3\n");
+		r = crypt_dump(cd);
+out:
+	crypt_free(cd);
+	log_std("GELI tracking: 4\n");
+	return r;
+}
+
 static int action_close(void)
 {
 	struct crypt_device *cd = NULL;
@@ -1295,6 +1390,7 @@ out:
 
 static int action_open(void)
 {
+	log_std("GELI tracking: 14\n");
 	if (!opt_type)
 		return -EINVAL;
 
@@ -1314,6 +1410,11 @@ static int action_open(void)
 		if (action_argc < 2 && !opt_test_passphrase)
 			goto args;
 		return action_open_tcrypt();
+	} else if (!strcmp(opt_type, "geli")) {
+		log_std("GELI tracking: 8\n");
+		if (action_argc < 2)
+			goto args;
+		return action_open_geli();
 	}
 
 	log_err(_("Unrecognized metadata device type %s.\n"), opt_type);
@@ -1388,6 +1489,7 @@ static struct action_type {
 	{ "isLuks",       action_isLuks,       1, 0, N_("<device>"), N_("tests <device> for LUKS partition header") },
 	{ "luksDump",     action_luksDump,     1, 1, N_("<device>"), N_("dump LUKS partition information") },
 	{ "tcryptDump",   action_tcryptDump,   1, 1, N_("<device>"), N_("dump TCRYPT device information") },
+	{ "geliDump",     action_geliDump,     1, 1, N_("<device>"), N_("dump GELI device information") },
 	{ "luksSuspend",  action_luksSuspend,  1, 1, N_("<device>"), N_("Suspend LUKS device and wipe key (all IOs are frozen).") },
 	{ "luksResume",   action_luksResume,   1, 1, N_("<device>"), N_("Resume suspended LUKS device.") },
 	{ "luksHeaderBackup", action_luksBackup,1,1, N_("<device>"), N_("Backup LUKS device header and keyslots") },
@@ -1524,7 +1626,7 @@ int main(int argc, const char **argv)
 		{ "tcrypt-system",     '\0', POPT_ARG_NONE, &opt_tcrypt_system,         0, N_("Device is system TCRYPT drive (with bootloader)."), NULL },
 		{ "tcrypt-backup",     '\0', POPT_ARG_NONE, &opt_tcrypt_backup,         0, N_("Use backup (secondary) TCRYPT header."), NULL },
 		{ "veracrypt",         '\0', POPT_ARG_NONE, &opt_veracrypt,             0, N_("Scan also for VeraCrypt compatible device."), NULL },
-		{ "type",               'M', POPT_ARG_STRING, &opt_type,                0, N_("Type of device metadata: luks, plain, loopaes, tcrypt."), NULL },
+		{ "type",               'M', POPT_ARG_STRING, &opt_type,                0, N_("Type of device metadata: luks, plain, loopaes, tcrypt, geli."), NULL },
 		{ "force-password",    '\0', POPT_ARG_NONE, &opt_force_password,        0, N_("Disable password quality check (if enabled)."), NULL },
 		{ "perf-same_cpu_crypt",'\0', POPT_ARG_NONE, &opt_perf_same_cpu_crypt,  0, N_("Use dm-crypt same_cpu_crypt performance compatibility option."), NULL },
 		{ "perf-submit_from_crypt_cpus",'\0', POPT_ARG_NONE, &opt_perf_submit_from_crypt_cpus,0,N_("Use dm-crypt submit_from_crypt_cpus performance compatibility option."), NULL },
@@ -1631,6 +1733,8 @@ int main(int argc, const char **argv)
 		opt_type = "tcrypt";
 	} else if (!strcmp(aname, "tcryptDump")) {
 		opt_type = "tcrypt";
+	} else if (!strcmp(aname, "geliDump")) {
+		opt_type = "geli";
 	} else if (!strcmp(aname, "remove") ||
 		   !strcmp(aname, "plainClose") ||
 		   !strcmp(aname, "luksClose") ||
-- 
1.9.1



More information about the dm-crypt mailing list