From 60addcffa6794c29dccf33d8db5347f24b75f2fc Mon Sep 17 00:00:00 2001
From: Ondrej Kozina <okozina@redhat.com>
Date: Sun, 2 Jan 2022 16:57:31 +0100
Subject: [PATCH] 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.

Conflict: move LUKS2_keyslot_reencrypt_digest_create to lib/luks2/luks2.h
	move LUKS2_keyslot_jobj_area to lib/luks2/luks2.h 
	reencrypt_keyslot_update -> reenc_keyslot_update

Signed-off-by: yanglongkang <yanglongkang@huawei.com>
---
 lib/Makemodule.am                  |   1 +
 lib/luks2/luks2.h                  |  10 +
 lib/luks2/luks2_json_metadata.c    |  73 +++++-
 lib/luks2/luks2_keyslot.c          |  31 ++-
 lib/luks2/luks2_keyslot_reenc.c    |  18 +-
 lib/luks2/luks2_reencrypt.c        |  40 ++-
 lib/luks2/luks2_reencrypt_digest.c | 381 +++++++++++++++++++++++++++++
 lib/setup.c                        |  21 +-
 po/POTFILES.in                     |   1 +
 9 files changed, 533 insertions(+), 43 deletions(-)
 create mode 100644 lib/luks2/luks2_reencrypt_digest.c

diff --git a/lib/Makemodule.am b/lib/Makemodule.am
index bf4230b..69159fa 100644
--- a/lib/Makemodule.am
+++ b/lib/Makemodule.am
@@ -104,6 +104,7 @@ libcryptsetup_la_SOURCES = \
 	lib/luks2/luks2_keyslot_luks2.c	\
 	lib/luks2/luks2_keyslot_reenc.c	\
 	lib/luks2/luks2_reencrypt.c	\
+	lib/luks2/luks2_reencrypt_digest.c	\
 	lib/luks2/luks2_segment.c	\
 	lib/luks2/luks2_token_keyring.c	\
 	lib/luks2/luks2_token.c		\
diff --git a/lib/luks2/luks2.h b/lib/luks2/luks2.h
index 6ab753a..610917b 100644
--- a/lib/luks2/luks2.h
+++ b/lib/luks2/luks2.h
@@ -297,9 +297,15 @@ int LUKS2_keyslot_wipe(struct crypt_device *cd,
 	int keyslot,
 	int wipe_area_only);
 
+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 keyslot);
 
+int LUKS2_keyslot_jobj_area(json_object *jobj_keyslot, uint64_t *offset, uint64_t *length);
+
 crypt_keyslot_priority LUKS2_keyslot_priority_get(struct crypt_device *cd,
 	struct luks2_hdr *hdr,
 	int keyslot);
