mirror of
https://gitlab.com/cryptsetup/cryptsetup.git
synced 2025-12-05 16:00:05 +01:00
Fix CVE-2021-4122 - LUKS2 reencryption crash recovery attack
Fix possible attacks against data confidentiality through LUKS2 online reencryption extension crash recovery. An attacker can modify on-disk metadata to simulate decryption in progress with crashed (unfinished) reencryption step and persistently decrypt part of the LUKS device. This attack requires repeated physical access to the LUKS device but no knowledge of user passphrases. The decryption step is performed after a valid user activates the device with a correct passphrase and modified metadata. There are no visible warnings for the user that such recovery happened (except using the luksDump command). The attack can also be reversed afterward (simulating crashed encryption from a plaintext) with possible modification of revealed plaintext. The problem was caused by reusing a mechanism designed for actual reencryption operation without reassessing the security impact for new encryption and decryption operations. While the reencryption requires calculating and verifying both key digests, no digest was needed to initiate decryption recovery if the destination is plaintext (no encryption key). Also, some metadata (like encryption cipher) is not protected, and an attacker could change it. Note that LUKS2 protects visible metadata only when a random change occurs. It does not protect against intentional modification but such modification must not cause a violation of data confidentiality. The fix introduces additional digest protection of reencryption metadata. The digest is calculated from known keys and critical reencryption metadata. Now an attacker cannot create correct metadata digest without knowledge of a passphrase for used keyslots. For more details, see LUKS2 On-Disk Format Specification version 1.1.0.
This commit is contained in:
committed by
Milan Broz
parent
5a17d677c4
commit
0113ac2d88
@@ -100,6 +100,7 @@ libcryptsetup_la_SOURCES = \
|
|||||||
lib/luks2/luks2_keyslot_luks2.c \
|
lib/luks2/luks2_keyslot_luks2.c \
|
||||||
lib/luks2/luks2_keyslot_reenc.c \
|
lib/luks2/luks2_keyslot_reenc.c \
|
||||||
lib/luks2/luks2_reencrypt.c \
|
lib/luks2/luks2_reencrypt.c \
|
||||||
|
lib/luks2/luks2_reencrypt_digest.c \
|
||||||
lib/luks2/luks2_segment.c \
|
lib/luks2/luks2_segment.c \
|
||||||
lib/luks2/luks2_token_keyring.c \
|
lib/luks2/luks2_token_keyring.c \
|
||||||
lib/luks2/luks2_token.c \
|
lib/luks2/luks2_token.c \
|
||||||
|
|||||||
@@ -453,4 +453,8 @@ int LUKS2_reencrypt_check_device_size(struct crypt_device *cd,
|
|||||||
bool activation,
|
bool activation,
|
||||||
bool dynamic);
|
bool dynamic);
|
||||||
|
|
||||||
|
int LUKS2_reencrypt_digest_verify(struct crypt_device *cd,
|
||||||
|
struct luks2_hdr *hdr,
|
||||||
|
struct volume_key *vks);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -239,9 +239,15 @@ int LUKS2_keyslot_reencrypt_create(struct crypt_device *cd,
|
|||||||
int keyslot,
|
int keyslot,
|
||||||
const struct crypt_params_reencrypt *params);
|
const struct crypt_params_reencrypt *params);
|
||||||
|
|
||||||
|
int LUKS2_keyslot_reencrypt_digest_create(struct crypt_device *cd,
|
||||||
|
struct luks2_hdr *hdr,
|
||||||
|
struct volume_key *vks);
|
||||||
|
|
||||||
int LUKS2_keyslot_dump(struct crypt_device *cd,
|
int LUKS2_keyslot_dump(struct crypt_device *cd,
|
||||||
int keyslot);
|
int keyslot);
|
||||||
|
|
||||||
|
int LUKS2_keyslot_jobj_area(json_object *jobj_keyslot, uint64_t *offset, uint64_t *length);
|
||||||
|
|
||||||
/* JSON helpers */
|
/* JSON helpers */
|
||||||
uint64_t json_segment_get_offset(json_object *jobj_segment, unsigned blockwise);
|
uint64_t json_segment_get_offset(json_object *jobj_segment, unsigned blockwise);
|
||||||
const char *json_segment_type(json_object *jobj_segment);
|
const char *json_segment_type(json_object *jobj_segment);
|
||||||
|
|||||||
@@ -1389,24 +1389,63 @@ int LUKS2_config_set_flags(struct crypt_device *cd, struct luks2_hdr *hdr, uint3
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* LUKS2 library requirements */
|
/* LUKS2 library requirements */
|
||||||
static const struct {
|
struct requirement_flag {
|
||||||
uint32_t flag;
|
uint32_t flag;
|
||||||
|
uint32_t version;
|
||||||
const char *description;
|
const char *description;
|
||||||
} requirements_flags[] = {
|
|
||||||
{ CRYPT_REQUIREMENT_OFFLINE_REENCRYPT, "offline-reencrypt" },
|
|
||||||
{ CRYPT_REQUIREMENT_ONLINE_REENCRYPT, "online-reencrypt" },
|
|
||||||
{ 0, NULL }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static uint32_t get_requirement_by_name(const char *requirement)
|
static const struct requirement_flag unknown_requirement_flag = { CRYPT_REQUIREMENT_UNKNOWN, 0, NULL };
|
||||||
|
|
||||||
|
static const struct requirement_flag requirements_flags[] = {
|
||||||
|
{ CRYPT_REQUIREMENT_OFFLINE_REENCRYPT,1, "offline-reencrypt" },
|
||||||
|
{ CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 2, "online-reencrypt-v2" },
|
||||||
|
{ CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 1, "online-reencrypt" },
|
||||||
|
{ 0, 0, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct requirement_flag *get_requirement_by_name(const char *requirement)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; requirements_flags[i].description; i++)
|
for (i = 0; requirements_flags[i].description; i++)
|
||||||
if (!strcmp(requirement, requirements_flags[i].description))
|
if (!strcmp(requirement, requirements_flags[i].description))
|
||||||
return requirements_flags[i].flag;
|
return requirements_flags + i;
|
||||||
|
|
||||||
return CRYPT_REQUIREMENT_UNKNOWN;
|
return &unknown_requirement_flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct requirement_flag *stored_requirement_name_by_id(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t req_id)
|
||||||
|
{
|
||||||
|
json_object *jobj_config, *jobj_requirements, *jobj_mandatory, *jobj;
|
||||||
|
int i, len;
|
||||||
|
const struct requirement_flag *req;
|
||||||
|
|
||||||
|
assert(hdr);
|
||||||
|
if (!hdr)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!json_object_object_get_ex(hdr->jobj, "config", &jobj_config))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!json_object_object_get_ex(jobj_config, "requirements", &jobj_requirements))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!json_object_object_get_ex(jobj_requirements, "mandatory", &jobj_mandatory))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
len = (int) json_object_array_length(jobj_mandatory);
|
||||||
|
if (len <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
jobj = json_object_array_get_idx(jobj_mandatory, i);
|
||||||
|
req = get_requirement_by_name(json_object_get_string(jobj));
|
||||||
|
if (req->flag == req_id)
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1416,7 +1455,7 @@ int LUKS2_config_get_requirements(struct crypt_device *cd, struct luks2_hdr *hdr
|
|||||||
{
|
{
|
||||||
json_object *jobj_config, *jobj_requirements, *jobj_mandatory, *jobj;
|
json_object *jobj_config, *jobj_requirements, *jobj_mandatory, *jobj;
|
||||||
int i, len;
|
int i, len;
|
||||||
uint32_t req;
|
const struct requirement_flag *req;
|
||||||
|
|
||||||
assert(hdr);
|
assert(hdr);
|
||||||
if (!hdr || !reqs)
|
if (!hdr || !reqs)
|
||||||
@@ -1443,8 +1482,8 @@ int LUKS2_config_get_requirements(struct crypt_device *cd, struct luks2_hdr *hdr
|
|||||||
jobj = json_object_array_get_idx(jobj_mandatory, i);
|
jobj = json_object_array_get_idx(jobj_mandatory, i);
|
||||||
req = get_requirement_by_name(json_object_get_string(jobj));
|
req = get_requirement_by_name(json_object_get_string(jobj));
|
||||||
log_dbg(cd, "%s - %sknown", json_object_get_string(jobj),
|
log_dbg(cd, "%s - %sknown", json_object_get_string(jobj),
|
||||||
reqs_unknown(req) ? "un" : "");
|
reqs_unknown(req->flag) ? "un" : "");
|
||||||
*reqs |= req;
|
*reqs |= req->flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1454,6 +1493,8 @@ int LUKS2_config_set_requirements(struct crypt_device *cd, struct luks2_hdr *hdr
|
|||||||
{
|
{
|
||||||
json_object *jobj_config, *jobj_requirements, *jobj_mandatory, *jobj;
|
json_object *jobj_config, *jobj_requirements, *jobj_mandatory, *jobj;
|
||||||
int i, r = -EINVAL;
|
int i, r = -EINVAL;
|
||||||
|
const struct requirement_flag *req;
|
||||||
|
uint32_t req_id;
|
||||||
|
|
||||||
if (!hdr)
|
if (!hdr)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
@@ -1463,8 +1504,14 @@ int LUKS2_config_set_requirements(struct crypt_device *cd, struct luks2_hdr *hdr
|
|||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
for (i = 0; requirements_flags[i].description; i++) {
|
for (i = 0; requirements_flags[i].description; i++) {
|
||||||
if (reqs & requirements_flags[i].flag) {
|
req_id = reqs & requirements_flags[i].flag;
|
||||||
jobj = json_object_new_string(requirements_flags[i].description);
|
if (req_id) {
|
||||||
|
/* retain already stored version of requirement flag */
|
||||||
|
req = stored_requirement_name_by_id(cd, hdr, req_id);
|
||||||
|
if (req)
|
||||||
|
jobj = json_object_new_string(req->description);
|
||||||
|
else
|
||||||
|
jobj = json_object_new_string(requirements_flags[i].description);
|
||||||
if (!jobj) {
|
if (!jobj) {
|
||||||
r = -ENOMEM;
|
r = -ENOMEM;
|
||||||
goto err;
|
goto err;
|
||||||
|
|||||||
@@ -288,19 +288,9 @@ crypt_keyslot_info LUKS2_keyslot_info(struct luks2_hdr *hdr, int keyslot)
|
|||||||
return CRYPT_SLOT_ACTIVE;
|
return CRYPT_SLOT_ACTIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int LUKS2_keyslot_area(struct luks2_hdr *hdr,
|
int LUKS2_keyslot_jobj_area(json_object *jobj_keyslot, uint64_t *offset, uint64_t *length)
|
||||||
int keyslot,
|
|
||||||
uint64_t *offset,
|
|
||||||
uint64_t *length)
|
|
||||||
{
|
{
|
||||||
json_object *jobj_keyslot, *jobj_area, *jobj;
|
json_object *jobj_area, *jobj;
|
||||||
|
|
||||||
if(LUKS2_keyslot_info(hdr, keyslot) == CRYPT_SLOT_INVALID)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot);
|
|
||||||
if (!jobj_keyslot)
|
|
||||||
return -ENOENT;
|
|
||||||
|
|
||||||
if (!json_object_object_get_ex(jobj_keyslot, "area", &jobj_area))
|
if (!json_object_object_get_ex(jobj_keyslot, "area", &jobj_area))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
@@ -316,6 +306,23 @@ int LUKS2_keyslot_area(struct luks2_hdr *hdr,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int LUKS2_keyslot_area(struct luks2_hdr *hdr,
|
||||||
|
int keyslot,
|
||||||
|
uint64_t *offset,
|
||||||
|
uint64_t *length)
|
||||||
|
{
|
||||||
|
json_object *jobj_keyslot;
|
||||||
|
|
||||||
|
if (LUKS2_keyslot_info(hdr, keyslot) == CRYPT_SLOT_INVALID)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot);
|
||||||
|
if (!jobj_keyslot)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
return LUKS2_keyslot_jobj_area(jobj_keyslot, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
static int _open_and_verify(struct crypt_device *cd,
|
static int _open_and_verify(struct crypt_device *cd,
|
||||||
struct luks2_hdr *hdr,
|
struct luks2_hdr *hdr,
|
||||||
const keyslot_handler *h,
|
const keyslot_handler *h,
|
||||||
|
|||||||
@@ -176,9 +176,17 @@ static int reenc_keyslot_store(struct crypt_device *cd,
|
|||||||
return r < 0 ? r : keyslot;
|
return r < 0 ? r : keyslot;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int reenc_keyslot_wipe(struct crypt_device *cd __attribute__((unused)),
|
static int reenc_keyslot_wipe(struct crypt_device *cd,
|
||||||
int keyslot __attribute__((unused)))
|
int keyslot)
|
||||||
{
|
{
|
||||||
|
struct luks2_hdr *hdr;
|
||||||
|
|
||||||
|
if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2)))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* remove reencryption verification data */
|
||||||
|
LUKS2_digest_assign(cd, hdr, keyslot, CRYPT_ANY_DIGEST, 0, 0);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ struct luks2_reencrypt {
|
|||||||
static int reencrypt_keyslot_update(struct crypt_device *cd,
|
static int reencrypt_keyslot_update(struct crypt_device *cd,
|
||||||
const struct luks2_reencrypt *rh)
|
const struct luks2_reencrypt *rh)
|
||||||
{
|
{
|
||||||
|
int r;
|
||||||
json_object *jobj_keyslot, *jobj_area, *jobj_area_type;
|
json_object *jobj_keyslot, *jobj_area, *jobj_area_type;
|
||||||
struct luks2_hdr *hdr;
|
struct luks2_hdr *hdr;
|
||||||
|
|
||||||
@@ -124,7 +125,11 @@ static int reencrypt_keyslot_update(struct crypt_device *cd,
|
|||||||
} else
|
} else
|
||||||
log_dbg(cd, "No update of reencrypt keyslot needed.");
|
log_dbg(cd, "No update of reencrypt keyslot needed.");
|
||||||
|
|
||||||
return 0;
|
r = LUKS2_keyslot_reencrypt_digest_create(cd, hdr, rh->vks);
|
||||||
|
if (r < 0)
|
||||||
|
log_err(cd, "Failed to refresh reencryption verification digest.");
|
||||||
|
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
static json_object *reencrypt_segment(struct luks2_hdr *hdr, unsigned new)
|
static json_object *reencrypt_segment(struct luks2_hdr *hdr, unsigned new)
|
||||||
@@ -2484,6 +2489,10 @@ static int reencrypt_init(struct crypt_device *cd,
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
r = LUKS2_keyslot_reencrypt_digest_create(cd, hdr, *vks);
|
||||||
|
if (r < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
if (name && params->mode != CRYPT_REENCRYPT_ENCRYPT) {
|
if (name && params->mode != CRYPT_REENCRYPT_ENCRYPT) {
|
||||||
r = reencrypt_verify_and_upload_keys(cd, hdr, LUKS2_reencrypt_digest_old(hdr), LUKS2_reencrypt_digest_new(hdr), *vks);
|
r = reencrypt_verify_and_upload_keys(cd, hdr, LUKS2_reencrypt_digest_old(hdr), LUKS2_reencrypt_digest_new(hdr), *vks);
|
||||||
if (r)
|
if (r)
|
||||||
@@ -2614,20 +2623,28 @@ static int reencrypt_context_update(struct crypt_device *cd,
|
|||||||
static int reencrypt_load(struct crypt_device *cd, struct luks2_hdr *hdr,
|
static int reencrypt_load(struct crypt_device *cd, struct luks2_hdr *hdr,
|
||||||
uint64_t device_size,
|
uint64_t device_size,
|
||||||
const struct crypt_params_reencrypt *params,
|
const struct crypt_params_reencrypt *params,
|
||||||
|
struct volume_key *vks,
|
||||||
struct luks2_reencrypt **rh)
|
struct luks2_reencrypt **rh)
|
||||||
{
|
{
|
||||||
int r;
|
int r;
|
||||||
struct luks2_reencrypt *tmp = NULL;
|
struct luks2_reencrypt *tmp = NULL;
|
||||||
crypt_reencrypt_info ri = LUKS2_reencrypt_status(hdr);
|
crypt_reencrypt_info ri = LUKS2_reencrypt_status(hdr);
|
||||||
|
|
||||||
|
if (ri == CRYPT_REENCRYPT_NONE) {
|
||||||
|
log_err(cd, _("Device not marked for LUKS2 reencryption."));
|
||||||
|
return -EINVAL;
|
||||||
|
} else if (ri == CRYPT_REENCRYPT_INVALID)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
r = LUKS2_reencrypt_digest_verify(cd, hdr, vks);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
if (ri == CRYPT_REENCRYPT_CLEAN)
|
if (ri == CRYPT_REENCRYPT_CLEAN)
|
||||||
r = reencrypt_load_clean(cd, hdr, device_size, &tmp, params);
|
r = reencrypt_load_clean(cd, hdr, device_size, &tmp, params);
|
||||||
else if (ri == CRYPT_REENCRYPT_CRASH)
|
else if (ri == CRYPT_REENCRYPT_CRASH)
|
||||||
r = reencrypt_load_crashed(cd, hdr, device_size, &tmp);
|
r = reencrypt_load_crashed(cd, hdr, device_size, &tmp);
|
||||||
else if (ri == CRYPT_REENCRYPT_NONE) {
|
else
|
||||||
log_err(cd, _("Device not marked for LUKS2 reencryption."));
|
|
||||||
return -EINVAL;
|
|
||||||
} else
|
|
||||||
r = -EINVAL;
|
r = -EINVAL;
|
||||||
|
|
||||||
if (r < 0 || !tmp) {
|
if (r < 0 || !tmp) {
|
||||||
@@ -2876,7 +2893,7 @@ static int reencrypt_load_by_passphrase(struct crypt_device *cd,
|
|||||||
rparams.device_size = required_size;
|
rparams.device_size = required_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = reencrypt_load(cd, hdr, device_size, &rparams, &rh);
|
r = reencrypt_load(cd, hdr, device_size, &rparams, *vks, &rh);
|
||||||
if (r < 0 || !rh)
|
if (r < 0 || !rh)
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
@@ -3096,13 +3113,6 @@ static reenc_status_t reencrypt_step(struct crypt_device *cd,
|
|||||||
{
|
{
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
/* update reencrypt keyslot protection parameters in memory only */
|
|
||||||
r = reencrypt_keyslot_update(cd, rh);
|
|
||||||
if (r < 0) {
|
|
||||||
log_dbg(cd, "Keyslot update failed.");
|
|
||||||
return REENC_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* in memory only */
|
/* in memory only */
|
||||||
r = reencrypt_make_segments(cd, hdr, rh, device_size);
|
r = reencrypt_make_segments(cd, hdr, rh, device_size);
|
||||||
if (r)
|
if (r)
|
||||||
@@ -3370,6 +3380,15 @@ int crypt_reencrypt_run(
|
|||||||
|
|
||||||
rs = REENC_OK;
|
rs = REENC_OK;
|
||||||
|
|
||||||
|
/* update reencrypt keyslot protection parameters in memory only */
|
||||||
|
if (!quit && (rh->device_size > rh->progress)) {
|
||||||
|
r = reencrypt_keyslot_update(cd, rh);
|
||||||
|
if (r < 0) {
|
||||||
|
log_dbg(cd, "Keyslot update failed.");
|
||||||
|
return reencrypt_teardown(cd, hdr, rh, REENC_ERR, quit, progress, usrptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (!quit && (rh->device_size > rh->progress)) {
|
while (!quit && (rh->device_size > rh->progress)) {
|
||||||
rs = reencrypt_step(cd, hdr, rh, rh->device_size, rh->online);
|
rs = reencrypt_step(cd, hdr, rh, rh->device_size, rh->online);
|
||||||
if (rs != REENC_OK)
|
if (rs != REENC_OK)
|
||||||
@@ -3409,7 +3428,7 @@ static int reencrypt_recovery(struct crypt_device *cd,
|
|||||||
int r;
|
int r;
|
||||||
struct luks2_reencrypt *rh = NULL;
|
struct luks2_reencrypt *rh = NULL;
|
||||||
|
|
||||||
r = reencrypt_load(cd, hdr, device_size, NULL, &rh);
|
r = reencrypt_load(cd, hdr, device_size, NULL, vks, &rh);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
log_err(cd, _("Failed to load LUKS2 reencryption context."));
|
log_err(cd, _("Failed to load LUKS2 reencryption context."));
|
||||||
return r;
|
return r;
|
||||||
|
|||||||
381
lib/luks2/luks2_reencrypt_digest.c
Normal file
381
lib/luks2/luks2_reencrypt_digest.c
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
/*
|
||||||
|
* LUKS - Linux Unified Key Setup v2, reencryption digest helpers
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022, Red Hat, Inc. All rights reserved.
|
||||||
|
* Copyright (C) 2022, Ondrej Kozina
|
||||||
|
* Copyright (C) 2022, Milan Broz
|
||||||
|
*
|
||||||
|
* 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, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "luks2_internal.h"
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#define MAX_STR 64
|
||||||
|
|
||||||
|
struct jtype {
|
||||||
|
enum { JNONE = 0, JSTR, JU64, JX64, JU32 } type;
|
||||||
|
json_object *jobj;
|
||||||
|
const char *id;
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t sr(struct jtype *j, uint8_t *ptr)
|
||||||
|
{
|
||||||
|
json_object *jobj;
|
||||||
|
size_t len = 0;
|
||||||
|
uint64_t u64;
|
||||||
|
uint32_t u32;
|
||||||
|
|
||||||
|
if (!json_object_is_type(j->jobj, json_type_object))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!json_object_object_get_ex(j->jobj, j->id, &jobj))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
switch(j->type) {
|
||||||
|
case JSTR: /* JSON string */
|
||||||
|
if (!json_object_is_type(jobj, json_type_string))
|
||||||
|
return 0;
|
||||||
|
len = strlen(json_object_get_string(jobj));
|
||||||
|
if (len > MAX_STR)
|
||||||
|
return 0;
|
||||||
|
if (ptr)
|
||||||
|
memcpy(ptr, json_object_get_string(jobj), len);
|
||||||
|
break;
|
||||||
|
case JU64: /* Unsigned 64bit integer stored as string */
|
||||||
|
if (!json_object_is_type(jobj, json_type_string))
|
||||||
|
break;
|
||||||
|
len = sizeof(u64);
|
||||||
|
if (ptr) {
|
||||||
|
u64 = cpu_to_be64(crypt_jobj_get_uint64(jobj));
|
||||||
|
memcpy(ptr, &u64, len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case JX64: /* Unsigned 64bit segment size (allows "dynamic") */
|
||||||
|
if (!json_object_is_type(jobj, json_type_string))
|
||||||
|
break;
|
||||||
|
if (!strcmp(json_object_get_string(jobj), "dynamic")) {
|
||||||
|
len = strlen("dynamic");
|
||||||
|
if (ptr)
|
||||||
|
memcpy(ptr, json_object_get_string(jobj), len);
|
||||||
|
} else {
|
||||||
|
len = sizeof(u64);
|
||||||
|
u64 = cpu_to_be64(crypt_jobj_get_uint64(jobj));
|
||||||
|
if (ptr)
|
||||||
|
memcpy(ptr, &u64, len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case JU32: /* Unsigned 32bit integer, stored as JSON int */
|
||||||
|
if (!json_object_is_type(jobj, json_type_int))
|
||||||
|
return 0;
|
||||||
|
len = sizeof(u32);
|
||||||
|
if (ptr) {
|
||||||
|
u32 = cpu_to_be32(crypt_jobj_get_uint32(jobj));
|
||||||
|
memcpy(ptr, &u32, len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case JNONE:
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t srs(struct jtype j[], uint8_t *ptr)
|
||||||
|
{
|
||||||
|
size_t l, len = 0;
|
||||||
|
|
||||||
|
while(j->jobj) {
|
||||||
|
l = sr(j, ptr);
|
||||||
|
if (!l)
|
||||||
|
return 0;
|
||||||
|
len += l;
|
||||||
|
if (ptr)
|
||||||
|
ptr += l;
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t segment_linear_serialize(json_object *jobj_segment, uint8_t *buffer)
|
||||||
|
{
|
||||||
|
struct jtype j[] = {
|
||||||
|
{ JSTR, jobj_segment, "type" },
|
||||||
|
{ JU64, jobj_segment, "offset" },
|
||||||
|
{ JX64, jobj_segment, "size" },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
return srs(j, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t segment_crypt_serialize(json_object *jobj_segment, uint8_t *buffer)
|
||||||
|
{
|
||||||
|
struct jtype j[] = {
|
||||||
|
{ JSTR, jobj_segment, "type" },
|
||||||
|
{ JU64, jobj_segment, "offset" },
|
||||||
|
{ JX64, jobj_segment, "size" },
|
||||||
|
{ JU64, jobj_segment, "iv_tweak" },
|
||||||
|
{ JSTR, jobj_segment, "encryption" },
|
||||||
|
{ JU32, jobj_segment, "sector_size" },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
return srs(j, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t segment_serialize(json_object *jobj_segment, uint8_t *buffer)
|
||||||
|
{
|
||||||
|
json_object *jobj_type;
|
||||||
|
const char *segment_type;
|
||||||
|
|
||||||
|
if (!json_object_object_get_ex(jobj_segment, "type", &jobj_type))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!(segment_type = json_object_get_string(jobj_type)))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!strcmp(segment_type, "crypt"))
|
||||||
|
return segment_crypt_serialize(jobj_segment, buffer);
|
||||||
|
else if (!strcmp(segment_type, "linear"))
|
||||||
|
return segment_linear_serialize(jobj_segment, buffer);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t backup_segments_serialize(struct luks2_hdr *hdr, uint8_t *buffer)
|
||||||
|
{
|
||||||
|
json_object *jobj_segment;
|
||||||
|
size_t l, len = 0;
|
||||||
|
|
||||||
|
jobj_segment = LUKS2_get_segment_by_flag(hdr, "backup-previous");
|
||||||
|
if (!jobj_segment || !(l = segment_serialize(jobj_segment, buffer)))
|
||||||
|
return 0;
|
||||||
|
len += l;
|
||||||
|
if (buffer)
|
||||||
|
buffer += l;
|
||||||
|
|
||||||
|
jobj_segment = LUKS2_get_segment_by_flag(hdr, "backup-final");
|
||||||
|
if (!jobj_segment || !(l = segment_serialize(jobj_segment, buffer)))
|
||||||
|
return 0;
|
||||||
|
len += l;
|
||||||
|
if (buffer)
|
||||||
|
buffer += l;
|
||||||
|
|
||||||
|
jobj_segment = LUKS2_get_segment_by_flag(hdr, "backup-moved-segment");
|
||||||
|
if (jobj_segment) {
|
||||||
|
if (!(l = segment_serialize(jobj_segment, buffer)))
|
||||||
|
return 0;
|
||||||
|
len += l;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t reenc_keyslot_serialize(struct luks2_hdr *hdr, uint8_t *buffer)
|
||||||
|
{
|
||||||
|
json_object *jobj_keyslot, *jobj_area, *jobj_type;
|
||||||
|
const char *area_type;
|
||||||
|
int keyslot_reencrypt;
|
||||||
|
|
||||||
|
keyslot_reencrypt = LUKS2_find_keyslot(hdr, "reencrypt");
|
||||||
|
if (keyslot_reencrypt < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!(jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot_reencrypt)))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!json_object_object_get_ex(jobj_keyslot, "area", &jobj_area))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!json_object_object_get_ex(jobj_area, "type", &jobj_type))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!(area_type = json_object_get_string(jobj_type)))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
struct jtype j[] = {
|
||||||
|
{ JSTR, jobj_keyslot, "mode" },
|
||||||
|
{ JSTR, jobj_keyslot, "direction" },
|
||||||
|
{ JSTR, jobj_area, "type" },
|
||||||
|
{ JU64, jobj_area, "offset" },
|
||||||
|
{ JU64, jobj_area, "size" },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
struct jtype j_datashift[] = {
|
||||||
|
{ JSTR, jobj_keyslot, "mode" },
|
||||||
|
{ JSTR, jobj_keyslot, "direction" },
|
||||||
|
{ JSTR, jobj_area, "type" },
|
||||||
|
{ JU64, jobj_area, "offset" },
|
||||||
|
{ JU64, jobj_area, "size" },
|
||||||
|
{ JU64, jobj_area, "shift_size" },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
struct jtype j_checksum[] = {
|
||||||
|
{ JSTR, jobj_keyslot, "mode" },
|
||||||
|
{ JSTR, jobj_keyslot, "direction" },
|
||||||
|
{ JSTR, jobj_area, "type" },
|
||||||
|
{ JU64, jobj_area, "offset" },
|
||||||
|
{ JU64, jobj_area, "size" },
|
||||||
|
{ JSTR, jobj_area, "hash" },
|
||||||
|
{ JU32, jobj_area, "sector_size" },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!strcmp(area_type, "datashift"))
|
||||||
|
return srs(j_datashift, buffer);
|
||||||
|
else if (!strcmp(area_type, "checksum"))
|
||||||
|
return srs(j_checksum, buffer);
|
||||||
|
|
||||||
|
return srs(j, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t blob_serialize(void *blob, size_t length, uint8_t *buffer)
|
||||||
|
{
|
||||||
|
if (buffer)
|
||||||
|
memcpy(buffer, blob, length);
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int reencrypt_assembly_verification_data(struct crypt_device *cd,
|
||||||
|
struct luks2_hdr *hdr,
|
||||||
|
struct volume_key *vks,
|
||||||
|
struct volume_key **verification_data)
|
||||||
|
{
|
||||||
|
uint8_t *ptr;
|
||||||
|
int digest_new, digest_old;
|
||||||
|
struct volume_key *data = NULL, *vk_old = NULL, *vk_new = NULL;
|
||||||
|
size_t keyslot_data_len, segments_data_len, data_len = 2;
|
||||||
|
|
||||||
|
/* Keys - calculate length */
|
||||||
|
digest_new = LUKS2_reencrypt_digest_new(hdr);
|
||||||
|
digest_old = LUKS2_reencrypt_digest_old(hdr);
|
||||||
|
|
||||||
|
if (digest_old >= 0) {
|
||||||
|
vk_old = crypt_volume_key_by_id(vks, digest_old);
|
||||||
|
if (!vk_old)
|
||||||
|
return -EINVAL;
|
||||||
|
data_len += blob_serialize(vk_old->key, vk_old->keylength, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (digest_new >= 0 && digest_old != digest_new) {
|
||||||
|
vk_new = crypt_volume_key_by_id(vks, digest_new);
|
||||||
|
if (!vk_new)
|
||||||
|
return -EINVAL;
|
||||||
|
data_len += blob_serialize(vk_new->key, vk_new->keylength, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_len == 2)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Metadata - calculate length */
|
||||||
|
if (!(keyslot_data_len = reenc_keyslot_serialize(hdr, NULL)))
|
||||||
|
return -EINVAL;
|
||||||
|
data_len += keyslot_data_len;
|
||||||
|
|
||||||
|
if (!(segments_data_len = backup_segments_serialize(hdr, NULL)))
|
||||||
|
return -EINVAL;
|
||||||
|
data_len += segments_data_len;
|
||||||
|
|
||||||
|
/* Alloc and fill serialization data */
|
||||||
|
data = crypt_alloc_volume_key(data_len, NULL);
|
||||||
|
if (!data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ptr = (uint8_t*)data->key;
|
||||||
|
|
||||||
|
/* v2 */
|
||||||
|
*ptr++ = 0x76;
|
||||||
|
*ptr++ = 0x32;
|
||||||
|
|
||||||
|
if (vk_old)
|
||||||
|
ptr += blob_serialize(vk_old->key, vk_old->keylength, ptr);
|
||||||
|
|
||||||
|
if (vk_new)
|
||||||
|
ptr += blob_serialize(vk_new->key, vk_new->keylength, ptr);
|
||||||
|
|
||||||
|
if (!reenc_keyslot_serialize(hdr, ptr))
|
||||||
|
goto bad;
|
||||||
|
ptr += keyslot_data_len;
|
||||||
|
|
||||||
|
if (!backup_segments_serialize(hdr, ptr))
|
||||||
|
goto bad;
|
||||||
|
ptr += segments_data_len;
|
||||||
|
|
||||||
|
assert((size_t)(ptr - (uint8_t*)data->key) == data_len);
|
||||||
|
|
||||||
|
*verification_data = data;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
bad:
|
||||||
|
crypt_free_volume_key(data);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LUKS2_keyslot_reencrypt_digest_create(struct crypt_device *cd,
|
||||||
|
struct luks2_hdr *hdr,
|
||||||
|
struct volume_key *vks)
|
||||||
|
{
|
||||||
|
int digest_reencrypt, keyslot_reencrypt, r;
|
||||||
|
struct volume_key *data;
|
||||||
|
|
||||||
|
keyslot_reencrypt = LUKS2_find_keyslot(hdr, "reencrypt");
|
||||||
|
if (keyslot_reencrypt < 0)
|
||||||
|
return keyslot_reencrypt;
|
||||||
|
|
||||||
|
r = reencrypt_assembly_verification_data(cd, hdr, vks, &data);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = LUKS2_digest_create(cd, "pbkdf2", hdr, data);
|
||||||
|
crypt_free_volume_key(data);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
digest_reencrypt = r;
|
||||||
|
|
||||||
|
r = LUKS2_digest_assign(cd, hdr, keyslot_reencrypt, CRYPT_ANY_DIGEST, 0, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
return LUKS2_digest_assign(cd, hdr, keyslot_reencrypt, digest_reencrypt, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int LUKS2_reencrypt_digest_verify(struct crypt_device *cd,
|
||||||
|
struct luks2_hdr *hdr,
|
||||||
|
struct volume_key *vks)
|
||||||
|
{
|
||||||
|
int r, keyslot_reencrypt;
|
||||||
|
struct volume_key *data;
|
||||||
|
|
||||||
|
keyslot_reencrypt = LUKS2_find_keyslot(hdr, "reencrypt");
|
||||||
|
if (keyslot_reencrypt < 0)
|
||||||
|
return keyslot_reencrypt;
|
||||||
|
|
||||||
|
r = reencrypt_assembly_verification_data(cd, hdr, vks, &data);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = LUKS2_digest_verify(cd, hdr, data, keyslot_reencrypt);
|
||||||
|
crypt_free_volume_key(data);
|
||||||
|
|
||||||
|
if (r < 0) {
|
||||||
|
if (r == -ENOENT)
|
||||||
|
log_dbg(cd, "Reencryption digest is missing.");
|
||||||
|
log_err(cd, _("Reencryption metadata is invalid."));
|
||||||
|
} else
|
||||||
|
log_dbg(cd, "Reencryption metadata verified.");
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
21
lib/setup.c
21
lib/setup.c
@@ -4131,6 +4131,12 @@ static int _open_and_activate_reencrypt_device(struct crypt_device *cd,
|
|||||||
keyslot = r;
|
keyslot = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (r >= 0) {
|
||||||
|
r = LUKS2_reencrypt_digest_verify(cd, hdr, vks);
|
||||||
|
if (r < 0)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
log_dbg(cd, "Entering clean reencryption state mode.");
|
log_dbg(cd, "Entering clean reencryption state mode.");
|
||||||
|
|
||||||
if (r >= 0)
|
if (r >= 0)
|
||||||
@@ -4158,8 +4164,9 @@ static int _open_and_activate_luks2(struct crypt_device *cd,
|
|||||||
uint32_t flags)
|
uint32_t flags)
|
||||||
{
|
{
|
||||||
crypt_reencrypt_info ri;
|
crypt_reencrypt_info ri;
|
||||||
int r;
|
int r, rv;
|
||||||
struct luks2_hdr *hdr = &cd->u.luks2.hdr;
|
struct luks2_hdr *hdr = &cd->u.luks2.hdr;
|
||||||
|
struct volume_key *vks = NULL;
|
||||||
|
|
||||||
ri = LUKS2_reencrypt_status(hdr);
|
ri = LUKS2_reencrypt_status(hdr);
|
||||||
if (ri == CRYPT_REENCRYPT_INVALID)
|
if (ri == CRYPT_REENCRYPT_INVALID)
|
||||||
@@ -4169,9 +4176,17 @@ static int _open_and_activate_luks2(struct crypt_device *cd,
|
|||||||
if (name)
|
if (name)
|
||||||
r = _open_and_activate_reencrypt_device(cd, hdr, keyslot, name, passphrase,
|
r = _open_and_activate_reencrypt_device(cd, hdr, keyslot, name, passphrase,
|
||||||
passphrase_size, flags);
|
passphrase_size, flags);
|
||||||
else
|
else {
|
||||||
r = _open_all_keys(cd, hdr, keyslot, passphrase,
|
r = _open_all_keys(cd, hdr, keyslot, passphrase,
|
||||||
passphrase_size, flags, NULL);
|
passphrase_size, flags, &vks);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
rv = LUKS2_reencrypt_digest_verify(cd, hdr, vks);
|
||||||
|
crypt_free_volume_key(vks);
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
} else
|
} else
|
||||||
r = _open_and_activate(cd, keyslot, name, passphrase,
|
r = _open_and_activate(cd, keyslot, name, passphrase,
|
||||||
passphrase_size, flags);
|
passphrase_size, flags);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ lib/luks2/luks2_keyslot_luks2.c
|
|||||||
lib/luks2/luks2_keyslot_reenc.c
|
lib/luks2/luks2_keyslot_reenc.c
|
||||||
lib/luks2/luks2_luks1_convert.c
|
lib/luks2/luks2_luks1_convert.c
|
||||||
lib/luks2/luks2_reencrypt.c
|
lib/luks2/luks2_reencrypt.c
|
||||||
|
lib/luks2/luks2_reencrypt_digest.c
|
||||||
lib/luks2/luks2_segment.c
|
lib/luks2/luks2_segment.c
|
||||||
lib/luks2/luks2_token.c
|
lib/luks2/luks2_token.c
|
||||||
lib/luks2/luks2_token_keyring.c
|
lib/luks2/luks2_token_keyring.c
|
||||||
|
|||||||
Reference in New Issue
Block a user