All of lore.kernel.org
 help / color / mirror / Atom feed
* [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup
@ 2013-11-30 15:25 f000m
  2013-11-30 17:33 ` Milan Broz
  0 siblings, 1 reply; 9+ messages in thread
From: f000m @ 2013-11-30 15:25 UTC (permalink / raw)
  To: dm-crypt

Hello,

I am planing to make dm-crypt and cryptsetup able to
handle FreeBSD's GEOM_ELI crypted devices (without its
integrity stuff).

In the kernel modul there would be minor changes
concerning two more IV generators needed to add:
First, because of a slightly different handling of plain type
(it uses offsets instead of sectors).
Second, GEOM_ELI uses CBC with unpredictable IV instead of
ESSIV Mode.

Additionally cryptsetup must be patched to be able
to deal with the metadata structure of GEOM_ELI devices.

Would there be any interests in committing me this code
to the upstream?

Kind regards.



______________________________________________________
powered by Secure-Mail.biz - anonymous and secure e-mail accounts.

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup
  2013-11-30 15:25 [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup f000m
@ 2013-11-30 17:33 ` Milan Broz
  2013-12-13  6:42   ` f000m
  0 siblings, 1 reply; 9+ messages in thread
From: Milan Broz @ 2013-11-30 17:33 UTC (permalink / raw)
  To: f000m; +Cc: dm-crypt

Hi,

On 11/30/2013 04:25 PM, f000m@z1p.biz wrote:
> I am planing to make dm-crypt and cryptsetup able to
> handle FreeBSD's GEOM_ELI crypted devices (without its
> integrity stuff).

I think that even integrity stuff would be interesting
but that's a lot of kernel work.
(But I am quite interested how FreeBSD approach looks like anyway.)

> In the kernel modul there would be minor changes
> concerning two more IV generators needed to add:
> First, because of a slightly different handling of plain type
> (it uses offsets instead of sectors).
> Second, GEOM_ELI uses CBC with unpredictable IV instead of
> ESSIV Mode.

Not sure I understand offset/sectors problem.
Can you elaborate more here? Is it just multiplication
of sector number by sector size or something else?
(Or just point me to the docs :)

Anyway, adding new IV generators to dmcrypt should not be big problem
(in principle).

> Additionally cryptsetup must be patched to be able
> to deal with the metadata structure of GEOM_ELI devices.
> 
> Would there be any interests in committing me this code
> to the upstream?

Well, I am not sure how broadly this format is used and if
there are potential users in Linux world, so if you can
post more description here it would be nice.

(And if anyone on list is interested, plesase say so... now :)

But in general, yes, I think it is good idea.

I would suggest you to do these steps:

1) provide links to documentation of format, limitation
of your approach etc (also should be included in patch later)

2) first, implement needed changes in Linux kernel (dmcrypt IVs)
(if it is only new IV, it should be straightforward)
Please post patches to DM devel list (dm-devel@redhat.com)

This should be done in advance - for testing, you can use
dmsetup to configure dmcrypt device and test it works for your
images.

Cryptsetup support can come later (we need patch in stable kernel
first to release build supporting it).

3) second, post patches for cryptsetup to this list.

Please keep format specific code in separate directory,
lib/geli/* or so.
(I am planning some unified interface for formats in future (1.7),
so see how is e.g. TCRYPT done - it should be very similar).

Please post at least simple regression tests together with patches
(see tests/ dir).

License of new code must be compatible with released code,
basically GPL2+/LGPL2.1+ for cryptsetup.

(And be prepared it will take some time and perhaps reiterated
patch posts - mainly for kernel part :)

Thanks,
Milan

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup
  2013-11-30 17:33 ` Milan Broz
@ 2013-12-13  6:42   ` f000m
  2016-12-28 15:46     ` Carl-Daniel Hailfinger
  0 siblings, 1 reply; 9+ messages in thread
From: f000m @ 2013-12-13  6:42 UTC (permalink / raw)
  To: Milan Broz; +Cc: dm-crypt

Hi Milan,

sorry for my late reply.

--- Ursprüngliche Nachricht ---
Von: Milan Broz <gmazyland@gmail.com>
Datum: 30.11.2013 18:33:02
An: f000m@z1p.biz
Betreff: Re: [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup

> Hi,
>
> On 11/30/2013 04:25 PM, f000m@z1p.biz wrote:
> > I am planing to make dm-crypt and cryptsetup able to
> > handle FreeBSD's GEOM_ELI crypted devices (without its
> > integrity stuff).
>
> I think that even integrity stuff would be interesting
> but that's a lot of kernel work.
> (But I am quite interested how FreeBSD approach looks like anyway.)
>
The first comment in the header file explains how it works.
http://svnweb.freebsd.org/base/head/sys/geom/eli/g_eli_integrity.c?view=co

> > In the kernel modul there would be minor changes
> > concerning two more IV generators needed to add:
> > First, because of a slightly different handling of plain type
> > (it uses offsets instead of sectors).
> > Second, GEOM_ELI uses CBC with unpredictable IV instead of
> > ESSIV Mode.
>
> Not sure I understand offset/sectors problem.
> Can you elaborate more here? Is it just multiplication
> of sector number by sector size or something else?
> (Or just point me to the docs :)
>
Yes it is for plain mode. You can see it in g_eli_crypto_ivgen().
http://svnweb.freebsd.org/base/head/sys/geom/eli/g_eli.c?view=co

The unpredicable IV generation creates a SHA256 digest. It uses a IV seed
and the offset number (sector number by sector size).

Also it would need to add a routine to calculate (HMAC-SHA-512) the
encryption key on the fly because GEOM_ELI uses multiple encryption keys
since version 5. It uses a different key for every 2^20 sectors.
So I would put it in the IV generators, too.

> Anyway, adding new IV generators to dmcrypt should not be big problem
> (in principle).
>
> > Additionally cryptsetup must be patched to be able
> > to deal with the metadata structure of GEOM_ELI devices.
> >
> > Would there be any interests in committing me this code
> > to the upstream?
>
> Well, I am not sure how broadly this format is used and if
> there are potential users in Linux world, so if you can
> post more description here it would be nice.
>
It is a common tool in FreeBSD for cryptography. So it would be
nice one could access these FreeBSD devices with Linux, too (I think =).

> (And if anyone on list is interested, plesase say so... now :)
>
> But in general, yes, I think it is good idea.
>
> I would suggest you to do these steps:
>
> 1) provide links to documentation of format, limitation
> of your approach etc (also should be included in patch later)
>
> 2) first, implement needed changes in Linux kernel (dmcrypt IVs)
> (if it is only new IV, it should be straightforward)
> Please post patches to DM devel list (dm-devel@redhat.com)
>
> This should be done in advance - for testing, you can use
> dmsetup to configure dmcrypt device and test it works for your
> images.
>
> Cryptsetup support can come later (we need patch in stable kernel
> first to release build supporting it).
>
> 3) second, post patches for cryptsetup to this list.
>
> Please keep format specific code in separate directory,
> lib/geli/* or so.
> (I am planning some unified interface for formats in future (1.7),
> so see how is e.g. TCRYPT done - it should be very similar).
>
> Please post at least simple regression tests together with patches
> (see tests/ dir).
>
> License of new code must be compatible with released code,
> basically GPL2+/LGPL2.1+ for cryptsetup.
>
> (And be prepared it will take some time and perhaps reiterated
> patch posts - mainly for kernel part :)
>
> Thanks,
> Milan
>
Thanks for your suggestions.

Best regards.


______________________________________________________
powered by Secure-Mail.biz - anonymous and secure e-mail accounts.

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup
  2013-12-13  6:42   ` f000m
@ 2016-12-28 15:46     ` Carl-Daniel Hailfinger
  2016-12-28 18:06       ` Carl-Daniel Hailfinger
  0 siblings, 1 reply; 9+ messages in thread
From: Carl-Daniel Hailfinger @ 2016-12-28 15:46 UTC (permalink / raw)
  To: dm-crypt; +Cc: f000m, gmazyland

Hello everyone,

I was looking for a way to access a GELI encrypted disk from within
Linux (for forensics of disks used in FreeBSD, but people wanting to
access encrypted FreeNAS data from OpenMediaVault could benefit as well)
and found this email. Did the code for GEOM_ELI encryption support in
cryptsetup/dm-crypt ever get written? If not, I might have a go at it.

Admittedly the only part I'm interested right now is the unauthenticated
aes-xts with the bytecount64 IV because that is apparently the most
commonly used variant.


On Fri Dec 13 07:42:52 CET 2013, f000m at z1p.biz wrote:
> --- Ursprüngliche Nachricht ---
> Von: Milan Broz <gmazyland at gmail.com>
> Datum: 30.11.2013 18:33:02
> An: f000m at z1p.biz
> Betreff: Re: [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup
> 
>> On 11/30/2013 04:25 PM, f000m at z1p.biz wrote:
>> > I am planing to make dm-crypt and cryptsetup able to
>> > handle FreeBSD's GEOM_ELI crypted devices (without its
>> > integrity stuff).
>>
>> I think that even integrity stuff would be interesting
>> but that's a lot of kernel work.
>> (But I am quite interested how FreeBSD approach looks like anyway.)
>>
> The first comment in the header file explains how it works.
> http://svnweb.freebsd.org/base/head/sys/geom/eli/g_eli_integrity.c?view=co
> 
>> > In the kernel modul there would be minor changes
>> > concerning two more IV generators needed to add:
>> > First, because of a slightly different handling of plain type
>> > (it uses offsets instead of sectors).
>> > Second, GEOM_ELI uses CBC with unpredictable IV instead of
>> > ESSIV Mode.
>>
>> Not sure I understand offset/sectors problem.
>> Can you elaborate more here? Is it just multiplication
>> of sector number by sector size or something else?
>> (Or just point me to the docs :)
>>
> Yes it is for plain mode. You can see it in g_eli_crypto_ivgen().
> http://svnweb.freebsd.org/base/head/sys/geom/eli/g_eli.c?view=co
> 
> The unpredicable IV generation creates a SHA256 digest. It uses a IV seed
> and the offset number (sector number by sector size).
> 
> Also it would need to add a routine to calculate (HMAC-SHA-512) the
> encryption key on the fly because GEOM_ELI uses multiple encryption keys
> since version 5. It uses a different key for every 2^20 sectors.
> So I would put it in the IV generators, too.
> 
>> Anyway, adding new IV generators to dmcrypt should not be big problem
>> (in principle).
>>
>> > Additionally cryptsetup must be patched to be able
>> > to deal with the metadata structure of GEOM_ELI devices.
>> >
>> > Would there be any interests in committing me this code
>> > to the upstream?
>>
>> Well, I am not sure how broadly this format is used and if
>> there are potential users in Linux world, so if you can
>> post more description here it would be nice.
>>
> It is a common tool in FreeBSD for cryptography. So it would be
> nice one could access these FreeBSD devices with Linux, too (I think =).
> 
>> (And if anyone on list is interested, plesase say so... now :)
>>
>> But in general, yes, I think it is good idea.
>>
>> I would suggest you to do these steps:
>>
>> 1) provide links to documentation of format, limitation
>> of your approach etc (also should be included in patch later)
>>
>> 2) first, implement needed changes in Linux kernel (dmcrypt IVs)
>> (if it is only new IV, it should be straightforward)
>> Please post patches to DM devel list (dm-devel at redhat.com)
>>
>> This should be done in advance - for testing, you can use
>> dmsetup to configure dmcrypt device and test it works for your
>> images.
>>
>> Cryptsetup support can come later (we need patch in stable kernel
>> first to release build supporting it).
>>
>> 3) second, post patches for cryptsetup to this list.
>>
>> Please keep format specific code in separate directory,
>> lib/geli/* or so.
>> (I am planning some unified interface for formats in future (1.7),
>> so see how is e.g. TCRYPT done - it should be very similar).
>>
>> Please post at least simple regression tests together with patches
>> (see tests/ dir).
>>
>> License of new code must be compatible with released code,
>> basically GPL2+/LGPL2.1+ for cryptsetup.
>>
>> (And be prepared it will take some time and perhaps reiterated
>> patch posts - mainly for kernel part :)
>>
>> Thanks,
>> Milan
>>
> Thanks for your suggestions.
> 
> Best regards.

Regards,
Carl-Daniel

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup
  2016-12-28 15:46     ` Carl-Daniel Hailfinger
@ 2016-12-28 18:06       ` Carl-Daniel Hailfinger
  2016-12-30  6:31         ` Carl-Daniel Hailfinger
  0 siblings, 1 reply; 9+ messages in thread
From: Carl-Daniel Hailfinger @ 2016-12-28 18:06 UTC (permalink / raw)
  To: dm-crypt; +Cc: gmazyland, f000m

Hello everyone,

a short followup with a quick-and-dirty Linux kernel patch. Compile
tested only.

From 97309be452816b634d19bc61ba95d27fe48ff366 Mon Sep 17 00:00:00 2001
From: Carl-Daniel Hailfinger <c-d.hailfinger.devel.2006@gmx.net>
Date: Wed, 28 Dec 2016 18:52:49 +0100
Subject: [PATCH] Add FreeBSD GEOM::ELI (GELI) compatible IV mode.

Signed-off-by: Carl-Daniel Hailfinger <@>
---
 drivers/md/dm-crypt.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c
index 7c6c572..feae131 100644
--- a/drivers/md/dm-crypt.c
+++ b/drivers/md/dm-crypt.c
@@ -111,6 +111,10 @@ struct iv_tcw_private {
 	u8 *whitening;
 };
 
+struct iv_byte64_private {
+	int sector_shift;
+};
+
 /*
  * Crypt: maps a linear range of a block device
  * and encrypts / decrypts at the same time.
@@ -151,6 +155,7 @@ struct crypt_config {
 		struct iv_benbi_private benbi;
 		struct iv_lmk_private lmk;
 		struct iv_tcw_private tcw;
+		struct iv_byte64_private byte64;
 	} iv_gen_private;
 	sector_t iv_offset;
 	unsigned int iv_size;
@@ -241,6 +246,10 @@ static struct crypto_skcipher *any_tfm(struct crypt_config *cc)
  *       Note that this encryption scheme is vulnerable to watermarking attacks
  *       and should be used for old compatible containers access only.
  *
+ * byte64: Compatible implementation of the IV scheme used by FreeBSD GEOM::ELI.
+ *       The initial vector is the 64-bit little-endian version of the byte
+ *       number, padded with zeros if necessary.
+ *
  * plumb: unimplemented, see:
  * http://article.gmane.org/gmane.linux.kernel.device-mapper.dm-crypt/454
  */
