qcrypto/luks: implement encryption key management

Next few patches will expose that functionality to the user.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Message-Id: <20200608094030.670121-3-mlevitsk@redhat.com>
Signed-off-by: Max Reitz <mreitz@redhat.com>
This commit is contained in:
Maxim Levitsky 2020-06-25 14:55:37 +02:00 committed by Max Reitz
parent 43cbd06df2
commit 557d2bdcca
2 changed files with 469 additions and 6 deletions

View File

@ -32,6 +32,7 @@
#include "qemu/uuid.h"
#include "qemu/coroutine.h"
#include "qemu/bitmap.h"
/*
* Reference for the LUKS format implemented here is
@ -70,6 +71,9 @@ typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot;
#define QCRYPTO_BLOCK_LUKS_SECTOR_SIZE 512LL
#define QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS 2000
#define QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS 40
static const char qcrypto_block_luks_magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN] = {
'L', 'U', 'K', 'S', 0xBA, 0xBE
};
@ -219,6 +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;
};
@ -720,7 +727,7 @@ qcrypto_block_luks_store_key(QCryptoBlock *block,
Error **errp)
{
QCryptoBlockLUKS *luks = block->opaque;
QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx];
QCryptoBlockLUKSKeySlot *slot;
g_autofree uint8_t *splitkey = NULL;
size_t splitkeylen;
g_autofree uint8_t *slotkey = NULL;
@ -730,6 +737,8 @@ qcrypto_block_luks_store_key(QCryptoBlock *block,
uint64_t iters;
int ret = -1;
assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
slot = &luks->header.key_slots[slot_idx];
if (qcrypto_random_bytes(slot->salt,
QCRYPTO_BLOCK_LUKS_SALT_LEN,
errp) < 0) {
@ -890,7 +899,7 @@ qcrypto_block_luks_load_key(QCryptoBlock *block,
Error **errp)
{
QCryptoBlockLUKS *luks = block->opaque;
const QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx];
const QCryptoBlockLUKSKeySlot *slot;
g_autofree uint8_t *splitkey = NULL;
size_t splitkeylen;
g_autofree uint8_t *possiblekey = NULL;
@ -900,6 +909,8 @@ qcrypto_block_luks_load_key(QCryptoBlock *block,
g_autoptr(QCryptoIVGen) ivgen = NULL;
size_t niv;
assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
slot = &luks->header.key_slots[slot_idx];
if (slot->active != QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED) {
return 0;
}
@ -1069,6 +1080,126 @@ qcrypto_block_luks_find_key(QCryptoBlock *block,
return -1;
}
/*
* Returns true if a slot i is marked as active
* (contains encrypted copy of the master key)
*/
static bool
qcrypto_block_luks_slot_active(const QCryptoBlockLUKS *luks,
unsigned int slot_idx)
{
uint32_t val;
assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
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
* (slots that contain encrypted copy of the master key)
*/
static unsigned int
qcrypto_block_luks_count_active_slots(const QCryptoBlockLUKS *luks)
{
size_t i = 0;
unsigned int ret = 0;
for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
if (qcrypto_block_luks_slot_active(luks, i)) {
ret++;
}
}
return ret;
}
/*
* Finds first key slot which is not active
* Returns the key slot index, or -1 if it doesn't exist
*/
static int
qcrypto_block_luks_find_free_keyslot(const QCryptoBlockLUKS *luks)
{
size_t i;
for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
if (!qcrypto_block_luks_slot_active(luks, i)) {
return i;
}
}
return -1;
}
/*
* Erases an keyslot given its index
* Returns:
* 0 if the keyslot was erased successfully
* -1 if a error occurred while erasing the keyslot
*
*/
static int
qcrypto_block_luks_erase_key(QCryptoBlock *block,
unsigned int slot_idx,
QCryptoBlockWriteFunc writefunc,
void *opaque,
Error **errp)
{
QCryptoBlockLUKS *luks = block->opaque;
QCryptoBlockLUKSKeySlot *slot;
g_autofree uint8_t *garbagesplitkey = NULL;
size_t splitkeylen;
size_t i;
Error *local_err = NULL;
int ret;
assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
slot = &luks->header.key_slots[slot_idx];
splitkeylen = luks->header.master_key_len * slot->stripes;
assert(splitkeylen > 0);
garbagesplitkey = g_new0(uint8_t, 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;
ret = qcrypto_block_luks_store_header(block, writefunc,
opaque, &local_err);
if (ret < 0) {
error_propagate(errp, local_err);
}
/*
* Now try to erase the key material, even if the header
* update failed
*/
for (i = 0; i < QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS; i++) {
if (qcrypto_random_bytes(garbagesplitkey,
splitkeylen, &local_err) < 0) {
/*
* If we failed to get the random data, still write
* at least zeros to the key slot at least once
*/
error_propagate(errp, local_err);
if (i > 0) {
return -1;
}
}
if (writefunc(block,
slot->key_offset_sector * QCRYPTO_BLOCK_LUKS_SECTOR_SIZE,
garbagesplitkey,
splitkeylen,
opaque,
&local_err) != splitkeylen) {
error_propagate(errp, local_err);
return -1;
}
}
return ret;
}
static int
qcrypto_block_luks_open(QCryptoBlock *block,
@ -1099,6 +1230,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
luks = g_new0(QCryptoBlockLUKS, 1);
block->opaque = luks;
luks->secret = g_strdup(options->u.luks.key_secret);
if (qcrypto_block_luks_load_header(block, readfunc, opaque, errp) < 0) {
goto fail;
@ -1164,6 +1296,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
fail:
qcrypto_block_free_cipher(block);
qcrypto_ivgen_free(block->ivgen);
g_free(luks->secret);
g_free(luks);
return -1;
}
@ -1204,7 +1337,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_MS;
}
if (!luks_opts.has_cipher_alg) {
luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256;
@ -1244,6 +1377,8 @@ qcrypto_block_luks_create(QCryptoBlock *block,
optprefix ? optprefix : "");
goto error;
}
luks->secret = g_strdup(options->u.luks.key_secret);
password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret, errp);
if (!password) {
goto error;
@ -1471,10 +1606,278 @@ qcrypto_block_luks_create(QCryptoBlock *block,
qcrypto_block_free_cipher(block);
qcrypto_ivgen_free(block->ivgen);
g_free(luks->secret);
g_free(luks);
return -1;
}
static int
qcrypto_block_luks_amend_add_keyslot(QCryptoBlock *block,
QCryptoBlockReadFunc readfunc,
QCryptoBlockWriteFunc writefunc,
void *opaque,
QCryptoBlockAmendOptionsLUKS *opts_luks,
bool force,
Error **errp)
{
QCryptoBlockLUKS *luks = block->opaque;
uint64_t iter_time = opts_luks->has_iter_time ?
opts_luks->iter_time :
QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
int keyslot;
g_autofree char *old_password = NULL;
g_autofree char *new_password = NULL;
g_autofree uint8_t *master_key = NULL;
char *secret = opts_luks->has_secret ? opts_luks->secret : luks->secret;
if (!opts_luks->has_new_secret) {
error_setg(errp, "'new-secret' is required to activate a keyslot");
return -1;
}
if (opts_luks->has_old_secret) {
error_setg(errp,
"'old-secret' must not be given when activating keyslots");
return -1;
}
if (opts_luks->has_keyslot) {
keyslot = opts_luks->keyslot;
if (keyslot < 0 || keyslot >= QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS) {
error_setg(errp,
"Invalid keyslot %u specified, must be between 0 and %u",
keyslot, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS - 1);
return -1;
}
} else {
keyslot = qcrypto_block_luks_find_free_keyslot(luks);
if (keyslot == -1) {
error_setg(errp,
"Can't add a keyslot - all keyslots are in use");
return -1;
}
}
if (!force && qcrypto_block_luks_slot_active(luks, keyslot)) {
error_setg(errp,
"Refusing to overwrite active keyslot %i - "
"please erase it first",
keyslot);
return -1;
}
/* Locate the password that will be used to retrieve the master key */
old_password = qcrypto_secret_lookup_as_utf8(secret, errp);
if (!old_password) {
return -1;
}
/* Retrieve the master key */
master_key = g_new0(uint8_t, luks->header.master_key_len);
if (qcrypto_block_luks_find_key(block, old_password, master_key,
readfunc, opaque, errp) < 0) {
error_append_hint(errp, "Failed to retrieve the master key");
return -1;
}
/* Locate the new password*/
new_password = qcrypto_secret_lookup_as_utf8(opts_luks->new_secret, errp);
if (!new_password) {
return -1;
}
/* Now set the new keyslots */
if (qcrypto_block_luks_store_key(block, keyslot, new_password, master_key,
iter_time, writefunc, opaque, errp)) {
error_append_hint(errp, "Failed to write to keyslot %i", keyslot);
return -1;
}
return 0;
}
static int
qcrypto_block_luks_amend_erase_keyslots(QCryptoBlock *block,
QCryptoBlockReadFunc readfunc,
QCryptoBlockWriteFunc writefunc,
void *opaque,
QCryptoBlockAmendOptionsLUKS *opts_luks,
bool force,
Error **errp)
{
QCryptoBlockLUKS *luks = block->opaque;
g_autofree uint8_t *tmpkey = NULL;
g_autofree char *old_password = NULL;
if (opts_luks->has_new_secret) {
error_setg(errp,
"'new-secret' must not be given when erasing keyslots");
return -1;
}
if (opts_luks->has_iter_time) {
error_setg(errp,
"'iter-time' must not be given when erasing keyslots");
return -1;
}
if (opts_luks->has_secret) {
error_setg(errp,
"'secret' must not be given when erasing keyslots");
return -1;
}
/* Load the old password if given */
if (opts_luks->has_old_secret) {
old_password = qcrypto_secret_lookup_as_utf8(opts_luks->old_secret,
errp);
if (!old_password) {
return -1;
}
/*
* Allocate a temporary key buffer that we will need when
* checking if slot matches the given old password
*/
tmpkey = g_new0(uint8_t, luks->header.master_key_len);
}
/* Erase an explicitly given keyslot */
if (opts_luks->has_keyslot) {
int keyslot = opts_luks->keyslot;
if (keyslot < 0 || keyslot >= QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS) {
error_setg(errp,
"Invalid keyslot %i specified, must be between 0 and %i",
keyslot, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS - 1);
return -1;
}
if (opts_luks->has_old_secret) {
int rv = qcrypto_block_luks_load_key(block,
keyslot,
old_password,
tmpkey,
readfunc,
opaque,
errp);
if (rv == -1) {
return -1;
} else if (rv == 0) {
error_setg(errp,
"Given keyslot %i doesn't contain the given "
"old password for erase operation",
keyslot);
return -1;
}
}
if (!force && !qcrypto_block_luks_slot_active(luks, keyslot)) {
error_setg(errp,
"Given keyslot %i is already erased (inactive) ",
keyslot);
return -1;
}
if (!force && qcrypto_block_luks_count_active_slots(luks) == 1) {
error_setg(errp,
"Attempt to erase the only active keyslot %i "
"which will erase all the data in the image "
"irreversibly - refusing operation",
keyslot);
return -1;
}
if (qcrypto_block_luks_erase_key(block, keyslot,
writefunc, opaque, errp)) {
error_append_hint(errp, "Failed to erase keyslot %i", keyslot);
return -1;
}
/* Erase all keyslots that match the given old password */
} else if (opts_luks->has_old_secret) {
unsigned long slots_to_erase_bitmap = 0;
size_t i;
int slot_count;
assert(QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS <=
sizeof(slots_to_erase_bitmap) * 8);
for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
int rv = qcrypto_block_luks_load_key(block,
i,
old_password,
tmpkey,
readfunc,
opaque,
errp);
if (rv == -1) {
return -1;
} else if (rv == 1) {
bitmap_set(&slots_to_erase_bitmap, i, 1);
}
}
slot_count = bitmap_count_one(&slots_to_erase_bitmap,
QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
if (slot_count == 0) {
error_setg(errp,
"No keyslots match given (old) password for erase operation");
return -1;
}
if (!force &&
slot_count == qcrypto_block_luks_count_active_slots(luks)) {
error_setg(errp,
"All the active keyslots match the (old) password that "
"was given and erasing them will erase all the data in "
"the image irreversibly - refusing operation");
return -1;
}
/* Now apply the update */
for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
if (!test_bit(i, &slots_to_erase_bitmap)) {
continue;
}
if (qcrypto_block_luks_erase_key(block, i, writefunc,
opaque, errp)) {
error_append_hint(errp, "Failed to erase keyslot %zu", i);
return -1;
}
}
} else {
error_setg(errp,
"To erase keyslot(s), either explicit keyslot index "
"or the password currently contained in them must be given");
return -1;
}
return 0;
}
static int
qcrypto_block_luks_amend_options(QCryptoBlock *block,
QCryptoBlockReadFunc readfunc,
QCryptoBlockWriteFunc writefunc,
void *opaque,
QCryptoBlockAmendOptions *options,
bool force,
Error **errp)
{
QCryptoBlockAmendOptionsLUKS *opts_luks = &options->u.luks;
switch (opts_luks->state) {
case Q_CRYPTO_BLOCKLUKS_KEYSLOT_STATE_ACTIVE:
return qcrypto_block_luks_amend_add_keyslot(block, readfunc,
writefunc, opaque,
opts_luks, force, errp);
case Q_CRYPTO_BLOCKLUKS_KEYSLOT_STATE_INACTIVE:
return qcrypto_block_luks_amend_erase_keyslots(block, readfunc,
writefunc, opaque,
opts_luks, force, errp);
default:
g_assert_not_reached();
}
}
static int qcrypto_block_luks_get_info(QCryptoBlock *block,
QCryptoBlockInfo *info,
@ -1523,7 +1926,11 @@ 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;
if (luks) {
g_free(luks->secret);
g_free(luks);
}
}
@ -1560,6 +1967,7 @@ qcrypto_block_luks_encrypt(QCryptoBlock *block,
const QCryptoBlockDriver qcrypto_block_driver_luks = {
.open = qcrypto_block_luks_open,
.create = qcrypto_block_luks_create,
.amend = qcrypto_block_luks_amend_options,
.get_info = qcrypto_block_luks_get_info,
.cleanup = qcrypto_block_luks_cleanup,
.decrypt = qcrypto_block_luks_decrypt,

View File

@ -297,7 +297,6 @@
'uuid': 'str',
'slots': [ 'QCryptoBlockInfoLUKSSlot' ] }}
##
# @QCryptoBlockInfo:
#
@ -310,8 +309,64 @@
'discriminator': 'format',
'data': { 'luks': 'QCryptoBlockInfoLUKS' } }
##
# @QCryptoBlockLUKSKeyslotState:
#
# Defines state of keyslots that are affected by the update
#
# @active: The slots contain the given password and marked as active
# @inactive: The slots are erased (contain garbage) and marked as inactive
#
# Since: 5.1
##
{ 'enum': 'QCryptoBlockLUKSKeyslotState',
'data': [ 'active', 'inactive' ] }
##
# @QCryptoBlockAmendOptionsLUKS:
#
# This struct defines the update parameters that activate/de-activate set
# of keyslots
#
# @state: the desired state of the keyslots
#
# @new-secret: The ID of a QCryptoSecret object providing the password to be
# written into added active keyslots
#
# @old-secret: Optional (for deactivation only)
# If given will deactive all keyslots that
# match password located in QCryptoSecret with this ID
#
# @iter-time: Optional (for activation only)
# Number of milliseconds to spend in
# PBKDF passphrase processing for the newly activated keyslot.
# Currently defaults to 2000.
#
# @keyslot: Optional. ID of the keyslot to activate/deactivate.
# For keyslot activation, keyslot should not be active already
# (this is unsafe to update an active keyslot),
# but possible if 'force' parameter is given.
# If keyslot is not given, first free keyslot will be written.
#
# For keyslot deactivation, this parameter specifies the exact
# keyslot to deactivate
#
# @secret: Optional. The ID of a QCryptoSecret object providing the
# password to use to retrive current master key.
# Defaults to the same secret that was used to open the image
#
#
# Since 5.1
##
{ 'struct': 'QCryptoBlockAmendOptionsLUKS',
'data': { 'state': 'QCryptoBlockLUKSKeyslotState',
'*new-secret': 'str',
'*old-secret': 'str',
'*keyslot': 'int',
'*iter-time': 'int',
'*secret': 'str' } }
##
# @QCryptoBlockAmendOptions:
#
@ -324,4 +379,4 @@
'base': 'QCryptoBlockOptionsBase',
'discriminator': 'format',
'data': {
} }
'luks': 'QCryptoBlockAmendOptionsLUKS' } }