From f6c84b5a1823bbfd576f99bd3c03b40e47063d7c Mon Sep 17 00:00:00 2001 From: Carl-Daniel Hailfinger 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 +#include +#include +#include +#include +#include + +#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 + +/* 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, ¶ms); + 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, ¶ms); + 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_(""), N_("tests for LUKS partition header") }, { "luksDump", action_luksDump, 1, 1, N_(""), N_("dump LUKS partition information") }, { "tcryptDump", action_tcryptDump, 1, 1, N_(""), N_("dump TCRYPT device information") }, + { "geliDump", action_geliDump, 1, 1, N_(""), N_("dump GELI device information") }, { "luksSuspend", action_luksSuspend, 1, 1, N_(""), N_("Suspend LUKS device and wipe key (all IOs are frozen).") }, { "luksResume", action_luksResume, 1, 1, N_(""), N_("Resume suspended LUKS device.") }, { "luksHeaderBackup", action_luksBackup,1,1, N_(""), 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