From 3cea5dcc7b80571d705f922ac112b0577b4fae47 Mon Sep 17 00:00:00 2001 From: Milan Broz Date: Wed, 2 Sep 2009 12:47:21 +0000 Subject: [PATCH] * Add luksSuspend (freeze device and wipe key) and luksResume (with provided passphrase). Signed-off-by: Milan Broz git-svn-id: https://cryptsetup.googlecode.com/svn/trunk@104 36d66b0a-2a48-0410-832c-cd162a569da5 --- ChangeLog | 3 + lib/internal.h | 7 +- lib/libcryptsetup.h | 57 +++++++++++ lib/libdevmapper.c | 80 +++++++++++++++- lib/setup.c | 225 +++++++++++++++++++++++++++++++++++++++----- man/cryptsetup.8 | 12 +++ src/cryptsetup.c | 39 ++++++++ tests/apitest.c | 30 ++++++ 8 files changed, 423 insertions(+), 30 deletions(-) diff --git a/ChangeLog b/ChangeLog index dd6fe965..6e4ccd24 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +2009-09-02 Milan Broz + * Add luksSuspend (freeze device and wipe key) and luksResume (with provided passphrase). + 2009-08-30 Milan Broz * Require device device-mapper to build and do not use backend wrapper for dm calls. * Move memory locking and dm initialization to command layer. diff --git a/lib/internal.h b/lib/internal.h index 344cd7c7..62c44f5d 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -80,11 +80,16 @@ int dm_query_device(const char *name, char **cipher, int *key_size, char **key, - int *read_only); + int *read_only, + int *suspended); int dm_create_device(const char *name, const char *device, const char *cipher, const char *uuid, uint64_t size, uint64_t skip, uint64_t offset, size_t key_size, const char *key, int read_only, int reload); +int dm_suspend_and_wipe_key(const char *name); +int dm_resume_and_reinstate_key(const char *name, + size_t key_size, + const char *key); int sector_size_for_device(const char *device); ssize_t write_blockwise(int fd, const void *buf, size_t count); diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index 3805fd0a..3c296a9b 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -14,12 +14,24 @@ struct crypt_device; /* crypt device handle */ * Returns 0 on success or negative errno value otherwise. * * @cd - returns pointer to crypt device handle + * @device - path to device * * Note that logging is not initialized here, possible messages uses * default log function. */ int crypt_init(struct crypt_device **cd, const char *device); +/** + * Initialise crypt device handle from provided active device name + * and check if provided device exists. + * + * Returns 0 on success or negative errno value otherwise. + * + * @cd - crypt device handle + * @name - name of active crypt device + */ +int crypt_init_by_name(struct crypt_device **cd, const char *name); + /** * Set log function. * @@ -152,6 +164,51 @@ int crypt_load(struct crypt_device *cd, const char *requested_type, void *params); +/** + * Suspends crypt device. + * + * Returns 0 on success or negative errno value otherwise. + * + * @cd - crypt device handle, can be NULL + * @name - name of device to suspend + */ +int crypt_suspend(struct crypt_device *cd, + const char *name); + +/** + * Resumes crypt device using passphrase. + * + * Returns unlocked key slot number or negative errno otherwise. + * + * @cd - crypt device handle + * @name - name of device to resume + * @keyslot - requested keyslot or CRYPT_ANY_SLOT + * @passphrase - passphrase used to unlock volume key, NULL for query + * @passphrase_size - size of @passphrase (binary data) + */ +int crypt_resume_by_passphrase(struct crypt_device *cd, + const char *name, + int keyslot, + const char *passphrase, + size_t passphrase_size); + +/** + * Resumes crypt device using key file. + * + * Returns unlocked key slot number or negative errno otherwise. + * + * @cd - crypt device handle + * @name - name of device to resume + * @keyslot - requested keyslot or CRYPT_ANY_SLOT + * @keyfile - key file used to unlock volume key, NULL for passphrase query + * @keyfile_size - number of bytes to read from @keyfile, 0 is unlimited + */ +int crypt_resume_by_keyfile(struct crypt_device *cd, + const char *name, + int keyslot, + const char *keyfile, + size_t keyfile_size); + /** * Releases crypt device context and used memory. * diff --git a/lib/libdevmapper.c b/lib/libdevmapper.c index 7d72a2fe..61d03695 100644 --- a/lib/libdevmapper.c +++ b/lib/libdevmapper.c @@ -143,19 +143,25 @@ static int _dev_read_ahead(const char *dev, uint32_t *read_ahead) return r; } +static void hex_key(char *hexkey, size_t key_size, const char *key) +{ + int i; + + for(i = 0; i < key_size; i++) + sprintf(&hexkey[i * 2], "%02x", (unsigned char)key[i]); +} + static char *get_params(const char *device, uint64_t skip, uint64_t offset, const char *cipher, size_t key_size, const char *key) { char *params; char *hexkey; - int i; hexkey = safe_alloc(key_size * 2 + 1); if (!hexkey) return NULL; - for(i = 0; i < key_size; i++) - sprintf(&hexkey[i * 2], "%02x", (unsigned char)key[i]); + hex_key(hexkey, key_size, key); params = safe_alloc(strlen(hexkey) + strlen(cipher) + strlen(device) + 64); if (!params) @@ -405,7 +411,8 @@ int dm_query_device(const char *name, char **cipher, int *key_size, char **key, - int *read_only) + int *read_only, + int *suspended) { struct dm_task *dmt; struct dm_info dmi; @@ -495,10 +502,12 @@ int dm_query_device(const char *name, } memset(key_, 0, strlen(key_)); - /* read_only */ if (read_only) *read_only = dmi.read_only; + if (suspended) + *suspended = dmi.suspended; + r = (dmi.open_count > 0); out: if (dmt) @@ -507,6 +516,67 @@ out: return r; } +static int _dm_message(const char *name, const char *msg) +{ + int r = 0; + struct dm_task *dmt; + + if (!(dmt = dm_task_create(DM_DEVICE_TARGET_MSG))) + return 0; + + if (name && !dm_task_set_name(dmt, name)) + goto out; + + if (!dm_task_set_sector(dmt, (uint64_t) 0)) + goto out; + + if (!dm_task_set_message(dmt, msg)) + goto out; + + r = dm_task_run(dmt); + + out: + dm_task_destroy(dmt); + return r; +} + +int dm_suspend_and_wipe_key(const char *name) +{ + if (!_dm_simple(DM_DEVICE_SUSPEND, name)) + return -EINVAL; + + if (!_dm_message(name, "key wipe")) { + _dm_simple(DM_DEVICE_RESUME, name); + return -EINVAL; + } + + return 0; +} + +int dm_resume_and_reinstate_key(const char *name, + size_t key_size, + const char *key) +{ + int msg_size = key_size * 2 + 10; // key set + char *msg; + int r = 0; + + msg = safe_alloc(msg_size); + if (!msg) + return -ENOMEM; + + memset(msg, 0, msg_size); + strcpy(msg, "key set "); + hex_key(&msg[8], key_size, key); + + if (!_dm_message(name, msg) || + !_dm_simple(DM_DEVICE_RESUME, name)) + r = -EINVAL; + + safe_free(msg); + return r; +} + const char *dm_get_dir(void) { return dm_dir(); diff --git a/lib/setup.c b/lib/setup.c index dcb1224b..4854cc06 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -443,6 +443,45 @@ static void key_from_terminal(struct crypt_device *cd, char *msg, char **key, } } +static int volume_key_by_terminal_passphrase(struct crypt_device *cd, int keyslot, + struct luks_masterkey **mk) +{ + char *prompt = NULL, *passphrase_read = NULL; + unsigned int passphrase_size_read; + int r = -EINVAL, tries = cd->tries; + + if(asprintf(&prompt, _("Enter passphrase for %s: "), cd->device) < 0) + return -ENOMEM; + + *mk = NULL; + do { + if (*mk) + LUKS_dealloc_masterkey(*mk); + *mk = NULL; + + key_from_terminal(cd, prompt, &passphrase_read, + &passphrase_size_read, 0); + if(!passphrase_read) { + r = -EINVAL; + break; + } + + r = LUKS_open_key_with_hdr(cd->device, keyslot, passphrase_read, + passphrase_size_read, &cd->hdr, mk, cd); + safe_free(passphrase_read); + passphrase_read = NULL; + } while (r == -EPERM && (--tries > 0)); + + if (r < 0 && *mk) { + LUKS_dealloc_masterkey(*mk); + *mk = NULL; + } + free(prompt); + + return r; + +} + static void key_from_file(struct crypt_device *cd, char *msg, char **key, unsigned int *key_len, const char *key_file, size_t key_size) @@ -572,7 +611,7 @@ int crypt_resize_device(struct crypt_options *options) int key_size, read_only, r; r = dm_query_device(options->name, &device, &size, &skip, &offset, - &cipher, &key_size, &key, &read_only); + &cipher, &key_size, &key, &read_only, NULL); if (r < 0) return r; @@ -606,7 +645,7 @@ int crypt_query_device(struct crypt_options *options) r = dm_query_device(options->name, (char **)&options->device, &options->size, &options->skip, &options->offset, (char **)&options->cipher, - &options->key_size, NULL, &read_only); + &options->key_size, NULL, &read_only, NULL); if (r < 0) return r; @@ -889,6 +928,29 @@ int crypt_init(struct crypt_device **cd, const char *device) return 0; } +int crypt_init_by_name(struct crypt_device **cd, const char *name) +{ + crypt_status_info ci; + char *device = NULL; + int r; + + log_dbg("Allocating crypt device context by device %s.", name); + + ci = crypt_status(NULL, name); + if (ci < ACTIVE) { + log_err(NULL, _("Device %s is not active.\n"), name); + return -EINVAL; + } + + r = dm_query_device(name, &device, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL); + if (!r) + r = crypt_init(cd, device); + + free(device); + return r; +} + static int _crypt_format_plain(struct crypt_device *cd, const char *cipher, const char *cipher_mode, @@ -1046,6 +1108,137 @@ void crypt_free(struct crypt_device *cd) } } +int crypt_suspend(struct crypt_device *cd, + const char *name) +{ + crypt_status_info ci; + int r, suspended = 0; + + log_dbg("Suspending volume %s.", name); + + ci = crypt_status(NULL, name); + if (ci < ACTIVE) { + log_err(cd, _("Volume %s is not active.\n"), name); + return -EINVAL; + } + + r = dm_query_device(name, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &suspended); + if (r < 0) + return r; + + if (suspended) { + log_err(cd, _("Volume %s is already suspended.\n"), name); + return -EINVAL; + } + + r = dm_suspend_and_wipe_key(name); + if (r) + log_err(cd, "Error during suspending device %s.\n", name); + + return r; +} + +int crypt_resume_by_passphrase(struct crypt_device *cd, + const char *name, + int keyslot, + const char *passphrase, + size_t passphrase_size) +{ + struct luks_masterkey *mk = NULL; + int r, suspended = 0; + + log_dbg("Resuming volume %s.", name); + + if (!isLUKS(cd->type)) { + log_err(cd, _("This operation is supported only for LUKS device.\n")); + r = -EINVAL; + goto out; + } + + r = dm_query_device(name, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &suspended); + if (r < 0) + return r; + + if (!suspended) { + log_err(cd, _("Volume %s is not suspended.\n"), name); + return -EINVAL; + } + + if (passphrase) { + r = LUKS_open_key_with_hdr(cd->device, keyslot, passphrase, + passphrase_size, &cd->hdr, &mk, cd); + } else + r = volume_key_by_terminal_passphrase(cd, keyslot, &mk); + + if (r >= 0) { + keyslot = r; + r = dm_resume_and_reinstate_key(name, mk->keyLength, mk->key); + if (r) + log_err(cd, "Error during resuming device %s.\n", name); + } else + r = keyslot; +out: + LUKS_dealloc_masterkey(mk); + return r < 0 ? r : keyslot; +} + +int crypt_resume_by_keyfile(struct crypt_device *cd, + const char *name, + int keyslot, + const char *keyfile, + size_t keyfile_size) +{ + struct luks_masterkey *mk = NULL; + char *passphrase_read = NULL; + unsigned int passphrase_size_read; + int r, suspended = 0; + + log_dbg("Resuming volume %s.", name); + + if (!isLUKS(cd->type)) { + log_err(cd, _("This operation is supported only for LUKS device.\n")); + r = -EINVAL; + goto out; + } + + r = dm_query_device(name, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &suspended); + if (r < 0) + return r; + + if (!suspended) { + log_err(cd, _("Volume %s is not suspended.\n"), name); + return -EINVAL; + } + + if (!keyfile) + return -EINVAL; + + key_from_file(cd, _("Enter passphrase: "), &passphrase_read, + &passphrase_size_read, keyfile, keyfile_size); + + if(!passphrase_read) + r = -EINVAL; + else { + r = LUKS_open_key_with_hdr(cd->device, keyslot, passphrase_read, + passphrase_size_read, &cd->hdr, &mk, cd); + safe_free(passphrase_read); + } + + if (r >= 0) { + keyslot = r; + r = dm_resume_and_reinstate_key(name, mk->keyLength, mk->key); + if (r) + log_err(cd, "Error during resuming device %s.\n", name); + } else + r = keyslot; +out: + LUKS_dealloc_masterkey(mk); + return r < 0 ? r : keyslot; +} + // slot manipulation int crypt_keyslot_add_by_passphrase(struct crypt_device *cd, int keyslot, // -1 any @@ -1288,9 +1481,8 @@ int crypt_activate_by_passphrase(struct crypt_device *cd, { crypt_status_info ci; struct luks_masterkey *mk = NULL; - char *prompt = NULL, *passphrase_read = NULL; - unsigned int passphrase_size_read; - int r, tries = cd->tries; + char *prompt = NULL; + int r; log_dbg("%s volume %s [keyslot %d] using %spassphrase.", name ? "Activating" : "Checking", name ?: "", @@ -1321,25 +1513,10 @@ int crypt_activate_by_passphrase(struct crypt_device *cd, /* provided passphrase, do not retry */ if (passphrase) { - r = LUKS_open_key_with_hdr(cd->device, keyslot, passphrase, - passphrase_size, &cd->hdr, &mk, cd); - } else do { - if (mk) - LUKS_dealloc_masterkey(mk); - mk = NULL; - - key_from_terminal(cd, prompt, &passphrase_read, - &passphrase_size_read, 0); - if(!passphrase_read) { - r = -EINVAL; - break; - } - - r = LUKS_open_key_with_hdr(cd->device, keyslot, passphrase_read, - passphrase_size_read, &cd->hdr, &mk, cd); - safe_free(passphrase_read); - passphrase_read = NULL; - } while (r == -EPERM && (--tries > 0)); + r = LUKS_open_key_with_hdr(cd->device, keyslot, passphrase, + passphrase_size, &cd->hdr, &mk, cd); + } else + r = volume_key_by_terminal_passphrase(cd, keyslot, &mk); if (r >= 0) { keyslot = r; diff --git a/man/cryptsetup.8 b/man/cryptsetup.8 index 30b9f54f..8c9ac469 100644 --- a/man/cryptsetup.8 +++ b/man/cryptsetup.8 @@ -50,6 +50,18 @@ opens the LUKS partition and sets up a mapping after successful .IP identical to \fIremove\fR. .PP +\fIluksSuspend\fR +.IP +suspends active device (all IO operations are frozen) and wipes encryption key from kernel. Kernel version 2.6.19 or later is required. + +After that operation you have to use \fIluksResume\fR to reinstate encryption key (and resume device) or \fIluksClose\fR to remove mapped device. + +\fBWARNING:\fR never try to suspend device where is the cryptsetup binary itself. +.PP +\fIluksResume\fR +.IP +Resumes suspended device and reinstates encryption key. You will need provide passphrase identical to \fIluksOpen\fR command (using prompting or key file). +.PP \fIluksAddKey\fR [] .IP add a new key file/passphrase. An existing passphrase or key file (via \-\-key-file) must be supplied. The key file with the new material is supplied as a positional argument. can be [\-\-key-file, \-\-key-slot]. diff --git a/src/cryptsetup.c b/src/cryptsetup.c index 253a420c..db51c146 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -52,6 +52,8 @@ static int action_luksRemoveKey(int arg); static int action_isLuks(int arg); static int action_luksUUID(int arg); static int action_luksDump(int arg); +static int action_luksSuspend(int arg); +static int action_luksResume(int arg); static struct action_type { const char *type; @@ -77,6 +79,8 @@ static struct action_type { { "isLuks", action_isLuks, 0, 1, 0, 0, 0, N_(""), N_("tests for LUKS partition header") }, { "luksClose", action_remove, 0, 1, 1, 0, 1, N_(""), N_("remove LUKS mapping") }, { "luksDump", action_luksDump, 0, 1, 0, 0, 1, N_(""), N_("dump LUKS partition information") }, + { "luksSuspend",action_luksSuspend, 0, 1, 1, 1, 1, N_(""), N_("Suspend LUKS device and wipe key (all IOs are frozen).") }, + { "luksResume", action_luksResume, 0, 1, 1, 1, 1, N_(""), N_("Resume suspended LUKS device.") }, { "luksDelKey", action_luksDelKey, 0, 2, 1, 1, 1, N_(" "), N_("identical to luksKillSlot - DEPRECATED - see man page") }, { "reload", action_create, 1, 2, 1, 1, 1, N_(" "), N_("modify active device - DEPRECATED - see man page") }, { NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL } @@ -490,6 +494,41 @@ static int action_luksDump(int arg) return crypt_luksDump(&options); } +static int action_luksSuspend(int arg) +{ + struct crypt_device *cd = NULL; + int r; + + r = crypt_init_by_name(&cd, action_argv[0]); + if (!r) + r = crypt_suspend(cd, action_argv[0]); + + crypt_free(cd); + return r; +} + +static int action_luksResume(int arg) +{ + struct crypt_device *cd = NULL; + int r; + + if ((r = crypt_init_by_name(&cd, action_argv[0]))) + goto out; + + if ((r = crypt_load(cd, CRYPT_LUKS1, NULL))) + goto out; + + if (opt_key_file) + r = crypt_resume_by_keyfile(cd, action_argv[0], CRYPT_ANY_SLOT, + opt_key_file, opt_key_size / 8); + else + r = crypt_resume_by_passphrase(cd, action_argv[0], CRYPT_ANY_SLOT, + NULL, 0); +out: + crypt_free(cd); + return r; +} + static void usage(poptContext popt_context, int exitcode, const char *error, const char *more) { diff --git a/tests/apitest.c b/tests/apitest.c index 9764c5e1..9ca62b79 100644 --- a/tests/apitest.c +++ b/tests/apitest.c @@ -582,6 +582,35 @@ static void UseLuksDevice(void) crypt_free(cd); } +static void SuspendDevice(void) +{ + struct crypt_device *cd; + char key[128]; + size_t key_size; + int fd; + + OK_(crypt_init(&cd, DEVICE_1)); + OK_(crypt_load(cd, CRYPT_LUKS1, NULL)); + OK_(crypt_activate_by_passphrase(cd, CDEVICE_1, CRYPT_ANY_SLOT, KEY1, strlen(KEY1), 0)); + + OK_(crypt_suspend(cd, CDEVICE_1)); + FAIL_(crypt_suspend(cd, CDEVICE_1), "already suspended"); + + FAIL_(crypt_resume_by_passphrase(cd, CDEVICE_1, CRYPT_ANY_SLOT, KEY1, strlen(KEY1)-1), "wrong key"); + OK_(crypt_resume_by_passphrase(cd, CDEVICE_1, CRYPT_ANY_SLOT, KEY1, strlen(KEY1))); + FAIL_(crypt_resume_by_passphrase(cd, CDEVICE_1, CRYPT_ANY_SLOT, KEY1, strlen(KEY1)), "not suspended"); + + OK_(_prepare_keyfile(KEYFILE1, KEY1)); + OK_(crypt_suspend(cd, CDEVICE_1)); + FAIL_(crypt_resume_by_keyfile(cd, CDEVICE_1, CRYPT_ANY_SLOT, KEYFILE1 "blah", 0), "wrong keyfile"); + OK_(crypt_resume_by_keyfile(cd, CDEVICE_1, CRYPT_ANY_SLOT, KEYFILE1, 0)); + FAIL_(crypt_resume_by_keyfile(cd, CDEVICE_1, CRYPT_ANY_SLOT, KEYFILE1, 0), "not suspended"); + _remove_keyfiles(); + + OK_(crypt_deactivate(cd, CDEVICE_1)); + crypt_free(cd); +} + static void AddDeviceLuks(void) { struct crypt_device *cd; @@ -678,6 +707,7 @@ int main (int argc, char *argv[]) RUN_(AddDevicePlain, "plain device API creation exercise"); RUN_(AddDeviceLuks, "Format and use LUKS device"); RUN_(UseLuksDevice, "Use pre-formated LUKS device"); + RUN_(SuspendDevice, "Suspend/Resume test"); _cleanup();