--- /dev/null
+/*
+ * TCRYPT compatible volume handling
+ *
+ * Copyright (C) 2012, Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2012, Milan Broz
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; 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 "tcrypt.h"
+#include "internal.h"
+
+/* TCRYPT PBKDF variants */
+static struct {
+ char *name;
+ char *hash;
+ unsigned int iterations;
+} tcrypt_kdf[] = {
+ { "pbkdf2", "ripemd160", 2000 },
+ { "pbkdf2", "ripemd160", 1000 },
+ { "pbkdf2", "sha512", 1000 },
+ { "pbkdf2", "whirlpool", 1000 },
+ { NULL, NULL, 0 }
+};
+
+/* TCRYPT cipher variants */
+static struct {
+ const char *cipher[3];
+ const char *mode;
+ int key_size;
+} tcrypt_cipher[] = {
+ { { "aes", NULL, NULL }, "xts-plain64", 64 },
+ { { "twofish", NULL, NULL }, "xts-plain64", 64 },
+ { { "serpent", NULL, NULL }, "xts-plain64", 64 },
+ { { "aes", "twofish", "serpent" }, "xts-plain64", 64 },
+ { { "serpent", "twofish", "aes" }, "xts-plain64", 64 },
+ { { "twofish", "aes", NULL }, "xts-plain64", 64 },
+ { { "aes", "serpent", NULL }, "xts-plain64", 64 },
+ { { "serpent", "twofish", NULL }, "xts-plain64", 64 },
+ { { "aes", NULL, NULL }, "lrw-benbi", 48 },
+ { { "twofish", NULL, NULL }, "lrw-benbi", 48 },
+ { { "serpent", NULL, NULL }, "lrw-benbi", 48 },
+ { { "aes", "twofish", "serpent" }, "lrw-benbi", 48 },
+ { { "serpent", "twofish", "aes" }, "lrw-benbi", 48 },
+ { { "twofish", "aes", NULL }, "lrw-benbi", 48 },
+ { { "aes", "serpent", NULL }, "lrw-benbi", 48 },
+ { { "serpent", "twofish", NULL }, "lrw-benbi", 48 },
+ { { NULL, NULL, NULL }, NULL, 0 }
+};
+
+static void hdr_info(struct crypt_device *cd, struct tcrypt_phdr *hdr,
+ struct crypt_params_tcrypt *params)
+{
+ log_dbg("Version: %d, required %d", (int)hdr->d.version, (int)hdr->d.version_tc);
+
+ log_dbg("Hidden size: %" PRIu64, hdr->d.hidden_volume_size);
+ log_dbg("Volume size: %" PRIu64, hdr->d.volume_size);
+
+ log_dbg("Sector size: %" PRIu64, hdr->d.sector_size);
+ log_dbg("Flags: %d", (int)hdr->d.flags);
+ log_dbg("MK: offset %d, size %d", (int)hdr->d.mk_offset, (int)hdr->d.mk_size);
+ log_dbg("KDF: PBKDF2, hash %s", params->hash_name);
+ log_dbg("Cipher: %s%s%s%s%s-%s",
+ params->cipher[0],
+ params->cipher[1] ? "-" : "", params->cipher[1] ?: "",
+ params->cipher[2] ? "-" : "", params->cipher[2] ?: "",
+ params->mode);
+}
+
+static int hdr_from_disk(struct tcrypt_phdr *hdr,
+ struct crypt_params_tcrypt *params,
+ int kdf_index, int cipher_index)
+{
+ uint32_t crc32;
+ size_t size;
+
+ /* Check CRC32 of header */
+ size = TCRYPT_HDR_LEN - sizeof(hdr->d.keys) - sizeof(hdr->d.header_crc32);
+ crc32 = crypt_crc32(~0, (unsigned char*)&hdr->d, size) ^ ~0;
+ if (be16_to_cpu(hdr->d.version) > 3 &&
+ crc32 != be32_to_cpu(hdr->d.header_crc32)) {
+ log_dbg("TCRYPT header CRC32 mismatch.");
+ return -EINVAL;
+ }
+
+ /* Check CRC32 of keys */
+ crc32 = crypt_crc32(~0, (unsigned char*)hdr->d.keys, sizeof(hdr->d.keys)) ^ ~0;
+ if (crc32 != be32_to_cpu(hdr->d.keys_crc32)) {
+ log_dbg("TCRYPT keys CRC32 mismatch.");
+ return -EINVAL;
+ }
+
+ /* Convert header to cpu format */
+ hdr->d.version = be16_to_cpu(hdr->d.version);
+ hdr->d.version_tc = le16_to_cpu(hdr->d.version_tc); // ???
+
+ hdr->d.keys_crc32 = be32_to_cpu(hdr->d.keys_crc32);
+
+ hdr->d.hidden_volume_size = be64_to_cpu(hdr->d.hidden_volume_size);
+ hdr->d.volume_size = be64_to_cpu(hdr->d.volume_size);
+
+ hdr->d.mk_offset = be64_to_cpu(hdr->d.mk_offset);
+ if (!hdr->d.mk_offset)
+ hdr->d.mk_offset = 512;
+
+ hdr->d.mk_size = be64_to_cpu(hdr->d.mk_size);
+
+ hdr->d.flags = be32_to_cpu(hdr->d.flags);
+
+ hdr->d.sector_size = be32_to_cpu(hdr->d.sector_size);
+ if (!hdr->d.sector_size)
+ hdr->d.sector_size = 512;
+
+ hdr->d.header_crc32 = be32_to_cpu(hdr->d.header_crc32);
+
+ /* Set params */
+ params->passphrase = NULL;
+ params->passphrase_size = 0;
+
+ params->hash_name = tcrypt_kdf[kdf_index].hash;
+
+ params->cipher[0] = tcrypt_cipher[cipher_index].cipher[0];
+ params->cipher[1] = tcrypt_cipher[cipher_index].cipher[1];
+ params->cipher[2] = tcrypt_cipher[cipher_index].cipher[2];
+ params->mode = tcrypt_cipher[cipher_index].mode;
+ params->key_size = tcrypt_cipher[cipher_index].key_size;
+
+ return 0;
+}
+
+static int decrypt_hdr_one(const char *name, const char *mode,
+ const char *key, size_t key_size,
+ struct tcrypt_phdr *hdr)
+{
+ char iv[TCRYPT_HDR_IV_LEN] = {};
+ char mode_name[MAX_CIPHER_LEN];
+ struct crypt_cipher *cipher;
+ void *buf = &hdr->e;
+ char *c;
+ int r;
+
+ /* Remove IV if present */
+ strncpy(mode_name, mode, MAX_CIPHER_LEN);
+ c = strchr(mode_name, '-');
+ if (c)
+ *c = '\0';
+
+ if (!strncmp(mode, "lrw", 3))
+ iv[15] = 1;
+
+ r = crypt_cipher_init(&cipher, name, mode_name, key, key_size);
+ if (r < 0)
+ return r;
+
+ r = crypt_cipher_decrypt(cipher, buf, buf, TCRYPT_HDR_LEN,
+ iv, TCRYPT_HDR_IV_LEN);
+ crypt_cipher_destroy(cipher);
+
+ return r;
+}
+
+static void copy_key(char *out_key, const char *key, int key_num,
+ int ks, int ki, const char *mode)
+{
+ if (!strncmp(mode, "xts", 3)) {
+ int ks2 = ks / 2;
+ memcpy(out_key, &key[ks2 * ki], ks2);
+ memcpy(&out_key[ks2], &key[ks2 * (++key_num + ki)], ks2);
+ } else if (!strncmp(mode, "lrw", 3)) {
+ /* First is LRW index key */
+ ki++;
+ ks -= TCRYPT_LRW_IKEY_LEN;
+ memcpy(out_key, &key[ks * ki], ks);
+ memcpy(&out_key[ks * ki], key, TCRYPT_LRW_IKEY_LEN);
+ }
+}
+
+static int top_cipher(const char *cipher[3])
+{
+ if (cipher[2])
+ return 2;
+
+ if (cipher[1])
+ return 1;
+
+ return 0;
+}
+
+static int decrypt_hdr(struct crypt_device *cd, struct tcrypt_phdr *hdr,
+ const char *key)
+{
+ char one_key[TCRYPT_HDR_KEY_LEN];
+ struct tcrypt_phdr hdr2;
+ int i, j, r;
+
+ for (i = 0; tcrypt_cipher[i].cipher[0]; i++) {
+ log_dbg("TCRYPT: trying cipher: %s%s%s%s%s-%s.",
+ tcrypt_cipher[i].cipher[0],
+ tcrypt_cipher[i].cipher[1] ? "-" : "", tcrypt_cipher[i].cipher[1] ?: "",
+ tcrypt_cipher[i].cipher[2] ? "-" : "", tcrypt_cipher[i].cipher[2] ?: "",
+ tcrypt_cipher[i].mode);
+
+ memcpy(&hdr2.e, &hdr->e, TCRYPT_HDR_LEN);
+
+ for (j = 2; j >= 0 ; j--) {
+ if (!tcrypt_cipher[i].cipher[j])
+ continue;
+ copy_key(one_key, key, top_cipher(tcrypt_cipher[i].cipher),
+ tcrypt_cipher[i].key_size,
+ j, tcrypt_cipher[i].mode);
+ r = decrypt_hdr_one(tcrypt_cipher[i].cipher[j],
+ tcrypt_cipher[i].mode, one_key,
+ tcrypt_cipher[i].key_size, &hdr2);
+ if (r < 0)
+ break;
+ }
+
+ 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);
+ memset(&hdr2.e, 0, TCRYPT_HDR_LEN);
+ r = i;
+ break;
+ }
+ r = -EPERM;
+ }
+
+ memset(one_key, 0, sizeof(*one_key));
+ return r;
+}
+
+static int TCRYPT_init_hdr(struct crypt_device *cd,
+ struct tcrypt_phdr *hdr,
+ struct crypt_params_tcrypt *params,
+ const char *passphrase,
+ size_t passphrase_size)
+{
+ char *key;
+ int r, i;
+
+ if (posix_memalign((void*)&key, crypt_getpagesize(), TCRYPT_HDR_KEY_LEN))
+ return -ENOMEM;
+
+ for (i = 0; tcrypt_kdf[i].name; i++) {
+ /* 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,
+ passphrase, passphrase_size,
+ hdr->salt, TCRYPT_HDR_SALT_LEN,
+ key, TCRYPT_HDR_KEY_LEN,
+ tcrypt_kdf[i].iterations);
+ if (r < 0)
+ break;
+
+ /* Decrypt header */
+ r = decrypt_hdr(cd, hdr, key);
+ if (r != -EPERM)
+ break;
+ }
+ free(key);
+
+ if (r < 0)
+ return r;
+
+ r = hdr_from_disk(hdr, params, i, r);
+ if (r < 0)
+ return r;
+
+ hdr_info(cd, hdr, params);
+ return 0;
+}
+
+int TCRYPT_read_phdr(struct crypt_device *cd,
+ struct tcrypt_phdr *hdr,
+ struct crypt_params_tcrypt *params,
+ const char *passphrase,
+ size_t passphrase_size,
+ uint32_t flags)
+{
+ struct device *device = crypt_metadata_device(cd);
+ ssize_t hdr_size = sizeof(struct tcrypt_phdr);
+ int devfd = 0, r;
+
+ assert(sizeof(struct tcrypt_phdr) == 512);
+
+ log_dbg("Reading TCRYPT header of size %d bytes from device %s.",
+ hdr_size, device_path(device));
+
+ devfd = open(device_path(device), O_RDONLY | O_DIRECT);
+ if (devfd == -1) {
+ log_err(cd, _("Cannot open device %s.\n"), device_path(device));
+ return -EINVAL;
+ }
+
+ if ((flags & CRYPT_TCRYPT_HIDDEN_HEADER) &&
+ lseek(devfd, TCRYPT_HDR_HIDDEN_OFFSET, SEEK_SET) < 0) {
+ log_err(cd, _("Cannot seek to hidden header for %s.\n"), device_path(device));
+ r = -EIO;
+ goto out;
+ }
+
+ if (read_blockwise(devfd, device_block_size(device), hdr, hdr_size) == hdr_size) {
+ params->flags = flags;
+ r = TCRYPT_init_hdr(cd, hdr, params, passphrase, passphrase_size);
+ } else
+ r = -EIO;
+out:
+ close(devfd);
+ return r;
+}
+
+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];
+ struct device *device = NULL;
+ int i, r;
+ 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),
+ }
+ };
+
+ r = device_block_adjust(cd, dmd.data_device, DEV_EXCL,
+ dmd.u.crypt.offset, &dmd.size, &dmd.flags);
+ if (r)
+ return r;
+
+ dmd.u.crypt.vk = crypt_alloc_volume_key(params->key_size, NULL);
+ if (!dmd.u.crypt.vk)
+ return -ENOMEM;
+
+ for (i = 2; i >= 0; i--) {
+
+ if (!params->cipher[i])
+ continue;
+
+ if (i == 0) {
+ strncpy(dm_name, name, sizeof(dm_name));
+ dmd.flags = flags;
+ } else {
+ snprintf(dm_name, sizeof(dm_name), "%s_%d", name, i);
+ dmd.flags = flags | CRYPT_ACTIVATE_PRIVATE;
+ }
+
+ snprintf(cipher, sizeof(cipher), "%s-%s",
+ params->cipher[i], params->mode);
+ copy_key(dmd.u.crypt.vk->key, hdr->d.keys,
+ top_cipher(params->cipher),
+ params->key_size, i, params->mode);
+
+ if (top_cipher(params->cipher) != i) {
+ snprintf(dm_dev_name, sizeof(dm_dev_name), "%s/%s_%d",
+ dm_get_dir(), name, i + 1);
+ 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 && !(dm_flags() & DM_PLAIN64_SUPPORTED)) {
+ log_err(cd, _("Kernel doesn't support plain64 IV.\n"));
+ r = -ENOTSUP;
+ }
+
+ crypt_free_volume_key(dmd.u.crypt.vk);
+ return r;
+}
static int opt_shared = 0;
static int opt_allow_discards = 0;
static int opt_test_passphrase = 0;
+static int opt_hidden = 0;
static const char **action_argv;
static int action_argc;
static int action_luksRestore(int arg);
static int action_loopaesOpen(int arg);
static int action_luksRepair(int arg);
+static int action_tcryptOpen(int arg);
static struct action_type {
const char *type;
{ "luksHeaderRestore",action_luksRestore,0,1, 1, N_("<device>"), N_("Restore LUKS device header and keyslots") },
{ "loopaesOpen",action_loopaesOpen, 0, 2, 1, N_("<device> <name> "), N_("open loop-AES device as mapping <name>") },
{ "loopaesClose",action_remove, 0, 1, 1, N_("<name>"), N_("remove loop-AES mapping") },
+ { "tcryptOpen", action_tcryptOpen, 0, 2, 1, N_("<device> <name> "), N_("open TCRYPT device as mapping <name>") },
{ NULL, NULL, 0, 0, 0, NULL, NULL }
};
return r;
}
+static int action_tcryptOpen(int arg __attribute__((unused)))
+{
+ struct crypt_device *cd = NULL;
+ struct crypt_params_tcrypt params = {};
+ const char *activated_name;
+ uint32_t flags = 0;
+ int r;
+
+ activated_name = opt_test_passphrase ? NULL : action_argv[1];
+
+ if ((r = crypt_init(&cd, action_argv[0])))
+ goto out;
+
+ /* TCRYPT header is encrypted, get passphrase now */
+ r = crypt_get_key(_("Enter passphrase: "),
+ CONST_CAST(char**)¶ms.passphrase,
+ ¶ms.passphrase_size,
+ opt_keyfile_offset, opt_keyfile_size,
+ NULL, opt_timeout,
+ _verify_passphrase(0),
+ cd);
+ if (r < 0)
+ goto out;
+
+ if (opt_hidden)
+ params.flags |= CRYPT_TCRYPT_HIDDEN_HEADER;
+
+ r = crypt_load(cd, CRYPT_TCRYPT, ¶ms);
+ if (r < 0)
+ goto out;
+
+ if (opt_readonly)
+ flags |= CRYPT_ACTIVATE_READONLY;
+
+ r = crypt_activate_by_volume_key(cd, activated_name, NULL, 0, flags);
+out:
+ crypt_free(cd);
+ crypt_safe_free(CONST_CAST(char*)params.passphrase);
+ return r;
+}
+
static int action_remove(int arg __attribute__((unused)))
{
struct crypt_device *cd = NULL;
{ "allow-discards", '\0', POPT_ARG_NONE, &opt_allow_discards, 0, N_("Allow discards (aka TRIM) requests for device."), NULL },
{ "header", '\0', POPT_ARG_STRING, &opt_header_device, 0, N_("Device or file with separated LUKS header."), NULL },
{ "test-passphrase", '\0', POPT_ARG_NONE, &opt_test_passphrase, 0, N_("Do not activate device, just check passphrase."), NULL },
+ { "hidden", '\0', POPT_ARG_NONE, &opt_hidden, 0, N_("Use hidden header (hiden TCRYPT device) ."), NULL },
POPT_TABLEEND
};
poptContext popt_context;
_("Option --offset is supported only for create and loopaesOpen commands.\n"),
poptGetInvocationName(popt_context));
+ if (opt_hidden && strcmp(aname, "tcryptOpen"))
+ usage(popt_context, EXIT_FAILURE,
+ _("Option --hidden is supported only for tcryptOpen command.\n"),
+ poptGetInvocationName(popt_context));
+
if (opt_debug) {
opt_verbose = 1;
crypt_set_debug_level(-1);