From patchwork Wed Aug 14 20:22:07 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094485 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 299201395 for ; Wed, 14 Aug 2019 20:24:35 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 182E828833 for ; Wed, 14 Aug 2019 20:24:35 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 0BDD52883B; Wed, 14 Aug 2019 20:24:35 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id AE0D828833 for ; Wed, 14 Aug 2019 20:24:34 +0000 (UTC) Received: from localhost ([::1]:35700 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzov-0005sU-CF for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:24:33 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33060) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzn1-0003uc-5K for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:22:36 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxzn0-0003or-5d for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:22:35 -0400 Received: from mx1.redhat.com ([209.132.183.28]:43256) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxzmx-0003ld-Ic; Wed, 14 Aug 2019 16:22:31 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id D9BFC31CD7A; Wed, 14 Aug 2019 20:22:30 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id 674B410016EB; Wed, 14 Aug 2019 20:22:28 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:07 +0300 Message-Id: <20190814202219.1870-2-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.29]); Wed, 14 Aug 2019 20:22:30 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 01/13] block-crypto: misc refactoring X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP * rename the write_func to create_write_func, and init_func to create_init_func this is preparation for other write_func that will be used to update the encryption keys. No functional changes Signed-off-by: Maxim Levitsky Reviewed-by: Daniel P. Berrangé --- block/crypto.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/block/crypto.c b/block/crypto.c index 8237424ae6..42a3f0898b 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -51,7 +51,6 @@ static int block_crypto_probe_generic(QCryptoBlockFormat format, } } - static ssize_t block_crypto_read_func(QCryptoBlock *block, size_t offset, uint8_t *buf, @@ -77,7 +76,7 @@ struct BlockCryptoCreateData { }; -static ssize_t block_crypto_write_func(QCryptoBlock *block, +static ssize_t block_crypto_create_write_func(QCryptoBlock *block, size_t offset, const uint8_t *buf, size_t buflen, @@ -95,8 +94,7 @@ static ssize_t block_crypto_write_func(QCryptoBlock *block, return ret; } - -static ssize_t block_crypto_init_func(QCryptoBlock *block, +static ssize_t block_crypto_create_init_func(QCryptoBlock *block, size_t headerlen, void *opaque, Error **errp) @@ -108,7 +106,8 @@ static ssize_t block_crypto_init_func(QCryptoBlock *block, return -EFBIG; } - /* User provided size should reflect amount of space made + /* + * User provided size should reflect amount of space made * available to the guest, so we must take account of that * which will be used by the crypto header */ @@ -117,6 +116,8 @@ static ssize_t block_crypto_init_func(QCryptoBlock *block, } + + static QemuOptsList block_crypto_runtime_opts_luks = { .name = "crypto", .head = QTAILQ_HEAD_INITIALIZER(block_crypto_runtime_opts_luks.head), @@ -272,8 +273,8 @@ static int block_crypto_co_create_generic(BlockDriverState *bs, }; crypto = qcrypto_block_create(opts, NULL, - block_crypto_init_func, - block_crypto_write_func, + block_crypto_create_init_func, + block_crypto_create_write_func, &data, errp); From patchwork Wed Aug 14 20:22:08 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094503 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id B6F3E1395 for ; Wed, 14 Aug 2019 20:28:28 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A4DEE28833 for ; Wed, 14 Aug 2019 20:28:28 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 9812428837; Wed, 14 Aug 2019 20:28:28 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id A994928833 for ; Wed, 14 Aug 2019 20:28:27 +0000 (UTC) Received: from localhost ([::1]:35728 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzsg-0001Fd-Fp for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:28:26 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33126) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxznE-00041S-2U for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:22:50 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxznA-0003vq-9n for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:22:47 -0400 Received: from mx1.redhat.com ([209.132.183.28]:58558) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxzn0-0003oj-P0; Wed, 14 Aug 2019 16:22:35 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id D2FD330A7B8F; Wed, 14 Aug 2019 20:22:33 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id 3E74410016EB; Wed, 14 Aug 2019 20:22:31 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:08 +0300 Message-Id: <20190814202219.1870-3-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.47]); Wed, 14 Aug 2019 20:22:33 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 02/13] qcrypto-luks: misc refactoring X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP This is also a preparation for key read/write/erase functions * use master key len from the header * prefer to use crypto params in the QCryptoBlockLUKS over passing them as function arguments * define QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME * Add comments to various crypto parameters in the QCryptoBlockLUKS Signed-off-by: Maxim Levitsky --- crypto/block-luks.c | 213 ++++++++++++++++++++++---------------------- 1 file changed, 105 insertions(+), 108 deletions(-) diff --git a/crypto/block-luks.c b/crypto/block-luks.c index 409ab50f20..48213abde7 100644 --- a/crypto/block-luks.c +++ b/crypto/block-luks.c @@ -70,6 +70,8 @@ typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot; #define QCRYPTO_BLOCK_LUKS_SECTOR_SIZE 512LL +#define QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME 2000 + static const char qcrypto_block_luks_magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN] = { 'L', 'U', 'K', 'S', 0xBA, 0xBE }; @@ -199,13 +201,25 @@ QEMU_BUILD_BUG_ON(sizeof(struct QCryptoBlockLUKSHeader) != 592); struct QCryptoBlockLUKS { QCryptoBlockLUKSHeader header; - /* Cache parsed versions of what's in header fields, - * as we can't rely on QCryptoBlock.cipher being - * non-NULL */ + /* Main encryption algorithm used for encryption*/ QCryptoCipherAlgorithm cipher_alg; + + /* Mode of encryption for the selected encryption algorithm */ QCryptoCipherMode cipher_mode; + + /* Initialization vector generation algorithm */ QCryptoIVGenAlgorithm ivgen_alg; + + /* Hash algorithm used for IV generation*/ QCryptoHashAlgorithm ivgen_hash_alg; + + /* + * Encryption algorithm used for IV generation. + * Usually the same as main encryption algorithm + */ + QCryptoCipherAlgorithm ivgen_cipher_alg; + + /* Hash algorithm used in pbkdf2 function */ QCryptoHashAlgorithm hash_alg; }; @@ -397,6 +411,12 @@ qcrypto_block_luks_essiv_cipher(QCryptoCipherAlgorithm cipher, } } +static int masterkeylen(QCryptoBlockLUKS *luks) +{ + return luks->header.key_bytes; +} + + /* * Given a key slot, and user password, this will attempt to unlock * the master encryption key from the key slot. @@ -410,21 +430,15 @@ qcrypto_block_luks_essiv_cipher(QCryptoCipherAlgorithm cipher, */ static int qcrypto_block_luks_load_key(QCryptoBlock *block, - QCryptoBlockLUKSKeySlot *slot, + uint slot_idx, const char *password, - QCryptoCipherAlgorithm cipheralg, - QCryptoCipherMode ciphermode, - QCryptoHashAlgorithm hash, - QCryptoIVGenAlgorithm ivalg, - QCryptoCipherAlgorithm ivcipheralg, - QCryptoHashAlgorithm ivhash, uint8_t *masterkey, - size_t masterkeylen, QCryptoBlockReadFunc readfunc, void *opaque, Error **errp) { QCryptoBlockLUKS *luks = block->opaque; + QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx]; uint8_t *splitkey; size_t splitkeylen; uint8_t *possiblekey; @@ -439,9 +453,9 @@ qcrypto_block_luks_load_key(QCryptoBlock *block, return 0; } - splitkeylen = masterkeylen * slot->stripes; + splitkeylen = masterkeylen(luks) * slot->stripes; splitkey = g_new0(uint8_t, splitkeylen); - possiblekey = g_new0(uint8_t, masterkeylen); + possiblekey = g_new0(uint8_t, masterkeylen(luks)); /* * The user password is used to generate a (possible) @@ -450,11 +464,11 @@ qcrypto_block_luks_load_key(QCryptoBlock *block, * the key is correct and validate the results of * decryption later. */ - if (qcrypto_pbkdf2(hash, + if (qcrypto_pbkdf2(luks->hash_alg, (const uint8_t *)password, strlen(password), slot->salt, QCRYPTO_BLOCK_LUKS_SALT_LEN, slot->iterations, - possiblekey, masterkeylen, + possiblekey, masterkeylen(luks), errp) < 0) { goto cleanup; } @@ -478,19 +492,19 @@ qcrypto_block_luks_load_key(QCryptoBlock *block, /* Setup the cipher/ivgen that we'll use to try to decrypt * the split master key material */ - cipher = qcrypto_cipher_new(cipheralg, ciphermode, - possiblekey, masterkeylen, + cipher = qcrypto_cipher_new(luks->cipher_alg, luks->cipher_mode, + possiblekey, masterkeylen(luks), errp); if (!cipher) { goto cleanup; } - niv = qcrypto_cipher_get_iv_len(cipheralg, - ciphermode); - ivgen = qcrypto_ivgen_new(ivalg, - ivcipheralg, - ivhash, - possiblekey, masterkeylen, + niv = qcrypto_cipher_get_iv_len(luks->cipher_alg, + luks->cipher_mode); + ivgen = qcrypto_ivgen_new(luks->ivgen_alg, + luks->ivgen_cipher_alg, + luks->ivgen_hash_alg, + possiblekey, masterkeylen(luks), errp); if (!ivgen) { goto cleanup; @@ -519,8 +533,8 @@ qcrypto_block_luks_load_key(QCryptoBlock *block, * Now we've decrypted the split master key, join * it back together to get the actual master key. */ - if (qcrypto_afsplit_decode(hash, - masterkeylen, + if (qcrypto_afsplit_decode(luks->hash_alg, + masterkeylen(luks), slot->stripes, splitkey, masterkey, @@ -537,8 +551,8 @@ qcrypto_block_luks_load_key(QCryptoBlock *block, * then comparing that to the hash stored in the key slot * header */ - if (qcrypto_pbkdf2(hash, - masterkey, masterkeylen, + if (qcrypto_pbkdf2(luks->hash_alg, + masterkey, masterkeylen(luks), luks->header.master_key_salt, QCRYPTO_BLOCK_LUKS_SALT_LEN, luks->header.master_key_iterations, @@ -577,37 +591,19 @@ qcrypto_block_luks_load_key(QCryptoBlock *block, static int qcrypto_block_luks_find_key(QCryptoBlock *block, const char *password, - QCryptoCipherAlgorithm cipheralg, - QCryptoCipherMode ciphermode, - QCryptoHashAlgorithm hash, - QCryptoIVGenAlgorithm ivalg, - QCryptoCipherAlgorithm ivcipheralg, - QCryptoHashAlgorithm ivhash, - uint8_t **masterkey, - size_t *masterkeylen, + uint8_t *masterkey, QCryptoBlockReadFunc readfunc, void *opaque, Error **errp) { - QCryptoBlockLUKS *luks = block->opaque; size_t i; int rv; - *masterkey = g_new0(uint8_t, luks->header.key_bytes); - *masterkeylen = luks->header.key_bytes; - for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { rv = qcrypto_block_luks_load_key(block, - &luks->header.key_slots[i], + i, password, - cipheralg, - ciphermode, - hash, - ivalg, - ivcipheralg, - ivhash, - *masterkey, - *masterkeylen, + masterkey, readfunc, opaque, errp); @@ -620,11 +616,7 @@ qcrypto_block_luks_find_key(QCryptoBlock *block, } error_setg(errp, "Invalid password, cannot unlock any keyslot"); - error: - g_free(*masterkey); - *masterkey = NULL; - *masterkeylen = 0; return -1; } @@ -639,21 +631,15 @@ qcrypto_block_luks_open(QCryptoBlock *block, size_t n_threads, Error **errp) { - QCryptoBlockLUKS *luks; + QCryptoBlockLUKS *luks = NULL; Error *local_err = NULL; int ret = 0; size_t i; ssize_t rv; uint8_t *masterkey = NULL; - size_t masterkeylen; char *ivgen_name, *ivhash_name; - QCryptoCipherMode ciphermode; - QCryptoCipherAlgorithm cipheralg; - QCryptoIVGenAlgorithm ivalg; - QCryptoCipherAlgorithm ivcipheralg; - QCryptoHashAlgorithm hash; - QCryptoHashAlgorithm ivhash; char *password = NULL; + char *cipher_mode = NULL; if (!(flags & QCRYPTO_BLOCK_OPEN_NO_IO)) { if (!options->u.luks.key_secret) { @@ -710,6 +696,8 @@ qcrypto_block_luks_open(QCryptoBlock *block, goto fail; } + cipher_mode = g_strdup(luks->header.cipher_mode); + /* * The cipher_mode header contains a string that we have * to further parse, of the format @@ -718,7 +706,7 @@ qcrypto_block_luks_open(QCryptoBlock *block, * * eg cbc-essiv:sha256, cbc-plain64 */ - ivgen_name = strchr(luks->header.cipher_mode, '-'); + ivgen_name = strchr(cipher_mode, '-'); if (!ivgen_name) { ret = -EINVAL; error_setg(errp, "Unexpected cipher mode string format %s", @@ -730,13 +718,13 @@ qcrypto_block_luks_open(QCryptoBlock *block, ivhash_name = strchr(ivgen_name, ':'); if (!ivhash_name) { - ivhash = 0; + luks->ivgen_hash_alg = 0; } else { *ivhash_name = '\0'; ivhash_name++; - ivhash = qcrypto_block_luks_hash_name_lookup(ivhash_name, - &local_err); + luks->ivgen_hash_alg = qcrypto_block_luks_hash_name_lookup(ivhash_name, + &local_err); if (local_err) { ret = -ENOTSUP; error_propagate(errp, local_err); @@ -744,25 +732,27 @@ qcrypto_block_luks_open(QCryptoBlock *block, } } - ciphermode = qcrypto_block_luks_cipher_mode_lookup(luks->header.cipher_mode, - &local_err); + luks->cipher_mode = qcrypto_block_luks_cipher_mode_lookup(cipher_mode, + &local_err); if (local_err) { ret = -ENOTSUP; error_propagate(errp, local_err); goto fail; } - cipheralg = qcrypto_block_luks_cipher_name_lookup(luks->header.cipher_name, - ciphermode, - luks->header.key_bytes, - &local_err); + luks->cipher_alg = + qcrypto_block_luks_cipher_name_lookup(luks->header.cipher_name, + luks->cipher_mode, + luks->header.key_bytes, + &local_err); if (local_err) { ret = -ENOTSUP; error_propagate(errp, local_err); goto fail; } - hash = qcrypto_block_luks_hash_name_lookup(luks->header.hash_spec, + luks->hash_alg = + qcrypto_block_luks_hash_name_lookup(luks->header.hash_spec, &local_err); if (local_err) { ret = -ENOTSUP; @@ -770,23 +760,24 @@ qcrypto_block_luks_open(QCryptoBlock *block, goto fail; } - ivalg = qcrypto_block_luks_ivgen_name_lookup(ivgen_name, - &local_err); + luks->ivgen_alg = qcrypto_block_luks_ivgen_name_lookup(ivgen_name, + &local_err); if (local_err) { ret = -ENOTSUP; error_propagate(errp, local_err); goto fail; } - if (ivalg == QCRYPTO_IVGEN_ALG_ESSIV) { + if (luks->ivgen_alg == QCRYPTO_IVGEN_ALG_ESSIV) { if (!ivhash_name) { ret = -EINVAL; error_setg(errp, "Missing IV generator hash specification"); goto fail; } - ivcipheralg = qcrypto_block_luks_essiv_cipher(cipheralg, - ivhash, - &local_err); + luks->ivgen_cipher_alg = + qcrypto_block_luks_essiv_cipher(luks->cipher_alg, + luks->ivgen_hash_alg, + &local_err); if (local_err) { ret = -ENOTSUP; error_propagate(errp, local_err); @@ -800,21 +791,25 @@ qcrypto_block_luks_open(QCryptoBlock *block, * ignore hash names with these ivgens rather than report * an error about the invalid usage */ - ivcipheralg = cipheralg; + luks->ivgen_cipher_alg = luks->cipher_alg; } + + g_free(cipher_mode); + cipher_mode = NULL; + ivgen_name = NULL; + ivhash_name = NULL; + if (!(flags & QCRYPTO_BLOCK_OPEN_NO_IO)) { /* Try to find which key slot our password is valid for * and unlock the master key from that slot. */ + + masterkey = g_new0(uint8_t, masterkeylen(luks)); + if (qcrypto_block_luks_find_key(block, password, - cipheralg, ciphermode, - hash, - ivalg, - ivcipheralg, - ivhash, - &masterkey, &masterkeylen, + masterkey, readfunc, opaque, errp) < 0) { ret = -EACCES; @@ -824,21 +819,24 @@ qcrypto_block_luks_open(QCryptoBlock *block, /* We have a valid master key now, so can setup the * block device payload decryption objects */ - block->kdfhash = hash; - block->niv = qcrypto_cipher_get_iv_len(cipheralg, - ciphermode); - block->ivgen = qcrypto_ivgen_new(ivalg, - ivcipheralg, - ivhash, - masterkey, masterkeylen, + block->kdfhash = luks->hash_alg; + block->niv = qcrypto_cipher_get_iv_len(luks->cipher_alg, + luks->cipher_mode); + + block->ivgen = qcrypto_ivgen_new(luks->ivgen_alg, + luks->ivgen_cipher_alg, + luks->ivgen_hash_alg, + masterkey, masterkeylen(luks), errp); if (!block->ivgen) { ret = -ENOTSUP; goto fail; } - ret = qcrypto_block_init_cipher(block, cipheralg, ciphermode, - masterkey, masterkeylen, n_threads, + ret = qcrypto_block_init_cipher(block, luks->cipher_alg, + luks->cipher_mode, + masterkey, masterkeylen(luks), + n_threads, errp); if (ret < 0) { ret = -ENOTSUP; @@ -850,12 +848,6 @@ qcrypto_block_luks_open(QCryptoBlock *block, block->payload_offset = luks->header.payload_offset * block->sector_size; - luks->cipher_alg = cipheralg; - luks->cipher_mode = ciphermode; - luks->ivgen_alg = ivalg; - luks->ivgen_hash_alg = ivhash; - luks->hash_alg = hash; - g_free(masterkey); g_free(password); @@ -910,7 +902,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; @@ -930,6 +922,17 @@ qcrypto_block_luks_create(QCryptoBlock *block, luks_opts.has_ivgen_hash_alg = true; } } + + luks = g_new0(QCryptoBlockLUKS, 1); + block->opaque = luks; + + luks->cipher_alg = luks_opts.cipher_alg; + luks->cipher_mode = luks_opts.cipher_mode; + luks->ivgen_alg = luks_opts.ivgen_alg; + luks->ivgen_hash_alg = luks_opts.ivgen_hash_alg; + luks->hash_alg = luks_opts.hash_alg; + + /* Note we're allowing ivgen_hash_alg to be set even for * non-essiv iv generators that don't need a hash. It will * be silently ignored, for compatibility with dm-crypt */ @@ -944,8 +947,6 @@ qcrypto_block_luks_create(QCryptoBlock *block, return -1; } - luks = g_new0(QCryptoBlockLUKS, 1); - block->opaque = luks; memcpy(luks->header.magic, qcrypto_block_luks_magic, QCRYPTO_BLOCK_LUKS_MAGIC_LEN); @@ -1003,6 +1004,8 @@ qcrypto_block_luks_create(QCryptoBlock *block, ivcipheralg = luks_opts.cipher_alg; } + luks->ivgen_cipher_alg = ivcipheralg; + strcpy(luks->header.cipher_name, cipher_alg); strcpy(luks->header.cipher_mode, cipher_mode_spec); strcpy(luks->header.hash_spec, hash_alg); @@ -1304,12 +1307,6 @@ qcrypto_block_luks_create(QCryptoBlock *block, goto error; } - luks->cipher_alg = luks_opts.cipher_alg; - luks->cipher_mode = luks_opts.cipher_mode; - luks->ivgen_alg = luks_opts.ivgen_alg; - luks->ivgen_hash_alg = luks_opts.ivgen_hash_alg; - luks->hash_alg = luks_opts.hash_alg; - memset(masterkey, 0, luks->header.key_bytes); g_free(masterkey); memset(slotkey, 0, luks->header.key_bytes); From patchwork Wed Aug 14 20:22:09 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094507 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 668FB13AC for ; Wed, 14 Aug 2019 20:28:41 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 5022028833 for ; Wed, 14 Aug 2019 20:28:41 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 4432A2883B; Wed, 14 Aug 2019 20:28:41 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id E928928833 for ; Wed, 14 Aug 2019 20:28:39 +0000 (UTC) Received: from localhost ([::1]:35732 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzss-0001XM-GG for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:28:38 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33234) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxznR-0004A1-8N for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:04 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxznL-000425-4E for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:22:59 -0400 Received: from mx1.redhat.com ([209.132.183.28]:37188) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxzn4-0003qz-VN; Wed, 14 Aug 2019 16:22:39 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id E8D00302C061; Wed, 14 Aug 2019 20:22:36 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id 3874710002BA; Wed, 14 Aug 2019 20:22:34 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:09 +0300 Message-Id: <20190814202219.1870-4-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.46]); Wed, 14 Aug 2019 20:22:36 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 03/13] qcrypto-luks: refactoring: extract load/store/check/parse header functions X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP With upcoming key management, the header will need to be stored after the image is created. Extracting load header isn't strictly needed, but do this anyway for the symmetry. Also I extracted a function that does basic sanity checks on the just read header, and a function which parses all the crypto format to make the code a bit more readable, plus now the code doesn't destruct the in-header cipher-mode string, so that the header now can be stored many times, which is needed for the key management. Also this allows to contain the endianess conversions in these functions alone The header is no longer endian swapped in place, to prevent (mostly theoretical races I think) races where someone could see the header in the process of beeing byteswapped. Signed-off-by: Maxim Levitsky --- crypto/block-luks.c | 756 ++++++++++++++++++++++++++------------------ 1 file changed, 440 insertions(+), 316 deletions(-) diff --git a/crypto/block-luks.c b/crypto/block-luks.c index 48213abde7..6bb369f3b4 100644 --- a/crypto/block-luks.c +++ b/crypto/block-luks.c @@ -417,6 +417,427 @@ static int masterkeylen(QCryptoBlockLUKS *luks) } +/* + * Stores the main LUKS header, taking care of endianess + */ +static int +qcrypto_block_luks_store_header(QCryptoBlock *block, + QCryptoBlockWriteFunc writefunc, + void *opaque, + Error **errp) +{ + QCryptoBlockLUKS *luks = block->opaque; + Error *local_err = NULL; + size_t i; + QCryptoBlockLUKSHeader *hdr_copy; + + /* Create a copy of the header */ + hdr_copy = g_new0(QCryptoBlockLUKSHeader, 1); + memcpy(hdr_copy, &luks->header, sizeof(QCryptoBlockLUKSHeader)); + + /* + * Everything on disk uses Big Endian (tm), so flip header fields + * before writing them + */ + cpu_to_be16s(&hdr_copy->version); + cpu_to_be32s(&hdr_copy->payload_offset); + cpu_to_be32s(&hdr_copy->key_bytes); + cpu_to_be32s(&hdr_copy->master_key_iterations); + + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + cpu_to_be32s(&hdr_copy->key_slots[i].active); + cpu_to_be32s(&hdr_copy->key_slots[i].iterations); + cpu_to_be32s(&hdr_copy->key_slots[i].key_offset); + cpu_to_be32s(&hdr_copy->key_slots[i].stripes); + } + + /* Write out the partition header and key slot headers */ + writefunc(block, 0, (const uint8_t *)hdr_copy, sizeof(*hdr_copy), + opaque, &local_err); + + g_free(hdr_copy); + + if (local_err) { + error_propagate(errp, local_err); + return -1; + } + return 0; +} + +/* + * Loads the main LUKS header,and byteswaps it to native endianess + * And run basic sanity checks on it + */ +static int +qcrypto_block_luks_load_header(QCryptoBlock *block, + QCryptoBlockReadFunc readfunc, + void *opaque, + Error **errp) +{ + ssize_t rv; + size_t i; + int ret = 0; + QCryptoBlockLUKS *luks = block->opaque; + + /* + * Read the entire LUKS header, minus the key material from + * the underlying device + */ + + rv = readfunc(block, 0, + (uint8_t *)&luks->header, + sizeof(luks->header), + opaque, + errp); + if (rv < 0) { + ret = rv; + goto fail; + } + + /* + * The header is always stored in big-endian format, so + * convert everything to native + */ + be16_to_cpus(&luks->header.version); + be32_to_cpus(&luks->header.payload_offset); + be32_to_cpus(&luks->header.key_bytes); + be32_to_cpus(&luks->header.master_key_iterations); + + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + be32_to_cpus(&luks->header.key_slots[i].active); + be32_to_cpus(&luks->header.key_slots[i].iterations); + be32_to_cpus(&luks->header.key_slots[i].key_offset); + be32_to_cpus(&luks->header.key_slots[i].stripes); + } + + + return 0; +fail: + return ret; +} + + +/* + * Does basic sanity checks on the LUKS header + */ +static int +qcrypto_block_luks_check_header(QCryptoBlockLUKS *luks, Error **errp) +{ + int ret; + + if (memcmp(luks->header.magic, qcrypto_block_luks_magic, + QCRYPTO_BLOCK_LUKS_MAGIC_LEN) != 0) { + error_setg(errp, "Volume is not in LUKS format"); + ret = -EINVAL; + goto fail; + } + + if (luks->header.version != QCRYPTO_BLOCK_LUKS_VERSION) { + error_setg(errp, "LUKS version %" PRIu32 " is not supported", + luks->header.version); + ret = -ENOTSUP; + goto fail; + } + + return 0; +fail: + return ret; +} + + +/* + * Parses the crypto parameters that are stored in the LUKS header + * to string + */ + +static int +qcrypto_block_luks_parse_header(QCryptoBlockLUKS *luks, Error **errp) +{ + char *cipher_mode = g_strdup(luks->header.cipher_mode); + char *ivgen_name, *ivhash_name; + int ret = -1; + Error *local_err = NULL; + + /* + * The cipher_mode header contains a string that we have + * to further parse, of the format + * + * -[:] + * + * eg cbc-essiv:sha256, cbc-plain64 + */ + ivgen_name = strchr(cipher_mode, '-'); + if (!ivgen_name) { + ret = -EINVAL; + error_setg(errp, "Unexpected cipher mode string format %s", + luks->header.cipher_mode); + goto out; + } + *ivgen_name = '\0'; + ivgen_name++; + + ivhash_name = strchr(ivgen_name, ':'); + if (!ivhash_name) { + luks->ivgen_hash_alg = 0; + } else { + *ivhash_name = '\0'; + ivhash_name++; + + luks->ivgen_hash_alg = qcrypto_block_luks_hash_name_lookup(ivhash_name, + &local_err); + if (local_err) { + ret = -ENOTSUP; + error_propagate(errp, local_err); + goto out; + } + } + + luks->cipher_mode = qcrypto_block_luks_cipher_mode_lookup(cipher_mode, + &local_err); + if (local_err) { + ret = -ENOTSUP; + error_propagate(errp, local_err); + goto out; + } + + luks->cipher_alg = + qcrypto_block_luks_cipher_name_lookup(luks->header.cipher_name, + luks->cipher_mode, + luks->header.key_bytes, + &local_err); + if (local_err) { + ret = -ENOTSUP; + error_propagate(errp, local_err); + goto out; + } + + luks->hash_alg = + qcrypto_block_luks_hash_name_lookup(luks->header.hash_spec, + &local_err); + if (local_err) { + ret = -ENOTSUP; + error_propagate(errp, local_err); + goto out; + } + + luks->ivgen_alg = qcrypto_block_luks_ivgen_name_lookup(ivgen_name, + &local_err); + if (local_err) { + ret = -ENOTSUP; + error_propagate(errp, local_err); + goto out; + } + + if (luks->ivgen_alg == QCRYPTO_IVGEN_ALG_ESSIV) { + if (!ivhash_name) { + ret = -EINVAL; + error_setg(errp, "Missing IV generator hash specification"); + goto out; + } + luks->ivgen_cipher_alg = + qcrypto_block_luks_essiv_cipher(luks->cipher_alg, + luks->ivgen_hash_alg, + &local_err); + if (local_err) { + ret = -ENOTSUP; + error_propagate(errp, local_err); + goto out; + } + } else { + + /* + * Note we parsed the ivhash_name earlier in the cipher_mode + * spec string even with plain/plain64 ivgens, but we + * will ignore it, since it is irrelevant for these ivgens. + * This is for compat with dm-crypt which will silently + * ignore hash names with these ivgens rather than report + * an error about the invalid usage + */ + luks->ivgen_cipher_alg = luks->cipher_alg; + } + ret = 0; +out: + g_free(cipher_mode); + return ret; + +} + +/* + * Given a key slot, user password, and the master key, + * will store the encrypted master key there, and update the + * in-memory header. User must then write the in-memory header + * + * Returns: + * 0 if the keyslot was written successfully + * with the provided password + * -1 if a fatal error occurred while storing the key + */ +static int +qcrypto_block_luks_store_key(QCryptoBlock *block, + uint slot_idx, + const char *password, + uint8_t *masterkey, + uint64_t iter_time, + QCryptoBlockWriteFunc writefunc, + void *opaque, + Error **errp) +{ + QCryptoBlockLUKS *luks = block->opaque; + QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx]; + uint8_t *splitkey = NULL; + size_t splitkeylen; + uint8_t *slotkey = NULL; + QCryptoCipher *cipher = NULL; + QCryptoIVGen *ivgen = NULL; + Error *local_err = NULL; + uint64_t iters; + int ret = -1; + + if (qcrypto_random_bytes(slot->salt, + QCRYPTO_BLOCK_LUKS_SALT_LEN, + errp) < 0) { + goto cleanup; + } + + splitkeylen = masterkeylen(luks) * slot->stripes; + + /* + * Determine how many iterations are required to + * hash the user password while consuming 1 second of compute + * time + */ + iters = qcrypto_pbkdf2_count_iters(luks->hash_alg, + (uint8_t *)password, strlen(password), + slot->salt, + QCRYPTO_BLOCK_LUKS_SALT_LEN, + masterkeylen(luks), + &local_err); + if (local_err) { + error_propagate(errp, local_err); + goto cleanup; + } + + if (iters > (ULLONG_MAX / iter_time)) { + error_setg_errno(errp, ERANGE, + "PBKDF iterations %llu too large to scale", + (unsigned long long)iters); + goto cleanup; + } + + /* iter_time was in millis, but count_iters reported for secs */ + iters = iters * iter_time / 1000; + + if (iters > UINT32_MAX) { + error_setg_errno(errp, ERANGE, + "PBKDF iterations %llu larger than %u", + (unsigned long long)iters, UINT32_MAX); + goto cleanup; + } + + slot->iterations = + MAX(iters, QCRYPTO_BLOCK_LUKS_MIN_SLOT_KEY_ITERS); + + + /* + * Generate a key that we'll use to encrypt the master + * key, from the user's password + */ + slotkey = g_new0(uint8_t, masterkeylen(luks)); + if (qcrypto_pbkdf2(luks->hash_alg, + (uint8_t *)password, strlen(password), + slot->salt, + QCRYPTO_BLOCK_LUKS_SALT_LEN, + slot->iterations, + slotkey, masterkeylen(luks), + errp) < 0) { + goto cleanup; + } + + + /* + * Setup the encryption objects needed to encrypt the + * master key material + */ + cipher = qcrypto_cipher_new(luks->cipher_alg, + luks->cipher_mode, + slotkey, masterkeylen(luks), + errp); + if (!cipher) { + goto cleanup; + } + + ivgen = qcrypto_ivgen_new(luks->ivgen_alg, + luks->ivgen_cipher_alg, + luks->ivgen_hash_alg, + slotkey, masterkeylen(luks), + errp); + if (!ivgen) { + goto cleanup; + } + + /* + * Before storing the master key, we need to vastly + * increase its size, as protection against forensic + * disk data recovery + */ + splitkey = g_new0(uint8_t, splitkeylen); + + if (qcrypto_afsplit_encode(luks->hash_alg, + masterkeylen(luks), + slot->stripes, + masterkey, + splitkey, + errp) < 0) { + goto cleanup; + } + + /* + * Now we encrypt the split master key with the key generated + * from the user's password, before storing it + */ + if (qcrypto_block_cipher_encrypt_helper(cipher, block->niv, ivgen, + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE, + 0, + splitkey, + splitkeylen, + errp) < 0) { + goto cleanup; + } + + /* Write out the slot's master key material. */ + if (writefunc(block, + slot->key_offset * + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE, + splitkey, splitkeylen, + opaque, + errp) != splitkeylen) { + goto cleanup; + } + + slot->active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED; + + if (qcrypto_block_luks_store_header(block, writefunc, opaque, errp)) { + goto cleanup; + } + + ret = 0; + +cleanup: + if (slotkey) { + memset(slotkey, 0, masterkeylen(luks)); + g_free(slotkey); + } + + if (splitkey) { + memset(splitkey, 0, splitkeylen); + g_free(splitkey); + } + + qcrypto_ivgen_free(ivgen); + qcrypto_cipher_free(cipher); + return ret; +} + /* * Given a key slot, and user password, this will attempt to unlock * the master encryption key from the key slot. @@ -632,14 +1053,9 @@ qcrypto_block_luks_open(QCryptoBlock *block, Error **errp) { QCryptoBlockLUKS *luks = NULL; - Error *local_err = NULL; int ret = 0; - size_t i; - ssize_t rv; uint8_t *masterkey = NULL; - char *ivgen_name, *ivhash_name; char *password = NULL; - char *cipher_mode = NULL; if (!(flags & QCRYPTO_BLOCK_OPEN_NO_IO)) { if (!options->u.luks.key_secret) { @@ -657,154 +1073,25 @@ qcrypto_block_luks_open(QCryptoBlock *block, luks = g_new0(QCryptoBlockLUKS, 1); block->opaque = luks; - /* Read the entire LUKS header, minus the key material from - * the underlying device */ - rv = readfunc(block, 0, - (uint8_t *)&luks->header, - sizeof(luks->header), - opaque, - errp); - if (rv < 0) { - ret = rv; - goto fail; - } - - /* The header is always stored in big-endian format, so - * convert everything to native */ - be16_to_cpus(&luks->header.version); - be32_to_cpus(&luks->header.payload_offset); - be32_to_cpus(&luks->header.key_bytes); - be32_to_cpus(&luks->header.master_key_iterations); - - for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { - be32_to_cpus(&luks->header.key_slots[i].active); - be32_to_cpus(&luks->header.key_slots[i].iterations); - be32_to_cpus(&luks->header.key_slots[i].key_offset); - be32_to_cpus(&luks->header.key_slots[i].stripes); - } - - if (memcmp(luks->header.magic, qcrypto_block_luks_magic, - QCRYPTO_BLOCK_LUKS_MAGIC_LEN) != 0) { - error_setg(errp, "Volume is not in LUKS format"); - ret = -EINVAL; - goto fail; - } - if (luks->header.version != QCRYPTO_BLOCK_LUKS_VERSION) { - error_setg(errp, "LUKS version %" PRIu32 " is not supported", - luks->header.version); - ret = -ENOTSUP; - goto fail; - } - - cipher_mode = g_strdup(luks->header.cipher_mode); - - /* - * The cipher_mode header contains a string that we have - * to further parse, of the format - * - * -[:] - * - * eg cbc-essiv:sha256, cbc-plain64 - */ - ivgen_name = strchr(cipher_mode, '-'); - if (!ivgen_name) { - ret = -EINVAL; - error_setg(errp, "Unexpected cipher mode string format %s", - luks->header.cipher_mode); - goto fail; - } - *ivgen_name = '\0'; - ivgen_name++; - - ivhash_name = strchr(ivgen_name, ':'); - if (!ivhash_name) { - luks->ivgen_hash_alg = 0; - } else { - *ivhash_name = '\0'; - ivhash_name++; - - luks->ivgen_hash_alg = qcrypto_block_luks_hash_name_lookup(ivhash_name, - &local_err); - if (local_err) { - ret = -ENOTSUP; - error_propagate(errp, local_err); - goto fail; - } - } - - luks->cipher_mode = qcrypto_block_luks_cipher_mode_lookup(cipher_mode, - &local_err); - if (local_err) { - ret = -ENOTSUP; - error_propagate(errp, local_err); - goto fail; - } - - luks->cipher_alg = - qcrypto_block_luks_cipher_name_lookup(luks->header.cipher_name, - luks->cipher_mode, - luks->header.key_bytes, - &local_err); - if (local_err) { - ret = -ENOTSUP; - error_propagate(errp, local_err); + ret = qcrypto_block_luks_load_header(block, readfunc, opaque, errp); + if (ret) { goto fail; } - luks->hash_alg = - qcrypto_block_luks_hash_name_lookup(luks->header.hash_spec, - &local_err); - if (local_err) { - ret = -ENOTSUP; - error_propagate(errp, local_err); + ret = qcrypto_block_luks_check_header(luks, errp); + if (ret) { goto fail; } - luks->ivgen_alg = qcrypto_block_luks_ivgen_name_lookup(ivgen_name, - &local_err); - if (local_err) { - ret = -ENOTSUP; - error_propagate(errp, local_err); + ret = qcrypto_block_luks_parse_header(luks, errp); + if (ret) { goto fail; } - if (luks->ivgen_alg == QCRYPTO_IVGEN_ALG_ESSIV) { - if (!ivhash_name) { - ret = -EINVAL; - error_setg(errp, "Missing IV generator hash specification"); - goto fail; - } - luks->ivgen_cipher_alg = - qcrypto_block_luks_essiv_cipher(luks->cipher_alg, - luks->ivgen_hash_alg, - &local_err); - if (local_err) { - ret = -ENOTSUP; - error_propagate(errp, local_err); - goto fail; - } - } else { - /* Note we parsed the ivhash_name earlier in the cipher_mode - * spec string even with plain/plain64 ivgens, but we - * will ignore it, since it is irrelevant for these ivgens. - * This is for compat with dm-crypt which will silently - * ignore hash names with these ivgens rather than report - * an error about the invalid usage - */ - luks->ivgen_cipher_alg = luks->cipher_alg; - } - - - g_free(cipher_mode); - cipher_mode = NULL; - ivgen_name = NULL; - ivhash_name = NULL; - if (!(flags & QCRYPTO_BLOCK_OPEN_NO_IO)) { /* Try to find which key slot our password is valid for * and unlock the master key from that slot. */ - masterkey = g_new0(uint8_t, masterkeylen(luks)); if (qcrypto_block_luks_find_key(block, @@ -845,12 +1132,10 @@ qcrypto_block_luks_open(QCryptoBlock *block, } block->sector_size = QCRYPTO_BLOCK_LUKS_SECTOR_SIZE; - block->payload_offset = luks->header.payload_offset * - block->sector_size; + block->payload_offset = luks->header.payload_offset * block->sector_size; g_free(masterkey); g_free(password); - return 0; fail: @@ -884,12 +1169,8 @@ qcrypto_block_luks_create(QCryptoBlock *block, QCryptoBlockCreateOptionsLUKS luks_opts; Error *local_err = NULL; uint8_t *masterkey = NULL; - uint8_t *slotkey = NULL; - uint8_t *splitkey = NULL; size_t splitkeylen = 0; size_t i; - QCryptoCipher *cipher = NULL; - QCryptoIVGen *ivgen = NULL; char *password; const char *cipher_alg; const char *cipher_mode; @@ -1112,9 +1393,7 @@ qcrypto_block_luks_create(QCryptoBlock *block, * to use the first key slot */ splitkeylen = luks->header.key_bytes * QCRYPTO_BLOCK_LUKS_STRIPES; for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { - luks->header.key_slots[i].active = i == 0 ? - QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED : - QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED; + luks->header.key_slots[i].active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED; luks->header.key_slots[i].stripes = QCRYPTO_BLOCK_LUKS_STRIPES; /* This calculation doesn't match that shown in the spec, @@ -1128,107 +1407,6 @@ qcrypto_block_luks_create(QCryptoBlock *block, QCRYPTO_BLOCK_LUKS_SECTOR_SIZE)) * i); } - if (qcrypto_random_bytes(luks->header.key_slots[0].salt, - QCRYPTO_BLOCK_LUKS_SALT_LEN, - errp) < 0) { - goto error; - } - - /* Again we determine how many iterations are required to - * hash the user password while consuming 1 second of compute - * time */ - iters = qcrypto_pbkdf2_count_iters(luks_opts.hash_alg, - (uint8_t *)password, strlen(password), - luks->header.key_slots[0].salt, - QCRYPTO_BLOCK_LUKS_SALT_LEN, - luks->header.key_bytes, - &local_err); - if (local_err) { - error_propagate(errp, local_err); - goto error; - } - - if (iters > (ULLONG_MAX / luks_opts.iter_time)) { - error_setg_errno(errp, ERANGE, - "PBKDF iterations %llu too large to scale", - (unsigned long long)iters); - goto error; - } - - /* iter_time was in millis, but count_iters reported for secs */ - iters = iters * luks_opts.iter_time / 1000; - - if (iters > UINT32_MAX) { - error_setg_errno(errp, ERANGE, - "PBKDF iterations %llu larger than %u", - (unsigned long long)iters, UINT32_MAX); - goto error; - } - - luks->header.key_slots[0].iterations = - MAX(iters, QCRYPTO_BLOCK_LUKS_MIN_SLOT_KEY_ITERS); - - - /* Generate a key that we'll use to encrypt the master - * key, from the user's password - */ - slotkey = g_new0(uint8_t, luks->header.key_bytes); - if (qcrypto_pbkdf2(luks_opts.hash_alg, - (uint8_t *)password, strlen(password), - luks->header.key_slots[0].salt, - QCRYPTO_BLOCK_LUKS_SALT_LEN, - luks->header.key_slots[0].iterations, - slotkey, luks->header.key_bytes, - errp) < 0) { - goto error; - } - - - /* Setup the encryption objects needed to encrypt the - * master key material - */ - cipher = qcrypto_cipher_new(luks_opts.cipher_alg, - luks_opts.cipher_mode, - slotkey, luks->header.key_bytes, - errp); - if (!cipher) { - goto error; - } - - ivgen = qcrypto_ivgen_new(luks_opts.ivgen_alg, - ivcipheralg, - luks_opts.ivgen_hash_alg, - slotkey, luks->header.key_bytes, - errp); - if (!ivgen) { - goto error; - } - - /* Before storing the master key, we need to vastly - * increase its size, as protection against forensic - * disk data recovery */ - splitkey = g_new0(uint8_t, splitkeylen); - - if (qcrypto_afsplit_encode(luks_opts.hash_alg, - luks->header.key_bytes, - luks->header.key_slots[0].stripes, - masterkey, - splitkey, - errp) < 0) { - goto error; - } - - /* Now we encrypt the split master key with the key generated - * from the user's password, before storing it */ - if (qcrypto_block_cipher_encrypt_helper(cipher, block->niv, ivgen, - QCRYPTO_BLOCK_LUKS_SECTOR_SIZE, - 0, - splitkey, - splitkeylen, - errp) < 0) { - goto error; - } - /* The total size of the LUKS headers is the partition header + key * slot headers, rounded up to the nearest sector, combined with @@ -1253,71 +1431,25 @@ qcrypto_block_luks_create(QCryptoBlock *block, goto error; } - /* Everything on disk uses Big Endian, so flip header fields - * before writing them */ - cpu_to_be16s(&luks->header.version); - cpu_to_be32s(&luks->header.payload_offset); - cpu_to_be32s(&luks->header.key_bytes); - cpu_to_be32s(&luks->header.master_key_iterations); - - for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { - cpu_to_be32s(&luks->header.key_slots[i].active); - cpu_to_be32s(&luks->header.key_slots[i].iterations); - cpu_to_be32s(&luks->header.key_slots[i].key_offset); - cpu_to_be32s(&luks->header.key_slots[i].stripes); - } - - - /* Write out the partition header and key slot headers */ - writefunc(block, 0, - (const uint8_t *)&luks->header, - sizeof(luks->header), - opaque, - &local_err); - - /* Delay checking local_err until we've byte-swapped */ - - /* Byte swap the header back to native, in case we need - * to read it again later */ - be16_to_cpus(&luks->header.version); - be32_to_cpus(&luks->header.payload_offset); - be32_to_cpus(&luks->header.key_bytes); - be32_to_cpus(&luks->header.master_key_iterations); - - for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { - be32_to_cpus(&luks->header.key_slots[i].active); - be32_to_cpus(&luks->header.key_slots[i].iterations); - be32_to_cpus(&luks->header.key_slots[i].key_offset); - be32_to_cpus(&luks->header.key_slots[i].stripes); - } - - if (local_err) { - error_propagate(errp, local_err); + /* populate the slot 0 with the password encrypted master key*/ + /* This will also store the header */ + if (qcrypto_block_luks_store_key(block, + 0, + password, + masterkey, + luks_opts.iter_time, + writefunc, + opaque, + errp)) { goto error; - } + } - /* Write out the master key material, starting at the - * sector immediately following the partition header. */ - if (writefunc(block, - luks->header.key_slots[0].key_offset * - QCRYPTO_BLOCK_LUKS_SECTOR_SIZE, - splitkey, splitkeylen, - opaque, - errp) != splitkeylen) { - goto error; - } memset(masterkey, 0, luks->header.key_bytes); g_free(masterkey); - memset(slotkey, 0, luks->header.key_bytes); - g_free(slotkey); - g_free(splitkey); g_free(password); g_free(cipher_mode_spec); - qcrypto_ivgen_free(ivgen); - qcrypto_cipher_free(cipher); - return 0; error: @@ -1325,17 +1457,9 @@ qcrypto_block_luks_create(QCryptoBlock *block, memset(masterkey, 0, luks->header.key_bytes); } g_free(masterkey); - if (slotkey) { - memset(slotkey, 0, luks->header.key_bytes); - } - g_free(slotkey); - g_free(splitkey); g_free(password); g_free(cipher_mode_spec); - qcrypto_ivgen_free(ivgen); - qcrypto_cipher_free(cipher); - qcrypto_block_free_cipher(block); qcrypto_ivgen_free(block->ivgen); From patchwork Wed Aug 14 20:22:10 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094505 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id DD4881399 for ; Wed, 14 Aug 2019 20:28:40 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CC96F28834 for ; Wed, 14 Aug 2019 20:28:40 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id BF79A2883B; Wed, 14 Aug 2019 20:28:40 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 4864C28834 for ; Wed, 14 Aug 2019 20:28:40 +0000 (UTC) Received: from localhost ([::1]:35730 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzss-0001X4-L7 for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:28:38 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33171) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxznL-00047k-8C for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:22:56 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxznI-00040d-Oe for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:22:54 -0400 Received: from mx1.redhat.com ([209.132.183.28]:48672) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxzn7-0003sq-67; Wed, 14 Aug 2019 16:22:41 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id B38F5317464A; Wed, 14 Aug 2019 20:22:39 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id 515541000321; Wed, 14 Aug 2019 20:22:37 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:10 +0300 Message-Id: <20190814202219.1870-5-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.41]); Wed, 14 Aug 2019 20:22:39 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 04/13] qcrypto-luks: refactoring: simplify the math used for keyslot locations X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Maxim Levitsky --- crypto/block-luks.c | 64 +++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/crypto/block-luks.c b/crypto/block-luks.c index 6bb369f3b4..e1a4df94b7 100644 --- a/crypto/block-luks.c +++ b/crypto/block-luks.c @@ -417,6 +417,33 @@ static int masterkeylen(QCryptoBlockLUKS *luks) } +/* + * Returns number of sectors needed to store the key material + * given number of anti forensic stripes + */ +static int splitkeylen_sectors(QCryptoBlockLUKS *luks, int stripes) + +{ + /* + * This calculation doesn't match that shown in the spec, + * but instead follows the cryptsetup implementation. + */ + + size_t header_sectors = QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE; + + size_t splitkeylen = masterkeylen(luks) * stripes; + + /* First align the key material size to block size*/ + size_t splitkeylen_sectors = + DIV_ROUND_UP(splitkeylen, QCRYPTO_BLOCK_LUKS_SECTOR_SIZE); + + /* Then also align the key material size to the size of the header */ + return ROUND_UP(splitkeylen_sectors, header_sectors); +} + + + /* * Stores the main LUKS header, taking care of endianess */ @@ -1169,7 +1196,7 @@ qcrypto_block_luks_create(QCryptoBlock *block, QCryptoBlockCreateOptionsLUKS luks_opts; Error *local_err = NULL; uint8_t *masterkey = NULL; - size_t splitkeylen = 0; + size_t next_sector; size_t i; char *password; const char *cipher_alg; @@ -1388,23 +1415,16 @@ qcrypto_block_luks_create(QCryptoBlock *block, goto error; } + /* start with the sector that follows the header*/ + next_sector = QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE; - /* Although LUKS has multiple key slots, we're just going - * to use the first key slot */ - splitkeylen = luks->header.key_bytes * QCRYPTO_BLOCK_LUKS_STRIPES; for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { - luks->header.key_slots[i].active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED; - luks->header.key_slots[i].stripes = QCRYPTO_BLOCK_LUKS_STRIPES; - - /* This calculation doesn't match that shown in the spec, - * but instead follows the cryptsetup implementation. - */ - luks->header.key_slots[i].key_offset = - (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / - QCRYPTO_BLOCK_LUKS_SECTOR_SIZE) + - (ROUND_UP(DIV_ROUND_UP(splitkeylen, QCRYPTO_BLOCK_LUKS_SECTOR_SIZE), - (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / - QCRYPTO_BLOCK_LUKS_SECTOR_SIZE)) * i); + QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[i]; + slot->active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED; + slot->key_offset = next_sector; + slot->stripes = QCRYPTO_BLOCK_LUKS_STRIPES; + next_sector += splitkeylen_sectors(luks, QCRYPTO_BLOCK_LUKS_STRIPES); } @@ -1412,17 +1432,9 @@ qcrypto_block_luks_create(QCryptoBlock *block, * slot headers, rounded up to the nearest sector, combined with * the size of each master key material region, also rounded up * to the nearest sector */ - luks->header.payload_offset = - (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / - QCRYPTO_BLOCK_LUKS_SECTOR_SIZE) + - (ROUND_UP(DIV_ROUND_UP(splitkeylen, QCRYPTO_BLOCK_LUKS_SECTOR_SIZE), - (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / - QCRYPTO_BLOCK_LUKS_SECTOR_SIZE)) * - QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS); - + luks->header.payload_offset = next_sector; block->sector_size = QCRYPTO_BLOCK_LUKS_SECTOR_SIZE; - block->payload_offset = luks->header.payload_offset * - block->sector_size; + block->payload_offset = luks->header.payload_offset * block->sector_size; /* Reserve header space to match payload offset */ initfunc(block, block->payload_offset, opaque, &local_err); From patchwork Wed Aug 14 20:22:11 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094511 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id E1B176C5 for ; Wed, 14 Aug 2019 20:31:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id C9D1228834 for ; Wed, 14 Aug 2019 20:31:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id B95D02883C; Wed, 14 Aug 2019 20:31:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 62CC728834 for ; Wed, 14 Aug 2019 20:31:24 +0000 (UTC) Received: from localhost ([::1]:35768 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzvW-0005IW-FW for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:31:22 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33200) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxznN-00049C-2D for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:22:58 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxznL-00042S-7T for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:22:56 -0400 Received: from mx1.redhat.com ([209.132.183.28]:59433) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxznA-0003uv-Jz; Wed, 14 Aug 2019 16:22:45 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 83BDA30BA1C9; Wed, 14 Aug 2019 20:22:42 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id 1A6F210002BA; Wed, 14 Aug 2019 20:22:39 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:11 +0300 Message-Id: <20190814202219.1870-6-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.48]); Wed, 14 Aug 2019 20:22:42 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 05/13] qcrypto-luks: clear the masterkey and password before freeing them always X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP While there are other places where these are still stored in memory, this is still one less key material area that can be sniffed with various side channel attacks Signed-off-by: Maxim Levitsky --- crypto/block-luks.c | 52 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/crypto/block-luks.c b/crypto/block-luks.c index e1a4df94b7..336e633df4 100644 --- a/crypto/block-luks.c +++ b/crypto/block-luks.c @@ -1023,8 +1023,18 @@ qcrypto_block_luks_load_key(QCryptoBlock *block, cleanup: qcrypto_ivgen_free(ivgen); qcrypto_cipher_free(cipher); - g_free(splitkey); - g_free(possiblekey); + + if (splitkey) { + memset(splitkey, 0, splitkeylen); + g_free(splitkey); + } + + if (possiblekey) { + memset(possiblekey, 0, masterkeylen(luks)); + g_free(possiblekey); + + } + return ret; } @@ -1161,16 +1171,34 @@ qcrypto_block_luks_open(QCryptoBlock *block, block->sector_size = QCRYPTO_BLOCK_LUKS_SECTOR_SIZE; block->payload_offset = luks->header.payload_offset * block->sector_size; - g_free(masterkey); - g_free(password); + if (masterkey) { + memset(masterkey, 0, masterkeylen(luks)); + g_free(masterkey); + } + + if (password) { + memset(password, 0, strlen(password)); + g_free(password); + } + return 0; fail: - g_free(masterkey); + + if (masterkey) { + memset(masterkey, 0, masterkeylen(luks)); + g_free(masterkey); + } + + if (password) { + memset(password, 0, strlen(password)); + g_free(password); + } + qcrypto_block_free_cipher(block); qcrypto_ivgen_free(block->ivgen); + g_free(luks); - g_free(password); return ret; } @@ -1459,7 +1487,10 @@ qcrypto_block_luks_create(QCryptoBlock *block, memset(masterkey, 0, luks->header.key_bytes); g_free(masterkey); + + memset(password, 0, strlen(password)); g_free(password); + g_free(cipher_mode_spec); return 0; @@ -1467,9 +1498,14 @@ qcrypto_block_luks_create(QCryptoBlock *block, error: if (masterkey) { memset(masterkey, 0, luks->header.key_bytes); + g_free(masterkey); } - g_free(masterkey); - g_free(password); + + if (password) { + memset(password, 0, strlen(password)); + g_free(password); + } + g_free(cipher_mode_spec); qcrypto_block_free_cipher(block); From patchwork Wed Aug 14 20:22:12 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094489 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id E9F9D1399 for ; Wed, 14 Aug 2019 20:24:56 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CDBE528834 for ; Wed, 14 Aug 2019 20:24:56 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id C0F0A2883B; Wed, 14 Aug 2019 20:24:56 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 70E6128834 for ; Wed, 14 Aug 2019 20:24:56 +0000 (UTC) Received: from localhost ([::1]:35704 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzpH-0006Pa-7g for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:24:55 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33257) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxznS-0004AL-S9 for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:04 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxznQ-00045g-My for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:02 -0400 Received: from mx1.redhat.com ([209.132.183.28]:37244) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxznJ-0003y4-2V; Wed, 14 Aug 2019 16:22:54 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id D5EB6302C07B; Wed, 14 Aug 2019 20:22:47 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id E046910016EB; Wed, 14 Aug 2019 20:22:42 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:12 +0300 Message-Id: <20190814202219.1870-7-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.46]); Wed, 14 Aug 2019 20:22:47 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 06/13] qcrypto-luks: implement more rigorous header checking X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP Check that keyslots don't overlap with the data, and check that keyslots don't overlap with each other. (this is done using naive O(n^2) nested loops, but since there are just 8 keyslots, this doens't really matter. Signed-off-by: Maxim Levitsky --- crypto/block-luks.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/crypto/block-luks.c b/crypto/block-luks.c index 336e633df4..1997e92fe1 100644 --- a/crypto/block-luks.c +++ b/crypto/block-luks.c @@ -551,6 +551,8 @@ static int qcrypto_block_luks_check_header(QCryptoBlockLUKS *luks, Error **errp) { int ret; + int i, j; + if (memcmp(luks->header.magic, qcrypto_block_luks_magic, QCRYPTO_BLOCK_LUKS_MAGIC_LEN) != 0) { @@ -566,6 +568,46 @@ qcrypto_block_luks_check_header(QCryptoBlockLUKS *luks, Error **errp) goto fail; } + /* Check all keyslots for corruption */ + for (i = 0 ; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS ; i++) { + + QCryptoBlockLUKSKeySlot *slot1 = &luks->header.key_slots[i]; + uint start1 = slot1->key_offset; + uint len1 = splitkeylen_sectors(luks, slot1->stripes); + + if (slot1->stripes == 0 || + (slot1->active != QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED && + slot1->active != QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED)) { + + error_setg(errp, "Keyslot %i is corrupted", i); + ret = -EINVAL; + goto fail; + } + + if (start1 + len1 > luks->header.payload_offset) { + error_setg(errp, + "Keyslot %i is overlapping with the encrypted payload", + i); + ret = -EINVAL; + goto fail; + } + + for (j = i + 1 ; j < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS ; j++) { + + QCryptoBlockLUKSKeySlot *slot2 = &luks->header.key_slots[j]; + uint start2 = slot2->key_offset; + uint len2 = splitkeylen_sectors(luks, slot2->stripes); + + if (start1 + len1 > start2 && start2 + len2 > start1) { + error_setg(errp, + "Keyslots %i and %i are overlapping in the header", + i, j); + ret = -EINVAL; + goto fail; + } + } + + } return 0; fail: return ret; From patchwork Wed Aug 14 20:22:13 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094487 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 1282C1399 for ; Wed, 14 Aug 2019 20:24:49 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 0040128833 for ; Wed, 14 Aug 2019 20:24:49 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id E89B628837; Wed, 14 Aug 2019 20:24:48 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 4920728833 for ; Wed, 14 Aug 2019 20:24:48 +0000 (UTC) Received: from localhost ([::1]:35702 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzp8-0006Cs-RU for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:24:46 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33259) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxznS-0004AS-Uc for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:04 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxznR-000468-7V for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:02 -0400 Received: from mx1.redhat.com ([209.132.183.28]:59294) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxznJ-0003zU-W9; Wed, 14 Aug 2019 16:22:55 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id A1FF9970EB; Wed, 14 Aug 2019 20:22:50 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id 3BAC910002BA; Wed, 14 Aug 2019 20:22:48 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:13 +0300 Message-Id: <20190814202219.1870-8-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.39]); Wed, 14 Aug 2019 20:22:50 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 07/13] block: add manage-encryption command (qmp and blockdev) X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP This adds: * x-blockdev-update-encryption and x-blockdev-erase-encryption qmp commands Both commands take the QCryptoKeyManageOptions the x-blockdev-update-encryption is meant for non destructive addition of key slots / whatever the encryption driver supports in the future x-blockdev-erase-encryption is meant for destructive encryption key erase, in some cases even without way to recover the data. * bdrv_setup_encryption callback in the block driver This callback does both the above functions with 'action' parameter * QCryptoKeyManageOptions with set of options that drivers can use for encryption managment Currently it has all the options that LUKS needs, and later it can be extended (via union) to support more encryption drivers if needed * blk_setup_encryption / bdrv_setup_encryption - the usual block layer wrappers. Note that bdrv_setup_encryption takes BlockDriverState and not BdrvChild, for the ease of use from the qmp code. It is not expected that this function will be used by anything but qmp and qemu-img code Signed-off-by: Maxim Levitsky --- block/block-backend.c | 9 ++++++++ block/io.c | 24 ++++++++++++++++++++ blockdev.c | 40 ++++++++++++++++++++++++++++++++++ include/block/block.h | 12 ++++++++++ include/block/block_int.h | 11 ++++++++++ include/sysemu/block-backend.h | 7 ++++++ qapi/block-core.json | 36 ++++++++++++++++++++++++++++++ qapi/crypto.json | 26 ++++++++++++++++++++++ 8 files changed, 165 insertions(+) diff --git a/block/block-backend.c b/block/block-backend.c index 0056b526b8..1b75f28d0c 100644 --- a/block/block-backend.c +++ b/block/block-backend.c @@ -2284,3 +2284,12 @@ const BdrvChild *blk_root(BlockBackend *blk) { return blk->root; } + +int blk_setup_encryption(BlockBackend *blk, + enum BlkSetupEncryptionAction action, + QCryptoEncryptionSetupOptions *options, + bool force, + Error **errp) +{ + return bdrv_setup_encryption(blk->root->bs, action, options, force, errp); +} diff --git a/block/io.c b/block/io.c index 06305c6ea6..50090afe68 100644 --- a/block/io.c +++ b/block/io.c @@ -3256,3 +3256,27 @@ int bdrv_truncate(BdrvChild *child, int64_t offset, PreallocMode prealloc, return tco.ret; } + + +int bdrv_setup_encryption(BlockDriverState *bs, + enum BlkSetupEncryptionAction action, + QCryptoEncryptionSetupOptions *options, + bool force, + Error **errp) +{ + Error *local_err = NULL; + int ret; + + if (!(bs->open_flags & BDRV_O_RDWR)) { + error_setg(errp, "Can't do key management on read only block device"); + return -ENOTSUP; + } + + ret = bs->drv->bdrv_setup_encryption(bs, action, options, force, + &local_err); + if (ret) { + error_propagate(errp, local_err); + return ret; + } + return 0; +} diff --git a/blockdev.c b/blockdev.c index 4d141e9a1f..27be251656 100644 --- a/blockdev.c +++ b/blockdev.c @@ -4563,6 +4563,46 @@ void qmp_block_latency_histogram_set( } } +void qmp_x_blockdev_update_encryption(const char *node_name, + bool has_force, bool force, + QCryptoEncryptionSetupOptions *options, + Error **errp) +{ + BlockDriverState *bs = bdrv_find_node(node_name); + Error *local_error = NULL; + + if (!bs) { + error_setg(errp, "Cannot find node %s", node_name); + return; + } + + if (bdrv_setup_encryption(bs, BLK_UPDATE_ENCRYPTION, options, + has_force ? force : false, &local_error)) { + error_propagate(errp, local_error); + } +} + + +void qmp_x_blockdev_erase_encryption(const char *node_name, + bool has_force, bool force, + QCryptoEncryptionSetupOptions *options, + Error **errp) +{ + BlockDriverState *bs = bdrv_find_node(node_name); + Error *local_error = NULL; + + if (!bs) { + error_setg(errp, "Cannot find node %s", node_name); + return; + } + + if (bdrv_setup_encryption(bs, BLK_ERASE_ENCRYPTION, options, + has_force ? force : false, &local_error)) { + error_propagate(errp, local_error); + } +} + + QemuOptsList qemu_common_drive_opts = { .name = "drive", .head = QTAILQ_HEAD_INITIALIZER(qemu_common_drive_opts.head), diff --git a/include/block/block.h b/include/block/block.h index 50a07c1c33..b55ef4c416 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -276,6 +276,12 @@ enum { DEFAULT_PERM_UNCHANGED = BLK_PERM_ALL & ~DEFAULT_PERM_PASSTHROUGH, }; +enum BlkSetupEncryptionAction { + BLK_UPDATE_ENCRYPTION, + BLK_ERASE_ENCRYPTION, + +}; + char *bdrv_perm_names(uint64_t perm); /* disk I/O throttling */ @@ -348,6 +354,12 @@ int coroutine_fn bdrv_co_truncate(BdrvChild *child, int64_t offset, int bdrv_truncate(BdrvChild *child, int64_t offset, PreallocMode prealloc, Error **errp); +int bdrv_setup_encryption(BlockDriverState *bs, + enum BlkSetupEncryptionAction action, + QCryptoEncryptionSetupOptions *options, + bool force, + Error **errp); + int64_t bdrv_nb_sectors(BlockDriverState *bs); int64_t bdrv_getlength(BlockDriverState *bs); int64_t bdrv_get_allocated_file_size(BlockDriverState *bs); diff --git a/include/block/block_int.h b/include/block/block_int.h index 3aa1e832a8..64c71fe269 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -556,6 +556,16 @@ struct BlockDriver { void (*bdrv_unregister_buf)(BlockDriverState *bs, void *host); QLIST_ENTRY(BlockDriver) list; + + /* Manage encryption keys on the block device */ + int (*bdrv_setup_encryption)(BlockDriverState *bs, + enum BlkSetupEncryptionAction action, + QCryptoEncryptionSetupOptions *options, + bool force, + Error **errp); + + + /* Pointer to a NULL-terminated array of names of strong options * that can be specified for bdrv_open(). A strong option is one * that changes the data of a BDS. @@ -1271,4 +1281,5 @@ int coroutine_fn bdrv_co_copy_range_to(BdrvChild *src, uint64_t src_offset, int refresh_total_sectors(BlockDriverState *bs, int64_t hint); + #endif /* BLOCK_INT_H */ diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h index 733c4957eb..18e98499fd 100644 --- a/include/sysemu/block-backend.h +++ b/include/sysemu/block-backend.h @@ -262,4 +262,11 @@ int coroutine_fn blk_co_copy_range(BlockBackend *blk_in, int64_t off_in, const BdrvChild *blk_root(BlockBackend *blk); + +int blk_setup_encryption(BlockBackend *blk, + enum BlkSetupEncryptionAction action, + QCryptoEncryptionSetupOptions *options, + bool force, + Error **errp); + #endif diff --git a/qapi/block-core.json b/qapi/block-core.json index 0d43d4f37c..53ed411eed 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -5327,3 +5327,39 @@ 'data' : { 'node-name': 'str', 'iothread': 'StrOrNull', '*force': 'bool' } } + + +## +# @x-blockdev-update-encryption: +# +# Update the encryption keys for an encrypted block device +# +# @node-name: Name of the blockdev to operate on +# @force: Disable safety checks (use with care) +# @options: Driver specific options +# + +# Since: 4.2 +## +{ 'command': 'x-blockdev-update-encryption', + 'data': { 'node-name' : 'str', + '*force' : 'bool', + 'options': 'QCryptoEncryptionSetupOptions' } } + +## +# @x-blockdev-erase-encryption: +# +# Erase the encryption keys for an encrypted block device +# +# @node-name: Name of the blockdev to operate on +# @force: Disable safety checks (use with care) +# @options: Driver specific options +# +# Returns: @QCryptoKeyManageResult +# +# Since: 4.2 +## +{ 'command': 'x-blockdev-erase-encryption', + 'data': { 'node-name' : 'str', + '*force' : 'bool', + 'options': 'QCryptoEncryptionSetupOptions' } } diff --git a/qapi/crypto.json b/qapi/crypto.json index b2a4cff683..69e8b086db 100644 --- a/qapi/crypto.json +++ b/qapi/crypto.json @@ -309,3 +309,29 @@ 'base': 'QCryptoBlockInfoBase', 'discriminator': 'format', 'data': { 'luks': 'QCryptoBlockInfoLUKS' } } + + +## +# @QCryptoEncryptionSetupOptions: +# +# Driver specific options for encryption key management. +# +# @key-secret: the ID of a QCryptoSecret object providing the password +# to add or to erase (optional for erase) +# +# @old-key-secret: the ID of a QCryptoSecret object providing the password +# that can currently unlock the image +# +# @slot: Key slot to update/erase +# (optional, for update will select a free slot, +# for erase will erase all slots that match the password) +# +# @iter-time: number of milliseconds to spend in +# PBKDF passphrase processing. Currently defaults to 2000 +# Since: 4.2 +## +{ 'struct': 'QCryptoEncryptionSetupOptions', + 'data': { '*key-secret': 'str', + '*old-key-secret': 'str', + '*slot': 'int', + '*iter-time': 'int' } } From patchwork Wed Aug 14 20:22:14 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094509 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 6286D1395 for ; Wed, 14 Aug 2019 20:28:47 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 52CA328833 for ; Wed, 14 Aug 2019 20:28:47 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 4746728837; Wed, 14 Aug 2019 20:28:47 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id DB59128833 for ; Wed, 14 Aug 2019 20:28:46 +0000 (UTC) Received: from localhost ([::1]:35734 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzsz-0001iU-K8 for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:28:45 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33345) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxznf-0004Er-AU for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:17 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxznd-0004Dw-4I for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:15 -0400 Received: from mx1.redhat.com ([209.132.183.28]:60880) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxznW-00049J-RT; Wed, 14 Aug 2019 16:23:08 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 8CB303067294; Wed, 14 Aug 2019 20:23:05 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id 065321000321; Wed, 14 Aug 2019 20:22:50 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:14 +0300 Message-Id: <20190814202219.1870-9-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.48]); Wed, 14 Aug 2019 20:23:05 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 08/13] qcrypto: add the plumbing for encryption management X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP This adds qcrypto_block_manage_encryption, which is thin wrapper around manage_encryption of the crypto driver which is also added Signed-off-by: Maxim Levitsky --- crypto/block.c | 29 +++++++++++++++++++++++++++++ crypto/blockpriv.h | 9 +++++++++ include/crypto/block.h | 27 +++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/crypto/block.c b/crypto/block.c index ee96759f7d..5916e49aba 100644 --- a/crypto/block.c +++ b/crypto/block.c @@ -20,6 +20,7 @@ #include "qemu/osdep.h" #include "qapi/error.h" + #include "blockpriv.h" #include "block-qcow.h" #include "block-luks.h" @@ -282,6 +283,34 @@ void qcrypto_block_free(QCryptoBlock *block) } +int qcrypto_block_setup_encryption(QCryptoBlock *block, + QCryptoBlockReadFunc readfunc, + QCryptoBlockWriteFunc writefunc, + void *opaque, + enum BlkSetupEncryptionAction action, + QCryptoEncryptionSetupOptions *options, + bool force, + Error **errp) +{ + if (!block->driver->setup_encryption) { + error_setg(errp, + "Crypto format %s doesn't support management of encryption keys", + QCryptoBlockFormat_str(block->format)); + return -1; + } + + return block->driver->setup_encryption(block, + readfunc, + writefunc, + opaque, + action, + options, + force, + errp); +} + + + typedef int (*QCryptoCipherEncDecFunc)(QCryptoCipher *cipher, const void *in, void *out, diff --git a/crypto/blockpriv.h b/crypto/blockpriv.h index 71c59cb542..804965dca3 100644 --- a/crypto/blockpriv.h +++ b/crypto/blockpriv.h @@ -81,6 +81,15 @@ struct QCryptoBlockDriver { bool (*has_format)(const uint8_t *buf, size_t buflen); + + int (*setup_encryption)(QCryptoBlock *block, + QCryptoBlockReadFunc readfunc, + QCryptoBlockWriteFunc writefunc, + void *opaque, + enum BlkSetupEncryptionAction action, + QCryptoEncryptionSetupOptions *options, + bool force, + Error **errp); }; diff --git a/include/crypto/block.h b/include/crypto/block.h index fe12899831..60d46e3efc 100644 --- a/include/crypto/block.h +++ b/include/crypto/block.h @@ -23,6 +23,7 @@ #include "crypto/cipher.h" #include "crypto/ivgen.h" +#include "block/block.h" typedef struct QCryptoBlock QCryptoBlock; @@ -268,4 +269,30 @@ uint64_t qcrypto_block_get_sector_size(QCryptoBlock *block); */ void qcrypto_block_free(QCryptoBlock *block); + +/** + * qcrypto_block_setup_encryption: + * @block: the block encryption object + * + * @readfunc: callback for reading data from the volume header + * @writefunc: callback for writing data to the volume header + * @opaque: data to pass to @readfunc and @writefunc + * @action: tell the driver the setup action (add/erase currently) + * @options: driver specific options, that specify + * what encryption settings to manage + * @force: hint for the driver to allow unsafe operation + * @errp: error pointer + * + * Adds/Erases a new encryption key using @options + * + */ +int qcrypto_block_setup_encryption(QCryptoBlock *block, + QCryptoBlockReadFunc readfunc, + QCryptoBlockWriteFunc writefunc, + void *opaque, + enum BlkSetupEncryptionAction action, + QCryptoEncryptionSetupOptions *options, + bool force, + Error **errp); + #endif /* QCRYPTO_BLOCK_H */ From patchwork Wed Aug 14 20:22:15 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094515 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 8BD226C5 for ; Wed, 14 Aug 2019 20:31:36 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 77E9E28834 for ; Wed, 14 Aug 2019 20:31:36 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 6ACDC2883B; Wed, 14 Aug 2019 20:31:36 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id A164228834 for ; Wed, 14 Aug 2019 20:31:35 +0000 (UTC) Received: from localhost ([::1]:35772 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzvi-0005UM-G9 for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:31:34 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33369) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxznk-0004Mm-V1 for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:22 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxznj-0004Ht-1d for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:20 -0400 Received: from mx1.redhat.com ([209.132.183.28]:37396) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxznd-0004Ch-28; Wed, 14 Aug 2019 16:23:15 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id EE39E302C094; Wed, 14 Aug 2019 20:23:10 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id E70AC10016EB; Wed, 14 Aug 2019 20:23:05 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:15 +0300 Message-Id: <20190814202219.1870-10-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.46]); Wed, 14 Aug 2019 20:23:11 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 09/13] qcrypto-luks: implement the encryption key management X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Maxim Levitsky --- crypto/block-luks.c | 374 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 373 insertions(+), 1 deletion(-) diff --git a/crypto/block-luks.c b/crypto/block-luks.c index 1997e92fe1..2c33643b52 100644 --- a/crypto/block-luks.c +++ b/crypto/block-luks.c @@ -72,6 +72,8 @@ typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot; #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 }; @@ -221,6 +223,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; }; @@ -1121,6 +1126,194 @@ qcrypto_block_luks_find_key(QCryptoBlock *block, } + +/* + * Returns true if a slot i is marked as containing as active + * (contains encrypted copy of the master key) + */ + +static bool +qcrypto_block_luks_slot_active(QCryptoBlockLUKS *luks, 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 int +qcrypto_block_luks_count_active_slots(QCryptoBlockLUKS *luks) +{ + int i, 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(QCryptoBlockLUKS *luks) +{ + uint 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, + uint slot_idx, + QCryptoBlockWriteFunc writefunc, + void *opaque, + Error **errp) +{ + QCryptoBlockLUKS *luks = block->opaque; + QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx]; + uint8_t *garbagekey = NULL; + size_t splitkeylen = masterkeylen(luks) * slot->stripes; + int i; + int ret = -1; + + assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS); + assert(splitkeylen > 0); + + garbagekey = 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(garbagekey, splitkeylen, errp) < 0) { + + /* + * If we failed to get the random data, still write + * *something* to the key slot at least once + */ + + if (i > 0) { + goto cleanup; + } + } + + if (writefunc(block, slot->key_offset * QCRYPTO_BLOCK_LUKS_SECTOR_SIZE, + garbagekey, + splitkeylen, + opaque, + errp) != splitkeylen) { + goto cleanup; + } + } + + ret = 0; +cleanup: + g_free(garbagekey); + return ret; +} + + +/* + * Erase all the keys that match the given password + * Will stop when only one keyslot is remaining + * Returns 0 is some keys 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; + uint i; + int rv, ret = -1; + uint8_t *masterkey; + uint erased_count = 0; + uint active_slot_count = qcrypto_block_luks_count_active_slots(luks); + + masterkey = g_new0(uint8_t, masterkeylen(luks)); + + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + + /* refuse to erase last key if not forced */ + if (!force && active_slot_count == 1) { + break; + } + + rv = qcrypto_block_luks_load_key(block, + i, + password, + masterkey, + readfunc, + opaque, + errp); + if (rv < 0) { + goto cleanup; + } + if (rv == 0) { + continue; + } + + rv = qcrypto_block_luks_erase_key(block, i, writefunc, opaque, errp); + if (rv < 0) { + goto cleanup; + } + + erased_count++; + active_slot_count--; + } + + if (!erased_count) { + error_setg(errp, "Didn't erase a keyslot, because no keyslots " + "match the given password"); + goto cleanup; + } + + ret = 0; +cleanup: + memset(masterkey, 0, masterkeylen(luks)); + g_free(masterkey); + return ret; +} + + static int qcrypto_block_luks_open(QCryptoBlock *block, QCryptoBlockOpenOptions *options, @@ -1151,6 +1344,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) { @@ -1320,6 +1514,8 @@ qcrypto_block_luks_create(QCryptoBlock *block, optprefix ? optprefix : ""); return -1; } + luks->secret = options->u.luks.key_secret; + password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret, errp); if (!password) { return -1; @@ -1605,7 +1801,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); } @@ -1638,6 +1836,179 @@ qcrypto_block_luks_encrypt(QCryptoBlock *block, offset, buf, len, errp); } +static int +qcrypto_block_luks_setup_encryption(QCryptoBlock *block, + QCryptoBlockReadFunc readfunc, + QCryptoBlockWriteFunc writefunc, + void *opaque, + enum BlkSetupEncryptionAction action, + QCryptoEncryptionSetupOptions *options, + bool force, + Error **errp) +{ + QCryptoBlockLUKS *luks = block->opaque; + char *old_password = NULL; + char *password = NULL; + const char *old_secret = luks->secret; + uint8_t *masterkey = NULL; + int slot = -1; + int ret = -1; + int64_t iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME; + + /* Read given slot and check it */ + if (options->has_slot) { + slot = options->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 (options->has_iter_time) { + iter_time = options->iter_time; + } + + switch (action) { + + case BLK_UPDATE_ENCRYPTION: + + /* 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 (!options->has_key_secret) { + error_setg(errp, "Can't add a key slot without a password"); + goto cleanup; + } + + if (options->has_old_key_secret) { + old_secret = options->old_key_secret; + } + + /* Read the old password */ + old_password = qcrypto_secret_lookup_as_utf8(old_secret, errp); + if (!old_password) { + goto cleanup; + } + + masterkey = g_new0(uint8_t, masterkeylen(luks)); + + /* Retrieve the master key*/ + if (qcrypto_block_luks_find_key(block, old_password, masterkey, + readfunc, opaque, errp) < 0) { + error_append_hint(errp, + "Old password that was given doesn't unlock the image"); + goto cleanup; + } + + /* Read the new password*/ + password = qcrypto_secret_lookup_as_utf8(options->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; + } + break; + + case BLK_ERASE_ENCRYPTION: + + /* 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, options->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 (!options->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(options->key_secret, errp); + if (!password) { + goto cleanup; + } + + if (qcrypto_block_luks_erase_matching_keys(block, password, + readfunc, writefunc, + opaque, force, errp)) { + goto cleanup; + } + } + break; + + default: + goto cleanup; + } + + ret = 0; +cleanup: + + if (old_password) { + memset(old_password, 0, strlen(old_password)); + g_free(old_password); + } + + if (password) { + memset(password, 0, strlen(password)); + g_free(password); + } + + if (masterkey) { + memset(masterkey, 0, masterkeylen(luks)); + g_free(masterkey); + } + return ret; +} + const QCryptoBlockDriver qcrypto_block_driver_luks = { .open = qcrypto_block_luks_open, @@ -1647,4 +2018,5 @@ const QCryptoBlockDriver qcrypto_block_driver_luks = { .decrypt = qcrypto_block_luks_decrypt, .encrypt = qcrypto_block_luks_encrypt, .has_format = qcrypto_block_luks_has_format, + .setup_encryption = qcrypto_block_luks_setup_encryption, }; From patchwork Wed Aug 14 20:22:16 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094513 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id F00A56C5 for ; Wed, 14 Aug 2019 20:31:29 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DDCDD28834 for ; Wed, 14 Aug 2019 20:31:29 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id D21912883C; Wed, 14 Aug 2019 20:31:29 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 66F5128834 for ; Wed, 14 Aug 2019 20:31:29 +0000 (UTC) Received: from localhost ([::1]:35770 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzvc-0005RI-2I for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:31:28 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33397) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxznq-0004UW-0x for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:27 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxzno-0004LA-Lt for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:25 -0400 Received: from mx1.redhat.com ([209.132.183.28]:48019) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxznl-0004J7-JU; Wed, 14 Aug 2019 16:23:21 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id E234E302C076; Wed, 14 Aug 2019 20:23:20 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id 571E310001B8; Wed, 14 Aug 2019 20:23:11 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:16 +0300 Message-Id: <20190814202219.1870-11-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.46]); Wed, 14 Aug 2019 20:23:20 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 10/13] block/crypto: implement the encryption key management X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP This implements the encryption key management using the generic code in qcrypto layer This code adds another 'write_func' because the initialization write_func works directly on the underlying file, because during the creation, there is no open instance of the luks driver, but during regular use, we have it, and should use it instead. Signed-off-by: Maxim Levitsky --- block/crypto.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/block/crypto.c b/block/crypto.c index 42a3f0898b..415b6db041 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -36,6 +36,7 @@ typedef struct BlockCrypto BlockCrypto; struct BlockCrypto { QCryptoBlock *block; + bool updating_keys; }; @@ -69,6 +70,24 @@ static ssize_t block_crypto_read_func(QCryptoBlock *block, return ret; } +static ssize_t block_crypto_write_func(QCryptoBlock *block, + size_t offset, + const uint8_t *buf, + size_t buflen, + void *opaque, + Error **errp) +{ + BlockDriverState *bs = opaque; + ssize_t ret; + + ret = bdrv_pwrite(bs->file, offset, buf, buflen); + if (ret < 0) { + error_setg_errno(errp, -ret, "Could not write encryption header"); + return ret; + } + return ret; +} + struct BlockCryptoCreateData { BlockBackend *blk; @@ -622,6 +641,78 @@ block_crypto_get_specific_info_luks(BlockDriverState *bs, Error **errp) return spec_info; } + +static int +block_crypto_setup_encryption(BlockDriverState *bs, + enum BlkSetupEncryptionAction action, + QCryptoEncryptionSetupOptions *options, + bool force, + Error **errp) +{ + BlockCrypto *crypto = bs->opaque; + int ret; + + assert(crypto); + assert(crypto->block); + + crypto->updating_keys = true; + + ret = bdrv_child_refresh_perms(bs, bs->file, errp); + + if (ret) { + crypto->updating_keys = false; + return ret; + } + + ret = qcrypto_block_setup_encryption(crypto->block, + block_crypto_read_func, + block_crypto_write_func, + bs, + action, + options, + force, + errp); + + crypto->updating_keys = false; + bdrv_child_refresh_perms(bs, bs->file, errp); + + + return ret; + +} + + +static void +block_crypto_child_perms(BlockDriverState *bs, BdrvChild *c, + const BdrvChildRole *role, + BlockReopenQueue *reopen_queue, + uint64_t perm, uint64_t shared, + uint64_t *nperm, uint64_t *nshared) +{ + + BlockCrypto *crypto = bs->opaque; + + /* + * This driver doesn't modify LUKS metadata except + * when updating the encryption slots. + * Allow share-rw=on as a special case. + * + * Encryption update will set the crypto->updating_keys + * during that period and refresh permissions + * + * */ + + if (crypto->updating_keys) { + /*need exclusive write access for header update */ + perm |= BLK_PERM_WRITE; + shared &= ~BLK_PERM_WRITE; + } + + bdrv_filter_default_perms(bs, c, role, reopen_queue, + perm, shared, nperm, nshared); +} + + static const char *const block_crypto_strong_runtime_opts[] = { BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET, @@ -634,9 +725,7 @@ static BlockDriver bdrv_crypto_luks = { .bdrv_probe = block_crypto_probe_luks, .bdrv_open = block_crypto_open_luks, .bdrv_close = block_crypto_close, - /* This driver doesn't modify LUKS metadata except when creating image. - * Allow share-rw=on as a special case. */ - .bdrv_child_perm = bdrv_filter_default_perms, + .bdrv_child_perm = block_crypto_child_perms, .bdrv_co_create = block_crypto_co_create_luks, .bdrv_co_create_opts = block_crypto_co_create_opts_luks, .bdrv_co_truncate = block_crypto_co_truncate, @@ -649,6 +738,7 @@ static BlockDriver bdrv_crypto_luks = { .bdrv_getlength = block_crypto_getlength, .bdrv_get_info = block_crypto_get_info_luks, .bdrv_get_specific_info = block_crypto_get_specific_info_luks, + .bdrv_setup_encryption = block_crypto_setup_encryption, .strong_runtime_opts = block_crypto_strong_runtime_opts, }; From patchwork Wed Aug 14 20:22:17 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094517 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 1EC1A13AC for ; Wed, 14 Aug 2019 20:32:21 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 0CD3728834 for ; Wed, 14 Aug 2019 20:32:21 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 008DA2883B; Wed, 14 Aug 2019 20:32:20 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id A8FE828834 for ; Wed, 14 Aug 2019 20:32:20 +0000 (UTC) Received: from localhost ([::1]:35774 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzwR-0005nE-6t for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:32:19 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33425) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxznt-0004aA-BH for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:30 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxzns-0004Nb-8V for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:29 -0400 Received: from mx1.redhat.com ([209.132.183.28]:50722) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxznp-0004Lh-WC; Wed, 14 Aug 2019 16:23:26 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 508653175298; Wed, 14 Aug 2019 20:23:25 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id 47A7D10001B8; Wed, 14 Aug 2019 20:23:21 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:17 +0300 Message-Id: <20190814202219.1870-12-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.49]); Wed, 14 Aug 2019 20:23:25 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 11/13] block/qcow2: implement the encryption key managment X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP This is the main purpose of the patchset, to enaable us to manage luks like header, embedded in the qcow2 image, which standard cryptosetup tools don't support. Signed-off-by: Maxim Levitsky --- block/qcow2.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/block/qcow2.c b/block/qcow2.c index 039bdc2f7e..a87e58f36a 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -5086,6 +5086,31 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset, s->signaled_corruption = true; } + +static int qcow2_setup_encryption(BlockDriverState *bs, + enum BlkSetupEncryptionAction action, + QCryptoEncryptionSetupOptions *options, + bool force, + Error **errp) +{ + BDRVQcow2State *s = bs->opaque; + + if (!s->crypto) { + error_setg(errp, "Can't manage encryption - image is not encrypted"); + return -EINVAL; + } + + return qcrypto_block_setup_encryption(s->crypto, + qcow2_crypto_hdr_read_func, + qcow2_crypto_hdr_write_func, + bs, + action, + options, + force, + errp); +} + + static QemuOptsList qcow2_create_opts = { .name = "qcow2-create-opts", .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head), @@ -5232,6 +5257,8 @@ BlockDriver bdrv_qcow2 = { .bdrv_reopen_bitmaps_rw = qcow2_reopen_bitmaps_rw, .bdrv_can_store_new_dirty_bitmap = qcow2_can_store_new_dirty_bitmap, .bdrv_remove_persistent_dirty_bitmap = qcow2_remove_persistent_dirty_bitmap, + + .bdrv_setup_encryption = qcow2_setup_encryption, }; static void bdrv_qcow2_init(void) From patchwork Wed Aug 14 20:22:18 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094519 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 5A3456C5 for ; Wed, 14 Aug 2019 20:35:09 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 4706F28834 for ; Wed, 14 Aug 2019 20:35:09 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 39E5C2883B; Wed, 14 Aug 2019 20:35:09 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id A4A9328834 for ; Wed, 14 Aug 2019 20:35:08 +0000 (UTC) Received: from localhost ([::1]:35826 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzz9-0000Ef-Qj for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:35:07 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33465) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzo2-0004ox-Kd for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:40 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxzo1-0004Tk-03 for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:38 -0400 Received: from mx1.redhat.com ([209.132.183.28]:58878) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxznx-0004Rj-Pr; Wed, 14 Aug 2019 16:23:33 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 2069A30ADBA2; Wed, 14 Aug 2019 20:23:33 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id AC4EF10016EB; Wed, 14 Aug 2019 20:23:25 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:18 +0300 Message-Id: <20190814202219.1870-13-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.47]); Wed, 14 Aug 2019 20:23:33 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 12/13] qemu-img: implement key management X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Maxim Levitsky --- block/crypto.c | 16 ++++++ block/crypto.h | 3 + qemu-img-cmds.hx | 13 +++++ qemu-img.c | 140 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 172 insertions(+) diff --git a/block/crypto.c b/block/crypto.c index 415b6db041..2fcdf9dd39 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -203,6 +203,22 @@ block_crypto_create_opts_init(QDict *opts, Error **errp) return ret; } +QCryptoEncryptionSetupOptions * +block_crypto_setup_opts_init(QDict *opts, Error **errp) +{ + Visitor *v; + QCryptoEncryptionSetupOptions *ret; + + v = qobject_input_visitor_new_flat_confused(opts, errp); + if (!v) { + return NULL; + } + + visit_type_QCryptoEncryptionSetupOptions(v, NULL, &ret, errp); + + visit_free(v); + return ret; +} static int block_crypto_open_generic(QCryptoBlockFormat format, QemuOptsList *opts_spec, diff --git a/block/crypto.h b/block/crypto.h index b935695e79..ece4d64aef 100644 --- a/block/crypto.h +++ b/block/crypto.h @@ -94,4 +94,7 @@ block_crypto_create_opts_init(QDict *opts, Error **errp); QCryptoBlockOpenOptions * block_crypto_open_opts_init(QDict *opts, Error **errp); +QCryptoEncryptionSetupOptions * +block_crypto_setup_opts_init(QDict *opts, Error **errp); + #endif /* BLOCK_CRYPTO_H */ diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 1c93e6d185..7816a0adfb 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -19,6 +19,18 @@ STEXI @item amend [--object @var{objectdef}] [--image-opts] [-p] [-q] [-f @var{fmt}] [-t @var{cache}] -o @var{options} @var{filename} ETEXI +DEF("add_encryption_key", img_add_encryption_key, + "add_encryption_key [--object objectdef] [--image-opts] [--force] -U --keydef key_definition filename") +STEXI +@item add_encryption_key [--object @var{objectdef}] [--image-opts] [--force] -U --keydef @var{key_definition} @var{filename} +ETEXI + +DEF("erase_encryption_key", img_erase_encryption_key, + "erase_encryption_key [--object objectdef] [--image-opts] [--force] -U --keydef key_definition filename") +STEXI +@item erase_encryption_key [--object @var{objectdef}] [--image-opts] [--force] -U --keydef @var{key_definition} @var{filename} +ETEXI + DEF("bench", img_bench, "bench [-c count] [-d depth] [-f fmt] [--flush-interval=flush_interval] [-n] [--no-drain] [-o offset] [--pattern=pattern] [-q] [-s buffer_size] [-S step_size] [-t cache] [-w] [-U] filename") STEXI @@ -97,6 +109,7 @@ STEXI @item resize [--object @var{objectdef}] [--image-opts] [-f @var{fmt}] [--preallocation=@var{prealloc}] [-q] [--shrink] @var{filename} [+ | -]@var{size} ETEXI + STEXI @end table ETEXI diff --git a/qemu-img.c b/qemu-img.c index 79983772de..bc6cd60df1 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -47,6 +47,7 @@ #include "block/blockjob.h" #include "block/qapi.h" #include "crypto/init.h" +#include "block/crypto.h" #include "trace/control.h" #define QEMU_IMG_VERSION "qemu-img version " QEMU_FULL_VERSION \ @@ -70,6 +71,8 @@ enum { OPTION_PREALLOCATION = 265, OPTION_SHRINK = 266, OPTION_SALVAGE = 267, + OPTION_FORCE = 268, + OPTION_KEYDEF = 269, }; typedef enum OutputFormat { @@ -223,6 +226,14 @@ static QemuOptsList qemu_source_opts = { }, }; +static QemuOptsList keydef_opts = { + .name = "encryption_opts", + .head = QTAILQ_HEAD_INITIALIZER(keydef_opts.head), + .desc = { + { } + }, +}; + static int GCC_FMT_ATTR(2, 3) qprintf(bool quiet, const char *fmt, ...) { int ret = 0; @@ -4997,6 +5008,135 @@ out: return ret; } + +static QemuOptsList keydef_options_list = { + .name = "encryption", + .head = QTAILQ_HEAD_INITIALIZER(keydef_options_list.head), + .desc = { + { } + }, +}; + +static int setup_encryption(int argc, char **argv, + enum BlkSetupEncryptionAction action) +{ + static const struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"object", required_argument, 0, OPTION_OBJECT}, + {"force", no_argument, 0, OPTION_FORCE}, + {"force-share", no_argument, 0, 'U'}, + {"keydef", required_argument, 0, OPTION_KEYDEF}, + {0, 0, 0, 0} + }; + + BlockBackend *blk = NULL; + const char *filename = NULL; + bool force_share = false; + QemuOpts *keydef_opts = NULL; + bool image_opts = false; + Error *local_err = NULL; + QDict *keydef_dict = NULL; + QCryptoEncryptionSetupOptions *qcrypto_options = NULL; + bool force = false; + + int ret = 1; + int c; + + while ((c = getopt_long(argc, argv, "hU:", long_options, NULL)) != -1) { + switch (c) { + case '?': + case 'h': + help(); + break; + case 'U': + force_share = true; + break; + + case OPTION_KEYDEF: + if (keydef_opts) { + error_report("Only single --keydef argument is allowed."); + goto out; + } + + keydef_opts = qemu_opts_parse_noisily(&keydef_options_list, + optarg, false); + if (!keydef_opts) { + goto out; + } + break; + + case OPTION_OBJECT: { + QemuOpts *object_opts = qemu_opts_parse_noisily(&qemu_object_opts, + optarg, true); + if (!object_opts) { + goto out; + } + break; + } + case OPTION_IMAGE_OPTS: + image_opts = true; + break; + case OPTION_FORCE: + force = true; + break; + break; + } + } + + if (qemu_opts_foreach(&qemu_object_opts, + user_creatable_add_opts_foreach, + NULL, &error_fatal)) { + goto out; + } + + if (argc - optind > 1) { + error_report("At most one filename argument is allowed."); + goto out; + } else if (argc - optind == 1) { + filename = argv[optind]; + } else { + error_report("filename is required"); + goto out; + + } + + blk = img_open(image_opts, filename, NULL, BDRV_O_RDWR, + false, false, force_share); + if (!blk) { + goto out; + } + + keydef_dict = qemu_opts_to_qdict(keydef_opts, NULL); + qcrypto_options = block_crypto_setup_opts_init(keydef_dict, &local_err); + if (!qcrypto_options) { + error_report_err(local_err); + goto out; + } + + if (blk_setup_encryption(blk, action, qcrypto_options, force, &local_err)) { + error_report_err(local_err); + goto out; + } + ret = 0; +out: + qemu_opts_del(keydef_opts); + qobject_unref(keydef_dict); + qapi_free_QCryptoEncryptionSetupOptions(qcrypto_options); + blk_unref(blk); + return ret; +} + +static int img_add_encryption_key(int argc, char **argv) +{ + return setup_encryption(argc, argv, BLK_UPDATE_ENCRYPTION); +} + +static int img_erase_encryption_key(int argc, char **argv) +{ + return setup_encryption(argc, argv, BLK_ERASE_ENCRYPTION); +} + static const img_cmd_t img_cmds[] = { #define DEF(option, callback, arg_string) \ { option, callback }, From patchwork Wed Aug 14 20:22:19 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 11094521 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 5DD186C5 for ; Wed, 14 Aug 2019 20:36:33 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 459A928837 for ; Wed, 14 Aug 2019 20:36:33 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 34CA82883C; Wed, 14 Aug 2019 20:36:33 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id B7A2C28837 for ; Wed, 14 Aug 2019 20:36:31 +0000 (UTC) Received: from localhost ([::1]:35854 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hy00U-0001sb-LT for patchwork-qemu-devel@patchwork.kernel.org; Wed, 14 Aug 2019 16:36:30 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33508) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hxzoA-00052n-TG for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:49 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hxzo7-0004XH-Bd for qemu-devel@nongnu.org; Wed, 14 Aug 2019 16:23:46 -0400 Received: from mx1.redhat.com ([209.132.183.28]:55362) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hxzo0-0004TC-LK; Wed, 14 Aug 2019 16:23:36 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 040B3C024AF4; Wed, 14 Aug 2019 20:23:36 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.39]) by smtp.corp.redhat.com (Postfix) with ESMTP id 7975E10016EB; Wed, 14 Aug 2019 20:23:33 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Date: Wed, 14 Aug 2019 23:22:19 +0300 Message-Id: <20190814202219.1870-14-mlevitsk@redhat.com> In-Reply-To: <20190814202219.1870-1-mlevitsk@redhat.com> References: <20190814202219.1870-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.32]); Wed, 14 Aug 2019 20:23:36 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 13/13] iotests : add tests for encryption key management X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Fam Zheng , =?utf-8?q?Dan?= =?utf-8?q?iel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Stefan Hajnoczi , Maxim Levitsky Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Maxim Levitsky --- tests/qemu-iotests/257 | 197 ++++++++++++++++++++++++++++++ tests/qemu-iotests/257.out | 96 +++++++++++++++ tests/qemu-iotests/258 | 95 +++++++++++++++ tests/qemu-iotests/258.out | 30 +++++ tests/qemu-iotests/259 | 199 +++++++++++++++++++++++++++++++ tests/qemu-iotests/259.out | 5 + tests/qemu-iotests/common.filter | 5 +- tests/qemu-iotests/group | 3 + 8 files changed, 628 insertions(+), 2 deletions(-) create mode 100755 tests/qemu-iotests/257 create mode 100644 tests/qemu-iotests/257.out create mode 100755 tests/qemu-iotests/258 create mode 100644 tests/qemu-iotests/258.out create mode 100644 tests/qemu-iotests/259 create mode 100644 tests/qemu-iotests/259.out diff --git a/tests/qemu-iotests/257 b/tests/qemu-iotests/257 new file mode 100755 index 0000000000..5991e4a8c7 --- /dev/null +++ b/tests/qemu-iotests/257 @@ -0,0 +1,197 @@ +#!/usr/bin/env bash +# +# Test encryption key management with luks +# Based on 134 +# +# Copyright (C) 2019 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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, see . +# + +# creator +owner=mlevitsk@redhat.com + +seq=`basename $0` +echo "QA output created by $seq" + +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt qcow2 luks +_supported_proto file #TODO + +QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT + +# you are supposed to see the password as *******, see :-) +SECRET0="--object secret,id=sec0,data=hunter0" +SECRET1="--object secret,id=sec1,data=hunter1" +SECRET2="--object secret,id=sec2,data=hunter2" +SECRET3="--object secret,id=sec3,data=hunter3" +SECRETS="$SECRET0 $SECRET1 $SECRET2 $SECRET3" + + +if [ "$IMGFMT" = "qcow2" ] ; then + OPTPREFIX="encrypt." + EXTRA_IMG_ARGS="-o encrypt.format=luks" +fi + +IMGSPEC0="driver=$IMGFMT,file.filename=$TEST_IMG,${OPTPREFIX}key-secret=sec0" +IMGSPEC1="driver=$IMGFMT,file.filename=$TEST_IMG,${OPTPREFIX}key-secret=sec1" +IMGSPEC2="driver=$IMGFMT,file.filename=$TEST_IMG,${OPTPREFIX}key-secret=sec2" +IMGSPEC3="driver=$IMGFMT,file.filename=$TEST_IMG,${OPTPREFIX}key-secret=sec3" + +echo "== creating a test image ==" +_make_test_img $SECRET0 $EXTRA_IMG_ARGS -o "${OPTPREFIX}key-secret=sec0,${OPTPREFIX}iter-time=10" 32M + +echo +echo "== test that key 0 opens the image ==" +$QEMU_IO $SECRET0 -c "read 0 4096" --image-opts $IMGSPEC0 | _filter_qemu_io | _filter_testdir + + +echo +echo "== adding a password to slot 1 ==" +$QEMU_IMG add_encryption_key $SECRETS --image-opts $IMGSPEC0 --keydef key-secret=sec1,iter-time=10 +echo "== adding a password to slot 3 ==" +$QEMU_IMG add_encryption_key $SECRETS --image-opts $IMGSPEC1 --keydef key-secret=sec3,iter-time=100,slot=3 +echo "== adding a password to slot 2 ==" +$QEMU_IMG add_encryption_key $SECRETS --image-opts $IMGSPEC3 --keydef key-secret=sec2,iter-time=10 + +echo +echo "== all secrets should work ==" +for IMGSPEC in $IMGSPEC0 $IMGSPEC1 $IMGSPEC2 $IMGSPEC3; do + $QEMU_IO $SECRETS -c "read 0 4096" --image-opts $IMGSPEC | _filter_qemu_io | _filter_testdir +done + + +echo +echo "== erase slot 0 and try it ==" +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC1 --keydef key-secret=sec0| _filter_img_create +$QEMU_IO $SECRETS -c "read 0 4096" --image-opts $IMGSPEC0 | _filter_qemu_io | _filter_testdir + +echo +echo "== erase slot 2 and try it ==" +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC1 --keydef slot=2| _filter_img_create +$QEMU_IO $SECRETS -c "read 0 4096" --image-opts $IMGSPEC2 | _filter_qemu_io | _filter_testdir + + +# at this point slots 1 and 3 should be active + +echo +echo "== filling 4 slots with secret 2 ==" +for i in $(seq 0 3) ; do + $QEMU_IMG add_encryption_key $SECRETS --image-opts $IMGSPEC3 --keydef key-secret=sec2,iter-time=10 +done + +echo +echo "== adding secret 0 ==" + $QEMU_IMG add_encryption_key $SECRETS --image-opts $IMGSPEC3 --keydef key-secret=sec0,iter-time=10 + +echo +echo "== adding secret 3 (last slot) ==" + $QEMU_IMG add_encryption_key $SECRETS --image-opts $IMGSPEC3 --keydef key-secret=sec3,iter-time=10 + +echo +echo "== trying to add another slot (should fail) ==" +$QEMU_IMG add_encryption_key $SECRETS --image-opts $IMGSPEC2 --keydef key-secret=sec3,iter-time=10 + +echo +echo "== all secrets should work again ==" +for IMGSPEC in $IMGSPEC0 $IMGSPEC1 $IMGSPEC2 $IMGSPEC3; do + $QEMU_IO $SECRETS -c "read 0 4096" --image-opts $IMGSPEC | _filter_qemu_io | _filter_testdir +done + +echo +echo "== erase all keys of secret 2==" +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC1 --keydef key-secret=sec2 +echo "== erase all keys of secret 1==" +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC1 --keydef key-secret=sec1 +echo "== erase all keys of secret 0==" +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC0 --keydef key-secret=sec0 +echo "== erase all keys of secret 3, except a remaining key ==" + + +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC3 --keydef key-secret=sec3 + + +echo +echo "== only secret3 should work now ==" +for IMGSPEC in $IMGSPEC0 $IMGSPEC1 $IMGSPEC2 $IMGSPEC3; do + $QEMU_IO $SECRETS -c "read 0 4096" --image-opts $IMGSPEC | _filter_qemu_io | _filter_testdir +done + +echo +echo "== add secret0 ==" +$QEMU_IMG add_encryption_key $SECRETS --image-opts $IMGSPEC3 --keydef key-secret=sec0,iter-time=10 + +echo "== erase secret3 ==" +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC0 --keydef key-secret=sec3 + +echo +echo "== only secret0 should work now == " +for IMGSPEC in $IMGSPEC0 $IMGSPEC1 $IMGSPEC2 $IMGSPEC3; do + $QEMU_IO $SECRETS -c "read 0 4096" --image-opts $IMGSPEC | _filter_qemu_io | _filter_testdir +done + +echo +echo "== replace secret0 with secret1 (should fail) ==" +$QEMU_IMG add_encryption_key $SECRETS --image-opts $IMGSPEC0 --keydef key-secret=sec1,iter-time=10,slot=0 + +echo +echo "== replace secret0 with secret1 with force (should work) ==" +$QEMU_IMG add_encryption_key $SECRETS --image-opts $IMGSPEC0 --keydef key-secret=sec1,iter-time=10,slot=0 --force + + +echo +echo "== only secret1 should work now ==" +for IMGSPEC in $IMGSPEC0 $IMGSPEC1 $IMGSPEC2 $IMGSPEC3; do + $QEMU_IO $SECRETS -c "read 0 4096" --image-opts $IMGSPEC | _filter_qemu_io | _filter_testdir +done + + + +echo +echo "== erase last secret (should fail) ==" +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC1 --keydef slot=0 +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC1 --keydef key-secret=sec1 + + +echo "== erase non existing secrets (should fail) ==" +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC1 --keydef key-secret=sec5 --force +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC1 --keydef key-secret=sec0 --force +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC1 --keydef slot=1 --force + +echo +echo "== erase last secret with force by slot (should work) ==" +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC1 --keydef slot=0 --force + +echo +echo "== we have no secrets now, data is lost forever ==" +for IMGSPEC in $IMGSPEC0 $IMGSPEC1 $IMGSPEC2 $IMGSPEC3; do + $QEMU_IO $SECRETS -c "read 0 4096" --image-opts $IMGSPEC | _filter_qemu_io | _filter_testdir +done + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 + diff --git a/tests/qemu-iotests/257.out b/tests/qemu-iotests/257.out new file mode 100644 index 0000000000..77f9c9136e --- /dev/null +++ b/tests/qemu-iotests/257.out @@ -0,0 +1,96 @@ +QA output created by 257 +== creating a test image == +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 + +== test that key 0 opens the image == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== adding a password to slot 1 == +== adding a password to slot 3 == +== adding a password to slot 2 == + +== all secrets should work == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== erase slot 0 and try it == +qemu-io: can't open: Invalid password, cannot unlock any keyslot + +== erase slot 2 and try it == +qemu-io: can't open: Invalid password, cannot unlock any keyslot + +== filling 4 slots with secret 2 == + +== adding secret 0 == + +== adding secret 3 (last slot) == + +== trying to add another slot (should fail) == +qemu-img: Can't add a keyslot - all key slots are in use + +== all secrets should work again == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== erase all keys of secret 2== +== erase all keys of secret 1== +== erase all keys of secret 0== +== erase all keys of secret 3, except a remaining key == + +== only secret3 should work now == +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== add secret0 == +== erase secret3 == + +== only secret0 should work now == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot + +== replace secret0 with secret1 (should fail) == +qemu-img: Can't update an active key slot 0 + +== replace secret0 with secret1 with force (should work) == + +== only secret1 should work now == +qemu-io: can't open: Invalid password, cannot unlock any keyslot +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot + +== erase last secret (should fail) == +qemu-img: Only one slot active - can't erase +qemu-img: Only one slot active - can't erase +== erase non existing secrets (should fail) == +qemu-img: No secret with id 'sec5' +qemu-img: Didn't erase a keyslot, because no keyslots match the given password + +== erase last secret with force by slot (should work) == + +== we have no secrets now, data is lost forever == +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot +*** done diff --git a/tests/qemu-iotests/258 b/tests/qemu-iotests/258 new file mode 100755 index 0000000000..84c8ab1907 --- /dev/null +++ b/tests/qemu-iotests/258 @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# +# Test that encryption key managment erases the key material +# in addition to the header +# Based on 134 +# +# Copyright (C) 2019 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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, see . +# + +# creator +owner=mlevitsk@redhat.com + +seq=`basename $0` +echo "QA output created by $seq" + +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt luks +_supported_proto file #TODO + +QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT + +# you are supposed to see the password as *******, see :-) +SECRET0="--object secret,id=sec0,data=hunter0" +SECRET1="--object secret,id=sec1,data=hunter1" +SECRETS="$SECRET0 $SECRET1" + + +IMGSPEC0="driver=$IMGFMT,file.filename=$TEST_IMG,key-secret=sec0" +IMGSPEC1="driver=$IMGFMT,file.filename=$TEST_IMG,key-secret=sec1" + +echo "== creating a test image ==" +_make_test_img $SECRET0 -o "key-secret=sec0,iter-time=10" 32M + +echo +echo "== test that key 0 opens the image ==" +$QEMU_IO $SECRET0 -c "read 0 4096" --image-opts $IMGSPEC0 | _filter_qemu_io | _filter_testdir + +echo +echo "== adding a password to slot 1 ==" +$QEMU_IMG add_encryption_key $SECRETS --image-opts $IMGSPEC0 --keydef key-secret=sec1,slot=1,iter-time=10 + +echo +echo "== 'backup' the image header ==" +dd if=$TEST_IMG_FILE of=${TEST_IMG_FILE}.bk bs=4K skip=0 count=1 + +echo +echo "== erase slot 0 ==" +$QEMU_IMG erase_encryption_key $SECRETS --image-opts $IMGSPEC1 --keydef slot=0 | _filter_img_create + +echo +echo "== test that key 0 doesn't open the image ==" +$QEMU_IO $SECRET0 -c "read 0 4096" --image-opts $IMGSPEC0 | _filter_qemu_io | _filter_testdir + +echo +echo "== 'restore' the image header ==" +dd if=${TEST_IMG_FILE}.bk of=${TEST_IMG_FILE} bs=4K skip=0 count=1 conv=notrunc + +echo +echo "== test that key 0 still doesn't open the image (key material is erased) ==" +$QEMU_IO $SECRETS -c "read 0 4096" --image-opts $IMGSPEC0 | _filter_qemu_io | _filter_testdir + +echo +echo "== test that key 1 still works ==" +$QEMU_IO $SECRETS -c "read 0 4096" --image-opts $IMGSPEC1 | _filter_qemu_io | _filter_testdir + +echo "*** done" +rm -f $seq.full +status=0 + + +exit 0 diff --git a/tests/qemu-iotests/258.out b/tests/qemu-iotests/258.out new file mode 100644 index 0000000000..ea448a0ac1 --- /dev/null +++ b/tests/qemu-iotests/258.out @@ -0,0 +1,30 @@ +QA output created by 258 +== creating a test image == +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 + +== test that key 0 opens the image == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== adding a password to slot 1 == + +== 'backup' the image header == +1+0 records in +1+0 records out + +== erase slot 0 == + +== test that key 0 doesn't open the image == +qemu-io: can't open: Invalid password, cannot unlock any keyslot + +== 'restore' the image header == +1+0 records in +1+0 records out + +== test that key 0 still doesn't open the image (key material is erased) == +qemu-io: can't open: Invalid password, cannot unlock any keyslot + +== test that key 1 still works == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +*** done diff --git a/tests/qemu-iotests/259 b/tests/qemu-iotests/259 new file mode 100644 index 0000000000..360e723a48 --- /dev/null +++ b/tests/qemu-iotests/259 @@ -0,0 +1,199 @@ +#!/usr/bin/env python +# +# Test case QMP's encrypted key management +# +# Copyright (C) 2019 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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, see . +# + +import iotests +import os +import time + +test_img = os.path.join(iotests.test_dir, 'test.img') + +class Secret: + def __init__(self, index): + self._id = "keysec" + str(index) + # you are not supposed to see the password... + self._secret = "hunter" + str(index) + + def id(self): + return self._id + + def secret(self): + return self._secret + + def to_cmdline_object(self): + return [ "secret,id=" + self._id + ",data=" + self._secret] + + def to_qmp_object(self): + return { "qom_type" : "secret", "id": self.id(), + "props": { "data": self.secret() } } + +class EncryptionSetupTestCase(iotests.QMPTestCase): + + # test case startup + def setUp(self): + # start the VM + self.vm = iotests.VM() + self.vm.launch() + + # create the secrets and load 'em into the VM + self.secrets = [ Secret(i) for i in range(0, 4) ] + for secret in self.secrets: + result = self.vm.qmp("object-add", **secret.to_qmp_object()) + self.assert_qmp(result, 'return', {}) + + # test case shutdown + def tearDown(self): + # stop the VM + self.vm.shutdown() + + # create the encrypted block device + def createImg(self, file, secret): + + if iotests.imgfmt == "qcow2": + prefix="encrypt." + extra_options = [ '-o', "encrypt.format=luks" ] + else: + prefix="" + extra_options = [] + + return iotests.qemu_img('create', + '--object', *secret.to_cmdline_object(), + '-f', iotests.imgfmt, + '-o', prefix+'key-secret=' + secret.id(), + '-o', prefix+'iter-time=10', + *extra_options, file, '1M') + + # open an encrypted block device + def openLUKS(self, id, file, secret): + if iotests.imgfmt == "qcow2": + encrypt_options = { + 'encrypt': { + 'format':'luks', + 'key-secret' : secret.id()} + } + else: + encrypt_options = { + 'key-secret' : secret.id() + } + + return self.vm.qmp('blockdev-add', ** + { + 'driver': iotests.imgfmt, + 'node-name': id, + + **encrypt_options, + + 'file': { + 'driver': 'file', + 'filename': test_img, + } + } + ) + + # close the encrypted block device + def closeLUKS(self, id): + return self.vm.qmp('blockdev-del', **{ 'node-name': id }) + + # add a key to an encrypted block device + def addKey(self, id, secret, old_secret = None, slot = None, force = False): + args = { + 'node-name': id, + 'options' : { 'key-secret' : secret.id(), 'iter-time' : 10} + } + + if slot != None: + args['options']['slot'] = slot + if old_secret != None: + args['options']['old-key-secret'] = old_secret.id() + if force == True: + args['force'] = True + + return self.vm.qmp('x-blockdev-update-encryption', **args) + + # erase a key from an encrypted block device + def eraseKey(self, id, secret = None, slot = None, force = False): + args = { + 'node-name': id, + 'options' : { } + } + + if secret != None: + args['options']['key-secret'] = secret.id() + if slot != None: + args['options']['slot'] = slot + if force == True: + args['force'] = True + + return self.vm.qmp('x-blockdev-erase-encryption', **args) + + def check(self, result): + self.assert_qmp(result, 'return', {}) + + # create image, and change its key + def testChangeKey(self): + + # create the image with secret0 and open it + result = self.createImg(test_img, self.secrets[0]); + self.check(self.openLUKS("testdev", test_img, self.secrets[0])) + + # add key to slot 1 + self.check(self.addKey("testdev", secret=self.secrets[1])) + + + # erase key from slot 0 + self.check(self.eraseKey("testdev", secret=self.secrets[0])) + + #reopen the image with secret1 + self.check(self.closeLUKS("testdev")) + self.check(self.openLUKS("testdev", test_img, self.secrets[1])) + + # close and erase the image for good + self.check(self.closeLUKS("testdev")) + os.remove(test_img) + + # test that if we erase the old password, + # we can still change the encryption keys using 'old-secret' + def testOldPassword(self): + + # create the image with secret0 and open it + result = self.createImg(test_img, self.secrets[0]); + self.check(self.openLUKS("testdev", test_img, self.secrets[0])) + + # add key to slot 1 + self.check(self.addKey("testdev", secret=self.secrets[1])) + + # erase key from slot 0 + self.check(self.eraseKey("testdev", secret=self.secrets[0])) + + # this will fail as the old password is no longer valid + result = self.addKey("testdev", secret=self.secrets[2]) + self.assert_qmp(result, 'error/desc', "Invalid password, cannot unlock any keyslot") + + # this will work + self.check(self.addKey("testdev", secret=self.secrets[2], old_secret=self.secrets[1])) + + + # close and erase the image for good + self.check(self.closeLUKS("testdev")) + os.remove(test_img) + + +if __name__ == '__main__': + # Encrypted formats support + iotests.main(iotests.main(supported_fmts=['qcow2','luks'])) diff --git a/tests/qemu-iotests/259.out b/tests/qemu-iotests/259.out new file mode 100644 index 0000000000..fbc63e62f8 --- /dev/null +++ b/tests/qemu-iotests/259.out @@ -0,0 +1,5 @@ +.. +---------------------------------------------------------------------- +Ran 2 tests + +OK diff --git a/tests/qemu-iotests/common.filter b/tests/qemu-iotests/common.filter index 35fddc746f..c0d68e8a0c 100644 --- a/tests/qemu-iotests/common.filter +++ b/tests/qemu-iotests/common.filter @@ -138,8 +138,9 @@ _filter_img_create() -e "s# block_state_zero=\\(on\\|off\\)##g" \ -e "s# log_size=[0-9]\\+##g" \ -e "s# refcount_bits=[0-9]\\+##g" \ - -e "s# key-secret=[a-zA-Z0-9]\\+##g" \ - -e "s# iter-time=[0-9]\\+##g" \ + -e "s# \\(encrypt\\.\\)\\?key-secret=[a-zA-Z0-9]\\+##g" \ + -e "s# encrypt\\.format=[a-zA-Z0-9]\\+##g" \ + -e "s# \\(encrypt\\.\\)\\?iter-time=[0-9]\\+##g" \ -e "s# force_size=\\(on\\|off\\)##g" } diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group index f13e5f2e23..3db7f0a863 100644 --- a/tests/qemu-iotests/group +++ b/tests/qemu-iotests/group @@ -271,3 +271,6 @@ 254 rw backing quick 255 rw quick 256 rw quick +257 rw auto +258 rw auto quick +259 rw auto