@@ -606,4 +612,8 @@ void crypt_reencrypt_unlock(struct crypt_device *cd, struct crypt_lock_handle *r
 
 int luks2_check_device_size(struct crypt_device *cd, struct luks2_hdr *hdr, uint64_t check_size, uint64_t *dev_size, bool activation, bool dynamic);
 
+int LUKS2_reencrypt_digest_verify(struct crypt_device *cd,
+	struct luks2_hdr *hdr,
+	struct volume_key *vks);
+
 #endif
diff --git a/lib/luks2/luks2_json_metadata.c b/lib/luks2/luks2_json_metadata.c
index e4e1424..fb6cef5 100644
--- a/lib/luks2/luks2_json_metadata.c
+++ b/lib/luks2/luks2_json_metadata.c
@@ -1368,24 +1368,63 @@ int LUKS2_config_set_flags(struct crypt_device *cd, struct luks2_hdr *hdr, uint3
  */
 
 /* LUKS2 library requirements */
-static const struct  {
+struct requirement_flag {
 	uint32_t flag;
+	uint32_t version;
 	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;
 
 	for (i = 0; requirements_flags[i].description; i++)
 		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;
 }
 
 /*
@@ -1395,7 +1434,7 @@ int LUKS2_config_get_requirements(struct crypt_device *cd, struct luks2_hdr *hdr
 {
 	json_object *jobj_config, *jobj_requirements, *jobj_mandatory, *jobj;
 	int i, len;
-	uint32_t req;
+	const struct requirement_flag *req;
 
 	assert(hdr);
 	if (!hdr || !reqs)
@@ -1422,8 +1461,8 @@ int LUKS2_config_get_requirements(struct crypt_device *cd, struct luks2_hdr *hdr
 		jobj = json_object_array_get_idx(jobj_mandatory, i);
 		req = get_requirement_by_name(json_object_get_string(jobj));
 		log_dbg(cd, "%s - %sknown", json_object_get_string(jobj),
-				        reqs_unknown(req) ? "un" : "");
-		*reqs |= req;
+				        reqs_unknown(req->flag) ? "un" : "");
+		*reqs |= req->flag;
 	}
 
 	return 0;
@@ -1433,6 +1472,8 @@ int LUKS2_config_set_requirements(struct crypt_device *cd, struct luks2_hdr *hdr
 {
 	json_object *jobj_config, *jobj_requirements, *jobj_mandatory, *jobj;
 	int i, r = -EINVAL;
+	const struct requirement_flag *req;
+	uint32_t req_id;
 
 	if (!hdr)
 		return -EINVAL;
@@ -1442,8 +1483,14 @@ int LUKS2_config_set_requirements(struct crypt_device *cd, struct luks2_hdr *hdr
 		return -ENOMEM;
 
 	for (i = 0; requirements_flags[i].description; i++) {
-		if (reqs & requirements_flags[i].flag) {
-			jobj = json_object_new_string(requirements_flags[i].description);
+		req_id = reqs & requirements_flags[i].flag;
+		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) {
 				r = -ENOMEM;
 				goto err;
diff --git a/lib/luks2/luks2_keyslot.c b/lib/luks2/luks2_keyslot.c
index 3b8c889..2435a1f 100644
--- a/lib/luks2/luks2_keyslot.c
+++ b/lib/luks2/luks2_keyslot.c
@@ -281,19 +281,9 @@ crypt_keyslot_info LUKS2_keyslot_info(struct luks2_hdr *hdr, int keyslot)
 	return CRYPT_SLOT_ACTIVE;
 }
 
-int LUKS2_keyslot_area(struct luks2_hdr *hdr,
-	int keyslot,
-	uint64_t *offset,
-	uint64_t *length)
+int LUKS2_keyslot_jobj_area(json_object *jobj_keyslot, uint64_t *offset, uint64_t *length)
 {
-	json_object *jobj_keyslot, *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;
+	json_object *jobj_area, *jobj;
 
 	if (!json_object_object_get_ex(jobj_keyslot, "area", &jobj_area))
 		return -EINVAL;
@@ -309,6 +299,23 @@ int LUKS2_keyslot_area(struct luks2_hdr *hdr,
 	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,
 	struct luks2_hdr *hdr,
 	const keyslot_handler *h,
diff --git a/lib/luks2/luks2_keyslot_reenc.c b/lib/luks2/luks2_keyslot_reenc.c
index b939467..854c926 100644
--- a/lib/luks2/luks2_keyslot_reenc.c
+++ b/lib/luks2/luks2_keyslot_reenc.c
@@ -179,6 +179,7 @@ static int reenc_keyslot_store(struct crypt_device *cd,
 int reenc_keyslot_update(struct crypt_device *cd,
 	const struct luks2_reenc_context *rh)
 {
+	int r;
 	json_object *jobj_keyslot, *jobj_area, *jobj_area_type;
 	struct luks2_hdr *hdr;
 
@@ -208,11 +209,24 @@ int reenc_keyslot_update(struct crypt_device *cd,
 	} else
 		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 int reenc_keyslot_wipe(struct crypt_device *cd, int keyslot)
+static int reenc_keyslot_wipe(struct crypt_device *cd,
+	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;
 }
 
diff --git a/lib/luks2/luks2_reencrypt.c b/lib/luks2/luks2_reencrypt.c
index 68d3194..d170f09 100644
--- a/lib/luks2/luks2_reencrypt.c
+++ b/lib/luks2/luks2_reencrypt.c
@@ -2390,6 +2390,10 @@ static int reencrypt_init(struct crypt_device *cd,
 	if (r < 0)
 		goto err;
 
+	r = LUKS2_keyslot_reencrypt_digest_create(cd, hdr, *vks);
+	if (r < 0)
+		goto err;
+
 	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);
 		if (r)
@@ -2520,20 +2524,28 @@ static int reencrypt_context_update(struct crypt_device *cd,
 static int reencrypt_load(struct crypt_device *cd, struct luks2_hdr *hdr,
 		uint64_t device_size,
 		const struct crypt_params_reencrypt *params,
+		struct volume_key *vks,
 		struct luks2_reenc_context **rh)
 {
 	int r;
 	struct luks2_reenc_context *tmp = NULL;
 	crypt_reencrypt_info ri = LUKS2_reenc_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)
 		r = reencrypt_load_clean(cd, hdr, device_size, &tmp, params);
 	else if (ri == CRYPT_REENCRYPT_CRASH)
 		r = reencrypt_load_crashed(cd, hdr, device_size, &tmp);
-	else if (ri == CRYPT_REENCRYPT_NONE) {
-		log_err(cd, _("Device not marked for LUKS2 reencryption."));
-		return -EINVAL;
-	} else
+	else
 		r = -EINVAL;
 
 	if (r < 0 || !tmp) {
@@ -2767,7 +2779,7 @@ static int reencrypt_load_by_passphrase(struct crypt_device *cd,
 		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)
 		goto err;
 
@@ -2987,13 +2999,6 @@ static reenc_status_t reencrypt_step(struct crypt_device *cd,
 {
 	int r;
 
-	/* update reencrypt keyslot protection parameters in memory only */
-	r = reenc_keyslot_update(cd, rh);
-	if (r < 0) {
-		log_dbg(cd, "Keyslot update failed.");
-		return REENC_ERR;
-	}
-
 	/* in memory only */
 	r = reencrypt_make_segments(cd, hdr, rh, device_size);
 	if (r)
@@ -3258,6 +3263,15 @@ int crypt_reencrypt(struct crypt_device *cd,
 
 	rs = REENC_OK;
 
+	/* update reencrypt keyslot protection parameters in memory only */
+	if (!quit && (rh->device_size > rh->progress)) {
+		r = reenc_keyslot_update(cd, rh);
+		if (r < 0) {
+			log_dbg(cd, "Keyslot update failed.");
+			return reencrypt_teardown(cd, hdr, rh, REENC_ERR, quit, progress);
+		}
+	}
+
 	while (!quit && (rh->device_size > rh->progress)) {
 		rs = reencrypt_step(cd, hdr, rh, rh->device_size, rh->online);
 		if (rs != REENC_OK)
@@ -3290,7 +3304,7 @@ static int reencrypt_recovery(struct crypt_device *cd,
 	int r;
 	struct luks2_reenc_context *rh = NULL;
 
-	r = reencrypt_load(cd, hdr, device_size, NULL, &rh);
+	r = reencrypt_load(cd, hdr, device_size, NULL, vks, &rh);
 	if (r < 0) {
 		log_err(cd, _("Failed to load LUKS2 reencryption context."));
 		return r;
diff --git a/lib/luks2/luks2_reencrypt_digest.c b/lib/luks2/luks2_reencrypt_digest.c
new file mode 100644
index 0000000..7ee277c
--- /dev/null
+++ b/lib/luks2/luks2_reencrypt_digest.c
@@ -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;
+}
diff --git a/lib/setup.c b/lib/setup.c
index 567f262..21a41a3 100644
--- a/lib/setup.c
+++ b/lib/setup.c
@@ -4023,6 +4023,12 @@ static int _open_and_activate_reencrypt_device(struct crypt_device *cd,
 			keyslot = r;
 	}
 
+	if (r >= 0) {
+		r = LUKS2_reencrypt_digest_verify(cd, hdr, vks);
+		if (r < 0)
+			goto err;
+	}
+
 	log_dbg(cd, "Entering clean reencryption state mode.");
 
 	if (r >= 0)
@@ -4050,8 +4056,9 @@ static int _open_and_activate_luks2(struct crypt_device *cd,
 	uint32_t flags)
 {
 	crypt_reencrypt_info ri;
-	int r;
+	int r, rv;
 	struct luks2_hdr *hdr = &cd->u.luks2.hdr;
+	struct volume_key *vks = NULL;
 
 	ri = LUKS2_reenc_status(hdr);
 	if (ri == CRYPT_REENCRYPT_INVALID)
@@ -4061,9 +4068,17 @@ static int _open_and_activate_luks2(struct crypt_device *cd,
 		if (name)
 			r = _open_and_activate_reencrypt_device(cd, hdr, keyslot, name, passphrase,
 					passphrase_size, flags);
-		else
+		else {
 			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
 		r = _open_and_activate(cd, keyslot, name, passphrase,
 				passphrase_size, flags);
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 8c1f2b7..9d4958b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -37,6 +37,7 @@ lib/luks2/luks2_keyslot_luks2.c
 lib/luks2/luks2_keyslot_reenc.c
 lib/luks2/luks2_luks1_convert.c
 lib/luks2/luks2_reencrypt.c
+lib/luks2/luks2_reencrypt_digest.c
 lib/luks2/luks2_segment.c
 lib/luks2/luks2_token.c
 lib/luks2/luks2_token_keyring.c
-- 
2.23.0