@@ -762,6 +771,37 @@ static int crypt_iv_tcw_post(struct crypt_config *cc, u8 *iv,
 	return r;
 }
 
+static int crypt_iv_byte64_ctr(struct crypt_config *cc, struct dm_target *ti,
+			       const char *opts)
+{
+	struct iv_byte64_private *byte64 = &cc->iv_gen_private.byte64;
+	int sector_shift = 9; //FIXME: Use the sector shift supplied by userspace
+
+	byte64->sector_shift = sector_shift;
+
+	return 0;
+}
+
+static void crypt_iv_byte64_dtr(struct crypt_config *cc)
+{
+}
+
+static int crypt_iv_byte64_gen(struct crypt_config *cc, u8 *iv,
+			       struct dm_crypt_request *dmreq)
+{
+	struct iv_byte64_private *byte64 = &cc->iv_gen_private.byte64;
+	int sector_shift = byte64->sector_shift;
+
+	memset(iv, 0, cc->iv_size);
+	/* FIXME: Make sure the sector number uses the right unit (512B vs. 4kiB sectors)
+	 * FIMXE: We want absolute byte numbers here, but those absolute byte numbers
+	 * are only calculated on boundaries specified by the GELI header.
+	 */
+	*(__le64 *)iv = cpu_to_le64(dmreq->iv_sector << sector_shift);
+
+	return 0;
+}
+
 static const struct crypt_iv_operations crypt_iv_plain_ops = {
 	.generator = crypt_iv_plain_gen
 };
