From: Maxim Levitsky <mlevitsk@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Kevin Wolf" <kwolf@redhat.com>,
"Daniel P. Berrangé" <berrange@redhat.com>,
qemu-block@nongnu.org, "Markus Armbruster" <armbru@redhat.com>,
"Max Reitz" <mreitz@redhat.com>,
"Maxim Levitsky" <mlevitsk@redhat.com>,
"John Snow" <jsnow@redhat.com>
Subject: [Qemu-devel] [PATCH 03/10] qcrypto-luks: implement the encryption key management
Date: Fri, 30 Aug 2019 23:56:01 +0300 [thread overview]
Message-ID: <20190830205608.18192-4-mlevitsk@redhat.com> (raw)
In-Reply-To: <20190830205608.18192-1-mlevitsk@redhat.com>
Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
crypto/block-luks.c | 366 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 364 insertions(+), 2 deletions(-)
diff --git a/crypto/block-luks.c b/crypto/block-luks.c
index ba20d55246..21325fbc79 100644
--- a/crypto/block-luks.c
+++ b/crypto/block-luks.c
@@ -70,6 +70,9 @@ typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot;
#define QCRYPTO_BLOCK_LUKS_SECTOR_SIZE 512LL
+#define QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME 2000
+#define QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS 40
+
static const char qcrypto_block_luks_magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN] = {
'L', 'U', 'K', 'S', 0xBA, 0xBE
};
@@ -219,6 +222,9 @@ struct QCryptoBlockLUKS {
/* Hash algorithm used in pbkdf2 function */
QCryptoHashAlgorithm hash_alg;
+
+ /* Name of the secret that was used to open the image */
+ char *secret;
};
@@ -1089,6 +1095,175 @@ qcrypto_block_luks_find_key(QCryptoBlock *block,
}
+
+/*
+ * Returns true if a slot i is marked as active
+ * (contains encrypted copy of the master key)
+ */
+
+static bool
+qcrypto_block_luks_slot_active(const QCryptoBlockLUKS *luks,
+ unsigned int slot_idx)
+{
+ uint32_t val = luks->header.key_slots[slot_idx].active;
+ return val == QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED;
+}
+
+/*
+ * Returns the number of slots that are marked as active
+ * (contains encrypted copy of the master key)
+ */
+
+static unsigned int
+qcrypto_block_luks_count_active_slots(const QCryptoBlockLUKS *luks)
+{
+ size_t i = 0;
+ unsigned int ret = 0;
+
+ for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+ if (qcrypto_block_luks_slot_active(luks, i)) {
+ ret++;
+ }
+ }
+ return ret;
+}
+
+
+/*
+ * Finds first key slot which is not active
+ * Returns the key slot index, or -1 if doesn't exist
+ */
+
+static int
+qcrypto_block_luks_find_free_keyslot(const QCryptoBlockLUKS *luks)
+{
+ size_t i;
+
+ for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+ if (!qcrypto_block_luks_slot_active(luks, i)) {
+ return i;
+ }
+ }
+ return -1;
+
+}
+
+/*
+ * Erases an keyslot given its index
+ * Returns:
+ * 0 if the keyslot was erased successfully
+ * -1 if a error occurred while erasing the keyslot
+ *
+ */
+
+static int
+qcrypto_block_luks_erase_key(QCryptoBlock *block,
+ unsigned int slot_idx,
+ QCryptoBlockWriteFunc writefunc,
+ void *opaque,
+ Error **errp)
+{
+ QCryptoBlockLUKS *luks = block->opaque;
+ QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx];
+ g_autofree uint8_t *garbagesplitkey = NULL;
+ size_t splitkeylen = luks->header.master_key_len * slot->stripes;
+ size_t i;
+
+ assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
+ assert(splitkeylen > 0);
+
+ garbagesplitkey = g_malloc0(splitkeylen);
+
+ /* Reset the key slot header */
+ memset(slot->salt, 0, QCRYPTO_BLOCK_LUKS_SALT_LEN);
+ slot->iterations = 0;
+ slot->active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED;
+
+ qcrypto_block_luks_store_header(block, writefunc, opaque, errp);
+
+ /*
+ * Now try to erase the key material, even if the header
+ * update failed
+ */
+
+ for (i = 0 ; i < QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS ; i++) {
+ if (qcrypto_random_bytes(garbagesplitkey, splitkeylen, errp) < 0) {
+ /*
+ * If we failed to get the random data, still write
+ * at least zeros to the key slot at least once
+ */
+
+ if (i > 0) {
+ return -1;
+ }
+ }
+
+ if (writefunc(block,
+ slot->key_offset_sector * QCRYPTO_BLOCK_LUKS_SECTOR_SIZE,
+ garbagesplitkey,
+ splitkeylen,
+ opaque,
+ errp) != splitkeylen) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+/*
+ * Erase all the keys that match the given password
+ * Will stop when only one keyslot is remaining
+ * Returns number of slots that were erased or -1 on failure
+ */
+
+static int
+qcrypto_block_luks_erase_matching_keys(QCryptoBlock *block,
+ const char *password,
+ QCryptoBlockReadFunc readfunc,
+ QCryptoBlockWriteFunc writefunc,
+ void *opaque,
+ bool force,
+ Error **errp)
+{
+ QCryptoBlockLUKS *luks = block->opaque;
+ size_t i;
+ int rv;
+ g_autofree uint8_t *masterkey = NULL;
+ unsigned int erased_cnt = 0;
+ unsigned int active_slot_cnt = qcrypto_block_luks_count_active_slots(luks);
+
+ masterkey = g_new0(uint8_t, luks->header.master_key_len);
+
+ for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+
+ /* refuse to erase last key if not forced */
+ if (!force && active_slot_cnt == 1) {
+ break;
+ }
+
+ rv = qcrypto_block_luks_load_key(block, i, password, masterkey,
+ readfunc, opaque, errp);
+ if (rv < 0) {
+ return -1;
+ }
+ if (rv == 0) {
+ continue;
+ }
+
+ rv = qcrypto_block_luks_erase_key(block, i, writefunc, opaque, errp);
+ if (rv < 0) {
+ return -1;
+ }
+
+ erased_cnt++;
+ active_slot_cnt--;
+ }
+
+ return erased_cnt;
+}
+
+
static int
qcrypto_block_luks_open(QCryptoBlock *block,
QCryptoBlockOpenOptions *options,
@@ -1119,6 +1294,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
luks = g_new0(QCryptoBlockLUKS, 1);
block->opaque = luks;
+ luks->secret = g_strdup(options->u.luks.key_secret);
ret = qcrypto_block_luks_load_header(block, readfunc, opaque, errp);
if (ret) {
@@ -1234,7 +1410,7 @@ qcrypto_block_luks_create(QCryptoBlock *block,
memcpy(&luks_opts, &options->u.luks, sizeof(luks_opts));
if (!luks_opts.has_iter_time) {
- luks_opts.iter_time = 2000;
+ luks_opts.iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME;
}
if (!luks_opts.has_cipher_alg) {
luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256;
@@ -1292,6 +1468,8 @@ qcrypto_block_luks_create(QCryptoBlock *block,
optprefix ? optprefix : "");
goto error;
}
+ luks->secret = g_strdup(options->u.luks.key_secret);
+
password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret, errp);
if (!password) {
goto error;
@@ -1522,6 +1700,187 @@ qcrypto_block_luks_create(QCryptoBlock *block,
}
+#define CHECK_NON_AMEND_OPTION(luks, luks_opts, name) \
+ if (luks_opts.has_##name && luks_opts.name != luks->name) { \
+ error_setg(errp, "Option \"" #name "\" can't be amended"); \
+ goto cleanup; \
+ }
+
+static int
+qcrypto_block_luks_amend_options(QCryptoBlock *block,
+ QCryptoBlockReadFunc readfunc,
+ QCryptoBlockWriteFunc writefunc,
+ void *opaque,
+ QCryptoBlockCreateOptions *options,
+ bool force,
+ Error **errp)
+{
+ QCryptoBlockLUKS *luks = block->opaque;
+ QCryptoBlockCreateOptionsLUKS luks_opts;
+ g_autofree char *old_password = NULL;
+ g_autofree char *password = NULL;
+ const char *unlock_secret = luks->secret;
+ g_autofree uint8_t *masterkey = NULL;
+ int slot = -1;
+ int ret = -1;
+ bool active = true;
+ int64_t iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME;
+
+ memcpy(&luks_opts, &options->u.luks, sizeof(luks_opts));
+
+ CHECK_NON_AMEND_OPTION(luks, luks_opts, cipher_alg);
+ CHECK_NON_AMEND_OPTION(luks, luks_opts, cipher_mode);
+ CHECK_NON_AMEND_OPTION(luks, luks_opts, ivgen_alg);
+ CHECK_NON_AMEND_OPTION(luks, luks_opts, ivgen_hash_alg);
+ CHECK_NON_AMEND_OPTION(luks, luks_opts, hash_alg);
+
+ /* Read given slot and check it */
+ if (luks_opts.has_slot) {
+ slot = luks_opts.slot;
+ if (slot < 0 || slot >= QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS) {
+ error_setg(errp,
+ "Given key slot %i is not supported by LUKS", slot);
+ goto cleanup;
+ }
+ }
+
+ if (luks_opts.has_iter_time) {
+ iter_time = luks_opts.iter_time;
+ }
+
+ if (luks_opts.has_active && luks_opts.active == false) {
+ active = false;
+ }
+
+ if (active) {
+
+ /* Check that we are not overwriting an active slot */
+ if (!force && slot != -1 &&
+ qcrypto_block_luks_slot_active(luks, slot)) {
+
+ error_setg(errp, "Can't update an active key slot %i",
+ slot);
+ goto cleanup;
+ }
+
+ /* check that we have the passwords*/
+ if (!luks_opts.has_key_secret) {
+ error_setg(errp, "Can't add a key slot without a password");
+ goto cleanup;
+ }
+
+ if (luks_opts.has_unlock_secret) {
+ unlock_secret = luks_opts.unlock_secret;
+ }
+
+ /* Read the old password */
+ old_password = qcrypto_secret_lookup_as_utf8(unlock_secret, errp);
+ if (!old_password) {
+ goto cleanup;
+ }
+
+ masterkey = g_new0(uint8_t, luks->header.master_key_len);
+
+ /* Retrieve the master key*/
+ if (qcrypto_block_luks_find_key(block, old_password, masterkey,
+ readfunc, opaque,
+ errp) < 0) {
+ error_append_hint(errp,
+ "unlock secret, doesn't unlock the image");
+ goto cleanup;
+ }
+
+ /* Read the new password*/
+ password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret, errp);
+ if (!password) {
+ goto cleanup;
+ }
+
+ /* Find the new slot to write to */
+ if (slot == -1) {
+ slot = qcrypto_block_luks_find_free_keyslot(luks);
+
+ if (slot == -1) {
+ error_setg(errp,
+ "Can't add a keyslot - all key slots are in use");
+ goto cleanup;
+
+ }
+ }
+
+ /* Store the master key to the new slot */
+ if (qcrypto_block_luks_store_key(block, slot, password, masterkey,
+ iter_time, writefunc, opaque,
+ errp)) {
+
+ error_append_hint(errp, "Failed to store the keyslot %i", slot);
+ goto cleanup;
+ }
+
+ } else {
+
+ /* Check that we are not erasing last key slot */
+ if (qcrypto_block_luks_count_active_slots(luks) <= 1) {
+
+ if (!force) {
+ error_setg(errp, "Only one slot active - can't erase");
+ goto cleanup;
+ }
+ }
+
+ if (slot != -1) {
+ /* Check that we are not erasing an inactive slot */
+ if (!qcrypto_block_luks_slot_active(luks, luks_opts.slot)) {
+ if (!force) {
+ error_setg(errp, "Can't erase an inactive key slot %i",
+ slot);
+ goto cleanup;
+ }
+ }
+
+ /* Erase the given slot */
+ if (qcrypto_block_luks_erase_key(block, slot,
+ writefunc, opaque, errp)) {
+ goto cleanup;
+ }
+
+ } else {
+ if (!luks_opts.has_key_secret) {
+ error_setg(errp,
+ "To erase a keyslot you have to specify either the"
+ "slot index or a password "
+ "(to erase all slots that match it)");
+ goto cleanup;
+ }
+
+ password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret,
+ errp);
+ if (!password) {
+ goto cleanup;
+ }
+
+ ret = qcrypto_block_luks_erase_matching_keys(block, password,
+ readfunc, writefunc,
+ opaque, force, errp);
+ if (ret == 0) {
+ error_setg(errp,
+ "Didn't erase a keyslot, because no keyslots match"
+ " the given password");
+ ret = -EINVAL;
+ goto cleanup;
+ }
+
+ if (ret < 0) {
+ goto cleanup;
+ }
+ }
+ }
+ ret = 0;
+cleanup:
+ return ret;
+}
+
+
static int qcrypto_block_luks_get_info(QCryptoBlock *block,
QCryptoBlockInfo *info,
Error **errp)
@@ -1569,7 +1928,9 @@ static int qcrypto_block_luks_get_info(QCryptoBlock *block,
static void qcrypto_block_luks_cleanup(QCryptoBlock *block)
{
- g_free(block->opaque);
+ QCryptoBlockLUKS *luks = block->opaque;
+ g_free(luks->secret);
+ g_free(luks);
}
@@ -1606,6 +1967,7 @@ qcrypto_block_luks_encrypt(QCryptoBlock *block,
const QCryptoBlockDriver qcrypto_block_driver_luks = {
.open = qcrypto_block_luks_open,
.create = qcrypto_block_luks_create,
+ .amend = qcrypto_block_luks_amend_options,
.get_info = qcrypto_block_luks_get_info,
.cleanup = qcrypto_block_luks_cleanup,
.decrypt = qcrypto_block_luks_decrypt,
--
2.17.2
next prev parent reply other threads:[~2019-08-30 21:02 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-08-30 20:55 [Qemu-devel] [PATCH 00/10] RFC crypto/luks: encryption key managment using amend interface Maxim Levitsky
2019-08-30 20:55 ` [Qemu-devel] [PATCH 01/10] qcrypto: add suport for amend options Maxim Levitsky
2019-09-06 13:40 ` Daniel P. Berrangé
2019-08-30 20:56 ` [Qemu-devel] [PATCH 02/10] qcrypto-luks: extend the create options for upcoming encryption key management Maxim Levitsky
2019-09-06 13:49 ` Daniel P. Berrangé
2019-09-06 13:57 ` Maxim Levitsky
2019-09-06 14:15 ` Daniel P. Berrangé
2019-08-30 20:56 ` Maxim Levitsky [this message]
2019-09-06 13:55 ` [Qemu-devel] [PATCH 03/10] qcrypto-luks: implement the " Daniel P. Berrangé
2019-09-12 9:48 ` Maxim Levitsky
2019-08-30 20:56 ` [Qemu-devel] [PATCH 04/10] block: amend: add 'force' option Maxim Levitsky
2019-09-06 13:59 ` Daniel P. Berrangé
2019-09-12 9:53 ` Maxim Levitsky
2019-08-30 20:56 ` [Qemu-devel] [PATCH 05/10] block/crypto: implement the encryption key management Maxim Levitsky
2019-09-06 14:04 ` Daniel P. Berrangé
2019-09-12 10:08 ` Maxim Levitsky
2019-08-30 20:56 ` [Qemu-devel] [PATCH 06/10] qcow2: implement crypto amend options Maxim Levitsky
2019-09-06 14:06 ` Daniel P. Berrangé
2019-09-12 19:11 ` Maxim Levitsky
2019-08-30 20:56 ` [Qemu-devel] [PATCH 07/10] block: add x-blockdev-amend qmp command Maxim Levitsky
2019-08-30 20:56 ` [Qemu-devel] [PATCH 08/10] block/crypto: implement blockdev-amend Maxim Levitsky
2019-09-06 14:10 ` Daniel P. Berrangé
2019-09-12 19:18 ` Maxim Levitsky
2019-08-30 20:56 ` [Qemu-devel] [PATCH 09/10] block/qcow2: " Maxim Levitsky
2019-09-06 14:12 ` Daniel P. Berrangé
2019-09-12 19:22 ` Maxim Levitsky
2019-08-30 20:56 ` [Qemu-devel] [PATCH 10/10] iotests : add tests for encryption key management Maxim Levitsky
2019-09-06 14:14 ` Daniel P. Berrangé
2019-09-06 14:26 ` Maxim Levitsky
2019-09-06 14:27 ` Daniel P. Berrangé
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190830205608.18192-4-mlevitsk@redhat.com \
--to=mlevitsk@redhat.com \
--cc=armbru@redhat.com \
--cc=berrange@redhat.com \
--cc=jsnow@redhat.com \
--cc=kwolf@redhat.com \
--cc=mreitz@redhat.com \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.