From 044cb58588282ce84a9ea836ff0063d48c424ba0 Mon Sep 17 00:00:00 2001 From: Milan Broz Date: Tue, 25 Jan 2011 18:48:56 +0000 Subject: [PATCH] Add loop-AES compatible handling code. git-svn-id: https://cryptsetup.googlecode.com/svn/trunk@418 36d66b0a-2a48-0410-832c-cd162a569da5 --- ChangeLog | 4 + configure.in | 1 + lib/Makefile.am | 14 ++- lib/libcryptsetup.h | 5 + lib/loopaes/Makefile.am | 16 +++ lib/loopaes/loopaes.c | 216 ++++++++++++++++++++++++++++++++++++++++ lib/loopaes/loopaes.h | 22 ++++ lib/setup.c | 92 ++++++++++++++++- man/cryptsetup.8 | 20 ++++ src/cryptsetup.c | 40 +++++++- 10 files changed, 422 insertions(+), 8 deletions(-) create mode 100644 lib/loopaes/Makefile.am create mode 100644 lib/loopaes/loopaes.c create mode 100644 lib/loopaes/loopaes.h diff --git a/ChangeLog b/ChangeLog index 9abf9ec2..2f0ed03d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2011-01-25 Milan Broz + * Add loop-AES handling (loopaesOpen and loopaesClose commands). + (requires kernel 2.6.38 and above) + 2011-01-05 Milan Broz * Fix static build (--disable-static-cryptsetup now works properly). diff --git a/configure.in b/configure.in index 04b87b50..cfa8f7f1 100644 --- a/configure.in +++ b/configure.in @@ -257,6 +257,7 @@ lib/Makefile lib/libcryptsetup.pc lib/crypto_backend/Makefile lib/luks1/Makefile +lib/loopaes/Makefile src/Makefile po/Makefile.in man/Makefile diff --git a/lib/Makefile.am b/lib/Makefile.am index 962601f2..fc83cb1d 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = crypto_backend luks1 +SUBDIRS = crypto_backend luks1 loopaes moduledir = $(libdir)/cryptsetup @@ -9,6 +9,7 @@ INCLUDES = \ -I$(top_srcdir) \ -I$(top_srcdir)/lib/crypto_backend \ -I$(top_srcdir)/lib/luks1 \ + -I$(top_srcdir)/lib/loopaes \ -DDATADIR=\""$(datadir)"\" \ -DLIBDIR=\""$(libdir)"\" \ -DPREFIX=\""$(prefix)"\" \ @@ -20,7 +21,12 @@ INCLUDES = \ lib_LTLIBRARIES = libcryptsetup.la -libcryptsetup_la_DEPENDENCIES = libcryptsetup.sym +common_ldadd = \ + crypto_backend/libcrypto_backend.la \ + luks1/libluks1.la \ + loopaes/libloopaes.la + +libcryptsetup_la_DEPENDENCIES = $(common_ldadd) libcryptsetup.sym libcryptsetup_la_LDFLAGS = \ -Wl,--version-script=$(top_srcdir)/lib/libcryptsetup.sym \ @@ -32,8 +38,8 @@ libcryptsetup_la_LIBADD = \ @UUID_LIBS@ \ @DEVMAPPER_LIBS@ \ @CRYPTO_LIBS@ \ - crypto_backend/libcrypto_backend.la \ - luks1/libluks1.la + $(common_ldadd) + libcryptsetup_la_SOURCES = \ setup.c \ diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index 872dd1d1..a86bea75 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -146,6 +146,7 @@ int crypt_memory_lock(struct crypt_device *cd, int lock); #define CRYPT_PLAIN "PLAIN" /* regular crypt device, no on-disk header */ #define CRYPT_LUKS1 "LUKS1" /* LUKS version 1 header on-disk */ +#define CRYPT_LOOPAES "LOOPAES" /* loop-AES compatibility mode */ /** * Get device type @@ -167,6 +168,10 @@ struct crypt_params_luks1 { size_t data_alignment; /* in sectors, data offset is multiple of this */ }; +struct crypt_params_loopaes { + const char *hash; /* key hash function */ + uint64_t offset; /* offset in sectors */ +}; /** * Create (format) new crypt device (and possible header on-disk) but not activates it. * diff --git a/lib/loopaes/Makefile.am b/lib/loopaes/Makefile.am new file mode 100644 index 00000000..db93af0f --- /dev/null +++ b/lib/loopaes/Makefile.am @@ -0,0 +1,16 @@ +moduledir = $(libdir)/cryptsetup + +noinst_LTLIBRARIES = libloopaes.la + +libloopaes_la_CFLAGS = -Wall @CRYPTO_CFLAGS@ + +libloopaes_la_SOURCES = \ + loopaes.c \ + loopaes.h + +INCLUDES = -D_GNU_SOURCE \ + -D_LARGEFILE64_SOURCE \ + -D_FILE_OFFSET_BITS=64 \ + -I$(top_srcdir)/lib \ + -I$(top_srcdir)/lib/crypto_backend + diff --git a/lib/loopaes/loopaes.c b/lib/loopaes/loopaes.c new file mode 100644 index 00000000..9219f372 --- /dev/null +++ b/lib/loopaes/loopaes.c @@ -0,0 +1,216 @@ +/* + * loop-AES compatible volume handling + * + * Copyright (C) 2011, Red Hat, Inc. All rights reserved. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +#include "crypto_backend.h" +#include "loopaes.h" + +static const char *get_hash(unsigned int key_size) +{ + char *hash; + + switch (key_size) { + case 16: hash = "sha256"; break; + case 24: hash = "sha384"; break; + case 32: hash = "sha512"; break; + default: hash = NULL; + } + + return hash; +} + +static unsigned char get_tweak(unsigned int keys_count) +{ + switch (keys_count) { + case 64: return 0x55; + case 65: return 0xF4; + default: break; + } + return 0x00; +} + +static int hash_key(const char *src, size_t src_len, + char *dst, size_t dst_len, + const char *hash_name) +{ + struct crypt_hash *hd = NULL; + + if (crypt_hash_init(&hd, hash_name)) + return -EINVAL; + crypt_hash_write(hd, src, src_len); + crypt_hash_final(hd, dst, dst_len); + crypt_hash_destroy(hd); + return 0; +} + +static int hash_keys(struct volume_key **vk, + const char **input_keys, + unsigned int keys_count, + unsigned int key_len_output) +{ + const char *hash_name; + char tweak, *key_ptr; + int r, i, key_len_input; + + hash_name = get_hash(key_len_output); + tweak = get_tweak(keys_count); + key_len_input = strlen(input_keys[0]); + + if (!keys_count || !key_len_output || !hash_name || !key_len_input) + return -EINVAL; + + *vk = crypt_alloc_volume_key(key_len_output * keys_count, NULL); + if (!*vk) + return -ENOMEM; + + for (i = 0; i < keys_count; i++) { + key_ptr = &(*vk)->key[i * key_len_output]; + r = hash_key(input_keys[i], key_len_input, key_ptr, + key_len_output, hash_name); + if (r < 0) + break; + + key_ptr[0] ^= tweak; + } + + if (r < 0 && *vk) { + crypt_free_volume_key(*vk); + *vk = NULL; + } + return r; +} + +static int keyfile_is_gpg(char *buffer, unsigned int buffer_len) +{ + int r = 0; + int index = buffer_len < 100 ? buffer_len - 1 : 100; + char eos = buffer[index]; + + buffer[index] = '\0'; + if (strstr(buffer, "BEGIN PGP MESSAGE")) + r = 1; + buffer[index] = eos; + return r; +} + +int LOOPAES_parse_keyfile(struct crypt_device *cd, + struct volume_key **vk, + unsigned int *keys_count, + char *buffer, + unsigned int buffer_len) + +{ + const char *keys[LOOPAES_KEYS_MAX]; + int i, key_index, key_len, offset; + + log_dbg("Parsing loop-AES keyfile of size %d.", buffer_len); + + if (buffer_len < LOOPAES_KEYFILE_MINSIZE) { + log_err(cd, _("Incompatible loop-AES keyfile detected.\n")); + return -EINVAL; + } + + if (keyfile_is_gpg(buffer, buffer_len)) { + log_err(cd, "Detected not yet supported GPG encrypted keyfile.\n"); + log_std(cd, "Please use gpg --decrypt | cryptsetup --keyfile=- ...\n"); + return -EINVAL; + } + + /* Remove EOL in buffer */ + for (i = 0; i < buffer_len; i++) + if (buffer[i] == '\n' || buffer[i] == '\r') + buffer[i] = '\0'; + + offset = 0; + key_index = 0; + while (offset < buffer_len && key_index < LOOPAES_KEYS_MAX) { + keys[key_index++] = &buffer[offset]; + while (offset < buffer_len && buffer[offset]) + offset++; + while (offset < buffer_len && !buffer[offset]) + offset++; + } + + /* All keys must be the same length */ + key_len = key_index ? strlen(keys[0]) : 0; + for (i = 0; i < key_index; i++) + if (key_len != strlen(keys[i])) { + log_dbg("Unexpected length %d of key #%d (should be %d).", + strlen(keys[i]), i, key_len); + key_len = 0; + break; + } + + log_dbg("Keyfile: %d keys of length %d.", key_index, key_len); + if (offset != buffer_len || key_len == 0 || + (key_index != 1 && key_index !=64 && key_index != 65)) { + log_err(cd, _("Incompatible loop-AES keyfile detected.\n")); + return -EINVAL; + } + + *keys_count = key_index; + return hash_keys(vk, keys, key_index, crypt_get_volume_key_size(cd)); +} + +int LOOPAES_activate(struct crypt_device *cd, + const char *name, + const char *base_cipher, + unsigned int keys_count, + struct volume_key *vk, + uint32_t flags) +{ + uint64_t size, offset; + char *cipher; + const char *device; + int read_only, r; + + size = 0; + offset = crypt_get_data_offset(cd); + device = crypt_get_device_name(cd); + read_only = flags & CRYPT_ACTIVATE_READONLY; + + r = device_check_and_adjust(cd, device, 1, &size, &offset, &read_only); + if (r) + return r; + + if (keys_count == 1) { + if (asprintf(&cipher, "%s-%s", base_cipher, "cbc-plain") < 0) + return -ENOMEM; + } else { + if (!(dm_flags() & DM_LMK_SUPPORTED)) { + log_err(cd, _("Kernel doesn't support loop-AES compatible mapping.\n")); + return -ENOTSUP; + } + if (asprintf(&cipher, "%s:%d-%s", base_cipher, 64, "cbc-lmk") < 0) + return -ENOMEM; + } + + log_dbg("Trying to activate loop-AES device %s using cipher %s.", name, cipher); + r = dm_create_device(name, device, + cipher, CRYPT_LOOPAES, + crypt_get_uuid(cd), + size, 0, offset, vk->keylength, vk->key, + read_only, 0); + free(cipher); + return r; +} diff --git a/lib/loopaes/loopaes.h b/lib/loopaes/loopaes.h new file mode 100644 index 00000000..4ea37c62 --- /dev/null +++ b/lib/loopaes/loopaes.h @@ -0,0 +1,22 @@ +#ifndef _LOOPAES_H +#define _LOOPAES_H + +#define LOOPAES_KEYS_MAX 65 +#define LOOPAES_KEYFILE_MINSIZE 60 +#define LOOPAES_KEYFILE_MAXSIZE 8000 + +#define DEFAULT_LOOPAES_CIPHER "aes" + +int LOOPAES_parse_keyfile(struct crypt_device *cd, + struct volume_key **vk, + unsigned int *keys_count, + char *buffer, + unsigned int buffer_len); + +int LOOPAES_activate(struct crypt_device *cd, + const char *name, + const char *base_cipher, + unsigned int keys_count, + struct volume_key *vk, + uint32_t flags); +#endif diff --git a/lib/setup.c b/lib/setup.c index 94e89414..01b202e0 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -7,6 +7,7 @@ #include "libcryptsetup.h" #include "luks.h" +#include "loopaes.h" #include "internal.h" #include "crypto_backend.h" @@ -31,6 +32,13 @@ struct crypt_device { char *plain_cipher_mode; char *plain_uuid; + /* used in CRYPT_LOOPAES */ + struct crypt_params_loopaes loopaes_hdr; + char *loopaes_cipher; + char *loopaes_cipher_mode; + char *loopaes_uuid; + unsigned int loopaes_key_size; + /* callbacks definitions */ void (*log)(int level, const char *msg, void *usrptr); void *log_usrptr; @@ -160,6 +168,11 @@ static int isLUKS(const char *type) return (type && !strcmp(CRYPT_LUKS1, type)); } +static int isLOOPAES(const char *type) +{ + return (type && !strcmp(CRYPT_LOOPAES, type)); +} + /* keyslot helpers */ static int keyslot_verify_or_find_empty(struct crypt_device *cd, int *keyslot) { @@ -991,7 +1004,7 @@ int crypt_init_by_name(struct crypt_device **cd, const char *name) char *device = NULL, *cipher_full = NULL, *device_uuid = NULL; char cipher[MAX_CIPHER_LEN], cipher_mode[MAX_CIPHER_LEN]; char *key = NULL; - int key_size = 0, r; + int key_size = 0, key_nums, r; log_dbg("Allocating crypt device context by device %s.", name); @@ -1040,6 +1053,21 @@ int crypt_init_by_name(struct crypt_device **cd, const char *name) (*cd)->plain_cipher = strdup(cipher); (*cd)->plain_cipher_mode = strdup(cipher_mode); } + } else if (!strncmp(CRYPT_LOOPAES, device_uuid, sizeof(CRYPT_LOOPAES)-1)) { + (*cd)->type = strdup(CRYPT_LOOPAES); + (*cd)->loopaes_uuid = strdup(device_uuid); + (*cd)->loopaes_hdr.offset = cad.offset; + + r = crypt_parse_name_and_mode(cipher_full, cipher, + &key_nums, cipher_mode); + if (!r) { + (*cd)->loopaes_cipher = strdup(cipher); + (*cd)->loopaes_cipher_mode = strdup(cipher_mode); + /* version 3 uses last key for IV */ + if (key_size % key_nums) + key_nums++; + (*cd)->loopaes_key_size = key_size / key_nums; + } } else if (!strncmp(CRYPT_LUKS1, device_uuid, sizeof(CRYPT_LUKS1)-1)) { if (device) { if (crypt_load(*cd, CRYPT_LUKS1, NULL) < 0 || @@ -1168,6 +1196,37 @@ static int _crypt_format_luks1(struct crypt_device *cd, return r; } +static int _crypt_format_loopaes(struct crypt_device *cd, + const char *cipher, + const char *uuid, + size_t volume_key_size, + struct crypt_params_loopaes *params) +{ + if (!cd->device) { + log_err(cd, _("Can't format LOOPAES without device.\n")); + return -EINVAL; + } + + if (volume_key_size > 1024) { + log_err(cd, _("Invalid key size.\n")); + return -EINVAL; + } + + cd->loopaes_key_size = volume_key_size; + + cd->loopaes_cipher = strdup(cipher ?: DEFAULT_LOOPAES_CIPHER); + + if (uuid) + cd->loopaes_uuid = strdup(uuid); + + if (params && params->hash) + cd->loopaes_hdr.hash = strdup(params->hash); + + cd->loopaes_hdr.offset = params ? params->offset : 0; + + return 0; +} + int crypt_format(struct crypt_device *cd, const char *type, const char *cipher, @@ -1194,6 +1253,8 @@ int crypt_format(struct crypt_device *cd, else if (isLUKS(type)) r = _crypt_format_luks1(cd, cipher, cipher_mode, uuid, volume_key, volume_key_size, params); + else if (isLOOPAES(type)) + r = _crypt_format_loopaes(cd, cipher, uuid, volume_key_size, params); else { /* FIXME: allow plugins here? */ log_err(cd, _("Unknown crypt device type %s requested.\n"), type); @@ -1815,7 +1876,7 @@ int crypt_activate_by_keyfile(struct crypt_device *cd, crypt_status_info ci; struct volume_key *vk = NULL; char *passphrase_read = NULL; - unsigned int passphrase_size_read; + unsigned int passphrase_size_read, key_count = 0; int r; log_dbg("Activating volume %s [keyslot %d] using keyfile %s.", @@ -1863,6 +1924,18 @@ int crypt_activate_by_keyfile(struct crypt_device *cd, goto out; } r = keyslot; + } else if (isLOOPAES(cd->type)) { + r = key_from_file(cd, NULL, &passphrase_read, &passphrase_size_read, + keyfile, LOOPAES_KEYFILE_MAXSIZE); + if (r < 0) + goto out; + r = LOOPAES_parse_keyfile(cd, &vk, &key_count, + passphrase_read, passphrase_size_read); + if (r < 0) + goto out; + if (name) + r = LOOPAES_activate(cd, name, cd->loopaes_cipher, + key_count, vk, flags); } else r = -EINVAL; @@ -2185,6 +2258,9 @@ const char *crypt_get_cipher(struct crypt_device *cd) if (isLUKS(cd->type)) return cd->hdr.cipherName; + if (isLOOPAES(cd->type)) + return cd->loopaes_cipher; + return NULL; } @@ -2196,6 +2272,9 @@ const char *crypt_get_cipher_mode(struct crypt_device *cd) if (isLUKS(cd->type)) return cd->hdr.cipherMode; + if (isLOOPAES(cd->type)) + return cd->loopaes_cipher_mode; + return NULL; } @@ -2207,6 +2286,9 @@ const char *crypt_get_uuid(struct crypt_device *cd) if (isPLAIN(cd->type)) return cd->plain_uuid; + if (isLOOPAES(cd->type)) + return cd->loopaes_uuid; + return NULL; } @@ -2223,6 +2305,9 @@ int crypt_get_volume_key_size(struct crypt_device *cd) if (isLUKS(cd->type)) return cd->hdr.keyBytes; + if (isLOOPAES(cd->type)) + return cd->loopaes_key_size; + return 0; } @@ -2234,6 +2319,9 @@ uint64_t crypt_get_data_offset(struct crypt_device *cd) if (isLUKS(cd->type)) return cd->hdr.payloadOffset; + if (isLOOPAES(cd->type)) + return cd->loopaes_hdr.offset; + return 0; } diff --git a/man/cryptsetup.8 b/man/cryptsetup.8 index 1ba2c04e..6b948985 100644 --- a/man/cryptsetup.8 +++ b/man/cryptsetup.8 @@ -134,6 +134,26 @@ This command allows restoring header if device do not contain LUKS header or if For more information about LUKS, see \fBhttp://code.google.com/p/cryptsetup/wiki/Specification\fR +.SH Loop-AES EXTENSION + +cryptsetup allows to map loop-AES encrypted partition using compatible dm-crypt mode. + +.PP +\fIloopaesOpen\fR \-\-key-file +.IP +opens the loop-AES and sets up a mapping . + +N.B. If keyfile is in GPG encrypted format, you have to use \-\-key-file=- and decrypt it before use. +gpg --decrypt | cryptsetup loopaesOpen \-\-key-file=- + +\fB\fR can be [\-\-key-file, \-\-key-size, \-\-offset, \-\-readonly]. +.PP +\fIloopaesClose\fR +.IP +identical to \fIremove\fR. +.PP +For more information about loop-AES, see \fBhttp://loop-aes.sourceforge.net\fR + .SH OPTIONS .TP .B "\-\-verbose, \-v" diff --git a/src/cryptsetup.c b/src/cryptsetup.c index 36394b41..25583f21 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -62,6 +62,7 @@ static int action_luksSuspend(int arg); static int action_luksResume(int arg); static int action_luksBackup(int arg); static int action_luksRestore(int arg); +static int action_loopaesOpen(int arg); static struct action_type { const char *type; @@ -89,6 +90,8 @@ static struct action_type { { "luksResume", action_luksResume, 0, 1, 1, N_(""), N_("Resume suspended LUKS device.") }, { "luksHeaderBackup",action_luksBackup, 0, 1, 1, N_(""), N_("Backup LUKS device header and keyslots") }, { "luksHeaderRestore",action_luksRestore,0,1, 1, N_(""), N_("Restore LUKS device header and keyslots") }, + { "loopaesOpen",action_loopaesOpen, 0, 2, 1, N_(" "), N_("open loop-AES device as mapping ") }, + { "loopaesClose",action_remove, 0, 1, 1, N_(""), N_("remove loop-AES mapping") }, { NULL, NULL, 0, 0, 0, NULL, NULL } }; @@ -253,6 +256,38 @@ out: return r; } +static int action_loopaesOpen(int arg) +{ + struct crypt_device *cd = NULL; + struct crypt_params_loopaes params = { + .hash = opt_hash ?: NULL, // FIXME + .offset = opt_offset, + }; + unsigned int key_size = (opt_key_size ?: 128) / 8; + int r; + + if (!opt_key_file) { + log_err(_("Option --key-file is required.\n")); + return -EINVAL; + } + + if ((r = crypt_init(&cd, action_argv[1]))) + goto out; + + r = crypt_format(cd, CRYPT_LOOPAES, NULL, NULL, NULL, NULL, + key_size, ¶ms); + if (r < 0) + goto out; + + r = crypt_activate_by_keyfile(cd, action_argv[0], + CRYPT_ANY_SLOT, opt_key_file, 0, + opt_readonly ? CRYPT_ACTIVATE_READONLY : 0); +out: + crypt_free(cd); + + return r; +} + static int action_remove(int arg) { struct crypt_device *cd = NULL; @@ -1027,9 +1062,10 @@ int main(int argc, char **argv) if (opt_key_size && strcmp(aname, "luksFormat") && - strcmp(aname, "create")) { + strcmp(aname, "create") && + strcmp(aname, "loopaesOpen")) { usage(popt_context, EXIT_FAILURE, - _("Option --key-size is allowed only for luksFormat and create.\n" + _("Option --key-size is allowed only for luksFormat, create and loopaesOpen.\n" "To limit read from keyfile use --keyfile-size=(bytes)."), poptGetInvocationName(popt_context)); }