@@ -806,6 +846,13 @@ static int crypt_iv_tcw_post(struct crypt_config *cc, u8 *iv,
 	.post	   = crypt_iv_tcw_post
 };
 
+static const struct crypt_iv_operations crypt_iv_byte64_ops = {
+	//FIXME: Use .init instead?
+	.ctr      = crypt_iv_byte64_ctr,
+	.dtr      = crypt_iv_byte64_dtr,
+	.generator = crypt_iv_byte64_gen
+};
+
 static void crypt_convert_init(struct crypt_config *cc,
 			       struct convert_context *ctx,
 			       struct bio *bio_out, struct bio *bio_in,
@@ -1816,6 +1863,8 @@ static int crypt_ctr_cipher(struct dm_target *ti,
 		cc->iv_gen_ops = &crypt_iv_tcw_ops;
 		cc->key_parts += 2; /* IV + whitening */
 		cc->key_extra_size = cc->iv_size + TCW_WHITENING_SIZE;
+	} else if (strcmp(ivmode, "byte64") == 0) {
+		cc->iv_gen_ops = &crypt_iv_byte64_ops;
 	} else {
 		ret = -EINVAL;
 		ti->error = "Invalid IV mode";
-- 
1.9.1


Really untested. Taking the sector_shift value from userspace cryptsetup
is not yet implemented.


On 28.12.2016 16:46, Carl-Daniel Hailfinger wrote:
> Hello everyone,
>
> I was looking for a way to access a GELI encrypted disk from within
> Linux (for forensics of disks used in FreeBSD, but people wanting to
> access encrypted FreeNAS data from OpenMediaVault could benefit as well)
> and found this email. Did the code for GEOM_ELI encryption support in
> cryptsetup/dm-crypt ever get written? If not, I might have a go at it.
>
> Admittedly the only part I'm interested right now is the unauthenticated
> aes-xts with the bytecount64 IV because that is apparently the most
> commonly used variant.
>
>
> On Fri Dec 13 07:42:52 CET 2013, f000m at z1p.biz wrote:
>> --- Ursprüngliche Nachricht ---
>> Von: Milan Broz <gmazyland at gmail.com>
>> Datum: 30.11.2013 18:33:02
>> An: f000m at z1p.biz
>> Betreff: Re: [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup
>>
>>> On 11/30/2013 04:25 PM, f000m at z1p.biz wrote:
>>>> I am planing to make dm-crypt and cryptsetup able to
>>>> handle FreeBSD's GEOM_ELI crypted devices (without its
>>>> integrity stuff).
>>> I think that even integrity stuff would be interesting
>>> but that's a lot of kernel work.
>>> (But I am quite interested how FreeBSD approach looks like anyway.)
>>>
>> The first comment in the header file explains how it works.
>> http://svnweb.freebsd.org/base/head/sys/geom/eli/g_eli_integrity.c?view=co
>>
>>>> In the kernel modul there would be minor changes
>>>> concerning two more IV generators needed to add:
>>>> First, because of a slightly different handling of plain type
>>>> (it uses offsets instead of sectors).
>>>> Second, GEOM_ELI uses CBC with unpredictable IV instead of
>>>> ESSIV Mode.
>>> Not sure I understand offset/sectors problem.
>>> Can you elaborate more here? Is it just multiplication
>>> of sector number by sector size or something else?
>>> (Or just point me to the docs :)
>>>
>> Yes it is for plain mode. You can see it in g_eli_crypto_ivgen().
>> http://svnweb.freebsd.org/base/head/sys/geom/eli/g_eli.c?view=co
>>
>> The unpredicable IV generation creates a SHA256 digest. It uses a IV seed
>> and the offset number (sector number by sector size).
>>
>> Also it would need to add a routine to calculate (HMAC-SHA-512) the
>> encryption key on the fly because GEOM_ELI uses multiple encryption keys
>> since version 5. It uses a different key for every 2^20 sectors.
>> So I would put it in the IV generators, too.
>>
>>> Anyway, adding new IV generators to dmcrypt should not be big problem
>>> (in principle).
>>>
>>>> Additionally cryptsetup must be patched to be able
>>>> to deal with the metadata structure of GEOM_ELI devices.
>>>>
>>>> Would there be any interests in committing me this code
>>>> to the upstream?
>>> Well, I am not sure how broadly this format is used and if
>>> there are potential users in Linux world, so if you can
>>> post more description here it would be nice.
>>>
>> It is a common tool in FreeBSD for cryptography. So it would be
>> nice one could access these FreeBSD devices with Linux, too (I think =).
>>
>>> (And if anyone on list is interested, plesase say so... now :)
>>>
>>> But in general, yes, I think it is good idea.
>>>
>>> I would suggest you to do these steps:
>>>
>>> 1) provide links to documentation of format, limitation
>>> of your approach etc (also should be included in patch later)
>>>
>>> 2) first, implement needed changes in Linux kernel (dmcrypt IVs)
>>> (if it is only new IV, it should be straightforward)
>>> Please post patches to DM devel list (dm-devel at redhat.com)
>>>
>>> This should be done in advance - for testing, you can use
>>> dmsetup to configure dmcrypt device and test it works for your
>>> images.
>>>
>>> Cryptsetup support can come later (we need patch in stable kernel
>>> first to release build supporting it).
>>>
>>> 3) second, post patches for cryptsetup to this list.
>>>
>>> Please keep format specific code in separate directory,
>>> lib/geli/* or so.
>>> (I am planning some unified interface for formats in future (1.7),
>>> so see how is e.g. TCRYPT done - it should be very similar).
>>>
>>> Please post at least simple regression tests together with patches
>>> (see tests/ dir).
>>>
>>> License of new code must be compatible with released code,
>>> basically GPL2+/LGPL2.1+ for cryptsetup.
>>>
>>> (And be prepared it will take some time and perhaps reiterated
>>> patch posts - mainly for kernel part :)
>>>
>>> Thanks,
>>> Milan
>>>
>> Thanks for your suggestions.
>>
>> Best regards.
> Regards,
> Carl-Daniel

I haven't yet looked at how cryptsetup feeds the setup data to the
kernel. AFAICS the only data the kernel needs to know is the key and the
sector shift value. Is there any quick howto for adding support for new
on-disk formats to cryptsetup? The GELI on-disk format I care about is
just one metadata sector at the end of the device, and sector 0 to n-2
store encrypted data.

Any help would be appreciated.

Regards,
Carl-Daniel

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup
  2016-12-28 18:06       ` Carl-Daniel Hailfinger
@ 2016-12-30  6:31         ` Carl-Daniel Hailfinger
  2017-01-01 22:23           ` Milan Broz
  0 siblings, 1 reply; 9+ messages in thread
From: Carl-Daniel Hailfinger @ 2016-12-30  6:31 UTC (permalink / raw)
  To: dm-crypt; +Cc: gmazyland

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.

Regards,
Carl-Daniel


On 28.12.2016 19:06, Carl-Daniel Hailfinger wrote:
> Hello everyone,
>
> a short followup with a quick-and-dirty Linux kernel patch. Compile
> tested only. [...]

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup
  2016-12-30  6:31         ` Carl-Daniel Hailfinger
@ 2017-01-01 22:23           ` Milan Broz
  2017-01-05  8:53             ` Carl-Daniel Hailfinger
  0 siblings, 1 reply; 9+ messages in thread
From: Milan Broz @ 2017-01-01 22:23 UTC (permalink / raw)
  To: Carl-Daniel Hailfinger, dm-crypt

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

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

Anyway, thanks for working on this!
Milan

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup
  2017-01-01 22:23           ` Milan Broz
@ 2017-01-05  8:53             ` Carl-Daniel Hailfinger
  2017-01-05 10:54               ` Carl-Daniel Hailfinger
  0 siblings, 1 reply; 9+ messages in thread
From: Carl-Daniel Hailfinger @ 2017-01-05  8:53 UTC (permalink / raw)
  To: Milan Broz, dm-crypt

[-- Attachment #1: Type: text/plain, Size: 3766 bytes --]

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

[-- Attachment #2: 0001-Initial-version-of-cryptsetup-with-GELI-support.patch.02 --]
[-- Type: text/plain, Size: 47746 bytes --]

From 8c0057315fe8ac8022120888c401ae843538289b Mon Sep 17 00:00:00 2001
From: Carl-Daniel Hailfinger <c-d.hailfinger.devel.2006@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


[-- Attachment #3: 0001-Initial-version-of-cryptsetup-with-GELI-support.patch.03 --]
[-- Type: text/plain, Size: 49179 bytes --]

From eaa23ad418e898ed9a395211e57e2d1d2cb598da Mon Sep 17 00:00:00 2001
From: Carl-Daniel Hailfinger <c-d.hailfinger.devel.2006@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


[-- Attachment #4: 0001-Initial-version-of-cryptsetup-with-GELI-support.patch.04 --]
[-- Type: text/plain, Size: 50786 bytes --]

From f6c84b5a1823bbfd576f99bd3c03b40e47063d7c Mon Sep 17 00:00:00 2001
From: Carl-Daniel Hailfinger <c-d.hailfinger.devel.2006@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


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup
  2017-01-05  8:53             ` Carl-Daniel Hailfinger
@ 2017-01-05 10:54               ` Carl-Daniel Hailfinger
  0 siblings, 0 replies; 9+ messages in thread
From: Carl-Daniel Hailfinger @ 2017-01-05 10:54 UTC (permalink / raw)
  To: Milan Broz, dm-crypt

On 05.01.2017 09:53, Carl-Daniel Hailfinger wrote:
> 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.

Please note that SHA512 is always used as HMAC-SHA512 with zero key.


> 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

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2017-01-05 10:54 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-11-30 15:25 [dm-crypt] GEOM_ELI support in dm-crypt/cryptsetup f000m
2013-11-30 17:33 ` Milan Broz
2013-12-13  6:42   ` f000m
2016-12-28 15:46     ` Carl-Daniel Hailfinger
2016-12-28 18:06       ` Carl-Daniel Hailfinger
2016-12-30  6:31         ` Carl-Daniel Hailfinger
2017-01-01 22:23           ` Milan Broz
2017-01-05  8:53             ` Carl-Daniel Hailfinger
2017-01-05 10:54               ` Carl-Daniel Hailfinger

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.