diff options
Diffstat (limited to '')
-rw-r--r-- | lib/luks2/luks2.h | 388 | ||||
-rw-r--r-- | lib/luks2/luks2_digest.c | 393 | ||||
-rw-r--r-- | lib/luks2/luks2_digest_pbkdf2.c | 211 | ||||
-rw-r--r-- | lib/luks2/luks2_disk_metadata.c | 769 | ||||
-rw-r--r-- | lib/luks2/luks2_internal.h | 182 | ||||
-rw-r--r-- | lib/luks2/luks2_json_format.c | 311 | ||||
-rw-r--r-- | lib/luks2/luks2_json_metadata.c | 1935 | ||||
-rw-r--r-- | lib/luks2/luks2_keyslot.c | 663 | ||||
-rw-r--r-- | lib/luks2/luks2_keyslot_luks2.c | 785 | ||||
-rw-r--r-- | lib/luks2/luks2_luks1_convert.c | 863 | ||||
-rw-r--r-- | lib/luks2/luks2_token.c | 606 | ||||
-rw-r--r-- | lib/luks2/luks2_token_keyring.c | 170 |
12 files changed, 7276 insertions, 0 deletions
diff --git a/lib/luks2/luks2.h b/lib/luks2/luks2.h new file mode 100644 index 0000000..261d622 --- /dev/null +++ b/lib/luks2/luks2.h @@ -0,0 +1,388 @@ +/* + * LUKS - Linux Unified Key Setup v2 + * + * Copyright (C) 2015-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2019 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. + */ + +#ifndef _CRYPTSETUP_LUKS2_ONDISK_H +#define _CRYPTSETUP_LUKS2_ONDISK_H + +#include "libcryptsetup.h" + +#define LUKS2_MAGIC_1ST "LUKS\xba\xbe" +#define LUKS2_MAGIC_2ND "SKUL\xba\xbe" +#define LUKS2_MAGIC_L 6 +#define LUKS2_UUID_L 40 +#define LUKS2_LABEL_L 48 +#define LUKS2_SALT_L 64 +#define LUKS2_CHECKSUM_ALG_L 32 +#define LUKS2_CHECKSUM_L 64 + +#define LUKS2_KEYSLOTS_MAX 32 +#define LUKS2_TOKENS_MAX 32 +#define LUKS2_SEGMENT_MAX 32 + +#define LUKS2_BUILTIN_TOKEN_PREFIX "luks2-" +#define LUKS2_BUILTIN_TOKEN_PREFIX_LEN 6 + +#define LUKS2_TOKEN_KEYRING LUKS2_BUILTIN_TOKEN_PREFIX "keyring" + +#define LUKS2_DIGEST_MAX 8 + +#define CRYPT_ANY_SEGMENT -1 +#define CRYPT_DEFAULT_SEGMENT 0 +#define CRYPT_DEFAULT_SEGMENT_STR "0" + +#define CRYPT_ANY_DIGEST -1 + +/* + * LUKS2 header on-disk. + * + * Binary header is followed by JSON area. + * JSON area is followed by keyslot area and data area, + * these are described in JSON metadata. + * + * Note: uuid, csum_alg are intentionally on the same offset as LUKS1 + * (checksum alg replaces hash in LUKS1) + * + * String (char) should be zero terminated. + * Padding should be wiped. + * Checksum is calculated with csum zeroed (+ full JSON area). + */ +struct luks2_hdr_disk { + char magic[LUKS2_MAGIC_L]; + uint16_t version; /* Version 2 */ + uint64_t hdr_size; /* in bytes, including JSON area */ + uint64_t seqid; /* increased on every update */ + char label[LUKS2_LABEL_L]; + char checksum_alg[LUKS2_CHECKSUM_ALG_L]; + uint8_t salt[LUKS2_SALT_L]; /* unique for every header/offset */ + char uuid[LUKS2_UUID_L]; + char subsystem[LUKS2_LABEL_L]; /* owner subsystem label */ + uint64_t hdr_offset; /* offset from device start in bytes */ + char _padding[184]; + uint8_t csum[LUKS2_CHECKSUM_L]; + char _padding4096[7*512]; + /* JSON area starts here */ +} __attribute__ ((packed)); + +/* + * LUKS2 header in-memory. + */ +typedef struct json_object json_object; +struct luks2_hdr { + size_t hdr_size; + uint64_t seqid; + unsigned int version; + char label[LUKS2_LABEL_L]; + char subsystem[LUKS2_LABEL_L]; + char checksum_alg[LUKS2_CHECKSUM_ALG_L]; + uint8_t salt1[LUKS2_SALT_L]; + uint8_t salt2[LUKS2_SALT_L]; + char uuid[LUKS2_UUID_L]; + json_object *jobj; +}; + +struct luks2_keyslot_params { + enum { LUKS2_KEYSLOT_AF_LUKS1 = 0 } af_type; + enum { LUKS2_KEYSLOT_AREA_RAW = 0 } area_type; + + union { + struct { + char hash[LUKS2_CHECKSUM_ALG_L]; // or include luks.h + unsigned int stripes; + } luks1; + } af; + + union { + struct { + char encryption[65]; // or include utils_crypt.h + size_t key_size; + } raw; + } area; +}; + +/* + * Supportable header sizes (hdr_disk + JSON area) + * Also used as offset for the 2nd header. + */ +#define LUKS2_HDR_16K_LEN 0x4000 + +#define LUKS2_HDR_BIN_LEN sizeof(struct luks2_hdr_disk) + +//#define LUKS2_DEFAULT_HDR_SIZE 0x400000 /* 4 MiB */ +#define LUKS2_DEFAULT_HDR_SIZE 0x1000000 /* 16 MiB */ + +#define LUKS2_MAX_KEYSLOTS_SIZE 0x8000000 /* 128 MiB */ + +#define LUKS2_HDR_OFFSET_MAX 0x400000 /* 4 MiB */ + +/* Offsets for secondary header (for scan if primary header is corrupted). */ +#define LUKS2_HDR2_OFFSETS { 0x04000, 0x008000, 0x010000, 0x020000, \ + 0x40000, 0x080000, 0x100000, 0x200000, LUKS2_HDR_OFFSET_MAX } + +int LUKS2_hdr_version_unlocked(struct crypt_device *cd, + const char *backup_file); + +int LUKS2_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr, int repair); +int LUKS2_hdr_write(struct crypt_device *cd, struct luks2_hdr *hdr); +int LUKS2_hdr_dump(struct crypt_device *cd, struct luks2_hdr *hdr); + +int LUKS2_hdr_uuid(struct crypt_device *cd, + struct luks2_hdr *hdr, + const char *uuid); + +int LUKS2_hdr_labels(struct crypt_device *cd, + struct luks2_hdr *hdr, + const char *label, + const char *subsystem, + int commit); + +void LUKS2_hdr_free(struct crypt_device *cd, struct luks2_hdr *hdr); + +int LUKS2_hdr_backup(struct crypt_device *cd, + struct luks2_hdr *hdr, + const char *backup_file); +int LUKS2_hdr_restore(struct crypt_device *cd, + struct luks2_hdr *hdr, + const char *backup_file); + +uint64_t LUKS2_hdr_and_areas_size(json_object *jobj); +uint64_t LUKS2_keyslots_size(json_object *jobj); +uint64_t LUKS2_metadata_size(json_object *jobj); + +int LUKS2_keyslot_cipher_incompatible(struct crypt_device *cd, const char *cipher_spec); + +/* + * Generic LUKS2 keyslot + */ +int LUKS2_keyslot_open(struct crypt_device *cd, + int keyslot, + int segment, + const char *password, + size_t password_len, + struct volume_key **vk); + +int LUKS2_keyslot_store(struct crypt_device *cd, + struct luks2_hdr *hdr, + int keyslot, + const char *password, + size_t password_len, + const struct volume_key *vk, + const struct luks2_keyslot_params *params); + +int LUKS2_keyslot_wipe(struct crypt_device *cd, + struct luks2_hdr *hdr, + int keyslot, + int wipe_area_only); + +int LUKS2_keyslot_dump(struct crypt_device *cd, + int keyslot); + +crypt_keyslot_priority LUKS2_keyslot_priority_get(struct crypt_device *cd, + struct luks2_hdr *hdr, + int keyslot); + +int LUKS2_keyslot_priority_set(struct crypt_device *cd, + struct luks2_hdr *hdr, + int keyslot, + crypt_keyslot_priority priority, + int commit); + +/* + * Generic LUKS2 token + */ +int LUKS2_token_json_get(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + const char **json); + +int LUKS2_token_assign(struct crypt_device *cd, + struct luks2_hdr *hdr, + int keyslot, + int token, + int assign, + int commit); + +int LUKS2_token_is_assigned(struct crypt_device *cd, + struct luks2_hdr *hdr, + int keyslot, + int token); + +int LUKS2_token_create(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + const char *json, + int commit); + +crypt_token_info LUKS2_token_status(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + const char **type); + +int LUKS2_builtin_token_get(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + const char *type, + void *params); + +int LUKS2_builtin_token_create(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + const char *type, + const void *params, + int commit); + +int LUKS2_token_open_and_activate(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + const char *name, + uint32_t flags, + void *usrptr); + +int LUKS2_token_open_and_activate_any(struct crypt_device *cd, + struct luks2_hdr *hdr, + const char *name, + uint32_t flags); + +int LUKS2_tokens_count(struct luks2_hdr *hdr); + +/* + * Generic LUKS2 digest + */ +int LUKS2_digest_by_segment(struct luks2_hdr *hdr, int segment); + +int LUKS2_digest_verify_by_segment(struct crypt_device *cd, + struct luks2_hdr *hdr, + int segment, + const struct volume_key *vk); + +void LUKS2_digests_erase_unused(struct crypt_device *cd, + struct luks2_hdr *hdr); + +int LUKS2_digest_verify(struct crypt_device *cd, + struct luks2_hdr *hdr, + struct volume_key *vk, + int keyslot); + +int LUKS2_digest_dump(struct crypt_device *cd, + int digest); + +int LUKS2_digest_assign(struct crypt_device *cd, + struct luks2_hdr *hdr, + int keyslot, + int digest, + int assign, + int commit); + +int LUKS2_digest_segment_assign(struct crypt_device *cd, + struct luks2_hdr *hdr, + int segment, + int digest, + int assign, + int commit); + +int LUKS2_digest_by_keyslot(struct luks2_hdr *hdr, int keyslot); + +int LUKS2_digest_create(struct crypt_device *cd, + const char *type, + struct luks2_hdr *hdr, + const struct volume_key *vk); + +/* + * LUKS2 generic + */ +int LUKS2_activate(struct crypt_device *cd, + const char *name, + struct volume_key *vk, + uint32_t flags); + +int LUKS2_keyslot_luks2_format(struct crypt_device *cd, + struct luks2_hdr *hdr, + int keyslot, + const char *cipher, + size_t keylength); + +int LUKS2_generate_hdr( + struct crypt_device *cd, + struct luks2_hdr *hdr, + const struct volume_key *vk, + const char *cipherName, + const char *cipherMode, + const char *integrity, + const char *uuid, + unsigned int sector_size, + uint64_t data_offset, + uint64_t align_offset, + uint64_t required_alignment, + uint64_t metadata_size, + uint64_t keyslots_size); + +int LUKS2_check_metadata_area_size(uint64_t metadata_size); +int LUKS2_check_keyslots_area_size(uint64_t keyslots_size); + +int LUKS2_wipe_header_areas(struct crypt_device *cd, + struct luks2_hdr *hdr); + +uint64_t LUKS2_get_data_offset(struct luks2_hdr *hdr); +int LUKS2_get_sector_size(struct luks2_hdr *hdr); +const char *LUKS2_get_cipher(struct luks2_hdr *hdr, int segment); +const char *LUKS2_get_integrity(struct luks2_hdr *hdr, int segment); +int LUKS2_keyslot_params_default(struct crypt_device *cd, struct luks2_hdr *hdr, + struct luks2_keyslot_params *params); +int LUKS2_get_volume_key_size(struct luks2_hdr *hdr, int segment); +int LUKS2_get_keyslot_stored_key_size(struct luks2_hdr *hdr, int keyslot); +const char *LUKS2_get_keyslot_cipher(struct luks2_hdr *hdr, int keyslot, size_t *key_size); +int LUKS2_keyslot_find_empty(struct luks2_hdr *hdr, const char *type); +int LUKS2_keyslot_active_count(struct luks2_hdr *hdr, int segment); +int LUKS2_keyslot_for_segment(struct luks2_hdr *hdr, int keyslot, int segment); +crypt_keyslot_info LUKS2_keyslot_info(struct luks2_hdr *hdr, int keyslot); +int LUKS2_keyslot_area(struct luks2_hdr *hdr, + int keyslot, + uint64_t *offset, + uint64_t *length); +int LUKS2_keyslot_pbkdf(struct luks2_hdr *hdr, int keyslot, struct crypt_pbkdf_type *pbkdf); + +/* + * Permanent activation flags stored in header + */ +int LUKS2_config_get_flags(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t *flags); +int LUKS2_config_set_flags(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t flags); + +/* + * Requirements for device activation or header modification + */ +int LUKS2_config_get_requirements(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t *reqs); +int LUKS2_config_set_requirements(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t reqs); + +int LUKS2_unmet_requirements(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t reqs_mask, int quiet); + +int LUKS2_key_description_by_segment(struct crypt_device *cd, + struct luks2_hdr *hdr, struct volume_key *vk, int segment); +int LUKS2_volume_key_load_in_keyring_by_keyslot(struct crypt_device *cd, + struct luks2_hdr *hdr, struct volume_key *vk, int keyslot); + +struct luks_phdr; +int LUKS2_luks1_to_luks2(struct crypt_device *cd, + struct luks_phdr *hdr1, + struct luks2_hdr *hdr2); +int LUKS2_luks2_to_luks1(struct crypt_device *cd, + struct luks2_hdr *hdr2, + struct luks_phdr *hdr1); + +#endif diff --git a/lib/luks2/luks2_digest.c b/lib/luks2/luks2_digest.c new file mode 100644 index 0000000..246903c --- /dev/null +++ b/lib/luks2/luks2_digest.c @@ -0,0 +1,393 @@ +/* + * LUKS - Linux Unified Key Setup v2, digest handling + * + * Copyright (C) 2015-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2019 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" + +extern const digest_handler PBKDF2_digest; + +static const digest_handler *digest_handlers[LUKS2_DIGEST_MAX] = { + &PBKDF2_digest, + NULL +}; + +const digest_handler *LUKS2_digest_handler_type(struct crypt_device *cd, const char *type) +{ + int i; + + for (i = 0; i < LUKS2_DIGEST_MAX && digest_handlers[i]; i++) { + if (!strcmp(digest_handlers[i]->name, type)) + return digest_handlers[i]; + } + + return NULL; +} + +static const digest_handler *LUKS2_digest_handler(struct crypt_device *cd, int digest) +{ + struct luks2_hdr *hdr; + json_object *jobj1, *jobj2; + + if (digest < 0) + return NULL; + + if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2))) + return NULL; + + if (!(jobj1 = LUKS2_get_digest_jobj(hdr, digest))) + return NULL; + + if (!json_object_object_get_ex(jobj1, "type", &jobj2)) + return NULL; + + return LUKS2_digest_handler_type(cd, json_object_get_string(jobj2)); +} + +static int LUKS2_digest_find_free(struct crypt_device *cd, struct luks2_hdr *hdr) +{ + int digest = 0; + + while (LUKS2_get_digest_jobj(hdr, digest) && digest < LUKS2_DIGEST_MAX) + digest++; + + return digest < LUKS2_DIGEST_MAX ? digest : -1; +} + +int LUKS2_digest_create(struct crypt_device *cd, + const char *type, + struct luks2_hdr *hdr, + const struct volume_key *vk) +{ + int digest; + const digest_handler *dh; + + dh = LUKS2_digest_handler_type(cd, type); + if (!dh) + return -EINVAL; + + digest = LUKS2_digest_find_free(cd, hdr); + if (digest < 0) + return -EINVAL; + + log_dbg(cd, "Creating new digest %d (%s).", digest, type); + + return dh->store(cd, digest, vk->key, vk->keylength) ?: digest; +} + +int LUKS2_digest_by_keyslot(struct luks2_hdr *hdr, int keyslot) +{ + char keyslot_name[16]; + json_object *jobj_digests, *jobj_digest_keyslots; + + if (snprintf(keyslot_name, sizeof(keyslot_name), "%u", keyslot) < 1) + return -ENOMEM; + + json_object_object_get_ex(hdr->jobj, "digests", &jobj_digests); + + json_object_object_foreach(jobj_digests, key, val) { + json_object_object_get_ex(val, "keyslots", &jobj_digest_keyslots); + if (LUKS2_array_jobj(jobj_digest_keyslots, keyslot_name)) + return atoi(key); + } + + return -ENOENT; +} + +int LUKS2_digest_verify(struct crypt_device *cd, + struct luks2_hdr *hdr, + struct volume_key *vk, + int keyslot) +{ + const digest_handler *h; + int digest, r; + + digest = LUKS2_digest_by_keyslot(hdr, keyslot); + if (digest < 0) + return digest; + + log_dbg(cd, "Verifying key from keyslot %d, digest %d.", keyslot, digest); + h = LUKS2_digest_handler(cd, digest); + if (!h) + return -EINVAL; + + r = h->verify(cd, digest, vk->key, vk->keylength); + if (r < 0) { + log_dbg(cd, "Digest %d (%s) verify failed with %d.", digest, h->name, r); + return r; + } + + return digest; +} + +int LUKS2_digest_dump(struct crypt_device *cd, int digest) +{ + const digest_handler *h; + + if (!(h = LUKS2_digest_handler(cd, digest))) + return -EINVAL; + + return h->dump(cd, digest); +} + +int LUKS2_digest_verify_by_segment(struct crypt_device *cd, + struct luks2_hdr *hdr, + int segment, + const struct volume_key *vk) +{ + const digest_handler *h; + int digest, r; + + digest = LUKS2_digest_by_segment(hdr, segment); + if (digest < 0) + return digest; + + log_dbg(cd, "Verifying key digest %d.", digest); + + h = LUKS2_digest_handler(cd, digest); + if (!h) + return -EINVAL; + + r = h->verify(cd, digest, vk->key, vk->keylength); + if (r < 0) { + log_dbg(cd, "Digest %d (%s) verify failed with %d.", digest, h->name, r); + return r; + } + + return digest; +} + +/* FIXME: segment can have more digests */ +int LUKS2_digest_by_segment(struct luks2_hdr *hdr, int segment) +{ + char segment_name[16]; + json_object *jobj_digests, *jobj_digest_segments; + + json_object_object_get_ex(hdr->jobj, "digests", &jobj_digests); + + if (snprintf(segment_name, sizeof(segment_name), "%u", segment) < 1) + return -EINVAL; + + json_object_object_foreach(jobj_digests, key, val) { + json_object_object_get_ex(val, "segments", &jobj_digest_segments); + if (!LUKS2_array_jobj(jobj_digest_segments, segment_name)) + continue; + + return atoi(key); + } + + return -ENOENT; +} + +static int assign_one_digest(struct crypt_device *cd, struct luks2_hdr *hdr, + int keyslot, int digest, int assign) +{ + json_object *jobj1, *jobj_digest, *jobj_digest_keyslots; + char num[16]; + + log_dbg(cd, "Keyslot %i %s digest %i.", keyslot, assign ? "assigned to" : "unassigned from", digest); + + jobj_digest = LUKS2_get_digest_jobj(hdr, digest); + if (!jobj_digest) + return -EINVAL; + + json_object_object_get_ex(jobj_digest, "keyslots", &jobj_digest_keyslots); + if (!jobj_digest_keyslots) + return -EINVAL; + + snprintf(num, sizeof(num), "%d", keyslot); + if (assign) { + jobj1 = LUKS2_array_jobj(jobj_digest_keyslots, num); + if (!jobj1) + json_object_array_add(jobj_digest_keyslots, json_object_new_string(num)); + } else { + jobj1 = LUKS2_array_remove(jobj_digest_keyslots, num); + if (jobj1) + json_object_object_add(jobj_digest, "keyslots", jobj1); + } + + return 0; +} + +int LUKS2_digest_assign(struct crypt_device *cd, struct luks2_hdr *hdr, + int keyslot, int digest, int assign, int commit) +{ + json_object *jobj_digests; + int r = 0; + + if (digest == CRYPT_ANY_DIGEST) { + json_object_object_get_ex(hdr->jobj, "digests", &jobj_digests); + + json_object_object_foreach(jobj_digests, key, val) { + UNUSED(val); + r = assign_one_digest(cd, hdr, keyslot, atoi(key), assign); + if (r < 0) + break; + } + } else + r = assign_one_digest(cd, hdr, keyslot, digest, assign); + + if (r < 0) + return r; + + // FIXME: do not write header in nothing changed + return commit ? LUKS2_hdr_write(cd, hdr) : 0; +} + +static int assign_one_segment(struct crypt_device *cd, struct luks2_hdr *hdr, + int segment, int digest, int assign) +{ + json_object *jobj1, *jobj_digest, *jobj_digest_segments; + char num[16]; + + log_dbg(cd, "Segment %i %s digest %i.", segment, assign ? "assigned to" : "unassigned from", digest); + + jobj_digest = LUKS2_get_digest_jobj(hdr, digest); + if (!jobj_digest) + return -EINVAL; + + json_object_object_get_ex(jobj_digest, "segments", &jobj_digest_segments); + if (!jobj_digest_segments) + return -EINVAL; + + snprintf(num, sizeof(num), "%d", segment); + if (assign) { + jobj1 = LUKS2_array_jobj(jobj_digest_segments, num); + if (!jobj1) + json_object_array_add(jobj_digest_segments, json_object_new_string(num)); + } else { + jobj1 = LUKS2_array_remove(jobj_digest_segments, num); + if (jobj1) + json_object_object_add(jobj_digest, "segments", jobj1); + } + + return 0; +} + +int LUKS2_digest_segment_assign(struct crypt_device *cd, struct luks2_hdr *hdr, + int segment, int digest, int assign, int commit) +{ + json_object *jobj_digests; + int r = 0; + + if (digest == CRYPT_ANY_DIGEST) { + json_object_object_get_ex(hdr->jobj, "digests", &jobj_digests); + + json_object_object_foreach(jobj_digests, key, val) { + UNUSED(val); + r = assign_one_segment(cd, hdr, segment, atoi(key), assign); + if (r < 0) + break; + } + } else + r = assign_one_segment(cd, hdr, segment, digest, assign); + + if (r < 0) + return r; + + // FIXME: do not write header in nothing changed + return commit ? LUKS2_hdr_write(cd, hdr) : 0; +} + +static int digest_unused(json_object *jobj_digest) +{ + json_object *jobj; + + json_object_object_get_ex(jobj_digest, "segments", &jobj); + if (!jobj || !json_object_is_type(jobj, json_type_array) || json_object_array_length(jobj) > 0) + return 0; + + json_object_object_get_ex(jobj_digest, "keyslots", &jobj); + if (!jobj || !json_object_is_type(jobj, json_type_array)) + return 0; + + return json_object_array_length(jobj) > 0 ? 0 : 1; +} + +void LUKS2_digests_erase_unused(struct crypt_device *cd, + struct luks2_hdr *hdr) +{ + json_object *jobj_digests; + + json_object_object_get_ex(hdr->jobj, "digests", &jobj_digests); + if (!jobj_digests || !json_object_is_type(jobj_digests, json_type_object)) + return; + + json_object_object_foreach(jobj_digests, key, val) { + if (digest_unused(val)) { + log_dbg(cd, "Erasing unused digest %d.", atoi(key)); + json_object_object_del(jobj_digests, key); + } + } +} + +/* Key description helpers */ +static char *get_key_description_by_digest(struct crypt_device *cd, int digest) +{ + char *desc, digest_str[3]; + int r; + size_t len; + + if (!crypt_get_uuid(cd)) + return NULL; + + r = snprintf(digest_str, sizeof(digest_str), "d%u", digest); + if (r < 0 || (size_t)r >= sizeof(digest_str)) + return NULL; + + /* "cryptsetup:<uuid>-<digest_str>" + \0 */ + len = strlen(crypt_get_uuid(cd)) + strlen(digest_str) + 13; + + desc = malloc(len); + if (!desc) + return NULL; + + r = snprintf(desc, len, "%s:%s-%s", "cryptsetup", crypt_get_uuid(cd), digest_str); + if (r < 0 || (size_t)r >= len) { + free(desc); + return NULL; + } + + return desc; +} + +int LUKS2_key_description_by_segment(struct crypt_device *cd, + struct luks2_hdr *hdr, struct volume_key *vk, int segment) +{ + char *desc = get_key_description_by_digest(cd, LUKS2_digest_by_segment(hdr, segment)); + int r; + + r = crypt_volume_key_set_description(vk, desc); + free(desc); + return r; +} + +int LUKS2_volume_key_load_in_keyring_by_keyslot(struct crypt_device *cd, + struct luks2_hdr *hdr, struct volume_key *vk, int keyslot) +{ + char *desc = get_key_description_by_digest(cd, LUKS2_digest_by_keyslot(hdr, keyslot)); + int r; + + r = crypt_volume_key_set_description(vk, desc); + if (!r) + r = crypt_volume_key_load_in_keyring(cd, vk); + + free(desc); + return r; +} diff --git a/lib/luks2/luks2_digest_pbkdf2.c b/lib/luks2/luks2_digest_pbkdf2.c new file mode 100644 index 0000000..e4413ab --- /dev/null +++ b/lib/luks2/luks2_digest_pbkdf2.c @@ -0,0 +1,211 @@ +/* + * LUKS - Linux Unified Key Setup v2, PBKDF2 digest handler (LUKS1 compatible) + * + * Copyright (C) 2015-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2019 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" + +#define LUKS_DIGESTSIZE 20 // since SHA1 +#define LUKS_SALTSIZE 32 +#define LUKS_MKD_ITERATIONS_MS 125 + +static int PBKDF2_digest_verify(struct crypt_device *cd, + int digest, + const char *volume_key, + size_t volume_key_len) +{ + char checkHashBuf[64]; + json_object *jobj_digest, *jobj1; + const char *hashSpec; + char *mkDigest = NULL, mkDigestSalt[LUKS_SALTSIZE]; + unsigned int mkDigestIterations; + size_t len; + int r; + + /* This can be done only for internally linked digests */ + jobj_digest = LUKS2_get_digest_jobj(crypt_get_hdr(cd, CRYPT_LUKS2), digest); + if (!jobj_digest) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_digest, "hash", &jobj1)) + return -EINVAL; + hashSpec = json_object_get_string(jobj1); + + if (!json_object_object_get_ex(jobj_digest, "iterations", &jobj1)) + return -EINVAL; + mkDigestIterations = json_object_get_int64(jobj1); + + if (!json_object_object_get_ex(jobj_digest, "salt", &jobj1)) + return -EINVAL; + len = sizeof(mkDigestSalt); + if (!base64_decode(json_object_get_string(jobj1), + json_object_get_string_len(jobj1), mkDigestSalt, &len)) + return -EINVAL; + if (len != LUKS_SALTSIZE) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_digest, "digest", &jobj1)) + return -EINVAL; + len = 0; + if (!base64_decode_alloc(json_object_get_string(jobj1), + json_object_get_string_len(jobj1), &mkDigest, &len)) + return -EINVAL; + if (len < LUKS_DIGESTSIZE || + len > sizeof(checkHashBuf) || + (len != LUKS_DIGESTSIZE && len != (size_t)crypt_hash_size(hashSpec))) { + free(mkDigest); + return -EINVAL; + } + + r = -EPERM; + if (crypt_pbkdf(CRYPT_KDF_PBKDF2, hashSpec, volume_key, volume_key_len, + mkDigestSalt, LUKS_SALTSIZE, + checkHashBuf, len, + mkDigestIterations, 0, 0) < 0) { + r = -EINVAL; + } else { + if (memcmp(checkHashBuf, mkDigest, len) == 0) + r = 0; + } + + free(mkDigest); + return r; +} + +static int PBKDF2_digest_store(struct crypt_device *cd, + int digest, + const char *volume_key, + size_t volume_key_len) +{ + json_object *jobj_digest, *jobj_digests; + char salt[LUKS_SALTSIZE], digest_raw[128]; + int hmac_size, r; + char *base64_str; + struct luks2_hdr *hdr; + struct crypt_pbkdf_limits pbkdf_limits; + const struct crypt_pbkdf_type *pbkdf_cd; + struct crypt_pbkdf_type pbkdf = { + .type = CRYPT_KDF_PBKDF2, + .time_ms = LUKS_MKD_ITERATIONS_MS, + }; + + /* Inherit hash from PBKDF setting */ + pbkdf_cd = crypt_get_pbkdf_type(cd); + if (pbkdf_cd) + pbkdf.hash = pbkdf_cd->hash; + if (!pbkdf.hash) + pbkdf.hash = DEFAULT_LUKS1_HASH; + + log_dbg(cd, "Setting PBKDF2 type key digest %d.", digest); + + r = crypt_random_get(cd, salt, LUKS_SALTSIZE, CRYPT_RND_SALT); + if (r < 0) + return r; + + r = crypt_pbkdf_get_limits(CRYPT_KDF_PBKDF2, &pbkdf_limits); + if (r < 0) + return r; + + if (crypt_get_pbkdf(cd)->flags & CRYPT_PBKDF_NO_BENCHMARK) + pbkdf.iterations = pbkdf_limits.min_iterations; + else { + r = crypt_benchmark_pbkdf_internal(cd, &pbkdf, volume_key_len); + if (r < 0) + return r; + } + + hmac_size = crypt_hmac_size(pbkdf.hash); + if (hmac_size < 0) + return hmac_size; + + r = crypt_pbkdf(CRYPT_KDF_PBKDF2, pbkdf.hash, volume_key, volume_key_len, + salt, LUKS_SALTSIZE, digest_raw, hmac_size, + pbkdf.iterations, 0, 0); + if (r < 0) + return r; + + jobj_digest = LUKS2_get_digest_jobj(crypt_get_hdr(cd, CRYPT_LUKS2), digest); + jobj_digests = NULL; + if (!jobj_digest) { + hdr = crypt_get_hdr(cd, CRYPT_LUKS2); + jobj_digest = json_object_new_object(); + json_object_object_get_ex(hdr->jobj, "digests", &jobj_digests); + } + + json_object_object_add(jobj_digest, "type", json_object_new_string("pbkdf2")); + json_object_object_add(jobj_digest, "keyslots", json_object_new_array()); + json_object_object_add(jobj_digest, "segments", json_object_new_array()); + json_object_object_add(jobj_digest, "hash", json_object_new_string(pbkdf.hash)); + json_object_object_add(jobj_digest, "iterations", json_object_new_int(pbkdf.iterations)); + + base64_encode_alloc(salt, LUKS_SALTSIZE, &base64_str); + if (!base64_str) { + json_object_put(jobj_digest); + return -ENOMEM; + } + json_object_object_add(jobj_digest, "salt", json_object_new_string(base64_str)); + free(base64_str); + + base64_encode_alloc(digest_raw, hmac_size, &base64_str); + if (!base64_str) { + json_object_put(jobj_digest); + return -ENOMEM; + } + json_object_object_add(jobj_digest, "digest", json_object_new_string(base64_str)); + free(base64_str); + + if (jobj_digests) + json_object_object_add_by_uint(jobj_digests, digest, jobj_digest); + + JSON_DBG(cd, jobj_digest, "Digest JSON:"); + return 0; +} + +static int PBKDF2_digest_dump(struct crypt_device *cd, int digest) +{ + json_object *jobj_digest, *jobj1; + + /* This can be done only for internally linked digests */ + jobj_digest = LUKS2_get_digest_jobj(crypt_get_hdr(cd, CRYPT_LUKS2), digest); + if (!jobj_digest) + return -EINVAL; + + json_object_object_get_ex(jobj_digest, "hash", &jobj1); + log_std(cd, "\tHash: %s\n", json_object_get_string(jobj1)); + + json_object_object_get_ex(jobj_digest, "iterations", &jobj1); + log_std(cd, "\tIterations: %" PRIu64 "\n", json_object_get_int64(jobj1)); + + json_object_object_get_ex(jobj_digest, "salt", &jobj1); + log_std(cd, "\tSalt: "); + hexprint_base64(cd, jobj1, " ", " "); + + json_object_object_get_ex(jobj_digest, "digest", &jobj1); + log_std(cd, "\tDigest: "); + hexprint_base64(cd, jobj1, " ", " "); + + return 0; +} + +const digest_handler PBKDF2_digest = { + .name = "pbkdf2", + .verify = PBKDF2_digest_verify, + .store = PBKDF2_digest_store, + .dump = PBKDF2_digest_dump, +}; diff --git a/lib/luks2/luks2_disk_metadata.c b/lib/luks2/luks2_disk_metadata.c new file mode 100644 index 0000000..c6a121a --- /dev/null +++ b/lib/luks2/luks2_disk_metadata.c @@ -0,0 +1,769 @@ +/* + * LUKS - Linux Unified Key Setup v2 + * + * Copyright (C) 2015-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2019 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 <assert.h> + +#include "luks2_internal.h" + +/* + * Helper functions + */ +json_object *parse_json_len(struct crypt_device *cd, const char *json_area, + uint64_t max_length, int *json_len) +{ + json_object *jobj; + struct json_tokener *jtok; + + /* INT32_MAX is internal (json-c) json_tokener_parse_ex() limit */ + if (!json_area || max_length > INT32_MAX) + return NULL; + + jtok = json_tokener_new(); + if (!jtok) { + log_dbg(cd, "ERROR: Failed to init json tokener"); + return NULL; + } + + jobj = json_tokener_parse_ex(jtok, json_area, max_length); + if (!jobj) + log_dbg(cd, "ERROR: Failed to parse json data (%d): %s", + json_tokener_get_error(jtok), + json_tokener_error_desc(json_tokener_get_error(jtok))); + else + *json_len = jtok->char_offset; + + json_tokener_free(jtok); + + return jobj; +} + +static void log_dbg_checksum(struct crypt_device *cd, + const uint8_t *csum, const char *csum_alg, const char *info) +{ + char csum_txt[2*LUKS2_CHECKSUM_L+1]; + int i; + + for (i = 0; i < crypt_hash_size(csum_alg); i++) + snprintf(&csum_txt[i*2], 3, "%02hhx", (const char)csum[i]); + csum_txt[i*2+1] = '\0'; /* Just to be safe, sprintf should write \0 there. */ + + log_dbg(cd, "Checksum:%s (%s)", &csum_txt[0], info); +} + +/* + * Calculate hash (checksum) of |LUKS2_bin|LUKS2_JSON_area| from in-memory structs. + * LUKS2 on-disk header contains uniques salt both for primary and secondary header. + * Checksum is always calculated with zeroed checksum field in binary header. + */ +static int hdr_checksum_calculate(const char *alg, struct luks2_hdr_disk *hdr_disk, + const char *json_area, size_t json_len) +{ + struct crypt_hash *hd = NULL; + int hash_size, r; + + hash_size = crypt_hash_size(alg); + if (hash_size <= 0 || crypt_hash_init(&hd, alg)) + return -EINVAL; + + /* Binary header, csum zeroed. */ + r = crypt_hash_write(hd, (char*)hdr_disk, LUKS2_HDR_BIN_LEN); + + /* JSON area (including unused space) */ + if (!r) + r = crypt_hash_write(hd, json_area, json_len); + + if (!r) + r = crypt_hash_final(hd, (char*)hdr_disk->csum, (size_t)hash_size); + + crypt_hash_destroy(hd); + return r; +} + +/* + * Compare hash (checksum) of on-disk and in-memory header. + */ +static int hdr_checksum_check(struct crypt_device *cd, + const char *alg, struct luks2_hdr_disk *hdr_disk, + const char *json_area, size_t json_len) +{ + struct luks2_hdr_disk hdr_tmp; + int hash_size, r; + + hash_size = crypt_hash_size(alg); + if (hash_size <= 0) + return -EINVAL; + + /* Copy header and zero checksum. */ + memcpy(&hdr_tmp, hdr_disk, LUKS2_HDR_BIN_LEN); + memset(&hdr_tmp.csum, 0, sizeof(hdr_tmp.csum)); + + r = hdr_checksum_calculate(alg, &hdr_tmp, json_area, json_len); + if (r < 0) + return r; + + log_dbg_checksum(cd, hdr_disk->csum, alg, "on-disk"); + log_dbg_checksum(cd, hdr_tmp.csum, alg, "in-memory"); + + if (memcmp(hdr_tmp.csum, hdr_disk->csum, (size_t)hash_size)) + return -EINVAL; + + return 0; +} + +/* + * Convert header from on-disk format to in-memory struct + */ +static void hdr_from_disk(struct luks2_hdr_disk *hdr_disk1, + struct luks2_hdr_disk *hdr_disk2, + struct luks2_hdr *hdr, + int secondary) +{ + hdr->version = be16_to_cpu(hdr_disk1->version); + hdr->hdr_size = be64_to_cpu(hdr_disk1->hdr_size); + hdr->seqid = be64_to_cpu(hdr_disk1->seqid); + + memcpy(hdr->label, hdr_disk1->label, LUKS2_LABEL_L); + hdr->label[LUKS2_LABEL_L - 1] = '\0'; + memcpy(hdr->subsystem, hdr_disk1->subsystem, LUKS2_LABEL_L); + hdr->subsystem[LUKS2_LABEL_L - 1] = '\0'; + memcpy(hdr->checksum_alg, hdr_disk1->checksum_alg, LUKS2_CHECKSUM_ALG_L); + hdr->checksum_alg[LUKS2_CHECKSUM_ALG_L - 1] = '\0'; + memcpy(hdr->uuid, hdr_disk1->uuid, LUKS2_UUID_L); + hdr->uuid[LUKS2_UUID_L - 1] = '\0'; + + if (secondary) { + memcpy(hdr->salt1, hdr_disk2->salt, LUKS2_SALT_L); + memcpy(hdr->salt2, hdr_disk1->salt, LUKS2_SALT_L); + } else { + memcpy(hdr->salt1, hdr_disk1->salt, LUKS2_SALT_L); + memcpy(hdr->salt2, hdr_disk2->salt, LUKS2_SALT_L); + } +} + +/* + * Convert header from in-memory struct to on-disk format + */ +static void hdr_to_disk(struct luks2_hdr *hdr, + struct luks2_hdr_disk *hdr_disk, + int secondary, uint64_t offset) +{ + assert(((char*)&(hdr_disk->_padding4096) - (char*)&(hdr_disk->magic)) == 512); + + memset(hdr_disk, 0, LUKS2_HDR_BIN_LEN); + + memcpy(&hdr_disk->magic, secondary ? LUKS2_MAGIC_2ND : LUKS2_MAGIC_1ST, LUKS2_MAGIC_L); + hdr_disk->version = cpu_to_be16(hdr->version); + hdr_disk->hdr_size = cpu_to_be64(hdr->hdr_size); + hdr_disk->hdr_offset = cpu_to_be64(offset); + hdr_disk->seqid = cpu_to_be64(hdr->seqid); + + strncpy(hdr_disk->label, hdr->label, LUKS2_LABEL_L); + hdr_disk->label[LUKS2_LABEL_L - 1] = '\0'; + strncpy(hdr_disk->subsystem, hdr->subsystem, LUKS2_LABEL_L); + hdr_disk->subsystem[LUKS2_LABEL_L - 1] = '\0'; + strncpy(hdr_disk->checksum_alg, hdr->checksum_alg, LUKS2_CHECKSUM_ALG_L); + hdr_disk->checksum_alg[LUKS2_CHECKSUM_ALG_L - 1] = '\0'; + strncpy(hdr_disk->uuid, hdr->uuid, LUKS2_UUID_L); + hdr_disk->uuid[LUKS2_UUID_L - 1] = '\0'; + + memcpy(hdr_disk->salt, secondary ? hdr->salt2 : hdr->salt1, LUKS2_SALT_L); +} + +/* + * Sanity checks before checksum is validated + */ +static int hdr_disk_sanity_check_pre(struct crypt_device *cd, + struct luks2_hdr_disk *hdr, + size_t *hdr_json_size, int secondary, + uint64_t offset) +{ + if (memcmp(hdr->magic, secondary ? LUKS2_MAGIC_2ND : LUKS2_MAGIC_1ST, LUKS2_MAGIC_L)) + return -EINVAL; + + if (be16_to_cpu(hdr->version) != 2) { + log_dbg(cd, "Unsupported LUKS2 header version %u.", be16_to_cpu(hdr->version)); + return -EINVAL; + } + + if (offset != be64_to_cpu(hdr->hdr_offset)) { + log_dbg(cd, "LUKS2 offset 0x%04x on device differs to expected offset 0x%04x.", + (unsigned)be64_to_cpu(hdr->hdr_offset), (unsigned)offset); + return -EINVAL; + } + + if (secondary && (offset != be64_to_cpu(hdr->hdr_size))) { + log_dbg(cd, "LUKS2 offset 0x%04x in secondary header doesn't match size 0x%04x.", + (unsigned)offset, (unsigned)be64_to_cpu(hdr->hdr_size)); + return -EINVAL; + } + + /* FIXME: sanity check checksum alg. */ + + log_dbg(cd, "LUKS2 header version %u of size %u bytes, checksum %s.", + (unsigned)be16_to_cpu(hdr->version), (unsigned)be64_to_cpu(hdr->hdr_size), + hdr->checksum_alg); + + *hdr_json_size = be64_to_cpu(hdr->hdr_size) - LUKS2_HDR_BIN_LEN; + return 0; +} + +/* + * Read LUKS2 header from disk at specific offset. + */ +static int hdr_read_disk(struct crypt_device *cd, + struct device *device, struct luks2_hdr_disk *hdr_disk, + char **json_area, uint64_t offset, int secondary) +{ + size_t hdr_json_size = 0; + int devfd = -1, r; + + log_dbg(cd, "Trying to read %s LUKS2 header at offset 0x%" PRIx64 ".", + secondary ? "secondary" : "primary", offset); + + devfd = device_open_locked(cd, device, O_RDONLY); + if (devfd < 0) + return devfd == -1 ? -EIO : devfd; + + /* + * Read binary header and run sanity check before reading + * JSON area and validating checksum. + */ + if (read_lseek_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), hdr_disk, + LUKS2_HDR_BIN_LEN, offset) != LUKS2_HDR_BIN_LEN) { + close(devfd); + return -EIO; + } + + r = hdr_disk_sanity_check_pre(cd, hdr_disk, &hdr_json_size, secondary, offset); + if (r < 0) { + close(devfd); + return r; + } + + /* + * Allocate and read JSON area. Always the whole area must be read. + */ + *json_area = malloc(hdr_json_size); + if (!*json_area) { + close(devfd); + return -ENOMEM; + } + + if (read_lseek_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), *json_area, hdr_json_size, + offset + LUKS2_HDR_BIN_LEN) != (ssize_t)hdr_json_size) { + close(devfd); + free(*json_area); + *json_area = NULL; + return -EIO; + } + + close(devfd); + + /* + * Calculate and validate checksum and zero it afterwards. + */ + if (hdr_checksum_check(cd, hdr_disk->checksum_alg, hdr_disk, + *json_area, hdr_json_size)) { + log_dbg(cd, "LUKS2 header checksum error (offset %" PRIu64 ").", offset); + r = -EINVAL; + } + memset(hdr_disk->csum, 0, LUKS2_CHECKSUM_L); + + return r; +} + +/* + * Write LUKS2 header to disk at specific offset. + */ +static int hdr_write_disk(struct crypt_device *cd, + struct device *device, struct luks2_hdr *hdr, + const char *json_area, int secondary) +{ + struct luks2_hdr_disk hdr_disk; + uint64_t offset = secondary ? hdr->hdr_size : 0; + size_t hdr_json_len; + int devfd = -1, r; + + log_dbg(cd, "Trying to write LUKS2 header (%zu bytes) at offset %" PRIu64 ".", + hdr->hdr_size, offset); + + /* FIXME: read-only device silent fail? */ + + devfd = device_open_locked(cd, device, O_RDWR); + if (devfd < 0) + return devfd == -1 ? -EINVAL : devfd; + + hdr_json_len = hdr->hdr_size - LUKS2_HDR_BIN_LEN; + + hdr_to_disk(hdr, &hdr_disk, secondary, offset); + + /* + * Write header without checksum but with proper seqid. + */ + if (write_lseek_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), (char *)&hdr_disk, + LUKS2_HDR_BIN_LEN, offset) < (ssize_t)LUKS2_HDR_BIN_LEN) { + close(devfd); + return -EIO; + } + + /* + * Write json area. + */ + if (write_lseek_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), + CONST_CAST(char*)json_area, hdr_json_len, + LUKS2_HDR_BIN_LEN + offset) < (ssize_t)hdr_json_len) { + close(devfd); + return -EIO; + } + + /* + * Calculate checksum and write header with checksum. + */ + r = hdr_checksum_calculate(hdr_disk.checksum_alg, &hdr_disk, + json_area, hdr_json_len); + if (r < 0) { + close(devfd); + return r; + } + log_dbg_checksum(cd, hdr_disk.csum, hdr_disk.checksum_alg, "in-memory"); + + if (write_lseek_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), (char *)&hdr_disk, + LUKS2_HDR_BIN_LEN, offset) < (ssize_t)LUKS2_HDR_BIN_LEN) + r = -EIO; + + device_sync(cd, device, devfd); + close(devfd); + return r; +} + +/* + * Convert in-memory LUKS2 header and write it to disk. + * This will increase sequence id, write both header copies and calculate checksum. + */ +int LUKS2_disk_hdr_write(struct crypt_device *cd, struct luks2_hdr *hdr, struct device *device) +{ + char *json_area; + const char *json_text; + size_t json_area_len; + int r; + + if (hdr->version != 2) { + log_dbg(cd, "Unsupported LUKS2 header version (%u).", hdr->version); + return -EINVAL; + } + + r = device_check_size(cd, crypt_metadata_device(cd), LUKS2_hdr_and_areas_size(hdr->jobj), 1); + if (r) + return r; + + /* + * Allocate and zero JSON area (of proper header size). + */ + json_area_len = hdr->hdr_size - LUKS2_HDR_BIN_LEN; + json_area = malloc(json_area_len); + if (!json_area) + return -ENOMEM; + memset(json_area, 0, json_area_len); + + /* + * Generate text space-efficient JSON representation to json area. + */ + json_text = json_object_to_json_string_ext(hdr->jobj, + JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE); + if (!json_text || !*json_text) { + log_dbg(cd, "Cannot parse JSON object to text representation."); + free(json_area); + return -ENOMEM; + } + if (strlen(json_text) > (json_area_len - 1)) { + log_dbg(cd, "JSON is too large (%zu > %zu).", strlen(json_text), json_area_len); + free(json_area); + return -EINVAL; + } + strncpy(json_area, json_text, json_area_len); + + /* Increase sequence id before writing it to disk. */ + hdr->seqid++; + + r = device_write_lock(cd, device); + if (r) { + log_err(cd, _("Failed to acquire write device lock.")); + free(json_area); + return r; + } + + /* Write primary and secondary header */ + r = hdr_write_disk(cd, device, hdr, json_area, 0); + if (!r) + r = hdr_write_disk(cd, device, hdr, json_area, 1); + + if (r) + log_dbg(cd, "LUKS2 header write failed (%d).", r); + + device_write_unlock(cd, device); + + /* FIXME: try recovery here? */ + + free(json_area); + return r; +} +static int validate_json_area(struct crypt_device *cd, const char *json_area, + uint64_t json_len, uint64_t max_length) +{ + char c; + + /* Enforce there are no needless opening bytes */ + if (*json_area != '{') { + log_dbg(cd, "ERROR: Opening character must be left curly bracket: '{'."); + return -EINVAL; + } + + if (json_len >= max_length) { + log_dbg(cd, "ERROR: Missing trailing null byte beyond parsed json data string."); + return -EINVAL; + } + + /* + * TODO: + * validate there are legal json format characters between + * 'json_area' and 'json_area + json_len' + */ + + do { + c = *(json_area + json_len); + if (c != '\0') { + log_dbg(cd, "ERROR: Forbidden ascii code 0x%02hhx found beyond json data string at offset %" PRIu64, + c, json_len); + return -EINVAL; + } + } while (++json_len < max_length); + + return 0; +} + +static int validate_luks2_json_object(struct crypt_device *cd, json_object *jobj_hdr, uint64_t length) +{ + int r; + + /* we require top level object to be of json_type_object */ + r = !json_object_is_type(jobj_hdr, json_type_object); + if (r) { + log_dbg(cd, "ERROR: Resulting object is not a json object type"); + return r; + } + + r = LUKS2_hdr_validate(cd, jobj_hdr, length); + if (r) { + log_dbg(cd, "Repairing JSON metadata."); + /* try to correct known glitches */ + LUKS2_hdr_repair(cd, jobj_hdr); + + /* run validation again */ + r = LUKS2_hdr_validate(cd, jobj_hdr, length); + } + + if (r) + log_dbg(cd, "ERROR: LUKS2 validation failed"); + + return r; +} + +static json_object *parse_and_validate_json(struct crypt_device *cd, + const char *json_area, uint64_t max_length) +{ + int json_len, r; + json_object *jobj = parse_json_len(cd, json_area, max_length, &json_len); + + if (!jobj) + return NULL; + + /* successful parse_json_len must not return offset <= 0 */ + assert(json_len > 0); + + r = validate_json_area(cd, json_area, json_len, max_length); + if (!r) + r = validate_luks2_json_object(cd, jobj, max_length); + + if (r) { + json_object_put(jobj); + jobj = NULL; + } + + return jobj; +} + +static int detect_device_signatures(struct crypt_device *cd, const char *path) +{ + blk_probe_status prb_state; + int r; + struct blkid_handle *h; + + if (!blk_supported()) { + log_dbg(cd, "Blkid probing of device signatures disabled."); + return 0; + } + + if ((r = blk_init_by_path(&h, path))) { + log_dbg(cd, "Failed to initialize blkid_handle by path."); + return -EINVAL; + } + + /* We don't care about details. Be fast. */ + blk_set_chains_for_fast_detection(h); + + /* Filter out crypto_LUKS. we don't care now */ + blk_superblocks_filter_luks(h); + + prb_state = blk_safeprobe(h); + + switch (prb_state) { + case PRB_AMBIGUOUS: + log_dbg(cd, "Blkid probe couldn't decide device type unambiguously."); + /* fall through */ + case PRB_FAIL: + log_dbg(cd, "Blkid probe failed."); + r = -EINVAL; + break; + case PRB_OK: /* crypto_LUKS type is filtered out */ + r = -EINVAL; + + if (blk_is_partition(h)) + log_dbg(cd, "Blkid probe detected partition type '%s'", blk_get_partition_type(h)); + else if (blk_is_superblock(h)) + log_dbg(cd, "blkid probe detected superblock type '%s'", blk_get_superblock_type(h)); + break; + case PRB_EMPTY: + log_dbg(cd, "Blkid probe detected no foreign device signature."); + } + blk_free(h); + return r; +} + +/* + * Read and convert on-disk LUKS2 header to in-memory representation.. + * Try to do recovery if on-disk state is not consistent. + */ +int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr, + struct device *device, int do_recovery, int do_blkprobe) +{ + enum { HDR_OK, HDR_OBSOLETE, HDR_FAIL, HDR_FAIL_IO } state_hdr1, state_hdr2; + struct luks2_hdr_disk hdr_disk1, hdr_disk2; + char *json_area1 = NULL, *json_area2 = NULL; + json_object *jobj_hdr1 = NULL, *jobj_hdr2 = NULL; + unsigned int i; + int r; + uint64_t hdr_size; + uint64_t hdr2_offsets[] = LUKS2_HDR2_OFFSETS; + + /* Skip auto-recovery if locks are disabled and we're not doing LUKS2 explicit repair */ + if (do_recovery && do_blkprobe && !crypt_metadata_locking_enabled()) { + do_recovery = 0; + log_dbg(cd, "Disabling header auto-recovery due to locking being disabled."); + } + + /* + * Read primary LUKS2 header (offset 0). + */ + state_hdr1 = HDR_FAIL; + r = hdr_read_disk(cd, device, &hdr_disk1, &json_area1, 0, 0); + if (r == 0) { + jobj_hdr1 = parse_and_validate_json(cd, json_area1, be64_to_cpu(hdr_disk1.hdr_size) - LUKS2_HDR_BIN_LEN); + state_hdr1 = jobj_hdr1 ? HDR_OK : HDR_OBSOLETE; + } else if (r == -EIO) + state_hdr1 = HDR_FAIL_IO; + + /* + * Read secondary LUKS2 header (follows primary). + */ + state_hdr2 = HDR_FAIL; + if (state_hdr1 != HDR_FAIL && state_hdr1 != HDR_FAIL_IO) { + r = hdr_read_disk(cd, device, &hdr_disk2, &json_area2, be64_to_cpu(hdr_disk1.hdr_size), 1); + if (r == 0) { + jobj_hdr2 = parse_and_validate_json(cd, json_area2, be64_to_cpu(hdr_disk2.hdr_size) - LUKS2_HDR_BIN_LEN); + state_hdr2 = jobj_hdr2 ? HDR_OK : HDR_OBSOLETE; + } else if (r == -EIO) + state_hdr2 = HDR_FAIL_IO; + } else { + /* + * No header size, check all known offsets. + */ + for (r = -EINVAL,i = 0; r < 0 && i < ARRAY_SIZE(hdr2_offsets); i++) + r = hdr_read_disk(cd, device, &hdr_disk2, &json_area2, hdr2_offsets[i], 1); + + if (r == 0) { + jobj_hdr2 = parse_and_validate_json(cd, json_area2, be64_to_cpu(hdr_disk2.hdr_size) - LUKS2_HDR_BIN_LEN); + state_hdr2 = jobj_hdr2 ? HDR_OK : HDR_OBSOLETE; + } else if (r == -EIO) + state_hdr2 = HDR_FAIL_IO; + } + + /* + * Check sequence id if both headers are read correctly. + */ + if (state_hdr1 == HDR_OK && state_hdr2 == HDR_OK) { + if (be64_to_cpu(hdr_disk1.seqid) > be64_to_cpu(hdr_disk2.seqid)) + state_hdr2 = HDR_OBSOLETE; + else if (be64_to_cpu(hdr_disk1.seqid) < be64_to_cpu(hdr_disk2.seqid)) + state_hdr1 = HDR_OBSOLETE; + } + + /* check header with keyslots to fit the device */ + if (state_hdr1 == HDR_OK) + hdr_size = LUKS2_hdr_and_areas_size(jobj_hdr1); + else if (state_hdr2 == HDR_OK) + hdr_size = LUKS2_hdr_and_areas_size(jobj_hdr2); + else { + r = (state_hdr1 == HDR_FAIL_IO && state_hdr2 == HDR_FAIL_IO) ? -EIO : -EINVAL; + goto err; + } + + r = device_check_size(cd, device, hdr_size, 0); + if (r) + goto err; + + /* + * Try to rewrite (recover) bad header. Always regenerate salt for bad header. + */ + if (state_hdr1 == HDR_OK && state_hdr2 != HDR_OK) { + log_dbg(cd, "Secondary LUKS2 header requires recovery."); + + if (do_blkprobe && (r = detect_device_signatures(cd, device_path(device)))) { + log_err(cd, _("Device contains ambiguous signatures, cannot auto-recover LUKS2.\n" + "Please run \"cryptsetup repair\" for recovery.")); + goto err; + } + + if (do_recovery) { + memcpy(&hdr_disk2, &hdr_disk1, LUKS2_HDR_BIN_LEN); + r = crypt_random_get(cd, (char*)hdr_disk2.salt, sizeof(hdr_disk2.salt), CRYPT_RND_SALT); + if (r) + log_dbg(cd, "Cannot generate master salt."); + else { + hdr_from_disk(&hdr_disk1, &hdr_disk2, hdr, 0); + r = hdr_write_disk(cd, device, hdr, json_area1, 1); + } + if (r) + log_dbg(cd, "Secondary LUKS2 header recovery failed."); + } + } else if (state_hdr1 != HDR_OK && state_hdr2 == HDR_OK) { + log_dbg(cd, "Primary LUKS2 header requires recovery."); + + if (do_blkprobe && (r = detect_device_signatures(cd, device_path(device)))) { + log_err(cd, _("Device contains ambiguous signatures, cannot auto-recover LUKS2.\n" + "Please run \"cryptsetup repair\" for recovery.")); + goto err; + } + + if (do_recovery) { + memcpy(&hdr_disk1, &hdr_disk2, LUKS2_HDR_BIN_LEN); + r = crypt_random_get(cd, (char*)hdr_disk1.salt, sizeof(hdr_disk1.salt), CRYPT_RND_SALT); + if (r) + log_dbg(cd, "Cannot generate master salt."); + else { + hdr_from_disk(&hdr_disk2, &hdr_disk1, hdr, 1); + r = hdr_write_disk(cd, device, hdr, json_area2, 0); + } + if (r) + log_dbg(cd, "Primary LUKS2 header recovery failed."); + } + } + + free(json_area1); + json_area1 = NULL; + free(json_area2); + json_area2 = NULL; + + /* wrong lock for write mode during recovery attempt */ + if (r == -EAGAIN) + goto err; + + /* + * Even if status is failed, the second header includes salt. + */ + if (state_hdr1 == HDR_OK) { + hdr_from_disk(&hdr_disk1, &hdr_disk2, hdr, 0); + hdr->jobj = jobj_hdr1; + json_object_put(jobj_hdr2); + } else if (state_hdr2 == HDR_OK) { + hdr_from_disk(&hdr_disk2, &hdr_disk1, hdr, 1); + hdr->jobj = jobj_hdr2; + json_object_put(jobj_hdr1); + } + + /* + * FIXME: should this fail? At least one header was read correctly. + * r = (state_hdr1 == HDR_FAIL_IO || state_hdr2 == HDR_FAIL_IO) ? -EIO : -EINVAL; + */ + return 0; +err: + log_dbg(cd, "LUKS2 header read failed (%d).", r); + + free(json_area1); + free(json_area2); + json_object_put(jobj_hdr1); + json_object_put(jobj_hdr2); + hdr->jobj = NULL; + return r; +} + +int LUKS2_hdr_version_unlocked(struct crypt_device *cd, const char *backup_file) +{ + struct { + char magic[LUKS2_MAGIC_L]; + uint16_t version; + } __attribute__ ((packed)) hdr; + struct device *device = NULL; + int r = 0, devfd = -1, flags; + + if (!backup_file) + device = crypt_metadata_device(cd); + else if (device_alloc(cd, &device, backup_file) < 0) + return 0; + + if (!device) + return 0; + + flags = O_RDONLY; + if (device_direct_io(device)) + flags |= O_DIRECT; + + devfd = open(device_path(device), flags); + if (devfd < 0) + goto err; + + if ((read_lseek_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), &hdr, sizeof(hdr), 0) == sizeof(hdr)) && + !memcmp(hdr.magic, LUKS2_MAGIC_1ST, LUKS2_MAGIC_L)) + r = (int)be16_to_cpu(hdr.version); +err: + if (devfd != -1) + close(devfd); + + if (backup_file) + device_free(cd, device); + + return r; +} diff --git a/lib/luks2/luks2_internal.h b/lib/luks2/luks2_internal.h new file mode 100644 index 0000000..5fbb56e --- /dev/null +++ b/lib/luks2/luks2_internal.h @@ -0,0 +1,182 @@ +/* + * LUKS - Linux Unified Key Setup v2 + * + * Copyright (C) 2015-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2019 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. + */ + +#ifndef _CRYPTSETUP_LUKS2_INTERNAL_H +#define _CRYPTSETUP_LUKS2_INTERNAL_H + +#include <stdio.h> +#include <fcntl.h> +#include <errno.h> +#include <json-c/json.h> + +#include "internal.h" +#include "base64.h" +#include "luks2.h" + +#define UNUSED(x) (void)(x) + +/* override useless forward slash escape when supported by json-c */ +#ifndef JSON_C_TO_STRING_NOSLASHESCAPE +#define JSON_C_TO_STRING_NOSLASHESCAPE 0 +#endif + +/* + * On-disk access function prototypes + */ +int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr, + struct device *device, int do_recovery, int do_blkprobe); +int LUKS2_disk_hdr_write(struct crypt_device *cd, struct luks2_hdr *hdr, + struct device *device); + +/* + * JSON struct access helpers + */ +json_object *LUKS2_get_keyslot_jobj(struct luks2_hdr *hdr, int keyslot); +json_object *LUKS2_get_token_jobj(struct luks2_hdr *hdr, int token); +json_object *LUKS2_get_digest_jobj(struct luks2_hdr *hdr, int digest); +json_object *LUKS2_get_segment_jobj(struct luks2_hdr *hdr, int segment); +json_object *LUKS2_get_tokens_jobj(struct luks2_hdr *hdr); + +void hexprint_base64(struct crypt_device *cd, json_object *jobj, + const char *sep, const char *line_sep); + +json_object *parse_json_len(struct crypt_device *cd, const char *json_area, + uint64_t max_length, int *json_len); +uint64_t json_object_get_uint64(json_object *jobj); +uint32_t json_object_get_uint32(json_object *jobj); +json_object *json_object_new_uint64(uint64_t value); +int json_object_object_add_by_uint(json_object *jobj, unsigned key, json_object *jobj_val); +void json_object_object_del_by_uint(json_object *jobj, unsigned key); + +void JSON_DBG(struct crypt_device *cd, json_object *jobj, const char *desc); + +/* + * LUKS2 JSON validation + */ + +/* validation helper */ +json_object *json_contains(struct crypt_device *cd, json_object *jobj, const char *name, + const char *section, const char *key, json_type type); + +int LUKS2_hdr_validate(struct crypt_device *cd, json_object *hdr_jobj, uint64_t json_size); +int LUKS2_keyslot_validate(struct crypt_device *cd, json_object *hdr_jobj, + json_object *hdr_keyslot, const char *key); +int LUKS2_check_json_size(struct crypt_device *cd, const struct luks2_hdr *hdr); +int LUKS2_token_validate(struct crypt_device *cd, json_object *hdr_jobj, + json_object *jobj_token, const char *key); +void LUKS2_token_dump(struct crypt_device *cd, int token); + +/* + * LUKS2 JSON repair for known glitches + */ +void LUKS2_hdr_repair(struct crypt_device *cd, json_object *jobj_hdr); +void LUKS2_keyslots_repair(struct crypt_device *cd, json_object *jobj_hdr); + +/* + * JSON array helpers + */ +struct json_object *LUKS2_array_jobj(struct json_object *array, const char *num); +struct json_object *LUKS2_array_remove(struct json_object *array, const char *num); + +/* + * Plugins API + */ + +/** + * LUKS2 keyslots handlers (EXPERIMENTAL) + */ +typedef int (*keyslot_alloc_func)(struct crypt_device *cd, int keyslot, + size_t volume_key_len, + const struct luks2_keyslot_params *params); +typedef int (*keyslot_update_func)(struct crypt_device *cd, int keyslot, + const struct luks2_keyslot_params *params); +typedef int (*keyslot_open_func) (struct crypt_device *cd, int keyslot, + const char *password, size_t password_len, + char *volume_key, size_t volume_key_len); +typedef int (*keyslot_store_func)(struct crypt_device *cd, int keyslot, + const char *password, size_t password_len, + const char *volume_key, size_t volume_key_len); +typedef int (*keyslot_wipe_func) (struct crypt_device *cd, int keyslot); +typedef int (*keyslot_dump_func) (struct crypt_device *cd, int keyslot); +typedef int (*keyslot_validate_func) (struct crypt_device *cd, json_object *jobj_keyslot); +typedef void(*keyslot_repair_func) (struct crypt_device *cd, json_object *jobj_keyslot); + +/* see LUKS2_luks2_to_luks1 */ +int placeholder_keyslot_alloc(struct crypt_device *cd, + int keyslot, + uint64_t area_offset, + uint64_t area_length, + size_t volume_key_len); + +/* validate all keyslot implementations in hdr json */ +int LUKS2_keyslots_validate(struct crypt_device *cd, json_object *hdr_jobj); + +typedef struct { + const char *name; + keyslot_alloc_func alloc; + keyslot_update_func update; + keyslot_open_func open; + keyslot_store_func store; + keyslot_wipe_func wipe; + keyslot_dump_func dump; + keyslot_validate_func validate; + keyslot_repair_func repair; +} keyslot_handler; + +/** + * LUKS2 digest handlers (EXPERIMENTAL) + */ +typedef int (*digest_verify_func)(struct crypt_device *cd, int digest, + const char *volume_key, size_t volume_key_len); +typedef int (*digest_store_func) (struct crypt_device *cd, int digest, + const char *volume_key, size_t volume_key_len); +typedef int (*digest_dump_func) (struct crypt_device *cd, int digest); + +typedef struct { + const char *name; + digest_verify_func verify; + digest_store_func store; + digest_dump_func dump; +} digest_handler; + +const digest_handler *LUKS2_digest_handler_type(struct crypt_device *cd, const char *type); + +/** + * LUKS2 token handlers (internal use only) + */ +typedef int (*builtin_token_get_func) (json_object *jobj_token, void *params); +typedef int (*builtin_token_set_func) (json_object **jobj_token, const void *params); + +typedef struct { + /* internal only section used by builtin tokens */ + builtin_token_get_func get; + builtin_token_set_func set; + /* public token handler */ + const crypt_token_handler *h; +} token_handler; + +int token_keyring_set(json_object **, const void *); +int token_keyring_get(json_object *, void *); + +int LUKS2_find_area_gap(struct crypt_device *cd, struct luks2_hdr *hdr, + size_t keylength, uint64_t *area_offset, uint64_t *area_length); + +#endif diff --git a/lib/luks2/luks2_json_format.c b/lib/luks2/luks2_json_format.c new file mode 100644 index 0000000..a2cd331 --- /dev/null +++ b/lib/luks2/luks2_json_format.c @@ -0,0 +1,311 @@ +/* + * LUKS - Linux Unified Key Setup v2, LUKS2 header format code + * + * Copyright (C) 2015-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2019 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 <uuid/uuid.h> +#include <assert.h> + +struct area { + uint64_t offset; + uint64_t length; +}; + +static size_t get_area_size(size_t keylength) +{ + //FIXME: calculate this properly, for now it is AF_split_sectors + return size_round_up(keylength * 4000, 4096); +} + +static size_t get_min_offset(struct luks2_hdr *hdr) +{ + return 2 * hdr->hdr_size; +} + +static size_t get_max_offset(struct crypt_device *cd) +{ + return crypt_get_data_offset(cd) * SECTOR_SIZE; +} + +int LUKS2_find_area_gap(struct crypt_device *cd, struct luks2_hdr *hdr, + size_t keylength, uint64_t *area_offset, uint64_t *area_length) +{ + struct area areas[LUKS2_KEYSLOTS_MAX], sorted_areas[LUKS2_KEYSLOTS_MAX] = {}; + int i, j, k, area_i; + size_t offset, length; + + /* fill area offset + length table */ + for (i = 0; i < LUKS2_KEYSLOTS_MAX; i++) { + if (!LUKS2_keyslot_area(hdr, i, &areas[i].offset, &areas[i].length)) + continue; + areas[i].length = 0; + areas[i].offset = 0; + } + + /* sort table */ + k = 0; /* index in sorted table */ + for (i = 0; i < LUKS2_KEYSLOTS_MAX; i++) { + offset = get_max_offset(cd) ?: UINT64_MAX; + area_i = -1; + /* search for the smallest offset in table */ + for (j = 0; j < LUKS2_KEYSLOTS_MAX; j++) + if (areas[j].offset && areas[j].offset <= offset) { + area_i = j; + offset = areas[j].offset; + } + + if (area_i >= 0) { + sorted_areas[k].length = areas[area_i].length; + sorted_areas[k].offset = areas[area_i].offset; + areas[area_i].length = 0; + areas[area_i].offset = 0; + k++; + } + } + + /* search for the gap we can use */ + offset = get_min_offset(hdr); + length = get_area_size(keylength); + for (i = 0; i < LUKS2_KEYSLOTS_MAX; i++) { + /* skip empty */ + if (sorted_areas[i].offset == 0 || sorted_areas[i].length == 0) + continue; + + /* enough space before the used area */ + if ((offset < sorted_areas[i].offset) && ((offset + length) <= sorted_areas[i].offset)) + break; + + /* both offset and length are already aligned to 4096 bytes */ + offset = sorted_areas[i].offset + sorted_areas[i].length; + } + + if (get_max_offset(cd) && (offset + length) > get_max_offset(cd)) { + log_err(cd, _("No space for new keyslot.")); + return -EINVAL; + } + + log_dbg(cd, "Found area %zu -> %zu", offset, length + offset); +/* + log_dbg("Area offset min: %zu, max %zu, slots max %u", + get_min_offset(hdr), get_max_offset(cd), LUKS2_KEYSLOTS_MAX); + for (i = 0; i < LUKS2_KEYSLOTS_MAX; i++) + log_dbg("SLOT[%02i]: %-8" PRIu64 " -> %-8" PRIu64, i, + sorted_areas[i].offset, + sorted_areas[i].length + sorted_areas[i].offset); +*/ + *area_offset = offset; + *area_length = length; + return 0; +} + +int LUKS2_check_metadata_area_size(uint64_t metadata_size) +{ + /* see LUKS2_HDR2_OFFSETS */ + return (metadata_size != 0x004000 && + metadata_size != 0x008000 && metadata_size != 0x010000 && + metadata_size != 0x020000 && metadata_size != 0x040000 && + metadata_size != 0x080000 && metadata_size != 0x100000 && + metadata_size != 0x200000 && metadata_size != 0x400000); +} + +int LUKS2_check_keyslots_area_size(uint64_t keyslots_size) +{ + return (MISALIGNED_4K(keyslots_size) || + keyslots_size > LUKS2_MAX_KEYSLOTS_SIZE); +} + +int LUKS2_generate_hdr( + struct crypt_device *cd, + struct luks2_hdr *hdr, + const struct volume_key *vk, + const char *cipherName, + const char *cipherMode, + const char *integrity, + const char *uuid, + unsigned int sector_size, /* in bytes */ + uint64_t data_offset, /* in bytes */ + uint64_t align_offset, /* in bytes */ + uint64_t required_alignment, + uint64_t metadata_size, + uint64_t keyslots_size) +{ + struct json_object *jobj_segment, *jobj_integrity, *jobj_keyslots, *jobj_segments, *jobj_config; + char cipher[128]; + uuid_t partitionUuid; + int digest; + + if (!metadata_size) + metadata_size = LUKS2_HDR_16K_LEN; + hdr->hdr_size = metadata_size; + + if (data_offset && data_offset < get_min_offset(hdr)) { + log_err(cd, _("Requested data offset is too small.")); + return -EINVAL; + } + + /* Increase keyslot size according to data offset */ + if (!keyslots_size && data_offset) + keyslots_size = data_offset - get_min_offset(hdr); + + /* keyslots size has to be 4 KiB aligned */ + keyslots_size -= (keyslots_size % 4096); + + if (keyslots_size > LUKS2_MAX_KEYSLOTS_SIZE) + keyslots_size = LUKS2_MAX_KEYSLOTS_SIZE; + + if (!keyslots_size) { + assert(LUKS2_DEFAULT_HDR_SIZE > 2 * LUKS2_HDR_OFFSET_MAX); + keyslots_size = LUKS2_DEFAULT_HDR_SIZE - get_min_offset(hdr); + } + + /* Decrease keyslots_size if we have smaller data_offset */ + if (data_offset && (keyslots_size + get_min_offset(hdr)) > data_offset) { + keyslots_size = data_offset - get_min_offset(hdr); + log_dbg(cd, "Decreasing keyslot area size to %" PRIu64 + " bytes due to the requested data offset %" + PRIu64 " bytes.", keyslots_size, data_offset); + } + + /* Data offset has priority */ + if (!data_offset && required_alignment) { + data_offset = size_round_up(get_min_offset(hdr) + keyslots_size, + (size_t)required_alignment); + data_offset += align_offset; + } + + log_dbg(cd, "Formatting LUKS2 with JSON metadata area %" PRIu64 + " bytes and keyslots area %" PRIu64 " bytes.", + metadata_size - LUKS2_HDR_BIN_LEN, keyslots_size); + + if (keyslots_size < (LUKS2_HDR_OFFSET_MAX - 2*LUKS2_HDR_16K_LEN)) + log_std(cd, _("WARNING: keyslots area (%" PRIu64 " bytes) is very small," + " available LUKS2 keyslot count is very limited.\n"), + keyslots_size); + + hdr->seqid = 1; + hdr->version = 2; + memset(hdr->label, 0, LUKS2_LABEL_L); + strcpy(hdr->checksum_alg, "sha256"); + crypt_random_get(cd, (char*)hdr->salt1, LUKS2_SALT_L, CRYPT_RND_SALT); + crypt_random_get(cd, (char*)hdr->salt2, LUKS2_SALT_L, CRYPT_RND_SALT); + + if (uuid && uuid_parse(uuid, partitionUuid) == -1) { + log_err(cd, _("Wrong LUKS UUID format provided.")); + return -EINVAL; + } + if (!uuid) + uuid_generate(partitionUuid); + + uuid_unparse(partitionUuid, hdr->uuid); + + if (*cipherMode != '\0') + snprintf(cipher, sizeof(cipher), "%s-%s", cipherName, cipherMode); + else + snprintf(cipher, sizeof(cipher), "%s", cipherName); + + hdr->jobj = json_object_new_object(); + + jobj_keyslots = json_object_new_object(); + json_object_object_add(hdr->jobj, "keyslots", jobj_keyslots); + json_object_object_add(hdr->jobj, "tokens", json_object_new_object()); + jobj_segments = json_object_new_object(); + json_object_object_add(hdr->jobj, "segments", jobj_segments); + json_object_object_add(hdr->jobj, "digests", json_object_new_object()); + jobj_config = json_object_new_object(); + json_object_object_add(hdr->jobj, "config", jobj_config); + + digest = LUKS2_digest_create(cd, "pbkdf2", hdr, vk); + if (digest < 0) { + json_object_put(hdr->jobj); + hdr->jobj = NULL; + return -EINVAL; + } + + if (LUKS2_digest_segment_assign(cd, hdr, CRYPT_DEFAULT_SEGMENT, digest, 1, 0) < 0) { + json_object_put(hdr->jobj); + hdr->jobj = NULL; + return -EINVAL; + } + + jobj_segment = json_object_new_object(); + json_object_object_add(jobj_segment, "type", json_object_new_string("crypt")); + json_object_object_add(jobj_segment, "offset", json_object_new_uint64(data_offset)); + json_object_object_add(jobj_segment, "iv_tweak", json_object_new_string("0")); + json_object_object_add(jobj_segment, "size", json_object_new_string("dynamic")); + json_object_object_add(jobj_segment, "encryption", json_object_new_string(cipher)); + json_object_object_add(jobj_segment, "sector_size", json_object_new_int(sector_size)); + + if (integrity) { + jobj_integrity = json_object_new_object(); + json_object_object_add(jobj_integrity, "type", json_object_new_string(integrity)); + json_object_object_add(jobj_integrity, "journal_encryption", json_object_new_string("none")); + json_object_object_add(jobj_integrity, "journal_integrity", json_object_new_string("none")); + json_object_object_add(jobj_segment, "integrity", jobj_integrity); + } + + json_object_object_add_by_uint(jobj_segments, CRYPT_DEFAULT_SEGMENT, jobj_segment); + + json_object_object_add(jobj_config, "json_size", json_object_new_uint64(metadata_size - LUKS2_HDR_BIN_LEN)); + json_object_object_add(jobj_config, "keyslots_size", json_object_new_uint64(keyslots_size)); + + JSON_DBG(cd, hdr->jobj, "Header JSON:"); + return 0; +} + +int LUKS2_wipe_header_areas(struct crypt_device *cd, + struct luks2_hdr *hdr) +{ + int r; + uint64_t offset, length; + size_t wipe_block; + + /* Wipe complete header, keyslots and padding areas with zeroes. */ + offset = 0; + length = LUKS2_get_data_offset(hdr) * SECTOR_SIZE; + wipe_block = 1024 * 1024; + + if (LUKS2_hdr_validate(cd, hdr->jobj, hdr->hdr_size - LUKS2_HDR_BIN_LEN)) + return -EINVAL; + + /* On detached header wipe at least the first 4k */ + if (length == 0) { + length = 4096; + wipe_block = 4096; + } + + log_dbg(cd, "Wiping LUKS areas (0x%06" PRIx64 " - 0x%06" PRIx64") with zeroes.", + offset, length + offset); + + r = crypt_wipe_device(cd, crypt_metadata_device(cd), CRYPT_WIPE_ZERO, + offset, length, wipe_block, NULL, NULL); + if (r < 0) + return r; + + /* Wipe keyslot area */ + wipe_block = 1024 * 1024; + offset = get_min_offset(hdr); + length = LUKS2_keyslots_size(hdr->jobj); + + log_dbg(cd, "Wiping keyslots area (0x%06" PRIx64 " - 0x%06" PRIx64") with random data.", + offset, length + offset); + + return crypt_wipe_device(cd, crypt_metadata_device(cd), CRYPT_WIPE_RANDOM, + offset, length, wipe_block, NULL, NULL); +} diff --git a/lib/luks2/luks2_json_metadata.c b/lib/luks2/luks2_json_metadata.c new file mode 100644 index 0000000..de8d018 --- /dev/null +++ b/lib/luks2/luks2_json_metadata.c @@ -0,0 +1,1935 @@ +/* + * LUKS - Linux Unified Key Setup v2 + * + * Copyright (C) 2015-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2019 Milan Broz + * Copyright (C) 2015-2019 Ondrej Kozina + * + * 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 "../integrity/integrity.h" +#include <assert.h> +#include <ctype.h> +#include <uuid/uuid.h> + +#define LUKS_STRIPES 4000 + +struct interval { + uint64_t offset; + uint64_t length; +}; + +void hexprint_base64(struct crypt_device *cd, json_object *jobj, + const char *sep, const char *line_sep) +{ + char *buf = NULL; + size_t buf_len; + unsigned int i; + + if (!base64_decode_alloc(json_object_get_string(jobj), + json_object_get_string_len(jobj), + &buf, &buf_len)) + return; + + for (i = 0; i < buf_len / 2; i++) + log_std(cd, "%02hhx%s", buf[i], sep); + log_std(cd, "\n\t%s", line_sep); + for (i = buf_len / 2; i < buf_len; i++) + log_std(cd, "%02hhx%s", buf[i], sep); + log_std(cd, "\n"); + free(buf); +} + +void JSON_DBG(struct crypt_device *cd, json_object *jobj, const char *desc) +{ + if (desc) + crypt_log(cd, CRYPT_LOG_DEBUG_JSON, desc); + crypt_log(cd, CRYPT_LOG_DEBUG_JSON, json_object_to_json_string_ext(jobj, + JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_NOSLASHESCAPE)); +} + +/* + * JSON array helpers + */ +struct json_object *LUKS2_array_jobj(struct json_object *array, const char *num) +{ + struct json_object *jobj1; + int i; + + for (i = 0; i < (int) json_object_array_length(array); i++) { + jobj1 = json_object_array_get_idx(array, i); + if (!strcmp(num, json_object_get_string(jobj1))) + return jobj1; + } + + return NULL; +} + +struct json_object *LUKS2_array_remove(struct json_object *array, const char *num) +{ + struct json_object *jobj1, *jobj_removing = NULL, *array_new; + int i; + + jobj_removing = LUKS2_array_jobj(array, num); + if (!jobj_removing) + return NULL; + + /* Create new array without jobj_removing. */ + array_new = json_object_new_array(); + for (i = 0; i < (int) json_object_array_length(array); i++) { + jobj1 = json_object_array_get_idx(array, i); + if (jobj1 != jobj_removing) + json_object_array_add(array_new, json_object_get(jobj1)); + } + + return array_new; +} + +/* + * JSON struct access helpers + */ +json_object *LUKS2_get_keyslot_jobj(struct luks2_hdr *hdr, int keyslot) +{ + json_object *jobj1, *jobj2; + char keyslot_name[16]; + + if (!hdr || keyslot < 0) + return NULL; + + if (snprintf(keyslot_name, sizeof(keyslot_name), "%u", keyslot) < 1) + return NULL; + + if (!json_object_object_get_ex(hdr->jobj, "keyslots", &jobj1)) + return NULL; + + json_object_object_get_ex(jobj1, keyslot_name, &jobj2); + return jobj2; +} + +json_object *LUKS2_get_tokens_jobj(struct luks2_hdr *hdr) +{ + json_object *jobj_tokens; + + if (!hdr || !json_object_object_get_ex(hdr->jobj, "tokens", &jobj_tokens)) + return NULL; + + return jobj_tokens; +} + +json_object *LUKS2_get_token_jobj(struct luks2_hdr *hdr, int token) +{ + json_object *jobj1, *jobj2; + char token_name[16]; + + if (!hdr || token < 0) + return NULL; + + jobj1 = LUKS2_get_tokens_jobj(hdr); + if (!jobj1) + return NULL; + + if (snprintf(token_name, sizeof(token_name), "%u", token) < 1) + return NULL; + + json_object_object_get_ex(jobj1, token_name, &jobj2); + return jobj2; +} + +json_object *LUKS2_get_digest_jobj(struct luks2_hdr *hdr, int digest) +{ + json_object *jobj1, *jobj2; + char digest_name[16]; + + if (!hdr || digest < 0) + return NULL; + + if (snprintf(digest_name, sizeof(digest_name), "%u", digest) < 1) + return NULL; + + if (!json_object_object_get_ex(hdr->jobj, "digests", &jobj1)) + return NULL; + + json_object_object_get_ex(jobj1, digest_name, &jobj2); + return jobj2; +} + +json_object *LUKS2_get_segment_jobj(struct luks2_hdr *hdr, int segment) +{ + json_object *jobj1, *jobj2; + char segment_name[16]; + + if (!hdr || segment < 0) + return NULL; + + if (snprintf(segment_name, sizeof(segment_name), "%u", segment) < 1) + return NULL; + + if (!json_object_object_get_ex(hdr->jobj, "segments", &jobj1)) + return NULL; + + if (!json_object_object_get_ex(jobj1, segment_name, &jobj2)) + return NULL; + + return jobj2; +} + +/* + * json_type_int needs to be validated first. + * See validate_json_uint32() + */ +uint32_t json_object_get_uint32(json_object *jobj) +{ + return json_object_get_int64(jobj); +} + +/* jobj has to be json_type_string and numbered */ +static json_bool json_str_to_uint64(json_object *jobj, uint64_t *value) +{ + char *endptr; + unsigned long long tmp; + + errno = 0; + tmp = strtoull(json_object_get_string(jobj), &endptr, 10); + if (*endptr || errno) { + *value = 0; + return FALSE; + } + + *value = tmp; + return TRUE; +} + +uint64_t json_object_get_uint64(json_object *jobj) +{ + uint64_t r; + return json_str_to_uint64(jobj, &r) ? r : 0; +} + +json_object *json_object_new_uint64(uint64_t value) +{ + /* 18446744073709551615 */ + char num[21]; + int r; + json_object *jobj; + + r = snprintf(num, sizeof(num), "%" PRIu64, value); + if (r < 0 || (size_t)r >= sizeof(num)) + return NULL; + + jobj = json_object_new_string(num); + return jobj; +} + +/* + * Validate helpers + */ +static json_bool numbered(struct crypt_device *cd, const char *name, const char *key) +{ + int i; + + for (i = 0; key[i]; i++) + if (!isdigit(key[i])) { + log_dbg(cd, "%s \"%s\" is not in numbered form.", name, key); + return FALSE; + } + return TRUE; +} + +json_object *json_contains(struct crypt_device *cd, json_object *jobj, const char *name, + const char *section, const char *key, json_type type) +{ + json_object *sobj; + + if (!json_object_object_get_ex(jobj, key, &sobj) || + !json_object_is_type(sobj, type)) { + log_dbg(cd, "%s \"%s\" is missing \"%s\" (%s) specification.", + section, name, key, json_type_to_name(type)); + return NULL; + } + + return sobj; +} + +/* use only on already validated 'segments' object */ +static uint64_t get_first_data_offset(json_object *jobj_segs, const char *type) +{ + json_object *jobj_offset, *jobj_type; + uint64_t tmp, min = UINT64_MAX; + + json_object_object_foreach(jobj_segs, key, val) { + UNUSED(key); + + if (type) { + json_object_object_get_ex(val, "type", &jobj_type); + if (strcmp(type, json_object_get_string(jobj_type))) + continue; + } + + json_object_object_get_ex(val, "offset", &jobj_offset); + tmp = json_object_get_uint64(jobj_offset); + + if (!tmp) + return tmp; + + if (tmp < min) + min = tmp; + } + + return min; +} + +static json_bool validate_json_uint32(json_object *jobj) +{ + int64_t tmp; + + errno = 0; + tmp = json_object_get_int64(jobj); + + return (errno || tmp < 0 || tmp > UINT32_MAX) ? FALSE : TRUE; +} + +static json_bool validate_keyslots_array(struct crypt_device *cd, + json_object *jarr, json_object *jobj_keys) +{ + json_object *jobj; + int i = 0, length = (int) json_object_array_length(jarr); + + while (i < length) { + jobj = json_object_array_get_idx(jarr, i); + if (!json_object_is_type(jobj, json_type_string)) { + log_dbg(cd, "Illegal value type in keyslots array at index %d.", i); + return FALSE; + } + + if (!json_contains(cd, jobj_keys, "", "Keyslots section", + json_object_get_string(jobj), json_type_object)) + return FALSE; + + i++; + } + + return TRUE; +} + +static json_bool validate_segments_array(struct crypt_device *cd, + json_object *jarr, json_object *jobj_segments) +{ + json_object *jobj; + int i = 0, length = (int) json_object_array_length(jarr); + + while (i < length) { + jobj = json_object_array_get_idx(jarr, i); + if (!json_object_is_type(jobj, json_type_string)) { + log_dbg(cd, "Illegal value type in segments array at index %d.", i); + return FALSE; + } + + if (!json_contains(cd, jobj_segments, "", "Segments section", + json_object_get_string(jobj), json_type_object)) + return FALSE; + + i++; + } + + return TRUE; +} + +static json_bool segment_has_digest(const char *segment_name, json_object *jobj_digests) +{ + json_object *jobj_segments; + + json_object_object_foreach(jobj_digests, key, val) { + UNUSED(key); + json_object_object_get_ex(val, "segments", &jobj_segments); + if (LUKS2_array_jobj(jobj_segments, segment_name)) + return TRUE; + } + + return FALSE; +} + +static json_bool validate_intervals(struct crypt_device *cd, + int length, const struct interval *ix, + uint64_t metadata_size, uint64_t keyslots_area_end) +{ + int j, i = 0; + + while (i < length) { + if (ix[i].offset < 2 * metadata_size) { + log_dbg(cd, "Illegal area offset: %" PRIu64 ".", ix[i].offset); + return FALSE; + } + + if (!ix[i].length) { + log_dbg(cd, "Area length must be greater than zero."); + return FALSE; + } + + if ((ix[i].offset + ix[i].length) > keyslots_area_end) { + log_dbg(cd, "Area [%" PRIu64 ", %" PRIu64 "] overflows binary keyslots area (ends at offset: %" PRIu64 ").", + ix[i].offset, ix[i].offset + ix[i].length, keyslots_area_end); + return FALSE; + } + + for (j = 0; j < length; j++) { + if (i == j) + continue; + if ((ix[i].offset >= ix[j].offset) && (ix[i].offset < (ix[j].offset + ix[j].length))) { + log_dbg(cd, "Overlapping areas [%" PRIu64 ",%" PRIu64 "] and [%" PRIu64 ",%" PRIu64 "].", + ix[i].offset, ix[i].offset + ix[i].length, + ix[j].offset, ix[j].offset + ix[j].length); + return FALSE; + } + } + + i++; + } + + return TRUE; +} + +int LUKS2_keyslot_validate(struct crypt_device *cd, json_object *hdr_jobj, json_object *hdr_keyslot, const char *key) +{ + json_object *jobj_key_size; + + if (!json_contains(cd, hdr_keyslot, key, "Keyslot", "type", json_type_string)) + return 1; + if (!(jobj_key_size = json_contains(cd, hdr_keyslot, key, "Keyslot", "key_size", json_type_int))) + return 1; + + /* enforce uint32_t type */ + if (!validate_json_uint32(jobj_key_size)) { + log_dbg(cd, "Illegal field \"key_size\":%s.", + json_object_get_string(jobj_key_size)); + return 1; + } + + return 0; +} + +int LUKS2_token_validate(struct crypt_device *cd, + json_object *hdr_jobj, json_object *jobj_token, const char *key) +{ + json_object *jarr, *jobj_keyslots; + + /* keyslots are not yet validated, but we need to know token doesn't reference missing keyslot */ + if (!json_object_object_get_ex(hdr_jobj, "keyslots", &jobj_keyslots)) + return 1; + + if (!json_contains(cd, jobj_token, key, "Token", "type", json_type_string)) + return 1; + + jarr = json_contains(cd, jobj_token, key, "Token", "keyslots", json_type_array); + if (!jarr) + return 1; + + if (!validate_keyslots_array(cd, jarr, jobj_keyslots)) + return 1; + + return 0; +} + +static int hdr_validate_json_size(struct crypt_device *cd, json_object *hdr_jobj, uint64_t hdr_json_size) +{ + json_object *jobj, *jobj1; + const char *json; + uint64_t json_area_size, json_size; + + json_object_object_get_ex(hdr_jobj, "config", &jobj); + json_object_object_get_ex(jobj, "json_size", &jobj1); + + json = json_object_to_json_string_ext(hdr_jobj, + JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE); + json_area_size = json_object_get_uint64(jobj1); + json_size = (uint64_t)strlen(json); + + if (hdr_json_size != json_area_size) { + log_dbg(cd, "JSON area size doesn't match value in binary header."); + return 1; + } + + if (json_size > json_area_size) { + log_dbg(cd, "JSON doesn't fit in the designated area."); + return 1; + } + + return 0; +} + +int LUKS2_check_json_size(struct crypt_device *cd, const struct luks2_hdr *hdr) +{ + return hdr_validate_json_size(cd, hdr->jobj, hdr->hdr_size - LUKS2_HDR_BIN_LEN); +} + +static int hdr_validate_keyslots(struct crypt_device *cd, json_object *hdr_jobj) +{ + json_object *jobj; + + if (!json_object_object_get_ex(hdr_jobj, "keyslots", &jobj)) { + log_dbg(cd, "Missing keyslots section."); + return 1; + } + + json_object_object_foreach(jobj, key, val) { + if (!numbered(cd, "Keyslot", key)) + return 1; + if (LUKS2_keyslot_validate(cd, hdr_jobj, val, key)) + return 1; + } + + return 0; +} + +static int hdr_validate_tokens(struct crypt_device *cd, json_object *hdr_jobj) +{ + json_object *jobj; + + if (!json_object_object_get_ex(hdr_jobj, "tokens", &jobj)) { + log_dbg(cd, "Missing tokens section."); + return 1; + } + + json_object_object_foreach(jobj, key, val) { + if (!numbered(cd, "Token", key)) + return 1; + if (LUKS2_token_validate(cd, hdr_jobj, val, key)) + return 1; + } + + return 0; +} + +static int hdr_validate_crypt_segment(struct crypt_device *cd, + json_object *jobj, const char *key, json_object *jobj_digests, + uint64_t offset, uint64_t size) +{ + json_object *jobj_ivoffset, *jobj_sector_size, *jobj_integrity; + uint32_t sector_size; + uint64_t ivoffset; + + if (!(jobj_ivoffset = json_contains(cd, jobj, key, "Segment", "iv_tweak", json_type_string)) || + !json_contains(cd, jobj, key, "Segment", "encryption", json_type_string) || + !(jobj_sector_size = json_contains(cd, jobj, key, "Segment", "sector_size", json_type_int))) + return 1; + + /* integrity */ + if (json_object_object_get_ex(jobj, "integrity", &jobj_integrity)) { + if (!json_contains(cd, jobj, key, "Segment", "integrity", json_type_object) || + !json_contains(cd, jobj_integrity, key, "Segment integrity", "type", json_type_string) || + !json_contains(cd, jobj_integrity, key, "Segment integrity", "journal_encryption", json_type_string) || + !json_contains(cd, jobj_integrity, key, "Segment integrity", "journal_integrity", json_type_string)) + return 1; + } + + /* enforce uint32_t type */ + if (!validate_json_uint32(jobj_sector_size)) { + log_dbg(cd, "Illegal field \"sector_size\":%s.", + json_object_get_string(jobj_sector_size)); + return 1; + } + + sector_size = json_object_get_uint32(jobj_sector_size); + if (!sector_size || MISALIGNED_512(sector_size)) { + log_dbg(cd, "Illegal sector size: %" PRIu32, sector_size); + return 1; + } + + if (!numbered(cd, "iv_tweak", json_object_get_string(jobj_ivoffset)) || + !json_str_to_uint64(jobj_ivoffset, &ivoffset)) { + log_dbg(cd, "Illegal iv_tweak value."); + return 1; + } + + if (size % sector_size) { + log_dbg(cd, "Size field has to be aligned to sector size: %" PRIu32, sector_size); + return 1; + } + + return !segment_has_digest(key, jobj_digests); +} + +static int hdr_validate_segments(struct crypt_device *cd, json_object *hdr_jobj) +{ + json_object *jobj, *jobj_digests, *jobj_offset, *jobj_size, *jobj_type, *jobj_flags; + int i; + uint64_t offset, size; + + if (!json_object_object_get_ex(hdr_jobj, "segments", &jobj)) { + log_dbg(cd, "Missing segments section."); + return 1; + } + + if (json_object_object_length(jobj) < 1) { + log_dbg(cd, "Empty segments section."); + return 1; + } + + /* digests should already be validated */ + if (!json_object_object_get_ex(hdr_jobj, "digests", &jobj_digests)) + return 1; + + json_object_object_foreach(jobj, key, val) { + if (!numbered(cd, "Segment", key)) + return 1; + + /* those fields are mandatory for all segment types */ + if (!(jobj_type = json_contains(cd, val, key, "Segment", "type", json_type_string)) || + !(jobj_offset = json_contains(cd, val, key, "Segment", "offset", json_type_string)) || + !(jobj_size = json_contains(cd, val, key, "Segment", "size", json_type_string))) + return 1; + + if (!numbered(cd, "offset", json_object_get_string(jobj_offset)) || + !json_str_to_uint64(jobj_offset, &offset)) + return 1; + + /* size "dynamic" means whole device starting at 'offset' */ + if (strcmp(json_object_get_string(jobj_size), "dynamic")) { + if (!numbered(cd, "size", json_object_get_string(jobj_size)) || + !json_str_to_uint64(jobj_size, &size) || !size) + return 1; + } else + size = 0; + + /* all device-mapper devices are aligned to 512 sector size */ + if (MISALIGNED_512(offset)) { + log_dbg(cd, "Offset field has to be aligned to sector size: %" PRIu32, SECTOR_SIZE); + return 1; + } + if (MISALIGNED_512(size)) { + log_dbg(cd, "Size field has to be aligned to sector size: %" PRIu32, SECTOR_SIZE); + return 1; + } + + /* flags array is optional and must contain strings */ + if (json_object_object_get_ex(val, "flags", NULL)) { + if (!(jobj_flags = json_contains(cd, val, key, "Segment", "flags", json_type_array))) + return 1; + for (i = 0; i < (int) json_object_array_length(jobj_flags); i++) + if (!json_object_is_type(json_object_array_get_idx(jobj_flags, i), json_type_string)) + return 1; + } + + /* crypt */ + if (!strcmp(json_object_get_string(jobj_type), "crypt") && + hdr_validate_crypt_segment(cd, val, key, jobj_digests, offset, size)) + return 1; + } + + return 0; +} + +uint64_t LUKS2_metadata_size(json_object *jobj) +{ + json_object *jobj1, *jobj2; + uint64_t json_size; + + json_object_object_get_ex(jobj, "config", &jobj1); + json_object_object_get_ex(jobj1, "json_size", &jobj2); + json_str_to_uint64(jobj2, &json_size); + + return json_size + LUKS2_HDR_BIN_LEN; +} + +static int hdr_validate_areas(struct crypt_device *cd, json_object *hdr_jobj) +{ + struct interval *intervals; + json_object *jobj_keyslots, *jobj_offset, *jobj_length, *jobj_segments, *jobj_area; + int length, ret, i = 0; + uint64_t metadata_size; + + if (!json_object_object_get_ex(hdr_jobj, "keyslots", &jobj_keyslots)) + return 1; + + /* segments are already validated */ + if (!json_object_object_get_ex(hdr_jobj, "segments", &jobj_segments)) + return 1; + + /* config is already validated */ + metadata_size = LUKS2_metadata_size(hdr_jobj); + + length = json_object_object_length(jobj_keyslots); + + /* Empty section */ + if (length == 0) + return 0; + + if (length < 0) { + log_dbg(cd, "Invalid keyslot areas specification."); + return 1; + } + + intervals = malloc(length * sizeof(*intervals)); + if (!intervals) { + log_dbg(cd, "Not enough memory."); + return -ENOMEM; + } + + json_object_object_foreach(jobj_keyslots, key, val) { + + if (!(jobj_area = json_contains(cd, val, key, "Keyslot", "area", json_type_object)) || + !(jobj_offset = json_contains(cd, jobj_area, key, "Keyslot", "offset", json_type_string)) || + !(jobj_length = json_contains(cd, jobj_area, key, "Keyslot", "size", json_type_string)) || + !numbered(cd, "offset", json_object_get_string(jobj_offset)) || + !numbered(cd, "size", json_object_get_string(jobj_length))) { + free(intervals); + return 1; + } + + /* rule out values > UINT64_MAX */ + if (!json_str_to_uint64(jobj_offset, &intervals[i].offset) || + !json_str_to_uint64(jobj_length, &intervals[i].length)) { + free(intervals); + return 1; + } + + i++; + } + + if (length != i) { + free(intervals); + return 1; + } + + ret = validate_intervals(cd, length, intervals, metadata_size, LUKS2_hdr_and_areas_size(hdr_jobj)) ? 0 : 1; + + free(intervals); + + return ret; +} + +static int hdr_validate_digests(struct crypt_device *cd, json_object *hdr_jobj) +{ + json_object *jarr_keys, *jarr_segs, *jobj, *jobj_keyslots, *jobj_segments; + + if (!json_object_object_get_ex(hdr_jobj, "digests", &jobj)) { + log_dbg(cd, "Missing digests section."); + return 1; + } + + /* keyslots are not yet validated, but we need to know digest doesn't reference missing keyslot */ + if (!json_object_object_get_ex(hdr_jobj, "keyslots", &jobj_keyslots)) + return 1; + + /* segments are not yet validated, but we need to know digest doesn't reference missing segment */ + if (!json_object_object_get_ex(hdr_jobj, "segments", &jobj_segments)) + return 1; + + json_object_object_foreach(jobj, key, val) { + if (!numbered(cd, "Digest", key)) + return 1; + + if (!json_contains(cd, val, key, "Digest", "type", json_type_string) || + !(jarr_keys = json_contains(cd, val, key, "Digest", "keyslots", json_type_array)) || + !(jarr_segs = json_contains(cd, val, key, "Digest", "segments", json_type_array))) + return 1; + + if (!validate_keyslots_array(cd, jarr_keys, jobj_keyslots)) + return 1; + if (!validate_segments_array(cd, jarr_segs, jobj_segments)) + return 1; + } + + return 0; +} + +static int hdr_validate_config(struct crypt_device *cd, json_object *hdr_jobj) +{ + json_object *jobj_config, *jobj, *jobj1; + int i; + uint64_t keyslots_size, metadata_size, segment_offset; + + if (!json_object_object_get_ex(hdr_jobj, "config", &jobj_config)) { + log_dbg(cd, "Missing config section."); + return 1; + } + + if (!(jobj = json_contains(cd, jobj_config, "section", "Config", "json_size", json_type_string)) || + !json_str_to_uint64(jobj, &metadata_size)) + return 1; + + /* single metadata instance is assembled from json area size plus + * binary header size */ + metadata_size += LUKS2_HDR_BIN_LEN; + + if (!(jobj = json_contains(cd, jobj_config, "section", "Config", "keyslots_size", json_type_string)) || + !json_str_to_uint64(jobj, &keyslots_size)) + return 1; + + if (LUKS2_check_metadata_area_size(metadata_size)) { + log_dbg(cd, "Unsupported LUKS2 header size (%" PRIu64 ").", metadata_size); + return 1; + } + + if (LUKS2_check_keyslots_area_size(keyslots_size)) { + log_dbg(cd, "Unsupported LUKS2 keyslots size (%" PRIu64 ").", keyslots_size); + return 1; + } + + /* + * validate keyslots_size fits in between (2 * metadata_size) and first + * segment_offset (except detached header) + */ + json_object_object_get_ex(hdr_jobj, "segments", &jobj); + segment_offset = get_first_data_offset(jobj, "crypt"); + if (segment_offset && + (segment_offset < keyslots_size || + (segment_offset - keyslots_size) < (2 * metadata_size))) { + log_dbg(cd, "keyslots_size is too large %" PRIu64 " (bytes). Data offset: %" PRIu64 + ", keyslots offset: %" PRIu64, keyslots_size, segment_offset, 2 * metadata_size); + return 1; + } + + /* Flags array is optional */ + if (json_object_object_get_ex(jobj_config, "flags", &jobj)) { + if (!json_contains(cd, jobj_config, "section", "Config", "flags", json_type_array)) + return 1; + + /* All array members must be strings */ + for (i = 0; i < (int) json_object_array_length(jobj); i++) + if (!json_object_is_type(json_object_array_get_idx(jobj, i), json_type_string)) + return 1; + } + + /* Requirements object is optional */ + if (json_object_object_get_ex(jobj_config, "requirements", &jobj)) { + if (!json_contains(cd, jobj_config, "section", "Config", "requirements", json_type_object)) + return 1; + + /* Mandatory array is optional */ + if (json_object_object_get_ex(jobj, "mandatory", &jobj1)) { + if (!json_contains(cd, jobj, "section", "Requirements", "mandatory", json_type_array)) + return 1; + + /* All array members must be strings */ + for (i = 0; i < (int) json_object_array_length(jobj1); i++) + if (!json_object_is_type(json_object_array_get_idx(jobj1, i), json_type_string)) + return 1; + } + } + + return 0; +} + +int LUKS2_hdr_validate(struct crypt_device *cd, json_object *hdr_jobj, uint64_t json_size) +{ + struct { + int (*validate)(struct crypt_device *, json_object *); + } checks[] = { + { hdr_validate_tokens }, + { hdr_validate_digests }, + { hdr_validate_segments }, + { hdr_validate_keyslots }, + { hdr_validate_config }, + { hdr_validate_areas }, + { NULL } + }; + int i; + + if (!hdr_jobj) + return 1; + + for (i = 0; checks[i].validate; i++) + if (checks[i].validate && checks[i].validate(cd, hdr_jobj)) + return 1; + + if (hdr_validate_json_size(cd, hdr_jobj, json_size)) + return 1; + + /* validate keyslot implementations */ + if (LUKS2_keyslots_validate(cd, hdr_jobj)) + return 1; + + return 0; +} + +/* FIXME: should we expose do_recovery parameter explicitly? */ +int LUKS2_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr, int repair) +{ + int r; + + r = device_read_lock(cd, crypt_metadata_device(cd)); + if (r) { + log_err(cd, _("Failed to acquire read lock on device %s."), + device_path(crypt_metadata_device(cd))); + return r; + } + + r = LUKS2_disk_hdr_read(cd, hdr, crypt_metadata_device(cd), 1, !repair); + if (r == -EAGAIN) { + /* unlikely: auto-recovery is required and failed due to read lock being held */ + device_read_unlock(cd, crypt_metadata_device(cd)); + + r = device_write_lock(cd, crypt_metadata_device(cd)); + if (r) { + log_err(cd, _("Failed to acquire write lock on device %s."), + device_path(crypt_metadata_device(cd))); + return r; + } + + r = LUKS2_disk_hdr_read(cd, hdr, crypt_metadata_device(cd), 1, !repair); + + device_write_unlock(cd, crypt_metadata_device(cd)); + } else + device_read_unlock(cd, crypt_metadata_device(cd)); + + return r; +} + +int LUKS2_hdr_write(struct crypt_device *cd, struct luks2_hdr *hdr) +{ + /* NOTE: is called before LUKS2 validation routines */ + /* erase unused digests (no assigned keyslot or segment) */ + LUKS2_digests_erase_unused(cd, hdr); + + if (LUKS2_hdr_validate(cd, hdr->jobj, hdr->hdr_size - LUKS2_HDR_BIN_LEN)) + return -EINVAL; + + return LUKS2_disk_hdr_write(cd, hdr, crypt_metadata_device(cd)); +} + +int LUKS2_hdr_uuid(struct crypt_device *cd, struct luks2_hdr *hdr, const char *uuid) +{ + uuid_t partitionUuid; + + if (uuid && uuid_parse(uuid, partitionUuid) == -1) { + log_err(cd, _("Wrong LUKS UUID format provided.")); + return -EINVAL; + } + if (!uuid) + uuid_generate(partitionUuid); + + uuid_unparse(partitionUuid, hdr->uuid); + + return LUKS2_hdr_write(cd, hdr); +} + +int LUKS2_hdr_labels(struct crypt_device *cd, struct luks2_hdr *hdr, + const char *label, const char *subsystem, int commit) +{ + //FIXME: check if the labels are the same and skip this. + + memset(hdr->label, 0, LUKS2_LABEL_L); + if (label) + strncpy(hdr->label, label, LUKS2_LABEL_L-1); + + memset(hdr->subsystem, 0, LUKS2_LABEL_L); + if (subsystem) + strncpy(hdr->subsystem, subsystem, LUKS2_LABEL_L-1); + + return commit ? LUKS2_hdr_write(cd, hdr) : 0; +} + +void LUKS2_hdr_free(struct crypt_device *cd, struct luks2_hdr *hdr) +{ + if (json_object_put(hdr->jobj)) + hdr->jobj = NULL; + else if (hdr->jobj) + log_dbg(cd, "LUKS2 header still in use"); +} + +uint64_t LUKS2_keyslots_size(json_object *jobj) +{ + json_object *jobj1, *jobj2; + uint64_t keyslots_size; + + json_object_object_get_ex(jobj, "config", &jobj1); + json_object_object_get_ex(jobj1, "keyslots_size", &jobj2); + json_str_to_uint64(jobj2, &keyslots_size); + + return keyslots_size; +} + +uint64_t LUKS2_hdr_and_areas_size(json_object *jobj) +{ + return 2 * LUKS2_metadata_size(jobj) + LUKS2_keyslots_size(jobj); +} + +int LUKS2_hdr_backup(struct crypt_device *cd, struct luks2_hdr *hdr, + const char *backup_file) +{ + struct device *device = crypt_metadata_device(cd); + int r = 0, devfd = -1; + ssize_t hdr_size; + ssize_t buffer_size; + char *buffer = NULL; + + hdr_size = LUKS2_hdr_and_areas_size(hdr->jobj); + buffer_size = size_round_up(hdr_size, crypt_getpagesize()); + + buffer = crypt_safe_alloc(buffer_size); + if (!buffer) + return -ENOMEM; + + log_dbg(cd, "Storing backup of header (%zu bytes).", hdr_size); + log_dbg(cd, "Output backup file size: %zu bytes.", buffer_size); + + r = device_read_lock(cd, device); + if (r) { + log_err(cd, _("Failed to acquire read lock on device %s."), + device_path(crypt_metadata_device(cd))); + crypt_safe_free(buffer); + return r; + } + + devfd = device_open_locked(cd, device, O_RDONLY); + if (devfd < 0) { + device_read_unlock(cd, device); + log_err(cd, _("Device %s is not a valid LUKS device."), device_path(device)); + crypt_safe_free(buffer); + return devfd == -1 ? -EINVAL : devfd; + } + + if (read_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), buffer, hdr_size) < hdr_size) { + close(devfd); + device_read_unlock(cd, device); + crypt_safe_free(buffer); + return -EIO; + } + close(devfd); + + device_read_unlock(cd, device); + + devfd = open(backup_file, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR); + if (devfd == -1) { + if (errno == EEXIST) + log_err(cd, _("Requested header backup file %s already exists."), backup_file); + else + log_err(cd, _("Cannot create header backup file %s."), backup_file); + crypt_safe_free(buffer); + return -EINVAL; + } + if (write_buffer(devfd, buffer, buffer_size) < buffer_size) { + log_err(cd, _("Cannot write header backup file %s."), backup_file); + r = -EIO; + } else + r = 0; + + close(devfd); + crypt_safe_free(buffer); + return r; +} + +static int reqs_unknown(uint32_t reqs) +{ + return reqs & CRYPT_REQUIREMENT_UNKNOWN; +} + +static int reqs_reencrypt(uint32_t reqs) +{ + return reqs & CRYPT_REQUIREMENT_OFFLINE_REENCRYPT; +} + +int LUKS2_hdr_restore(struct crypt_device *cd, struct luks2_hdr *hdr, + const char *backup_file) +{ + struct device *backup_device, *device = crypt_metadata_device(cd); + int r, devfd = -1, diff_uuid = 0; + ssize_t buffer_size = 0; + char *buffer = NULL, msg[1024]; + struct luks2_hdr hdr_file; + struct luks2_hdr tmp_hdr = {}; + uint32_t reqs = 0; + + r = device_alloc(cd, &backup_device, backup_file); + if (r < 0) + return r; + + /* FIXME: why lock backup device ? */ + r = device_read_lock(cd, backup_device); + if (r) { + log_err(cd, _("Failed to acquire read lock on device %s."), + device_path(backup_device)); + device_free(cd, backup_device); + return r; + } + + r = LUKS2_disk_hdr_read(cd, &hdr_file, backup_device, 0, 0); + device_read_unlock(cd, backup_device); + device_free(cd, backup_device); + + if (r < 0) { + log_err(cd, _("Backup file doesn't contain valid LUKS header.")); + goto out; + } + + /* do not allow header restore from backup with unmet requirements */ + if (LUKS2_unmet_requirements(cd, &hdr_file, 0, 1)) { + log_err(cd, _("Forbidden LUKS2 requirements detected in backup %s."), + backup_file); + r = -ETXTBSY; + goto out; + } + + buffer_size = LUKS2_hdr_and_areas_size(hdr_file.jobj); + buffer = crypt_safe_alloc(buffer_size); + if (!buffer) { + r = -ENOMEM; + goto out; + } + + devfd = open(backup_file, O_RDONLY); + if (devfd == -1) { + log_err(cd, _("Cannot open header backup file %s."), backup_file); + r = -EINVAL; + goto out; + } + + if (read_buffer(devfd, buffer, buffer_size) < buffer_size) { + log_err(cd, _("Cannot read header backup file %s."), backup_file); + r = -EIO; + goto out; + } + close(devfd); + devfd = -1; + + r = LUKS2_hdr_read(cd, &tmp_hdr, 0); + if (r == 0) { + log_dbg(cd, "Device %s already contains LUKS2 header, checking UUID and requirements.", device_path(device)); + r = LUKS2_config_get_requirements(cd, &tmp_hdr, &reqs); + if (r) + goto out; + + if (memcmp(tmp_hdr.uuid, hdr_file.uuid, LUKS2_UUID_L)) + diff_uuid = 1; + + if (!reqs_reencrypt(reqs)) { + log_dbg(cd, "Checking LUKS2 header size and offsets."); + if (LUKS2_get_data_offset(&tmp_hdr) != LUKS2_get_data_offset(&hdr_file)) { + log_err(cd, _("Data offset differ on device and backup, restore failed.")); + r = -EINVAL; + goto out; + } + /* FIXME: what could go wrong? Erase if we're fine with consequences */ + if (buffer_size != (ssize_t) LUKS2_hdr_and_areas_size(tmp_hdr.jobj)) { + log_err(cd, _("Binary header with keyslot areas size differ on device and backup, restore failed.")); + r = -EINVAL; + goto out; + } + } + } + + r = snprintf(msg, sizeof(msg), _("Device %s %s%s%s%s"), device_path(device), + r ? _("does not contain LUKS2 header. Replacing header can destroy data on that device.") : + _("already contains LUKS2 header. Replacing header will destroy existing keyslots."), + diff_uuid ? _("\nWARNING: real device header has different UUID than backup!") : "", + reqs_unknown(reqs) ? _("\nWARNING: unknown LUKS2 requirements detected in real device header!" + "\nReplacing header with backup may corrupt the data on that device!") : "", + reqs_reencrypt(reqs) ? _("\nWARNING: Unfinished offline reencryption detected on the device!" + "\nReplacing header with backup may corrupt data.") : ""); + if (r < 0 || (size_t) r >= sizeof(msg)) { + r = -ENOMEM; + goto out; + } + + if (!crypt_confirm(cd, msg)) { + r = -EINVAL; + goto out; + } + + log_dbg(cd, "Storing backup of header (%zu bytes) to device %s.", buffer_size, device_path(device)); + + /* TODO: perform header restore on bdev in stand-alone routine? */ + r = device_write_lock(cd, device); + if (r) { + log_err(cd, _("Failed to acquire write lock on device %s."), + device_path(device)); + goto out; + } + + devfd = device_open_locked(cd, device, O_RDWR); + if (devfd < 0) { + if (errno == EACCES) + log_err(cd, _("Cannot write to device %s, permission denied."), + device_path(device)); + else + log_err(cd, _("Cannot open device %s."), device_path(device)); + device_write_unlock(cd, device); + r = -EINVAL; + goto out; + } + + if (write_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), buffer, buffer_size) < buffer_size) + r = -EIO; + else + r = 0; + + device_write_unlock(cd, device); + /* end of TODO */ + +out: + LUKS2_hdr_free(cd, hdr); + LUKS2_hdr_free(cd, &hdr_file); + LUKS2_hdr_free(cd, &tmp_hdr); + crypt_memzero(&hdr_file, sizeof(hdr_file)); + crypt_memzero(&tmp_hdr, sizeof(tmp_hdr)); + crypt_safe_free(buffer); + + if (devfd >= 0) { + device_sync(cd, device, devfd); + close(devfd); + } + + + return r; +} + +/* + * Persistent config flags + */ +static const struct { + uint32_t flag; + const char *description; +} persistent_flags[] = { + { CRYPT_ACTIVATE_ALLOW_DISCARDS, "allow-discards" }, + { CRYPT_ACTIVATE_SAME_CPU_CRYPT, "same-cpu-crypt" }, + { CRYPT_ACTIVATE_SUBMIT_FROM_CRYPT_CPUS, "submit-from-crypt-cpus" }, + { CRYPT_ACTIVATE_NO_JOURNAL, "no-journal" }, + { 0, NULL } +}; + +int LUKS2_config_get_flags(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t *flags) +{ + json_object *jobj1, *jobj_config, *jobj_flags; + int i, j, found; + + if (!hdr || !flags) + return -EINVAL; + + *flags = 0; + + if (!json_object_object_get_ex(hdr->jobj, "config", &jobj_config)) + return 0; + + if (!json_object_object_get_ex(jobj_config, "flags", &jobj_flags)) + return 0; + + for (i = 0; i < (int) json_object_array_length(jobj_flags); i++) { + jobj1 = json_object_array_get_idx(jobj_flags, i); + found = 0; + for (j = 0; persistent_flags[j].description && !found; j++) + if (!strcmp(persistent_flags[j].description, + json_object_get_string(jobj1))) { + *flags |= persistent_flags[j].flag; + log_dbg(cd, "Using persistent flag %s.", + json_object_get_string(jobj1)); + found = 1; + } + if (!found) + log_verbose(cd, _("Ignored unknown flag %s."), + json_object_get_string(jobj1)); + } + + return 0; +} + +int LUKS2_config_set_flags(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t flags) +{ + json_object *jobj_config, *jobj_flags; + int i; + + if (!json_object_object_get_ex(hdr->jobj, "config", &jobj_config)) + return 0; + + jobj_flags = json_object_new_array(); + + for (i = 0; persistent_flags[i].description; i++) { + if (flags & persistent_flags[i].flag) { + log_dbg(cd, "Setting persistent flag: %s.", persistent_flags[i].description); + json_object_array_add(jobj_flags, + json_object_new_string(persistent_flags[i].description)); + } + } + + /* Replace or add new flags array */ + json_object_object_add(jobj_config, "flags", jobj_flags); + + return LUKS2_hdr_write(cd, hdr); +} + +/* + * json format example (mandatory array must not be ignored, + * all other future fields may be added later) + * + * "requirements": { + * mandatory : [], + * optional0 : [], + * optional1 : "lala" + * } + */ + +/* LUKS2 library requirements */ +static const struct { + uint32_t flag; + const char *description; +} requirements_flags[] = { + { CRYPT_REQUIREMENT_OFFLINE_REENCRYPT, "offline-reencrypt" }, + { 0, NULL } +}; + +static uint32_t 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 CRYPT_REQUIREMENT_UNKNOWN; +} + +/* + * returns count of requirements (past cryptsetup 2.0 release) + */ +int LUKS2_config_get_requirements(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t *reqs) +{ + json_object *jobj_config, *jobj_requirements, *jobj_mandatory, *jobj; + int i, len; + uint32_t req; + + assert(hdr); + if (!hdr || !reqs) + return -EINVAL; + + *reqs = 0; + + if (!json_object_object_get_ex(hdr->jobj, "config", &jobj_config)) + return 0; + + if (!json_object_object_get_ex(jobj_config, "requirements", &jobj_requirements)) + return 0; + + if (!json_object_object_get_ex(jobj_requirements, "mandatory", &jobj_mandatory)) + return 0; + + len = (int) json_object_array_length(jobj_mandatory); + if (len <= 0) + return 0; + + log_dbg(cd, "LUKS2 requirements detected:"); + + 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)); + log_dbg(cd, "%s - %sknown", json_object_get_string(jobj), + reqs_unknown(req) ? "un" : ""); + *reqs |= req; + } + + return 0; +} + +int LUKS2_config_set_requirements(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t reqs) +{ + json_object *jobj_config, *jobj_requirements, *jobj_mandatory, *jobj; + int i, r = -EINVAL; + + if (!hdr) + return -EINVAL; + + jobj_mandatory = json_object_new_array(); + if (!jobj_mandatory) + 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); + if (!jobj) { + r = -ENOMEM; + goto err; + } + json_object_array_add(jobj_mandatory, jobj); + /* erase processed flag from input set */ + reqs &= ~(requirements_flags[i].flag); + } + } + + /* any remaining bit in requirements is unknown therefore illegal */ + if (reqs) { + log_dbg(cd, "Illegal requirement flag(s) requested"); + goto err; + } + + if (!json_object_object_get_ex(hdr->jobj, "config", &jobj_config)) + goto err; + + if (!json_object_object_get_ex(jobj_config, "requirements", &jobj_requirements)) { + jobj_requirements = json_object_new_object(); + if (!jobj_requirements) { + r = -ENOMEM; + goto err; + } + json_object_object_add(jobj_config, "requirements", jobj_requirements); + } + + if (json_object_array_length(jobj_mandatory) > 0) { + /* replace mandatory field with new values */ + json_object_object_add(jobj_requirements, "mandatory", jobj_mandatory); + } else { + /* new mandatory field was empty, delete old one */ + json_object_object_del(jobj_requirements, "mandatory"); + json_object_put(jobj_mandatory); + } + + /* remove empty requirements object */ + if (!json_object_object_length(jobj_requirements)) + json_object_object_del(jobj_config, "requirements"); + + return LUKS2_hdr_write(cd, hdr); +err: + json_object_put(jobj_mandatory); + return r; +} + +/* + * Header dump + */ +static void hdr_dump_config(struct crypt_device *cd, json_object *hdr_jobj) +{ + + json_object *jobj1, *jobj_config, *jobj_flags, *jobj_requirements, *jobj_mandatory; + int i = 0, flags = 0, reqs = 0; + + log_std(cd, "Flags: \t"); + + if (json_object_object_get_ex(hdr_jobj, "config", &jobj_config)) { + if (json_object_object_get_ex(jobj_config, "flags", &jobj_flags)) + flags = (int) json_object_array_length(jobj_flags); + if (json_object_object_get_ex(jobj_config, "requirements", &jobj_requirements) && + json_object_object_get_ex(jobj_requirements, "mandatory", &jobj_mandatory)) + reqs = (int) json_object_array_length(jobj_mandatory); + } + + for (i = 0; i < flags; i++) { + jobj1 = json_object_array_get_idx(jobj_flags, i); + log_std(cd, "%s ", json_object_get_string(jobj1)); + } + + log_std(cd, "%s\n%s", flags > 0 ? "" : "(no flags)", reqs > 0 ? "" : "\n"); + + if (reqs > 0) { + log_std(cd, "Requirements:\t"); + for (i = 0; i < reqs; i++) { + jobj1 = json_object_array_get_idx(jobj_mandatory, i); + log_std(cd, "%s ", json_object_get_string(jobj1)); + } + log_std(cd, "\n\n"); + } +} + +static const char *get_priority_desc(json_object *jobj) +{ + crypt_keyslot_priority priority; + json_object *jobj_priority; + const char *text; + + if (json_object_object_get_ex(jobj, "priority", &jobj_priority)) + priority = (crypt_keyslot_priority)(int)json_object_get_int(jobj_priority); + else + priority = CRYPT_SLOT_PRIORITY_NORMAL; + + switch (priority) { + case CRYPT_SLOT_PRIORITY_IGNORE: text = "ignored"; break; + case CRYPT_SLOT_PRIORITY_PREFER: text = "preferred"; break; + case CRYPT_SLOT_PRIORITY_NORMAL: text = "normal"; break; + default: text = "invalid"; + } + + return text; +} + +static void hdr_dump_keyslots(struct crypt_device *cd, json_object *hdr_jobj) +{ + char slot[16]; + json_object *keyslots_jobj, *digests_jobj, *jobj2, *jobj3, *val; + const char *tmps; + int i, j, r; + + log_std(cd, "Keyslots:\n"); + json_object_object_get_ex(hdr_jobj, "keyslots", &keyslots_jobj); + + for (j = 0; j < LUKS2_KEYSLOTS_MAX; j++) { + (void) snprintf(slot, sizeof(slot), "%i", j); + json_object_object_get_ex(keyslots_jobj, slot, &val); + if (!val) + continue; + + json_object_object_get_ex(val, "type", &jobj2); + tmps = json_object_get_string(jobj2); + + r = LUKS2_keyslot_for_segment(crypt_get_hdr(cd, CRYPT_LUKS2), j, CRYPT_DEFAULT_SEGMENT); + log_std(cd, " %s: %s%s\n", slot, tmps, r == -ENOENT ? " (unbound)" : ""); + + if (json_object_object_get_ex(val, "key_size", &jobj2)) + log_std(cd, "\tKey: %u bits\n", json_object_get_uint32(jobj2) * 8); + + log_std(cd, "\tPriority: %s\n", get_priority_desc(val)); + + LUKS2_keyslot_dump(cd, j); + + json_object_object_get_ex(hdr_jobj, "digests", &digests_jobj); + json_object_object_foreach(digests_jobj, key2, val2) { + json_object_object_get_ex(val2, "keyslots", &jobj2); + for (i = 0; i < (int) json_object_array_length(jobj2); i++) { + jobj3 = json_object_array_get_idx(jobj2, i); + if (!strcmp(slot, json_object_get_string(jobj3))) { + log_std(cd, "\tDigest ID: %s\n", key2); + } + } + } + } +} + +static void hdr_dump_tokens(struct crypt_device *cd, json_object *hdr_jobj) +{ + char token[16]; + json_object *tokens_jobj, *jobj2, *jobj3, *val; + const char *tmps; + int i, j; + + log_std(cd, "Tokens:\n"); + json_object_object_get_ex(hdr_jobj, "tokens", &tokens_jobj); + + for (j = 0; j < LUKS2_TOKENS_MAX; j++) { + (void) snprintf(token, sizeof(token), "%i", j); + json_object_object_get_ex(tokens_jobj, token, &val); + if (!val) + continue; + + json_object_object_get_ex(val, "type", &jobj2); + tmps = json_object_get_string(jobj2); + log_std(cd, " %s: %s\n", token, tmps); + + LUKS2_token_dump(cd, j); + + json_object_object_get_ex(val, "keyslots", &jobj2); + for (i = 0; i < (int) json_object_array_length(jobj2); i++) { + jobj3 = json_object_array_get_idx(jobj2, i); + log_std(cd, "\tKeyslot: %s\n", json_object_get_string(jobj3)); + } + } +} + +static void hdr_dump_segments(struct crypt_device *cd, json_object *hdr_jobj) +{ + char segment[16]; + json_object *jobj_segments, *jobj_segment, *jobj1, *jobj2; + int i, j, flags; + uint64_t value; + + log_std(cd, "Data segments:\n"); + json_object_object_get_ex(hdr_jobj, "segments", &jobj_segments); + + for (i = 0; i < LUKS2_SEGMENT_MAX; i++) { + (void) snprintf(segment, sizeof(segment), "%i", i); + if (!json_object_object_get_ex(jobj_segments, segment, &jobj_segment)) + continue; + + json_object_object_get_ex(jobj_segment, "type", &jobj1); + log_std(cd, " %s: %s\n", segment, json_object_get_string(jobj1)); + + json_object_object_get_ex(jobj_segment, "offset", &jobj1); + json_str_to_uint64(jobj1, &value); + log_std(cd, "\toffset: %" PRIu64 " [bytes]\n", value); + + json_object_object_get_ex(jobj_segment, "size", &jobj1); + if (!(strcmp(json_object_get_string(jobj1), "dynamic"))) + log_std(cd, "\tlength: (whole device)\n"); + else { + json_str_to_uint64(jobj1, &value); + log_std(cd, "\tlength: %" PRIu64 " [bytes]\n", value); + } + + if (json_object_object_get_ex(jobj_segment, "encryption", &jobj1)) + log_std(cd, "\tcipher: %s\n", json_object_get_string(jobj1)); + + if (json_object_object_get_ex(jobj_segment, "sector_size", &jobj1)) + log_std(cd, "\tsector: %" PRIu32 " [bytes]\n", json_object_get_uint32(jobj1)); + + if (json_object_object_get_ex(jobj_segment, "integrity", &jobj1) && + json_object_object_get_ex(jobj1, "type", &jobj2)) + log_std(cd, "\tintegrity: %s\n", json_object_get_string(jobj2)); + + if (json_object_object_get_ex(jobj_segment, "flags", &jobj1) && + (flags = (int)json_object_array_length(jobj1)) > 0) { + jobj2 = json_object_array_get_idx(jobj1, 0); + log_std(cd, "\tflags : %s", json_object_get_string(jobj2)); + for (j = 1; j < flags; j++) { + jobj2 = json_object_array_get_idx(jobj1, j); + log_std(cd, ", %s", json_object_get_string(jobj2)); + } + log_std(cd, "\n"); + } + + log_std(cd, "\n"); + } +} + +static void hdr_dump_digests(struct crypt_device *cd, json_object *hdr_jobj) +{ + char key[16]; + json_object *jobj1, *jobj2, *val; + const char *tmps; + int i; + + log_std(cd, "Digests:\n"); + json_object_object_get_ex(hdr_jobj, "digests", &jobj1); + + for (i = 0; i < LUKS2_DIGEST_MAX; i++) { + (void) snprintf(key, sizeof(key), "%i", i); + json_object_object_get_ex(jobj1, key, &val); + if (!val) + continue; + + json_object_object_get_ex(val, "type", &jobj2); + tmps = json_object_get_string(jobj2); + log_std(cd, " %s: %s\n", key, tmps); + + LUKS2_digest_dump(cd, i); + } +} + +int LUKS2_hdr_dump(struct crypt_device *cd, struct luks2_hdr *hdr) +{ + if (!hdr->jobj) + return -EINVAL; + + JSON_DBG(cd, hdr->jobj, NULL); + + log_std(cd, "LUKS header information\n"); + log_std(cd, "Version: \t%u\n", hdr->version); + log_std(cd, "Epoch: \t%" PRIu64 "\n", hdr->seqid); + log_std(cd, "Metadata area: \t%" PRIu64 " [bytes]\n", LUKS2_metadata_size(hdr->jobj)); + log_std(cd, "Keyslots area: \t%" PRIu64 " [bytes]\n", LUKS2_keyslots_size(hdr->jobj)); + log_std(cd, "UUID: \t%s\n", *hdr->uuid ? hdr->uuid : "(no UUID)"); + log_std(cd, "Label: \t%s\n", *hdr->label ? hdr->label : "(no label)"); + log_std(cd, "Subsystem: \t%s\n", *hdr->subsystem ? hdr->subsystem : "(no subsystem)"); + + hdr_dump_config(cd, hdr->jobj); + hdr_dump_segments(cd, hdr->jobj); + hdr_dump_keyslots(cd, hdr->jobj); + hdr_dump_tokens(cd, hdr->jobj); + hdr_dump_digests(cd, hdr->jobj); + + return 0; +} + +uint64_t LUKS2_get_data_offset(struct luks2_hdr *hdr) +{ + json_object *jobj1; + + if (!json_object_object_get_ex(hdr->jobj, "segments", &jobj1)) + return 0; + + return get_first_data_offset(jobj1, "crypt") / SECTOR_SIZE; +} + +const char *LUKS2_get_cipher(struct luks2_hdr *hdr, int segment) +{ + json_object *jobj1, *jobj2, *jobj3; + char buf[16]; + + if (segment < 0 || snprintf(buf, sizeof(buf), "%u", segment) < 1) + return NULL; + + if (!json_object_object_get_ex(hdr->jobj, "segments", &jobj1)) + return NULL; + + if (!json_object_object_get_ex(jobj1, buf, &jobj2)) + return NULL; + + if (json_object_object_get_ex(jobj2, "encryption", &jobj3)) + return json_object_get_string(jobj3); + + /* FIXME: default encryption (for other segment types) must be string here. */ + return "null"; + +} + +const char *LUKS2_get_keyslot_cipher(struct luks2_hdr *hdr, int keyslot, size_t *key_size) +{ + json_object *jobj_keyslot, *jobj_area, *jobj1; + + jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot); + if (!jobj_keyslot) + return NULL; + + if (!json_object_object_get_ex(jobj_keyslot, "area", &jobj_area)) + return NULL; + + /* currently we only support raw length preserving area encryption */ + json_object_object_get_ex(jobj_area, "type", &jobj1); + if (strcmp(json_object_get_string(jobj1), "raw")) + return NULL; + + if (!json_object_object_get_ex(jobj_area, "key_size", &jobj1)) + return NULL; + *key_size = json_object_get_int(jobj1); + + if (!json_object_object_get_ex(jobj_area, "encryption", &jobj1)) + return NULL; + + return json_object_get_string(jobj1); +} + +const char *LUKS2_get_integrity(struct luks2_hdr *hdr, int segment) +{ + json_object *jobj1, *jobj2, *jobj3, *jobj4; + char buf[16]; + + if (segment < 0 || snprintf(buf, sizeof(buf), "%u", segment) < 1) + return NULL; + + if (!json_object_object_get_ex(hdr->jobj, "segments", &jobj1)) + return NULL; + + if (!json_object_object_get_ex(jobj1, buf, &jobj2)) + return NULL; + + if (!json_object_object_get_ex(jobj2, "integrity", &jobj3)) + return NULL; + + if (!json_object_object_get_ex(jobj3, "type", &jobj4)) + return NULL; + + return json_object_get_string(jobj4); +} + +/* FIXME: this only ensures that once we have journal encryption, it is not ignored. */ +static int LUKS2_integrity_compatible(struct luks2_hdr *hdr) +{ + json_object *jobj1, *jobj2, *jobj3, *jobj4; + const char *str; + + if (!json_object_object_get_ex(hdr->jobj, "segments", &jobj1)) + return 0; + + if (!json_object_object_get_ex(jobj1, CRYPT_DEFAULT_SEGMENT_STR, &jobj2)) + return 0; + + if (!json_object_object_get_ex(jobj2, "integrity", &jobj3)) + return 0; + + if (!json_object_object_get_ex(jobj3, "journal_encryption", &jobj4) || + !(str = json_object_get_string(jobj4)) || + strcmp(str, "none")) + return 0; + + if (!json_object_object_get_ex(jobj3, "journal_integrity", &jobj4) || + !(str = json_object_get_string(jobj4)) || + strcmp(str, "none")) + return 0; + + return 1; +} + +static int LUKS2_keyslot_get_volume_key_size(struct luks2_hdr *hdr, const char *keyslot) +{ + json_object *jobj1, *jobj2, *jobj3; + + if (!json_object_object_get_ex(hdr->jobj, "keyslots", &jobj1)) + return -1; + + if (!json_object_object_get_ex(jobj1, keyslot, &jobj2)) + return -1; + + if (!json_object_object_get_ex(jobj2, "key_size", &jobj3)) + return -1; + + return json_object_get_int(jobj3); +} + +/* Key size used for encryption of keyslot */ +int LUKS2_get_keyslot_stored_key_size(struct luks2_hdr *hdr, int keyslot) +{ + char keyslot_name[16]; + + if (snprintf(keyslot_name, sizeof(keyslot_name), "%u", keyslot) < 1) + return -1; + + return LUKS2_keyslot_get_volume_key_size(hdr, keyslot_name); +} + +int LUKS2_get_volume_key_size(struct luks2_hdr *hdr, int segment) +{ + json_object *jobj_digests, *jobj_digest_segments, *jobj_digest_keyslots, *jobj1; + char buf[16]; + + if (snprintf(buf, sizeof(buf), "%u", segment) < 1) + return -1; + + json_object_object_get_ex(hdr->jobj, "digests", &jobj_digests); + + json_object_object_foreach(jobj_digests, key, val) { + UNUSED(key); + json_object_object_get_ex(val, "segments", &jobj_digest_segments); + json_object_object_get_ex(val, "keyslots", &jobj_digest_keyslots); + + if (!LUKS2_array_jobj(jobj_digest_segments, buf)) + continue; + if (json_object_array_length(jobj_digest_keyslots) <= 0) + continue; + + jobj1 = json_object_array_get_idx(jobj_digest_keyslots, 0); + + return LUKS2_keyslot_get_volume_key_size(hdr, json_object_get_string(jobj1)); + } + + return -1; +} + +int LUKS2_get_sector_size(struct luks2_hdr *hdr) +{ + json_object *jobj1, *jobj_segment; + + jobj_segment = LUKS2_get_segment_jobj(hdr, CRYPT_DEFAULT_SEGMENT); + if (!jobj_segment) + return SECTOR_SIZE; + + json_object_object_get_ex(jobj_segment, "sector_size", &jobj1); + if (!jobj1) + return SECTOR_SIZE; + + return json_object_get_int(jobj1); +} + +int LUKS2_activate(struct crypt_device *cd, + const char *name, + struct volume_key *vk, + uint32_t flags) +{ + int r; + struct luks2_hdr *hdr = crypt_get_hdr(cd, CRYPT_LUKS2); + struct crypt_dm_active_device dmdi = {}, dmd = { + .uuid = crypt_get_uuid(cd), + }; + + /* do not allow activation when particular requirements detected */ + if ((r = LUKS2_unmet_requirements(cd, hdr, 0, 0))) + return r; + + r = dm_crypt_target_set(&dmd.segment, 0, dmd.size, crypt_data_device(cd), + vk, crypt_get_cipher_spec(cd), crypt_get_iv_offset(cd), + crypt_get_data_offset(cd), crypt_get_integrity(cd) ?: "none", + crypt_get_integrity_tag_size(cd), crypt_get_sector_size(cd)); + if (r < 0) + return r; + + /* Add persistent activation flags */ + if (!(flags & CRYPT_ACTIVATE_IGNORE_PERSISTENT)) + LUKS2_config_get_flags(cd, hdr, &dmd.flags); + + dmd.flags |= flags; + + if (crypt_get_integrity_tag_size(cd)) { + if (!LUKS2_integrity_compatible(hdr)) { + log_err(cd, "Unsupported device integrity configuration."); + return -EINVAL; + } + + r = INTEGRITY_create_dmd_device(cd, NULL, NULL, NULL, NULL, &dmdi, dmd.flags); + if (r) + return r; + + dmd.segment.u.crypt.offset = 0; + dmd.segment.size = dmdi.segment.size; + + r = create_or_reload_device_with_integrity(cd, name, CRYPT_LUKS2, &dmd, &dmdi); + } else + r = create_or_reload_device(cd, name, CRYPT_LUKS2, &dmd); + + dm_targets_free(cd, &dmd); + dm_targets_free(cd, &dmdi); + + return r; +} + +int LUKS2_unmet_requirements(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t reqs_mask, int quiet) +{ + uint32_t reqs; + int r = LUKS2_config_get_requirements(cd, hdr, &reqs); + + if (r) { + if (!quiet) + log_err(cd, _("Failed to read LUKS2 requirements.")); + return r; + } + + /* do not mask unknown requirements check */ + if (reqs_unknown(reqs)) { + if (!quiet) + log_err(cd, _("Unmet LUKS2 requirements detected.")); + return -ETXTBSY; + } + + /* mask out permitted requirements */ + reqs &= ~reqs_mask; + + if (reqs_reencrypt(reqs) && !quiet) + log_err(cd, _("Offline reencryption in progress. Aborting.")); + + /* any remaining unmasked requirement fails the check */ + return reqs ? -EINVAL : 0; +} + +/* + * NOTE: this routine is called on json object that failed validation. + * Proceed with caution :) + * + * known glitches so far: + * + * any version < 2.0.3: + * - luks2 keyslot pbkdf params change via crypt_keyslot_change_by_passphrase() + * could leave previous type parameters behind. Correct this by purging + * all params not needed by current type. + */ +void LUKS2_hdr_repair(struct crypt_device *cd, json_object *hdr_jobj) +{ + json_object *jobj_keyslots; + + if (!json_object_object_get_ex(hdr_jobj, "keyslots", &jobj_keyslots)) + return; + if (!json_object_is_type(jobj_keyslots, json_type_object)) + return; + + LUKS2_keyslots_repair(cd, jobj_keyslots); +} + +void json_object_object_del_by_uint(json_object *jobj, unsigned key) +{ + char key_name[16]; + + if (snprintf(key_name, sizeof(key_name), "%u", key) < 1) + return; + json_object_object_del(jobj, key_name); +} + +int json_object_object_add_by_uint(json_object *jobj, unsigned key, json_object *jobj_val) +{ + char key_name[16]; + + if (snprintf(key_name, sizeof(key_name), "%u", key) < 1) + return -EINVAL; + +#if HAVE_DECL_JSON_OBJECT_OBJECT_ADD_EX + return json_object_object_add_ex(jobj, key_name, jobj_val, 0) ? -ENOMEM : 0; +#else + json_object_object_add(jobj, key_name, jobj_val); + return 0; +#endif +} diff --git a/lib/luks2/luks2_keyslot.c b/lib/luks2/luks2_keyslot.c new file mode 100644 index 0000000..b897ba3 --- /dev/null +++ b/lib/luks2/luks2_keyslot.c @@ -0,0 +1,663 @@ +/* + * LUKS - Linux Unified Key Setup v2, keyslot handling + * + * Copyright (C) 2015-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2019 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" + +/* Internal implementations */ +extern const keyslot_handler luks2_keyslot; + +static const keyslot_handler *keyslot_handlers[LUKS2_KEYSLOTS_MAX] = { + &luks2_keyslot, + NULL +}; + +static const keyslot_handler +*LUKS2_keyslot_handler_type(struct crypt_device *cd, const char *type) +{ + int i; + + for (i = 0; i < LUKS2_KEYSLOTS_MAX && keyslot_handlers[i]; i++) { + if (!strcmp(keyslot_handlers[i]->name, type)) + return keyslot_handlers[i]; + } + + return NULL; +} + +static const keyslot_handler +*LUKS2_keyslot_handler(struct crypt_device *cd, int keyslot) +{ + struct luks2_hdr *hdr; + json_object *jobj1, *jobj2; + + if (keyslot < 0) + return NULL; + + if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2))) + return NULL; + + if (!(jobj1 = LUKS2_get_keyslot_jobj(hdr, keyslot))) + return NULL; + + if (!json_object_object_get_ex(jobj1, "type", &jobj2)) + return NULL; + + return LUKS2_keyslot_handler_type(cd, json_object_get_string(jobj2)); +} + +int LUKS2_keyslot_find_empty(struct luks2_hdr *hdr, const char *type) +{ + int i; + + for (i = 0; i < LUKS2_KEYSLOTS_MAX; i++) + if (!LUKS2_get_keyslot_jobj(hdr, i)) + return i; + + return -EINVAL; +} + +/* Check if a keyslot is asssigned to specific segment */ +int LUKS2_keyslot_for_segment(struct luks2_hdr *hdr, int keyslot, int segment) +{ + int keyslot_digest, segment_digest; + + /* no need to check anything */ + if (segment == CRYPT_ANY_SEGMENT) + return 0; + + keyslot_digest = LUKS2_digest_by_keyslot(hdr, keyslot); + if (keyslot_digest < 0) + return -EINVAL; + + segment_digest = LUKS2_digest_by_segment(hdr, segment); + if (segment_digest < 0) + return segment_digest; + + return segment_digest == keyslot_digest ? 0 : -ENOENT; +} + +/* Number of keyslots assigned to a segment or all keyslots for CRYPT_ANY_SEGMENT */ +int LUKS2_keyslot_active_count(struct luks2_hdr *hdr, int segment) +{ + int num = 0; + json_object *jobj_keyslots; + + json_object_object_get_ex(hdr->jobj, "keyslots", &jobj_keyslots); + + json_object_object_foreach(jobj_keyslots, slot, val) { + UNUSED(val); + if (!LUKS2_keyslot_for_segment(hdr, atoi(slot), segment)) + num++; + } + + return num; +} + +int LUKS2_keyslot_cipher_incompatible(struct crypt_device *cd, const char *cipher_spec) +{ + char cipher[MAX_CIPHER_LEN], cipher_mode[MAX_CIPHER_LEN]; + + if (!cipher_spec || !strcmp(cipher_spec, "null") || !strcmp(cipher_spec, "cipher_null")) + return 1; + + if (crypt_parse_name_and_mode(cipher_spec, cipher, NULL, cipher_mode) < 0) + return 1; + + /* Keyslot is already authenticated; we cannot use integrity tags here */ + if (crypt_get_integrity_tag_size(cd)) + return 1; + + /* Wrapped key schemes cannot be used for keyslot encryption */ + if (crypt_cipher_wrapped_key(cipher, cipher_mode)) + return 1; + + /* Check if crypto backend can use the cipher */ + if (crypt_cipher_ivsize(cipher, cipher_mode) < 0) + return 1; + + return 0; +} + +int LUKS2_keyslot_params_default(struct crypt_device *cd, struct luks2_hdr *hdr, + struct luks2_keyslot_params *params) +{ + const struct crypt_pbkdf_type *pbkdf = crypt_get_pbkdf_type(cd); + const char *cipher_spec; + size_t key_size; + int r; + + if (!hdr || !pbkdf || !params) + return -EINVAL; + + /* + * set keyslot area encryption parameters + */ + params->area_type = LUKS2_KEYSLOT_AREA_RAW; + cipher_spec = crypt_keyslot_get_encryption(cd, CRYPT_ANY_SLOT, &key_size); + if (!cipher_spec || !key_size) + return -EINVAL; + + params->area.raw.key_size = key_size; + r = snprintf(params->area.raw.encryption, sizeof(params->area.raw.encryption), "%s", cipher_spec); + if (r < 0 || (size_t)r >= sizeof(params->area.raw.encryption)) + return -EINVAL; + + /* + * set keyslot AF parameters + */ + params->af_type = LUKS2_KEYSLOT_AF_LUKS1; + /* currently we use hash for AF from pbkdf settings */ + r = snprintf(params->af.luks1.hash, sizeof(params->af.luks1.hash), "%s", pbkdf->hash); + if (r < 0 || (size_t)r >= sizeof(params->af.luks1.hash)) + return -EINVAL; + params->af.luks1.stripes = 4000; + + return 0; +} + +int LUKS2_keyslot_pbkdf(struct luks2_hdr *hdr, int keyslot, struct crypt_pbkdf_type *pbkdf) +{ + json_object *jobj_keyslot, *jobj_kdf, *jobj; + + if (!hdr || !pbkdf) + return -EINVAL; + + 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, "kdf", &jobj_kdf)) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_kdf, "type", &jobj)) + return -EINVAL; + + memset(pbkdf, 0, sizeof(*pbkdf)); + + pbkdf->type = json_object_get_string(jobj); + if (json_object_object_get_ex(jobj_kdf, "hash", &jobj)) + pbkdf->hash = json_object_get_string(jobj); + if (json_object_object_get_ex(jobj_kdf, "iterations", &jobj)) + pbkdf->iterations = json_object_get_int(jobj); + if (json_object_object_get_ex(jobj_kdf, "time", &jobj)) + pbkdf->iterations = json_object_get_int(jobj); + if (json_object_object_get_ex(jobj_kdf, "memory", &jobj)) + pbkdf->max_memory_kb = json_object_get_int(jobj); + if (json_object_object_get_ex(jobj_kdf, "cpus", &jobj)) + pbkdf->parallel_threads = json_object_get_int(jobj); + + return 0; +} + +static int LUKS2_keyslot_unbound(struct luks2_hdr *hdr, int keyslot) +{ + json_object *jobj_digest, *jobj_segments; + int digest = LUKS2_digest_by_keyslot(hdr, keyslot); + + if (digest < 0) + return 0; + + if (!(jobj_digest = LUKS2_get_digest_jobj(hdr, digest))) + return 0; + + json_object_object_get_ex(jobj_digest, "segments", &jobj_segments); + if (!jobj_segments || !json_object_is_type(jobj_segments, json_type_array) || + json_object_array_length(jobj_segments) == 0) + return 1; + + return 0; +} + +crypt_keyslot_info LUKS2_keyslot_info(struct luks2_hdr *hdr, int keyslot) +{ + if(keyslot >= LUKS2_KEYSLOTS_MAX || keyslot < 0) + return CRYPT_SLOT_INVALID; + + if (!LUKS2_get_keyslot_jobj(hdr, keyslot)) + return CRYPT_SLOT_INACTIVE; + + if (LUKS2_keyslot_unbound(hdr, keyslot)) + return CRYPT_SLOT_UNBOUND; + + if (LUKS2_keyslot_active_count(hdr, CRYPT_DEFAULT_SEGMENT) == 1 && + !LUKS2_keyslot_for_segment(hdr, keyslot, CRYPT_DEFAULT_SEGMENT)) + return CRYPT_SLOT_ACTIVE_LAST; + + return CRYPT_SLOT_ACTIVE; +} + +int LUKS2_keyslot_area(struct luks2_hdr *hdr, + int 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; + + if (!json_object_object_get_ex(jobj_keyslot, "area", &jobj_area)) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_area, "offset", &jobj)) + return -EINVAL; + *offset = json_object_get_int64(jobj); + + if (!json_object_object_get_ex(jobj_area, "size", &jobj)) + return -EINVAL; + *length = json_object_get_int64(jobj); + + return 0; +} + +static int LUKS2_open_and_verify(struct crypt_device *cd, + struct luks2_hdr *hdr, + int keyslot, + int segment, + const char *password, + size_t password_len, + struct volume_key **vk) +{ + const keyslot_handler *h; + int key_size, r; + + if (!(h = LUKS2_keyslot_handler(cd, keyslot))) + return -ENOENT; + + r = h->validate(cd, LUKS2_get_keyslot_jobj(hdr, keyslot)); + if (r) { + log_dbg(cd, "Keyslot %d validation failed.", keyslot); + return r; + } + + r = LUKS2_keyslot_for_segment(hdr, keyslot, segment); + if (r) { + if (r == -ENOENT) + log_dbg(cd, "Keyslot %d unusable for segment %d.", keyslot, segment); + return r; + } + + key_size = LUKS2_get_volume_key_size(hdr, segment); + if (key_size < 0) + key_size = LUKS2_get_keyslot_stored_key_size(hdr, keyslot); + if (key_size < 0) + return -EINVAL; + + *vk = crypt_alloc_volume_key(key_size, NULL); + if (!*vk) + return -ENOMEM; + + r = h->open(cd, keyslot, password, password_len, (*vk)->key, (*vk)->keylength); + if (r < 0) + log_dbg(cd, "Keyslot %d (%s) open failed with %d.", keyslot, h->name, r); + else + r = LUKS2_digest_verify(cd, hdr, *vk, keyslot); + + if (r < 0) { + crypt_free_volume_key(*vk); + *vk = NULL; + } + + return r < 0 ? r : keyslot; +} + +static int LUKS2_keyslot_open_priority(struct crypt_device *cd, + struct luks2_hdr *hdr, + crypt_keyslot_priority priority, + const char *password, + size_t password_len, + int segment, + struct volume_key **vk) +{ + json_object *jobj_keyslots, *jobj; + crypt_keyslot_priority slot_priority; + int keyslot, r = -ENOENT; + + json_object_object_get_ex(hdr->jobj, "keyslots", &jobj_keyslots); + + json_object_object_foreach(jobj_keyslots, slot, val) { + if (!json_object_object_get_ex(val, "priority", &jobj)) + slot_priority = CRYPT_SLOT_PRIORITY_NORMAL; + else + slot_priority = json_object_get_int(jobj); + + keyslot = atoi(slot); + if (slot_priority != priority) { + log_dbg(cd, "Keyslot %d priority %d != %d (required), skipped.", + keyslot, slot_priority, priority); + continue; + } + + r = LUKS2_open_and_verify(cd, hdr, keyslot, segment, password, password_len, vk); + + /* Do not retry for errors that are no -EPERM or -ENOENT, + former meaning password wrong, latter key slot unusable for segment */ + if ((r != -EPERM) && (r != -ENOENT)) + break; + } + + return r; +} + +int LUKS2_keyslot_open(struct crypt_device *cd, + int keyslot, + int segment, + const char *password, + size_t password_len, + struct volume_key **vk) +{ + struct luks2_hdr *hdr; + int r_prio, r = -EINVAL; + + hdr = crypt_get_hdr(cd, CRYPT_LUKS2); + + if (keyslot == CRYPT_ANY_SLOT) { + r_prio = LUKS2_keyslot_open_priority(cd, hdr, CRYPT_SLOT_PRIORITY_PREFER, + password, password_len, segment, vk); + if (r_prio >= 0) + r = r_prio; + else if (r_prio != -EPERM && r_prio != -ENOENT) + r = r_prio; + else + r = LUKS2_keyslot_open_priority(cd, hdr, CRYPT_SLOT_PRIORITY_NORMAL, + password, password_len, segment, vk); + /* Prefer password wrong to no entry from priority slot */ + if (r_prio == -EPERM && r == -ENOENT) + r = r_prio; + } else + r = LUKS2_open_and_verify(cd, hdr, keyslot, segment, password, password_len, vk); + + return r; +} + +int LUKS2_keyslot_store(struct crypt_device *cd, + struct luks2_hdr *hdr, + int keyslot, + const char *password, + size_t password_len, + const struct volume_key *vk, + const struct luks2_keyslot_params *params) +{ + const keyslot_handler *h; + int r; + + if (keyslot == CRYPT_ANY_SLOT) + return -EINVAL; + + if (!LUKS2_get_keyslot_jobj(hdr, keyslot)) { + /* Try to allocate default and empty keyslot type */ + h = LUKS2_keyslot_handler_type(cd, "luks2"); + if (!h) + return -EINVAL; + + r = h->alloc(cd, keyslot, vk->keylength, params); + if (r) + return r; + } else { + if (!(h = LUKS2_keyslot_handler(cd, keyslot))) + return -EINVAL; + + r = h->update(cd, keyslot, params); + if (r) { + log_dbg(cd, "Failed to update keyslot %d json.", keyslot); + return r; + } + } + + r = h->validate(cd, LUKS2_get_keyslot_jobj(hdr, keyslot)); + if (r) { + log_dbg(cd, "Keyslot validation failed."); + return r; + } + + return h->store(cd, keyslot, password, password_len, + vk->key, vk->keylength); +} + +int LUKS2_keyslot_wipe(struct crypt_device *cd, + struct luks2_hdr *hdr, + int keyslot, + int wipe_area_only) +{ + struct device *device = crypt_metadata_device(cd); + uint64_t area_offset, area_length; + int r; + json_object *jobj_keyslot, *jobj_keyslots; + const keyslot_handler *h; + + h = LUKS2_keyslot_handler(cd, keyslot); + + if (!json_object_object_get_ex(hdr->jobj, "keyslots", &jobj_keyslots)) + return -EINVAL; + + jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot); + if (!jobj_keyslot) + return -ENOENT; + + if (wipe_area_only) + log_dbg(cd, "Wiping keyslot %d area only.", keyslot); + + /* Just check that nobody uses the metadata now */ + r = device_write_lock(cd, device); + if (r) { + log_err(cd, _("Failed to acquire write lock on device %s."), + device_path(device)); + return r; + } + device_write_unlock(cd, device); + + /* secure deletion of possible key material in keyslot area */ + r = crypt_keyslot_area(cd, keyslot, &area_offset, &area_length); + if (r && r != -ENOENT) + return r; + + /* We can destroy the binary keyslot area now without lock */ + if (!r) { + r = crypt_wipe_device(cd, device, CRYPT_WIPE_SPECIAL, area_offset, + area_length, area_length, NULL, NULL); + if (r) { + if (r == -EACCES) { + log_err(cd, _("Cannot write to device %s, permission denied."), + device_path(device)); + r = -EINVAL; + } else + log_err(cd, _("Cannot wipe device %s."), device_path(device)); + return r; + } + } + + if (wipe_area_only) + return r; + + /* Slot specific wipe */ + if (h) { + r = h->wipe(cd, keyslot); + if (r < 0) + return r; + } else + log_dbg(cd, "Wiping keyslot %d without specific-slot handler loaded.", keyslot); + + json_object_object_del_by_uint(jobj_keyslots, keyslot); + + return LUKS2_hdr_write(cd, hdr); +} + +int LUKS2_keyslot_dump(struct crypt_device *cd, int keyslot) +{ + const keyslot_handler *h; + + if (!(h = LUKS2_keyslot_handler(cd, keyslot))) + return -EINVAL; + + return h->dump(cd, keyslot); +} + +crypt_keyslot_priority LUKS2_keyslot_priority_get(struct crypt_device *cd, + struct luks2_hdr *hdr, int keyslot) +{ + json_object *jobj_keyslot, *jobj_priority; + + jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot); + if (!jobj_keyslot) + return CRYPT_SLOT_PRIORITY_INVALID; + + if (!json_object_object_get_ex(jobj_keyslot, "priority", &jobj_priority)) + return CRYPT_SLOT_PRIORITY_NORMAL; + + return json_object_get_int(jobj_priority); +} + +int LUKS2_keyslot_priority_set(struct crypt_device *cd, struct luks2_hdr *hdr, + int keyslot, crypt_keyslot_priority priority, int commit) +{ + json_object *jobj_keyslot; + + jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot); + if (!jobj_keyslot) + return -EINVAL; + + if (priority == CRYPT_SLOT_PRIORITY_NORMAL) + json_object_object_del(jobj_keyslot, "priority"); + else + json_object_object_add(jobj_keyslot, "priority", json_object_new_int(priority)); + + return commit ? LUKS2_hdr_write(cd, hdr) : 0; +} + +int placeholder_keyslot_alloc(struct crypt_device *cd, + int keyslot, + uint64_t area_offset, + uint64_t area_length, + size_t volume_key_len) +{ + struct luks2_hdr *hdr; + json_object *jobj_keyslots, *jobj_keyslot, *jobj_area; + + log_dbg(cd, "Allocating placeholder keyslot %d for LUKS1 down conversion.", keyslot); + + if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2))) + return -EINVAL; + + if (keyslot < 0 || keyslot >= LUKS2_KEYSLOTS_MAX) + return -EINVAL; + + if (LUKS2_get_keyslot_jobj(hdr, keyslot)) + return -EINVAL; + + if (!json_object_object_get_ex(hdr->jobj, "keyslots", &jobj_keyslots)) + return -EINVAL; + + jobj_keyslot = json_object_new_object(); + json_object_object_add(jobj_keyslot, "type", json_object_new_string("placeholder")); + /* + * key_size = -1 makes placeholder keyslot impossible to pass validation. + * It's a safeguard against accidentally storing temporary conversion + * LUKS2 header. + */ + json_object_object_add(jobj_keyslot, "key_size", json_object_new_int(-1)); + + /* Area object */ + jobj_area = json_object_new_object(); + json_object_object_add(jobj_area, "offset", json_object_new_uint64(area_offset)); + json_object_object_add(jobj_area, "size", json_object_new_uint64(area_length)); + json_object_object_add(jobj_keyslot, "area", jobj_area); + + json_object_object_add_by_uint(jobj_keyslots, keyslot, jobj_keyslot); + + return 0; +} + +static unsigned LUKS2_get_keyslot_digests_count(json_object *hdr_jobj, int keyslot) +{ + char num[16]; + json_object *jobj_digests, *jobj_keyslots; + unsigned count = 0; + + if (!json_object_object_get_ex(hdr_jobj, "digests", &jobj_digests)) + return 0; + + if (snprintf(num, sizeof(num), "%u", keyslot) < 0) + return 0; + + json_object_object_foreach(jobj_digests, key, val) { + UNUSED(key); + json_object_object_get_ex(val, "keyslots", &jobj_keyslots); + if (LUKS2_array_jobj(jobj_keyslots, num)) + count++; + } + + return count; +} + +/* run only on header that passed basic format validation */ +int LUKS2_keyslots_validate(struct crypt_device *cd, json_object *hdr_jobj) +{ + const keyslot_handler *h; + int keyslot; + json_object *jobj_keyslots, *jobj_type; + + if (!json_object_object_get_ex(hdr_jobj, "keyslots", &jobj_keyslots)) + return -EINVAL; + + json_object_object_foreach(jobj_keyslots, slot, val) { + keyslot = atoi(slot); + json_object_object_get_ex(val, "type", &jobj_type); + h = LUKS2_keyslot_handler_type(cd, json_object_get_string(jobj_type)); + if (!h) + continue; + if (h->validate && h->validate(cd, val)) { + log_dbg(cd, "Keyslot type %s validation failed on keyslot %d.", h->name, keyslot); + return -EINVAL; + } + + if (!strcmp(h->name, "luks2") && LUKS2_get_keyslot_digests_count(hdr_jobj, keyslot) != 1) { + log_dbg(cd, "Keyslot %d is not assigned to exactly 1 digest.", keyslot); + return -EINVAL; + } + } + + return 0; +} + +void LUKS2_keyslots_repair(struct crypt_device *cd, json_object *jobj_keyslots) +{ + const keyslot_handler *h; + json_object *jobj_type; + + json_object_object_foreach(jobj_keyslots, slot, val) { + UNUSED(slot); + if (!json_object_is_type(val, json_type_object) || + !json_object_object_get_ex(val, "type", &jobj_type) || + !json_object_is_type(jobj_type, json_type_string)) + continue; + + h = LUKS2_keyslot_handler_type(cd, json_object_get_string(jobj_type)); + if (h && h->repair) + h->repair(cd, val); + } +} diff --git a/lib/luks2/luks2_keyslot_luks2.c b/lib/luks2/luks2_keyslot_luks2.c new file mode 100644 index 0000000..26fe9b1 --- /dev/null +++ b/lib/luks2/luks2_keyslot_luks2.c @@ -0,0 +1,785 @@ +/* + * LUKS - Linux Unified Key Setup v2, LUKS2 type keyslot handler + * + * Copyright (C) 2015-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2019 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" + +/* FIXME: move keyslot encryption to crypto backend */ +#include "../luks1/af.h" + +#define LUKS_SALTSIZE 32 +#define LUKS_SLOT_ITERATIONS_MIN 1000 +#define LUKS_STRIPES 4000 + +static int luks2_encrypt_to_storage(char *src, size_t srcLength, + const char *cipher, const char *cipher_mode, + struct volume_key *vk, unsigned int sector, + struct crypt_device *cd) +{ + struct device *device = crypt_metadata_device(cd); +#ifndef ENABLE_AF_ALG /* Support for old kernel without Crypto API */ + int r = device_write_lock(cd, device); + if (r) { + log_err(cd, _("Failed to acquire write lock on device %s."), device_path(device)); + return r; + } + r = LUKS_encrypt_to_storage(src, srcLength, cipher, cipher_mode, vk, sector, cd); + device_write_unlock(cd, crypt_metadata_device(cd)); + return r; +#else + struct crypt_storage *s; + int devfd = -1, r; + + /* Only whole sector writes supported */ + if (MISALIGNED_512(srcLength)) + return -EINVAL; + + /* Encrypt buffer */ + r = crypt_storage_init(&s, 0, cipher, cipher_mode, vk->key, vk->keylength); + if (r) { + log_dbg(cd, "Userspace crypto wrapper cannot use %s-%s (%d).", + cipher, cipher_mode, r); + return r; + } + + r = crypt_storage_encrypt(s, 0, srcLength / SECTOR_SIZE, src); + crypt_storage_destroy(s); + if (r) + return r; + + r = device_write_lock(cd, device); + if (r) { + log_err(cd, _("Failed to acquire write lock on device %s."), + device_path(device)); + return r; + } + + devfd = device_open_locked(cd, device, O_RDWR); + if (devfd >= 0) { + if (write_lseek_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), src, + srcLength, sector * SECTOR_SIZE) < 0) + r = -EIO; + else + r = 0; + + device_sync(cd, device, devfd); + close(devfd); + } else + r = -EIO; + + device_write_unlock(cd, device); + + if (r) + log_err(cd, _("IO error while encrypting keyslot.")); + + return r; +#endif +} + +static int luks2_decrypt_from_storage(char *dst, size_t dstLength, + const char *cipher, const char *cipher_mode, struct volume_key *vk, + unsigned int sector, struct crypt_device *cd) +{ + struct device *device = crypt_metadata_device(cd); +#ifndef ENABLE_AF_ALG /* Support for old kernel without Crypto API */ + int r = device_read_lock(cd, device); + if (r) { + log_err(cd, _("Failed to acquire read lock on device %s."), device_path(device)); + return r; + } + r = LUKS_decrypt_from_storage(dst, dstLength, cipher, cipher_mode, vk, sector, cd); + device_read_unlock(cd, crypt_metadata_device(cd)); + return r; +#else + struct crypt_storage *s; + int devfd = -1, r; + + /* Only whole sector writes supported */ + if (MISALIGNED_512(dstLength)) + return -EINVAL; + + r = crypt_storage_init(&s, 0, cipher, cipher_mode, vk->key, vk->keylength); + if (r) { + log_dbg(cd, "Userspace crypto wrapper cannot use %s-%s (%d).", + cipher, cipher_mode, r); + return r; + } + + r = device_read_lock(cd, device); + if (r) { + log_err(cd, _("Failed to acquire read lock on device %s."), + device_path(device)); + crypt_storage_destroy(s); + return r; + } + + devfd = device_open_locked(cd, device, O_RDONLY); + if (devfd >= 0) { + if (read_lseek_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), dst, + dstLength, sector * SECTOR_SIZE) < 0) + r = -EIO; + else + r = 0; + close(devfd); + } else + r = -EIO; + + device_read_unlock(cd, device); + + /* Decrypt buffer */ + if (!r) + r = crypt_storage_decrypt(s, 0, dstLength / SECTOR_SIZE, dst); + else + log_err(cd, _("IO error while decrypting keyslot.")); + + crypt_storage_destroy(s); + return r; +#endif +} + +static int luks2_keyslot_get_pbkdf_params(json_object *jobj_keyslot, + struct crypt_pbkdf_type *pbkdf, char *salt) +{ + json_object *jobj_kdf, *jobj1, *jobj2; + size_t salt_len; + + if (!jobj_keyslot || !pbkdf) + return -EINVAL; + + memset(pbkdf, 0, sizeof(*pbkdf)); + + if (!json_object_object_get_ex(jobj_keyslot, "kdf", &jobj_kdf)) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_kdf, "type", &jobj1)) + return -EINVAL; + pbkdf->type = json_object_get_string(jobj1); + if (!strcmp(pbkdf->type, CRYPT_KDF_PBKDF2)) { + if (!json_object_object_get_ex(jobj_kdf, "hash", &jobj2)) + return -EINVAL; + pbkdf->hash = json_object_get_string(jobj2); + if (!json_object_object_get_ex(jobj_kdf, "iterations", &jobj2)) + return -EINVAL; + pbkdf->iterations = json_object_get_int(jobj2); + pbkdf->max_memory_kb = 0; + pbkdf->parallel_threads = 0; + } else { + if (!json_object_object_get_ex(jobj_kdf, "time", &jobj2)) + return -EINVAL; + pbkdf->iterations = json_object_get_int(jobj2); + if (!json_object_object_get_ex(jobj_kdf, "memory", &jobj2)) + return -EINVAL; + pbkdf->max_memory_kb = json_object_get_int(jobj2); + if (!json_object_object_get_ex(jobj_kdf, "cpus", &jobj2)) + return -EINVAL; + pbkdf->parallel_threads = json_object_get_int(jobj2); + } + + if (!json_object_object_get_ex(jobj_kdf, "salt", &jobj2)) + return -EINVAL; + salt_len = LUKS_SALTSIZE; + if (!base64_decode(json_object_get_string(jobj2), + json_object_get_string_len(jobj2), + salt, &salt_len)) + return -EINVAL; + if (salt_len != LUKS_SALTSIZE) + return -EINVAL; + + return 0; +} + +static int luks2_keyslot_set_key(struct crypt_device *cd, + json_object *jobj_keyslot, + const char *password, size_t passwordLen, + const char *volume_key, size_t volume_key_len) +{ + struct volume_key *derived_key; + char salt[LUKS_SALTSIZE], cipher[MAX_CIPHER_LEN], cipher_mode[MAX_CIPHER_LEN]; + char *AfKey = NULL; + const char *af_hash = NULL; + size_t AFEKSize, keyslot_key_len; + json_object *jobj2, *jobj_kdf, *jobj_af, *jobj_area; + uint64_t area_offset; + struct crypt_pbkdf_type pbkdf; + int r; + + if (!json_object_object_get_ex(jobj_keyslot, "kdf", &jobj_kdf) || + !json_object_object_get_ex(jobj_keyslot, "af", &jobj_af) || + !json_object_object_get_ex(jobj_keyslot, "area", &jobj_area)) + return -EINVAL; + + /* prevent accidental volume key size change after allocation */ + if (!json_object_object_get_ex(jobj_keyslot, "key_size", &jobj2)) + return -EINVAL; + if (json_object_get_int(jobj2) != (int)volume_key_len) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_area, "offset", &jobj2)) + return -EINVAL; + area_offset = json_object_get_uint64(jobj2); + + if (!json_object_object_get_ex(jobj_area, "encryption", &jobj2)) + return -EINVAL; + r = crypt_parse_name_and_mode(json_object_get_string(jobj2), cipher, NULL, cipher_mode); + if (r < 0) + return r; + + if (!json_object_object_get_ex(jobj_area, "key_size", &jobj2)) + return -EINVAL; + keyslot_key_len = json_object_get_int(jobj2); + + if (!json_object_object_get_ex(jobj_af, "hash", &jobj2)) + return -EINVAL; + af_hash = json_object_get_string(jobj2); + + if (luks2_keyslot_get_pbkdf_params(jobj_keyslot, &pbkdf, salt)) + return -EINVAL; + + /* + * Allocate derived key storage. + */ + derived_key = crypt_alloc_volume_key(keyslot_key_len, NULL); + if (!derived_key) + return -ENOMEM; + /* + * Calculate keyslot content, split and store it to keyslot area. + */ + r = crypt_pbkdf(pbkdf.type, pbkdf.hash, password, passwordLen, + salt, LUKS_SALTSIZE, + derived_key->key, derived_key->keylength, + pbkdf.iterations, pbkdf.max_memory_kb, + pbkdf.parallel_threads); + if (r < 0) { + crypt_free_volume_key(derived_key); + return r; + } + + // FIXME: verity key_size to AFEKSize + AFEKSize = AF_split_sectors(volume_key_len, LUKS_STRIPES) * SECTOR_SIZE; + AfKey = crypt_safe_alloc(AFEKSize); + if (!AfKey) { + crypt_free_volume_key(derived_key); + return -ENOMEM; + } + + r = AF_split(cd, volume_key, AfKey, volume_key_len, LUKS_STRIPES, af_hash); + + if (r == 0) { + log_dbg(cd, "Updating keyslot area [0x%04x].", (unsigned)area_offset); + /* FIXME: sector_offset should be size_t, fix LUKS_encrypt... accordingly */ + r = luks2_encrypt_to_storage(AfKey, AFEKSize, cipher, cipher_mode, + derived_key, (unsigned)(area_offset / SECTOR_SIZE), cd); + } + + crypt_safe_free(AfKey); + crypt_free_volume_key(derived_key); + if (r < 0) + return r; + + return 0; +} + +static int luks2_keyslot_get_key(struct crypt_device *cd, + json_object *jobj_keyslot, + const char *password, size_t passwordLen, + char *volume_key, size_t volume_key_len) +{ + struct volume_key *derived_key; + struct crypt_pbkdf_type pbkdf; + char *AfKey; + size_t AFEKSize; + const char *af_hash = NULL; + char salt[LUKS_SALTSIZE], cipher[MAX_CIPHER_LEN], cipher_mode[MAX_CIPHER_LEN]; + json_object *jobj2, *jobj_af, *jobj_area; + uint64_t area_offset; + size_t keyslot_key_len; + int r; + + if (!json_object_object_get_ex(jobj_keyslot, "af", &jobj_af) || + !json_object_object_get_ex(jobj_keyslot, "area", &jobj_area)) + return -EINVAL; + + if (luks2_keyslot_get_pbkdf_params(jobj_keyslot, &pbkdf, salt)) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_af, "hash", &jobj2)) + return -EINVAL; + af_hash = json_object_get_string(jobj2); + + if (!json_object_object_get_ex(jobj_area, "offset", &jobj2)) + return -EINVAL; + area_offset = json_object_get_uint64(jobj2); + + if (!json_object_object_get_ex(jobj_area, "encryption", &jobj2)) + return -EINVAL; + r = crypt_parse_name_and_mode(json_object_get_string(jobj2), cipher, NULL, cipher_mode); + if (r < 0) + return r; + + if (!json_object_object_get_ex(jobj_area, "key_size", &jobj2)) + return -EINVAL; + keyslot_key_len = json_object_get_int(jobj2); + + /* + * Allocate derived key storage space. + */ + derived_key = crypt_alloc_volume_key(keyslot_key_len, NULL); + if (!derived_key) + return -ENOMEM; + + AFEKSize = AF_split_sectors(volume_key_len, LUKS_STRIPES) * SECTOR_SIZE; + AfKey = crypt_safe_alloc(AFEKSize); + if (!AfKey) { + crypt_free_volume_key(derived_key); + return -ENOMEM; + } + /* + * Calculate derived key, decrypt keyslot content and merge it. + */ + r = crypt_pbkdf(pbkdf.type, pbkdf.hash, password, passwordLen, + salt, LUKS_SALTSIZE, + derived_key->key, derived_key->keylength, + pbkdf.iterations, pbkdf.max_memory_kb, + pbkdf.parallel_threads); + + if (r == 0) { + log_dbg(cd, "Reading keyslot area [0x%04x].", (unsigned)area_offset); + /* FIXME: sector_offset should be size_t, fix LUKS_decrypt... accordingly */ + r = luks2_decrypt_from_storage(AfKey, AFEKSize, cipher, cipher_mode, + derived_key, (unsigned)(area_offset / SECTOR_SIZE), cd); + } + + if (r == 0) + r = AF_merge(cd, AfKey, volume_key, volume_key_len, LUKS_STRIPES, af_hash); + + crypt_free_volume_key(derived_key); + crypt_safe_free(AfKey); + + return r; +} + +/* + * currently we support update of only: + * + * - af hash function + * - kdf params + */ +static int luks2_keyslot_update_json(struct crypt_device *cd, + json_object *jobj_keyslot, + const struct luks2_keyslot_params *params) +{ + const struct crypt_pbkdf_type *pbkdf; + json_object *jobj_af, *jobj_area, *jobj_kdf; + char salt[LUKS_SALTSIZE], *salt_base64 = NULL; + int r; + + /* jobj_keyslot is not yet validated */ + + if (!json_object_object_get_ex(jobj_keyslot, "af", &jobj_af) || + !json_object_object_get_ex(jobj_keyslot, "area", &jobj_area)) + return -EINVAL; + + /* update area encryption parameters */ + json_object_object_add(jobj_area, "encryption", json_object_new_string(params->area.raw.encryption)); + json_object_object_add(jobj_area, "key_size", json_object_new_int(params->area.raw.key_size)); + + pbkdf = crypt_get_pbkdf_type(cd); + if (!pbkdf) + return -EINVAL; + + r = crypt_benchmark_pbkdf_internal(cd, CONST_CAST(struct crypt_pbkdf_type *)pbkdf, params->area.raw.key_size); + if (r < 0) + return r; + + /* refresh whole 'kdf' object */ + jobj_kdf = json_object_new_object(); + if (!jobj_kdf) + return -ENOMEM; + json_object_object_add(jobj_kdf, "type", json_object_new_string(pbkdf->type)); + if (!strcmp(pbkdf->type, CRYPT_KDF_PBKDF2)) { + json_object_object_add(jobj_kdf, "hash", json_object_new_string(pbkdf->hash)); + json_object_object_add(jobj_kdf, "iterations", json_object_new_int(pbkdf->iterations)); + } else { + json_object_object_add(jobj_kdf, "time", json_object_new_int(pbkdf->iterations)); + json_object_object_add(jobj_kdf, "memory", json_object_new_int(pbkdf->max_memory_kb)); + json_object_object_add(jobj_kdf, "cpus", json_object_new_int(pbkdf->parallel_threads)); + } + json_object_object_add(jobj_keyslot, "kdf", jobj_kdf); + + /* + * Regenerate salt and add it in 'kdf' object + */ + r = crypt_random_get(cd, salt, LUKS_SALTSIZE, CRYPT_RND_SALT); + if (r < 0) + return r; + base64_encode_alloc(salt, LUKS_SALTSIZE, &salt_base64); + if (!salt_base64) + return -ENOMEM; + json_object_object_add(jobj_kdf, "salt", json_object_new_string(salt_base64)); + free(salt_base64); + + /* update 'af' hash */ + json_object_object_add(jobj_af, "hash", json_object_new_string(params->af.luks1.hash)); + + JSON_DBG(cd, jobj_keyslot, "Keyslot JSON:"); + return 0; +} + +static int luks2_keyslot_alloc(struct crypt_device *cd, + int keyslot, + size_t volume_key_len, + const struct luks2_keyslot_params *params) +{ + struct luks2_hdr *hdr; + uint64_t area_offset, area_length; + json_object *jobj_keyslots, *jobj_keyslot, *jobj_af, *jobj_area; + int r; + + log_dbg(cd, "Trying to allocate LUKS2 keyslot %d.", keyslot); + + if (!params || params->area_type != LUKS2_KEYSLOT_AREA_RAW || + params->af_type != LUKS2_KEYSLOT_AF_LUKS1) { + log_dbg(cd, "Invalid LUKS2 keyslot parameters."); + return -EINVAL; + } + + if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2))) + return -EINVAL; + + if (keyslot == CRYPT_ANY_SLOT) + keyslot = LUKS2_keyslot_find_empty(hdr, "luks2"); + + if (keyslot < 0 || keyslot >= LUKS2_KEYSLOTS_MAX) + return -ENOMEM; + + if (LUKS2_get_keyslot_jobj(hdr, keyslot)) { + log_dbg(cd, "Cannot modify already active keyslot %d.", keyslot); + return -EINVAL; + } + + if (!json_object_object_get_ex(hdr->jobj, "keyslots", &jobj_keyslots)) + return -EINVAL; + + r = LUKS2_find_area_gap(cd, hdr, volume_key_len, &area_offset, &area_length); + if (r < 0) + return r; + + jobj_keyslot = json_object_new_object(); + json_object_object_add(jobj_keyslot, "type", json_object_new_string("luks2")); + json_object_object_add(jobj_keyslot, "key_size", json_object_new_int(volume_key_len)); + + /* AF object */ + jobj_af = json_object_new_object(); + json_object_object_add(jobj_af, "type", json_object_new_string("luks1")); + json_object_object_add(jobj_af, "stripes", json_object_new_int(params->af.luks1.stripes)); + json_object_object_add(jobj_keyslot, "af", jobj_af); + + /* Area object */ + jobj_area = json_object_new_object(); + json_object_object_add(jobj_area, "type", json_object_new_string("raw")); + json_object_object_add(jobj_area, "offset", json_object_new_uint64(area_offset)); + json_object_object_add(jobj_area, "size", json_object_new_uint64(area_length)); + json_object_object_add(jobj_keyslot, "area", jobj_area); + + json_object_object_add_by_uint(jobj_keyslots, keyslot, jobj_keyslot); + + r = luks2_keyslot_update_json(cd, jobj_keyslot, params); + + if (!r && LUKS2_check_json_size(cd, hdr)) { + log_dbg(cd, "Not enough space in header json area for new keyslot."); + r = -ENOSPC; + } + + if (r) + json_object_object_del_by_uint(jobj_keyslots, keyslot); + + return r; +} + +static int luks2_keyslot_open(struct crypt_device *cd, + int keyslot, + const char *password, + size_t password_len, + char *volume_key, + size_t volume_key_len) +{ + struct luks2_hdr *hdr; + json_object *jobj_keyslot; + + log_dbg(cd, "Trying to open LUKS2 keyslot %d.", keyslot); + + if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2))) + return -EINVAL; + + jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot); + if (!jobj_keyslot) + return -EINVAL; + + return luks2_keyslot_get_key(cd, jobj_keyslot, + password, password_len, + volume_key, volume_key_len); +} + +/* + * This function must not modify json. + * It's called after luks2 keyslot validation. + */ +static int luks2_keyslot_store(struct crypt_device *cd, + int keyslot, + const char *password, + size_t password_len, + const char *volume_key, + size_t volume_key_len) +{ + struct luks2_hdr *hdr; + json_object *jobj_keyslot; + int r; + + log_dbg(cd, "Calculating attributes for LUKS2 keyslot %d.", keyslot); + + if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2))) + return -EINVAL; + + jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot); + if (!jobj_keyslot) + return -EINVAL; + + r = luks2_keyslot_set_key(cd, jobj_keyslot, + password, password_len, + volume_key, volume_key_len); + if (r < 0) + return r; + + r = LUKS2_hdr_write(cd, hdr); + if (r < 0) + return r; + + return keyslot; +} + +static int luks2_keyslot_wipe(struct crypt_device *cd, int keyslot) +{ + struct luks2_hdr *hdr; + + if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2))) + return -EINVAL; + + /* Remove any reference of deleted keyslot from digests and tokens */ + LUKS2_digest_assign(cd, hdr, keyslot, CRYPT_ANY_DIGEST, 0, 0); + LUKS2_token_assign(cd, hdr, keyslot, CRYPT_ANY_TOKEN, 0, 0); + + return 0; +} + +static int luks2_keyslot_dump(struct crypt_device *cd, int keyslot) +{ + json_object *jobj_keyslot, *jobj1, *jobj_kdf, *jobj_af, *jobj_area; + + jobj_keyslot = LUKS2_get_keyslot_jobj(crypt_get_hdr(cd, CRYPT_LUKS2), keyslot); + if (!jobj_keyslot) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_keyslot, "kdf", &jobj_kdf) || + !json_object_object_get_ex(jobj_keyslot, "af", &jobj_af) || + !json_object_object_get_ex(jobj_keyslot, "area", &jobj_area)) + return -EINVAL; + + json_object_object_get_ex(jobj_area, "encryption", &jobj1); + log_std(cd, "\tCipher: %s\n", json_object_get_string(jobj1)); + + json_object_object_get_ex(jobj_area, "key_size", &jobj1); + log_std(cd, "\tCipher key: %u bits\n", json_object_get_uint32(jobj1) * 8); + + json_object_object_get_ex(jobj_kdf, "type", &jobj1); + log_std(cd, "\tPBKDF: %s\n", json_object_get_string(jobj1)); + + if (!strcmp(json_object_get_string(jobj1), CRYPT_KDF_PBKDF2)) { + json_object_object_get_ex(jobj_kdf, "hash", &jobj1); + log_std(cd, "\tHash: %s\n", json_object_get_string(jobj1)); + + json_object_object_get_ex(jobj_kdf, "iterations", &jobj1); + log_std(cd, "\tIterations: %" PRIu64 "\n", json_object_get_uint64(jobj1)); + } else { + json_object_object_get_ex(jobj_kdf, "time", &jobj1); + log_std(cd, "\tTime cost: %" PRIu64 "\n", json_object_get_int64(jobj1)); + + json_object_object_get_ex(jobj_kdf, "memory", &jobj1); + log_std(cd, "\tMemory: %" PRIu64 "\n", json_object_get_int64(jobj1)); + + json_object_object_get_ex(jobj_kdf, "cpus", &jobj1); + log_std(cd, "\tThreads: %" PRIu64 "\n", json_object_get_int64(jobj1)); + } + json_object_object_get_ex(jobj_kdf, "salt", &jobj1); + log_std(cd, "\tSalt: "); + hexprint_base64(cd, jobj1, " ", " "); + + + json_object_object_get_ex(jobj_af, "stripes", &jobj1); + log_std(cd, "\tAF stripes: %u\n", json_object_get_int(jobj1)); + + json_object_object_get_ex(jobj_af, "hash", &jobj1); + log_std(cd, "\tAF hash: %s\n", json_object_get_string(jobj1)); + + json_object_object_get_ex(jobj_area, "offset", &jobj1); + log_std(cd, "\tArea offset:%" PRIu64 " [bytes]\n", json_object_get_uint64(jobj1)); + + json_object_object_get_ex(jobj_area, "size", &jobj1); + log_std(cd, "\tArea length:%" PRIu64 " [bytes]\n", json_object_get_uint64(jobj1)); + + return 0; +} + +static int luks2_keyslot_validate(struct crypt_device *cd, json_object *jobj_keyslot) +{ + json_object *jobj_kdf, *jobj_af, *jobj_area, *jobj1; + const char *type; + int count; + + if (!jobj_keyslot) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_keyslot, "kdf", &jobj_kdf) || + !json_object_object_get_ex(jobj_keyslot, "af", &jobj_af) || + !json_object_object_get_ex(jobj_keyslot, "area", &jobj_area)) + return -EINVAL; + + count = json_object_object_length(jobj_kdf); + + jobj1 = json_contains(cd, jobj_kdf, "", "kdf section", "type", json_type_string); + if (!jobj1) + return -EINVAL; + type = json_object_get_string(jobj1); + + if (!strcmp(type, CRYPT_KDF_PBKDF2)) { + if (count != 4 || /* type, salt, hash, iterations only */ + !json_contains(cd, jobj_kdf, "kdf type", type, "hash", json_type_string) || + !json_contains(cd, jobj_kdf, "kdf type", type, "iterations", json_type_int) || + !json_contains(cd, jobj_kdf, "kdf type", type, "salt", json_type_string)) + return -EINVAL; + } else if (!strcmp(type, CRYPT_KDF_ARGON2I) || !strcmp(type, CRYPT_KDF_ARGON2ID)) { + if (count != 5 || /* type, salt, time, memory, cpus only */ + !json_contains(cd, jobj_kdf, "kdf type", type, "time", json_type_int) || + !json_contains(cd, jobj_kdf, "kdf type", type, "memory", json_type_int) || + !json_contains(cd, jobj_kdf, "kdf type", type, "cpus", json_type_int) || + !json_contains(cd, jobj_kdf, "kdf type", type, "salt", json_type_string)) + return -EINVAL; + } + + if (!json_object_object_get_ex(jobj_af, "type", &jobj1)) + return -EINVAL; + if (!strcmp(json_object_get_string(jobj1), "luks1")) { + if (!json_contains(cd, jobj_af, "", "luks1 af", "hash", json_type_string) || + !json_contains(cd, jobj_af, "", "luks1 af", "stripes", json_type_int)) + return -EINVAL; + } else + return -EINVAL; + + // FIXME check numbered + if (!json_object_object_get_ex(jobj_area, "type", &jobj1)) + return -EINVAL; + if (!strcmp(json_object_get_string(jobj1), "raw")) { + if (!json_contains(cd, jobj_area, "area", "raw type", "encryption", json_type_string) || + !json_contains(cd, jobj_area, "area", "raw type", "key_size", json_type_int) || + !json_contains(cd, jobj_area, "area", "raw type", "offset", json_type_string) || + !json_contains(cd, jobj_area, "area", "raw type", "size", json_type_string)) + return -EINVAL; + } else + return -EINVAL; + + return 0; +} + +static int luks2_keyslot_update(struct crypt_device *cd, + int keyslot, + const struct luks2_keyslot_params *params) +{ + struct luks2_hdr *hdr; + json_object *jobj_keyslot; + int r; + + log_dbg(cd, "Updating LUKS2 keyslot %d.", keyslot); + + if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2))) + return -EINVAL; + + jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot); + if (!jobj_keyslot) + return -EINVAL; + + r = luks2_keyslot_update_json(cd, jobj_keyslot, params); + + if (!r && LUKS2_check_json_size(cd, hdr)) { + log_dbg(cd, "Not enough space in header json area for updated keyslot %d.", keyslot); + r = -ENOSPC; + } + + return r; +} + +static void luks2_keyslot_repair(struct crypt_device *cd, json_object *jobj_keyslot) +{ + const char *type; + json_object *jobj_kdf, *jobj_type; + + if (!json_object_object_get_ex(jobj_keyslot, "kdf", &jobj_kdf) || + !json_object_is_type(jobj_kdf, json_type_object)) + return; + + if (!json_object_object_get_ex(jobj_kdf, "type", &jobj_type) || + !json_object_is_type(jobj_type, json_type_string)) + return; + + type = json_object_get_string(jobj_type); + + if (!strcmp(type, CRYPT_KDF_PBKDF2)) { + /* type, salt, hash, iterations only */ + json_object_object_foreach(jobj_kdf, key, val) { + UNUSED(val); + if (!strcmp(key, "type") || !strcmp(key, "salt") || + !strcmp(key, "hash") || !strcmp(key, "iterations")) + continue; + json_object_object_del(jobj_kdf, key); + } + } else if (!strcmp(type, CRYPT_KDF_ARGON2I) || !strcmp(type, CRYPT_KDF_ARGON2ID)) { + /* type, salt, time, memory, cpus only */ + json_object_object_foreach(jobj_kdf, key, val) { + UNUSED(val); + if (!strcmp(key, "type") || !strcmp(key, "salt") || + !strcmp(key, "time") || !strcmp(key, "memory") || + !strcmp(key, "cpus")) + continue; + json_object_object_del(jobj_kdf, key); + } + } +} + +const keyslot_handler luks2_keyslot = { + .name = "luks2", + .alloc = luks2_keyslot_alloc, + .update = luks2_keyslot_update, + .open = luks2_keyslot_open, + .store = luks2_keyslot_store, + .wipe = luks2_keyslot_wipe, + .dump = luks2_keyslot_dump, + .validate = luks2_keyslot_validate, + .repair = luks2_keyslot_repair +}; diff --git a/lib/luks2/luks2_luks1_convert.c b/lib/luks2/luks2_luks1_convert.c new file mode 100644 index 0000000..7884de0 --- /dev/null +++ b/lib/luks2/luks2_luks1_convert.c @@ -0,0 +1,863 @@ +/* + * LUKS - Linux Unified Key Setup v2, LUKS1 conversion code + * + * Copyright (C) 2015-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2019 Ondrej Kozina + * Copyright (C) 2015-2019 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 "../luks1/luks.h" +#include "../luks1/af.h" + +static int json_luks1_keyslot(const struct luks_phdr *hdr_v1, int keyslot, struct json_object **keyslot_object) +{ + char *base64_str, cipher[LUKS_CIPHERNAME_L+LUKS_CIPHERMODE_L]; + size_t base64_len; + struct json_object *keyslot_obj, *field, *jobj_kdf, *jobj_af, *jobj_area; + uint64_t offset, area_size, offs_a, offs_b, length; + + keyslot_obj = json_object_new_object(); + json_object_object_add(keyslot_obj, "type", json_object_new_string("luks2")); + json_object_object_add(keyslot_obj, "key_size", json_object_new_int64(hdr_v1->keyBytes)); + + /* KDF */ + jobj_kdf = json_object_new_object(); + json_object_object_add(jobj_kdf, "type", json_object_new_string(CRYPT_KDF_PBKDF2)); + json_object_object_add(jobj_kdf, "hash", json_object_new_string(hdr_v1->hashSpec)); + json_object_object_add(jobj_kdf, "iterations", json_object_new_int64(hdr_v1->keyblock[keyslot].passwordIterations)); + /* salt field */ + base64_len = base64_encode_alloc(hdr_v1->keyblock[keyslot].passwordSalt, LUKS_SALTSIZE, &base64_str); + if (!base64_str) { + json_object_put(keyslot_obj); + json_object_put(jobj_kdf); + if (!base64_len) + return -EINVAL; + return -ENOMEM; + } + field = json_object_new_string_len(base64_str, base64_len); + free(base64_str); + json_object_object_add(jobj_kdf, "salt", field); + json_object_object_add(keyslot_obj, "kdf", jobj_kdf); + + /* AF */ + jobj_af = json_object_new_object(); + json_object_object_add(jobj_af, "type", json_object_new_string("luks1")); + json_object_object_add(jobj_af, "hash", json_object_new_string(hdr_v1->hashSpec)); + /* stripes field ignored, fixed to LUKS_STRIPES (4000) */ + json_object_object_add(jobj_af, "stripes", json_object_new_int(4000)); + json_object_object_add(keyslot_obj, "af", jobj_af); + + /* Area */ + jobj_area = json_object_new_object(); + json_object_object_add(jobj_area, "type", json_object_new_string("raw")); + + /* encryption algorithm field */ + if (*hdr_v1->cipherMode != '\0') { + (void) snprintf(cipher, sizeof(cipher), "%s-%s", hdr_v1->cipherName, hdr_v1->cipherMode); + json_object_object_add(jobj_area, "encryption", json_object_new_string(cipher)); + } else + json_object_object_add(jobj_area, "encryption", json_object_new_string(hdr_v1->cipherName)); + + /* area */ + if (LUKS_keyslot_area(hdr_v1, 0, &offs_a, &length) || + LUKS_keyslot_area(hdr_v1, 1, &offs_b, &length) || + LUKS_keyslot_area(hdr_v1, keyslot, &offset, &length)) { + json_object_put(keyslot_obj); + json_object_put(jobj_area); + return -EINVAL; + } + area_size = offs_b - offs_a; + json_object_object_add(jobj_area, "key_size", json_object_new_int(hdr_v1->keyBytes)); + json_object_object_add(jobj_area, "offset", json_object_new_uint64(offset)); + json_object_object_add(jobj_area, "size", json_object_new_uint64(area_size)); + json_object_object_add(keyslot_obj, "area", jobj_area); + + *keyslot_object = keyslot_obj; + return 0; +} + +static int json_luks1_keyslots(const struct luks_phdr *hdr_v1, struct json_object **keyslots_object) +{ + int keyslot, r; + struct json_object *keyslot_obj, *field; + + keyslot_obj = json_object_new_object(); + if (!keyslot_obj) + return -ENOMEM; + + for (keyslot = 0; keyslot < LUKS_NUMKEYS; keyslot++) { + if (hdr_v1->keyblock[keyslot].active != LUKS_KEY_ENABLED) + continue; + r = json_luks1_keyslot(hdr_v1, keyslot, &field); + if (r) { + json_object_put(keyslot_obj); + return r; + } + json_object_object_add_by_uint(keyslot_obj, keyslot, field); + } + + *keyslots_object = keyslot_obj; + return 0; +} + +static int json_luks1_segment(const struct luks_phdr *hdr_v1, struct json_object **segment_object) +{ + const char *c; + char cipher[LUKS_CIPHERNAME_L+LUKS_CIPHERMODE_L]; + struct json_object *segment_obj, *field; + uint64_t number; + + segment_obj = json_object_new_object(); + if (!segment_obj) + return -ENOMEM; + + /* type field */ + field = json_object_new_string("crypt"); + if (!field) { + json_object_put(segment_obj); + return -ENOMEM; + } + json_object_object_add(segment_obj, "type", field); + + /* offset field */ + number = (uint64_t)hdr_v1->payloadOffset * SECTOR_SIZE; + + field = json_object_new_uint64(number); + if (!field) { + json_object_put(segment_obj); + return -ENOMEM; + } + json_object_object_add(segment_obj, "offset", field); + + /* iv_tweak field */ + field = json_object_new_string("0"); + if (!field) { + json_object_put(segment_obj); + return -ENOMEM; + } + json_object_object_add(segment_obj, "iv_tweak", field); + + /* length field */ + field = json_object_new_string("dynamic"); + if (!field) { + json_object_put(segment_obj); + return -ENOMEM; + } + json_object_object_add(segment_obj, "size", field); + + /* cipher field */ + if (*hdr_v1->cipherMode != '\0') { + (void) snprintf(cipher, sizeof(cipher), "%s-%s", hdr_v1->cipherName, hdr_v1->cipherMode); + c = cipher; + } else + c = hdr_v1->cipherName; + + field = json_object_new_string(c); + if (!field) { + json_object_put(segment_obj); + return -ENOMEM; + } + json_object_object_add(segment_obj, "encryption", field); + + /* block field */ + field = json_object_new_int(SECTOR_SIZE); + if (!field) { + json_object_put(segment_obj); + return -ENOMEM; + } + json_object_object_add(segment_obj, "sector_size", field); + + *segment_object = segment_obj; + return 0; +} + +static int json_luks1_segments(const struct luks_phdr *hdr_v1, struct json_object **segments_object) +{ + int r; + struct json_object *segments_obj, *field; + + segments_obj = json_object_new_object(); + if (!segments_obj) + return -ENOMEM; + + r = json_luks1_segment(hdr_v1, &field); + if (r) { + json_object_put(segments_obj); + return r; + } + json_object_object_add_by_uint(segments_obj, CRYPT_DEFAULT_SEGMENT, field); + + *segments_object = segments_obj; + return 0; +} + +static int json_luks1_digest(const struct luks_phdr *hdr_v1, struct json_object **digest_object) +{ + char keyslot_str[2], *base64_str; + int ks; + size_t base64_len; + struct json_object *digest_obj, *array, *field; + + digest_obj = json_object_new_object(); + if (!digest_obj) + return -ENOMEM; + + /* type field */ + field = json_object_new_string("pbkdf2"); + if (!field) { + json_object_put(digest_obj); + return -ENOMEM; + } + json_object_object_add(digest_obj, "type", field); + + /* keyslots array */ + array = json_object_new_array(); + if (!array) { + json_object_put(digest_obj); + return -ENOMEM; + } + json_object_object_add(digest_obj, "keyslots", json_object_get(array)); + + for (ks = 0; ks < LUKS_NUMKEYS; ks++) { + if (hdr_v1->keyblock[ks].active != LUKS_KEY_ENABLED) + continue; + (void) snprintf(keyslot_str, sizeof(keyslot_str), "%d", ks); + + field = json_object_new_string(keyslot_str); + if (!field || json_object_array_add(array, field) < 0) { + json_object_put(field); + json_object_put(array); + json_object_put(digest_obj); + return -ENOMEM; + } + } + + json_object_put(array); + + /* segments array */ + array = json_object_new_array(); + if (!array) { + json_object_put(digest_obj); + return -ENOMEM; + } + json_object_object_add(digest_obj, "segments", json_object_get(array)); + + field = json_object_new_string("0"); + if (!field || json_object_array_add(array, field) < 0) { + json_object_put(field); + json_object_put(array); + json_object_put(digest_obj); + return -ENOMEM; + } + + json_object_put(array); + + /* hash field */ + field = json_object_new_string(hdr_v1->hashSpec); + if (!field) { + json_object_put(digest_obj); + return -ENOMEM; + } + json_object_object_add(digest_obj, "hash", field); + + /* salt field */ + base64_len = base64_encode_alloc(hdr_v1->mkDigestSalt, LUKS_SALTSIZE, &base64_str); + if (!base64_str) { + json_object_put(digest_obj); + if (!base64_len) + return -EINVAL; + return -ENOMEM; + } + + field = json_object_new_string_len(base64_str, base64_len); + free(base64_str); + if (!field) { + json_object_put(digest_obj); + return -ENOMEM; + } + json_object_object_add(digest_obj, "salt", field); + + /* digest field */ + base64_len = base64_encode_alloc(hdr_v1->mkDigest, LUKS_DIGESTSIZE, &base64_str); + if (!base64_str) { + json_object_put(digest_obj); + if (!base64_len) + return -EINVAL; + return -ENOMEM; + } + + field = json_object_new_string_len(base64_str, base64_len); + free(base64_str); + if (!field) { + json_object_put(digest_obj); + return -ENOMEM; + } + json_object_object_add(digest_obj, "digest", field); + + /* iterations field */ + field = json_object_new_int64(hdr_v1->mkDigestIterations); + if (!field) { + json_object_put(digest_obj); + return -ENOMEM; + } + json_object_object_add(digest_obj, "iterations", field); + + *digest_object = digest_obj; + return 0; +} + +static int json_luks1_digests(const struct luks_phdr *hdr_v1, struct json_object **digests_object) +{ + int r; + struct json_object *digests_obj, *field; + + digests_obj = json_object_new_object(); + if (!digests_obj) + return -ENOMEM; + + r = json_luks1_digest(hdr_v1, &field); + if (r) { + json_object_put(digests_obj); + return r; + } + json_object_object_add(digests_obj, "0", field); + + *digests_object = digests_obj; + return 0; +} + +static int json_luks1_object(struct luks_phdr *hdr_v1, struct json_object **luks1_object, uint64_t keyslots_size) +{ + int r; + struct json_object *luks1_obj, *field; + uint64_t json_size; + + luks1_obj = json_object_new_object(); + if (!luks1_obj) + return -ENOMEM; + + /* keyslots field */ + r = json_luks1_keyslots(hdr_v1, &field); + if (r) { + json_object_put(luks1_obj); + return r; + } + json_object_object_add(luks1_obj, "keyslots", field); + + /* tokens field */ + field = json_object_new_object(); + if (!field) { + json_object_put(luks1_obj); + return -ENOMEM; + } + json_object_object_add(luks1_obj, "tokens", field); + + /* segments field */ + r = json_luks1_segments(hdr_v1, &field); + if (r) { + json_object_put(luks1_obj); + return r; + } + json_object_object_add(luks1_obj, "segments", field); + + /* digests field */ + r = json_luks1_digests(hdr_v1, &field); + if (r) { + json_object_put(luks1_obj); + return r; + } + json_object_object_add(luks1_obj, "digests", field); + + /* config field */ + /* anything else? */ + field = json_object_new_object(); + if (!field) { + json_object_put(luks1_obj); + return -ENOMEM; + } + json_object_object_add(luks1_obj, "config", field); + + json_size = LUKS2_HDR_16K_LEN - LUKS2_HDR_BIN_LEN; + json_object_object_add(field, "json_size", json_object_new_uint64(json_size)); + json_object_object_add(field, "keyslots_size", json_object_new_uint64(keyslots_size)); + + *luks1_object = luks1_obj; + return 0; +} + +static void move_keyslot_offset(json_object *jobj, int offset_add) +{ + json_object *jobj1, *jobj2, *jobj_area; + uint64_t offset = 0; + + json_object_object_get_ex(jobj, "keyslots", &jobj1); + json_object_object_foreach(jobj1, key, val) { + UNUSED(key); + json_object_object_get_ex(val, "area", &jobj_area); + json_object_object_get_ex(jobj_area, "offset", &jobj2); + offset = json_object_get_uint64(jobj2) + offset_add; + json_object_object_add(jobj_area, "offset", json_object_new_uint64(offset)); + } +} + +/* FIXME: return specific error code for partial write error (aka keyslots are gone) */ +static int move_keyslot_areas(struct crypt_device *cd, off_t offset_from, + off_t offset_to, size_t buf_size) +{ + struct device *device = crypt_metadata_device(cd); + void *buf = NULL; + int r = -EIO, devfd = -1; + + log_dbg(cd, "Moving keyslot areas of size %zu from %jd to %jd.", + buf_size, (intmax_t)offset_from, (intmax_t)offset_to); + + if (posix_memalign(&buf, crypt_getpagesize(), buf_size)) + return -ENOMEM; + + devfd = device_open(cd, device, O_RDWR); + if (devfd == -1) { + free(buf); + return -EIO; + } + + /* This can safely fail (for block devices). It only allocates space if it is possible. */ + if (posix_fallocate(devfd, offset_to, buf_size)) + log_dbg(cd, "Preallocation (fallocate) of new keyslot area not available."); + + /* Try to read *new* area to check that area is there (trimmed backup). */ + if (read_lseek_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), buf, buf_size, + offset_to)!= (ssize_t)buf_size) + goto out; + + if (read_lseek_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), buf, buf_size, + offset_from)!= (ssize_t)buf_size) + goto out; + + if (write_lseek_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), buf, buf_size, + offset_to) != (ssize_t)buf_size) + goto out; + + r = 0; +out: + device_sync(cd, device, devfd); + close(devfd); + crypt_memzero(buf, buf_size); + free(buf); + + return r; +} + +static int luks_header_in_use(struct crypt_device *cd) +{ + int r; + + r = lookup_dm_dev_by_uuid(cd, crypt_get_uuid(cd), crypt_get_type(cd)); + if (r < 0) + log_err(cd, _("Can not check status of device with uuid: %s."), crypt_get_uuid(cd)); + + return r; +} + +/* Check if there is a luksmeta area (foreign metadata created by the luksmeta package) */ +static int luksmeta_header_present(struct crypt_device *cd, off_t luks1_size) +{ + static const uint8_t LM_MAGIC[] = { 'L', 'U', 'K', 'S', 'M', 'E', 'T', 'A' }; + struct device *device = crypt_metadata_device(cd); + void *buf = NULL; + int devfd, r = 0; + + if (posix_memalign(&buf, crypt_getpagesize(), sizeof(LM_MAGIC))) + return -ENOMEM; + + devfd = device_open(cd, device, O_RDONLY); + if (devfd == -1) { + free(buf); + return -EIO; + } + + /* Note: we must not detect failure as problem here, header can be trimmed. */ + if (read_lseek_blockwise(devfd, device_block_size(cd, device), device_alignment(device), + buf, sizeof(LM_MAGIC), luks1_size) == (ssize_t)sizeof(LM_MAGIC) && + !memcmp(LM_MAGIC, buf, sizeof(LM_MAGIC))) { + log_err(cd, _("Unable to convert header with LUKSMETA additional metadata.")); + r = -EBUSY; + } + + close(devfd); + free(buf); + return r; +} + +/* Convert LUKS1 -> LUKS2 */ +int LUKS2_luks1_to_luks2(struct crypt_device *cd, struct luks_phdr *hdr1, struct luks2_hdr *hdr2) +{ + int r; + json_object *jobj = NULL; + size_t buf_size, buf_offset, luks1_size, luks1_shift = 2 * LUKS2_HDR_16K_LEN - LUKS_ALIGN_KEYSLOTS; + uint64_t max_size = crypt_get_data_offset(cd) * SECTOR_SIZE; + + /* for detached headers max size == device size */ + if (!max_size && (r = device_size(crypt_metadata_device(cd), &max_size))) + return r; + + luks1_size = LUKS_device_sectors(hdr1) << SECTOR_SHIFT; + luks1_size = size_round_up(luks1_size, LUKS_ALIGN_KEYSLOTS); + if (!luks1_size) + return -EINVAL; + + if (LUKS_keyslots_offset(hdr1) != (LUKS_ALIGN_KEYSLOTS / SECTOR_SIZE)) { + log_dbg(cd, "Unsupported keyslots material offset: %zu.", LUKS_keyslots_offset(hdr1)); + return -EINVAL; + } + + if (luksmeta_header_present(cd, luks1_size)) + return -EINVAL; + + log_dbg(cd, "Max size: %" PRIu64 ", LUKS1 (full) header size %zu , required shift: %zu", + max_size, luks1_size, luks1_shift); + if ((max_size - luks1_size) < luks1_shift) { + log_err(cd, _("Unable to move keyslot area. Not enough space.")); + return -EINVAL; + } + + r = json_luks1_object(hdr1, &jobj, max_size - 2 * LUKS2_HDR_16K_LEN); + if (r < 0) + return r; + + move_keyslot_offset(jobj, luks1_shift); + + // fill hdr2 + memset(hdr2, 0, sizeof(*hdr2)); + hdr2->hdr_size = LUKS2_HDR_16K_LEN; + hdr2->seqid = 1; + hdr2->version = 2; + strncpy(hdr2->checksum_alg, "sha256", LUKS2_CHECKSUM_ALG_L); + crypt_random_get(cd, (char*)hdr2->salt1, sizeof(hdr2->salt1), CRYPT_RND_SALT); + crypt_random_get(cd, (char*)hdr2->salt2, sizeof(hdr2->salt2), CRYPT_RND_SALT); + strncpy(hdr2->uuid, crypt_get_uuid(cd), LUKS2_UUID_L-1); /* UUID should be max 36 chars */ + hdr2->jobj = jobj; + + /* + * It duplicates check in LUKS2_hdr_write() but we don't want to move + * keyslot areas in case it would fail later + */ + if (max_size < LUKS2_hdr_and_areas_size(hdr2->jobj)) { + r = -EINVAL; + goto out; + } + + if ((r = luks_header_in_use(cd))) { + if (r > 0) + r = -EBUSY; + goto out; + } + + // move keyslots 4k -> 32k offset + buf_offset = 2 * LUKS2_HDR_16K_LEN; + buf_size = luks1_size - LUKS_ALIGN_KEYSLOTS; + if ((r = move_keyslot_areas(cd, 8 * SECTOR_SIZE, buf_offset, buf_size)) < 0) { + log_err(cd, _("Unable to move keyslot area.")); + goto out; + } + + // Write JSON hdr2 + r = LUKS2_hdr_write(cd, hdr2); +out: + LUKS2_hdr_free(cd, hdr2); + + return r; +} + +static int keyslot_LUKS1_compatible(struct crypt_device *cd, struct luks2_hdr *hdr, + int keyslot, uint32_t key_size, const char *hash) +{ + json_object *jobj_keyslot, *jobj, *jobj_kdf, *jobj_af; + uint64_t l2_offset, l2_length; + size_t ks_key_size; + const char *ks_cipher, *data_cipher; + + jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot); + if (!jobj_keyslot) + return 1; + + if (!json_object_object_get_ex(jobj_keyslot, "type", &jobj) || + strcmp(json_object_get_string(jobj), "luks2")) + return 0; + + /* Using PBKDF2, this implies memory and parallel is not used. */ + jobj = NULL; + if (!json_object_object_get_ex(jobj_keyslot, "kdf", &jobj_kdf) || + !json_object_object_get_ex(jobj_kdf, "type", &jobj) || + strcmp(json_object_get_string(jobj), CRYPT_KDF_PBKDF2) || + !json_object_object_get_ex(jobj_kdf, "hash", &jobj) || + strcmp(json_object_get_string(jobj), hash)) + return 0; + + jobj = NULL; + if (!json_object_object_get_ex(jobj_keyslot, "af", &jobj_af) || + !json_object_object_get_ex(jobj_af, "stripes", &jobj) || + json_object_get_int(jobj) != LUKS_STRIPES) + return 0; + + jobj = NULL; + if (!json_object_object_get_ex(jobj_af, "hash", &jobj) || + (crypt_hash_size(json_object_get_string(jobj)) < 0) || + strcmp(json_object_get_string(jobj), hash)) + return 0; + + /* FIXME: should this go to validation code instead (aka invalid luks2 header if assigned to segment 0)? */ + /* FIXME: check all keyslots are assigned to segment id 0, and segments count == 1 */ + ks_cipher = LUKS2_get_keyslot_cipher(hdr, keyslot, &ks_key_size); + data_cipher = LUKS2_get_cipher(hdr, CRYPT_DEFAULT_SEGMENT); + if (!ks_cipher || !data_cipher || key_size != ks_key_size || strcmp(ks_cipher, data_cipher)) { + log_dbg(cd, "Cipher in keyslot %d is different from volume key encryption.", keyslot); + return 0; + } + + if (LUKS2_keyslot_area(hdr, keyslot, &l2_offset, &l2_length)) + return 0; + + if (l2_length != (size_round_up(AF_split_sectors(key_size, LUKS_STRIPES) * SECTOR_SIZE, 4096))) { + log_dbg(cd, "Area length in LUKS2 keyslot (%d) is not compatible with LUKS1", keyslot); + return 0; + } + + return 1; +} + +/* Convert LUKS2 -> LUKS1 */ +int LUKS2_luks2_to_luks1(struct crypt_device *cd, struct luks2_hdr *hdr2, struct luks_phdr *hdr1) +{ + size_t buf_size, buf_offset; + char cipher[LUKS_CIPHERNAME_L-1], cipher_mode[LUKS_CIPHERMODE_L-1]; + char digest[LUKS_DIGESTSIZE], digest_salt[LUKS_SALTSIZE]; + const char *hash; + size_t len; + json_object *jobj_keyslot, *jobj_digest, *jobj_segment, *jobj_kdf, *jobj_area, *jobj1, *jobj2; + uint32_t key_size; + int i, r, last_active = 0; + uint64_t offset, area_length; + char buf[256], luksMagic[] = LUKS_MAGIC; + + jobj_digest = LUKS2_get_digest_jobj(hdr2, 0); + if (!jobj_digest) + return -EINVAL; + + jobj_segment = LUKS2_get_segment_jobj(hdr2, CRYPT_DEFAULT_SEGMENT); + if (!jobj_segment) + return -EINVAL; + + json_object_object_get_ex(hdr2->jobj, "digests", &jobj1); + if (!json_object_object_get_ex(jobj_digest, "type", &jobj2) || + strcmp(json_object_get_string(jobj2), "pbkdf2") || + json_object_object_length(jobj1) != 1) { + log_err(cd, _("Cannot convert to LUKS1 format - key slot digests are not LUKS1 compatible.")); + return -EINVAL; + } + if (!json_object_object_get_ex(jobj_digest, "hash", &jobj2)) + return -EINVAL; + hash = json_object_get_string(jobj2); + + r = crypt_parse_name_and_mode(LUKS2_get_cipher(hdr2, CRYPT_DEFAULT_SEGMENT), cipher, NULL, cipher_mode); + if (r < 0) + return r; + + if (crypt_cipher_wrapped_key(cipher, cipher_mode)) { + log_err(cd, _("Cannot convert to LUKS1 format - device uses wrapped key cipher %s."), cipher); + return -EINVAL; + } + + r = LUKS2_tokens_count(hdr2); + if (r < 0) + return r; + if (r > 0) { + log_err(cd, _("Cannot convert to LUKS1 format - LUKS2 header contains %u token(s)."), r); + return -EINVAL; + } + + r = LUKS2_get_volume_key_size(hdr2, 0); + if (r < 0) + return -EINVAL; + key_size = r; + + for (i = 0; i < LUKS2_KEYSLOTS_MAX; i++) { + if (LUKS2_keyslot_info(hdr2, i) == CRYPT_SLOT_INACTIVE) + continue; + + if (LUKS2_keyslot_info(hdr2, i) == CRYPT_SLOT_INVALID) { + log_err(cd, _("Cannot convert to LUKS1 format - keyslot %u is in invalid state."), i); + return -EINVAL; + } + + if (i >= LUKS_NUMKEYS) { + log_err(cd, _("Cannot convert to LUKS1 format - slot %u (over maximum slots) is still active."), i); + return -EINVAL; + } + + if (!keyslot_LUKS1_compatible(cd, hdr2, i, key_size, hash)) { + log_err(cd, _("Cannot convert to LUKS1 format - keyslot %u is not LUKS1 compatible."), i); + return -EINVAL; + } + } + + memset(hdr1, 0, sizeof(*hdr1)); + + for (i = 0; i < LUKS_NUMKEYS; i++) { + hdr1->keyblock[i].active = LUKS_KEY_DISABLED; + hdr1->keyblock[i].stripes = LUKS_STRIPES; + + jobj_keyslot = LUKS2_get_keyslot_jobj(hdr2, i); + + if (jobj_keyslot) { + if (!json_object_object_get_ex(jobj_keyslot, "area", &jobj_area)) + return -EINVAL; + if (!json_object_object_get_ex(jobj_area, "offset", &jobj1)) + return -EINVAL; + offset = json_object_get_uint64(jobj1); + } else { + if (LUKS2_find_area_gap(cd, hdr2, key_size, &offset, &area_length)) + return -EINVAL; + /* + * We have to create placeholder luks2 keyslots in place of all + * inactive keyslots. Otherwise we would allocate all + * inactive luks1 keyslots over same binary keyslot area. + */ + if (placeholder_keyslot_alloc(cd, i, offset, area_length, key_size)) + return -EINVAL; + } + + offset /= SECTOR_SIZE; + if (offset > UINT32_MAX) + return -EINVAL; + + hdr1->keyblock[i].keyMaterialOffset = offset; + hdr1->keyblock[i].keyMaterialOffset -= + ((2 * LUKS2_HDR_16K_LEN - LUKS_ALIGN_KEYSLOTS) / SECTOR_SIZE); + + if (!jobj_keyslot) + continue; + + hdr1->keyblock[i].active = LUKS_KEY_ENABLED; + last_active = i; + + if (!json_object_object_get_ex(jobj_keyslot, "kdf", &jobj_kdf)) + continue; + + if (!json_object_object_get_ex(jobj_kdf, "iterations", &jobj1)) + continue; + hdr1->keyblock[i].passwordIterations = json_object_get_uint32(jobj1); + + if (!json_object_object_get_ex(jobj_kdf, "salt", &jobj1)) + continue; + len = sizeof(buf); + memset(buf, 0, len); + if (!base64_decode(json_object_get_string(jobj1), + json_object_get_string_len(jobj1), buf, &len)) + continue; + if (len > 0 && len != LUKS_SALTSIZE) + continue; + memcpy(hdr1->keyblock[i].passwordSalt, buf, LUKS_SALTSIZE); + } + + if (!jobj_keyslot) { + jobj_keyslot = LUKS2_get_keyslot_jobj(hdr2, last_active); + if (!jobj_keyslot) + return -EINVAL; + } + + if (!json_object_object_get_ex(jobj_keyslot, "area", &jobj_area)) + return -EINVAL; + if (!json_object_object_get_ex(jobj_area, "encryption", &jobj1)) + return -EINVAL; + r = crypt_parse_name_and_mode(json_object_get_string(jobj1), cipher, NULL, cipher_mode); + if (r < 0) + return r; + + strncpy(hdr1->cipherName, cipher, sizeof(hdr1->cipherName) - 1); + strncpy(hdr1->cipherMode, cipher_mode, sizeof(hdr1->cipherMode) - 1); + + if (!json_object_object_get_ex(jobj_keyslot, "kdf", &jobj_kdf)) + return -EINVAL; + if (!json_object_object_get_ex(jobj_kdf, "hash", &jobj1)) + return -EINVAL; + strncpy(hdr1->hashSpec, json_object_get_string(jobj1), sizeof(hdr1->hashSpec) - 1); + + hdr1->keyBytes = key_size; + + if (!json_object_object_get_ex(jobj_digest, "iterations", &jobj1)) + return -EINVAL; + hdr1->mkDigestIterations = json_object_get_uint32(jobj1); + + if (!json_object_object_get_ex(jobj_digest, "digest", &jobj1)) + return -EINVAL; + len = sizeof(digest); + if (!base64_decode(json_object_get_string(jobj1), + json_object_get_string_len(jobj1), digest, &len)) + return -EINVAL; + /* We can store full digest here, not only sha1 length */ + if (len < LUKS_DIGESTSIZE) + return -EINVAL; + memcpy(hdr1->mkDigest, digest, LUKS_DIGESTSIZE); + + if (!json_object_object_get_ex(jobj_digest, "salt", &jobj1)) + return -EINVAL; + len = sizeof(digest_salt); + if (!base64_decode(json_object_get_string(jobj1), + json_object_get_string_len(jobj1), digest_salt, &len)) + return -EINVAL; + if (len != LUKS_SALTSIZE) + return -EINVAL; + memcpy(hdr1->mkDigestSalt, digest_salt, LUKS_SALTSIZE); + + if (!json_object_object_get_ex(jobj_segment, "offset", &jobj1)) + return -EINVAL; + offset = json_object_get_uint64(jobj1) / SECTOR_SIZE; + if (offset > UINT32_MAX) + return -EINVAL; + /* FIXME: LUKS1 requires offset == 0 || offset >= luks1_hdr_size */ + hdr1->payloadOffset = offset; + + strncpy(hdr1->uuid, hdr2->uuid, UUID_STRING_L); /* max 36 chars */ + hdr1->uuid[UUID_STRING_L-1] = '\0'; + + memcpy(hdr1->magic, luksMagic, LUKS_MAGIC_L); + + hdr1->version = 1; + + r = luks_header_in_use(cd); + if (r) + return r > 0 ? -EBUSY : r; + + // move keyslots 32k -> 4k offset + buf_offset = 2 * LUKS2_HDR_16K_LEN; + buf_size = LUKS2_keyslots_size(hdr2->jobj); + r = move_keyslot_areas(cd, buf_offset, 8 * SECTOR_SIZE, buf_size); + if (r < 0) { + log_err(cd, _("Unable to move keyslot area.")); + return r; + } + + crypt_wipe_device(cd, crypt_metadata_device(cd), CRYPT_WIPE_ZERO, 0, + 8 * SECTOR_SIZE, 8 * SECTOR_SIZE, NULL, NULL); + + // Write LUKS1 hdr + return LUKS_write_phdr(hdr1, cd); +} diff --git a/lib/luks2/luks2_token.c b/lib/luks2/luks2_token.c new file mode 100644 index 0000000..58ba61d --- /dev/null +++ b/lib/luks2/luks2_token.c @@ -0,0 +1,606 @@ +/* + * LUKS - Linux Unified Key Setup v2, token handling + * + * Copyright (C) 2016-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2016-2019 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 <assert.h> + +#include "luks2_internal.h" + +/* Builtin tokens */ +extern const crypt_token_handler keyring_handler; + +static token_handler token_handlers[LUKS2_TOKENS_MAX] = { + /* keyring builtin token */ + { + .get = token_keyring_get, + .set = token_keyring_set, + .h = &keyring_handler + }, +}; + +static int is_builtin_candidate(const char *type) +{ + return !strncmp(type, LUKS2_BUILTIN_TOKEN_PREFIX, LUKS2_BUILTIN_TOKEN_PREFIX_LEN); +} + +int crypt_token_register(const crypt_token_handler *handler) +{ + int i; + + if (is_builtin_candidate(handler->name)) { + log_dbg(NULL, "'" LUKS2_BUILTIN_TOKEN_PREFIX "' is reserved prefix for builtin tokens."); + return -EINVAL; + } + + for (i = 0; i < LUKS2_TOKENS_MAX && token_handlers[i].h; i++) { + if (!strcmp(token_handlers[i].h->name, handler->name)) { + log_dbg(NULL, "Keyslot handler %s is already registered.", handler->name); + return -EINVAL; + } + } + + if (i == LUKS2_TOKENS_MAX) + return -EINVAL; + + token_handlers[i].h = handler; + return 0; +} + +static const token_handler +*LUKS2_token_handler_type_internal(struct crypt_device *cd, const char *type) +{ + int i; + + for (i = 0; i < LUKS2_TOKENS_MAX && token_handlers[i].h; i++) + if (!strcmp(token_handlers[i].h->name, type)) + return token_handlers + i; + + return NULL; +} + +static const crypt_token_handler +*LUKS2_token_handler_type(struct crypt_device *cd, const char *type) +{ + const token_handler *th = LUKS2_token_handler_type_internal(cd, type); + + return th ? th->h : NULL; +} + +static const token_handler +*LUKS2_token_handler_internal(struct crypt_device *cd, int token) +{ + struct luks2_hdr *hdr; + json_object *jobj1, *jobj2; + + if (token < 0) + return NULL; + + if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2))) + return NULL; + + if (!(jobj1 = LUKS2_get_token_jobj(hdr, token))) + return NULL; + + if (!json_object_object_get_ex(jobj1, "type", &jobj2)) + return NULL; + + return LUKS2_token_handler_type_internal(cd, json_object_get_string(jobj2)); +} + +static const crypt_token_handler +*LUKS2_token_handler(struct crypt_device *cd, int token) +{ + const token_handler *th = LUKS2_token_handler_internal(cd, token); + + return th ? th->h : NULL; +} + +static int LUKS2_token_find_free(struct luks2_hdr *hdr) +{ + int i; + + for (i = 0; i < LUKS2_TOKENS_MAX; i++) + if (!LUKS2_get_token_jobj(hdr, i)) + return i; + + return -EINVAL; +} + +int LUKS2_token_create(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + const char *json, + int commit) +{ + const crypt_token_handler *h; + const token_handler *th; + json_object *jobj_tokens, *jobj_type, *jobj; + enum json_tokener_error jerr; + char num[16]; + + if (token == CRYPT_ANY_TOKEN) { + if (!json) + return -EINVAL; + token = LUKS2_token_find_free(hdr); + } + + if (token < 0 || token >= LUKS2_TOKENS_MAX) + return -EINVAL; + + if (!json_object_object_get_ex(hdr->jobj, "tokens", &jobj_tokens)) + return -EINVAL; + + snprintf(num, sizeof(num), "%d", token); + + /* Remove token */ + if (!json) + json_object_object_del(jobj_tokens, num); + else { + + jobj = json_tokener_parse_verbose(json, &jerr); + if (!jobj) { + log_dbg(cd, "Token JSON parse failed."); + return -EINVAL; + } + + if (LUKS2_token_validate(cd, hdr->jobj, jobj, num)) { + json_object_put(jobj); + return -EINVAL; + } + + json_object_object_get_ex(jobj, "type", &jobj_type); + if (is_builtin_candidate(json_object_get_string(jobj_type))) { + th = LUKS2_token_handler_type_internal(cd, json_object_get_string(jobj_type)); + if (!th || !th->set) { + log_dbg(cd, "%s is builtin token candidate with missing handler", json_object_get_string(jobj_type)); + json_object_put(jobj); + return -EINVAL; + } + h = th->h; + } else + h = LUKS2_token_handler_type(cd, json_object_get_string(jobj_type)); + + if (h && h->validate && h->validate(cd, json)) { + json_object_put(jobj); + log_dbg(cd, "Token type %s validation failed.", h->name); + return -EINVAL; + } + + json_object_object_add(jobj_tokens, num, jobj); + if (LUKS2_check_json_size(cd, hdr)) { + log_dbg(cd, "Not enough space in header json area for new token."); + json_object_object_del(jobj_tokens, num); + return -ENOSPC; + } + } + + if (commit) + return LUKS2_hdr_write(cd, hdr) ?: token; + + return token; +} + +crypt_token_info LUKS2_token_status(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + const char **type) +{ + const char *tmp; + const token_handler *th; + json_object *jobj_type, *jobj_token; + + if (token < 0 || token >= LUKS2_TOKENS_MAX) + return CRYPT_TOKEN_INVALID; + + if (!(jobj_token = LUKS2_get_token_jobj(hdr, token))) + return CRYPT_TOKEN_INACTIVE; + + json_object_object_get_ex(jobj_token, "type", &jobj_type); + tmp = json_object_get_string(jobj_type); + + if ((th = LUKS2_token_handler_type_internal(cd, tmp))) { + if (type) + *type = th->h->name; + return th->set ? CRYPT_TOKEN_INTERNAL : CRYPT_TOKEN_EXTERNAL; + } + + if (type) + *type = tmp; + + return is_builtin_candidate(tmp) ? CRYPT_TOKEN_INTERNAL_UNKNOWN : CRYPT_TOKEN_EXTERNAL_UNKNOWN; +} + +int LUKS2_builtin_token_get(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + const char *type, + void *params) +{ + const token_handler *th = LUKS2_token_handler_type_internal(cd, type); + + // internal error + assert(th && th->get); + + return th->get(LUKS2_get_token_jobj(hdr, token), params) ?: token; +} + +int LUKS2_builtin_token_create(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + const char *type, + const void *params, + int commit) +{ + const token_handler *th; + int r; + json_object *jobj_token, *jobj_tokens; + + th = LUKS2_token_handler_type_internal(cd, type); + + // at this point all builtin handlers must exist and have validate fn defined + assert(th && th->set && th->h->validate); + + if (token == CRYPT_ANY_TOKEN) { + if ((token = LUKS2_token_find_free(hdr)) < 0) + log_err(cd, _("No free token slot.")); + } + if (token < 0 || token >= LUKS2_TOKENS_MAX) + return -EINVAL; + + r = th->set(&jobj_token, params); + if (r) { + log_err(cd, _("Failed to create builtin token %s."), type); + return r; + } + + // builtin tokens must produce valid json + r = LUKS2_token_validate(cd, hdr->jobj, jobj_token, "new"); + assert(!r); + r = th->h->validate(cd, json_object_to_json_string_ext(jobj_token, + JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE)); + assert(!r); + + json_object_object_get_ex(hdr->jobj, "tokens", &jobj_tokens); + json_object_object_add_by_uint(jobj_tokens, token, jobj_token); + if (LUKS2_check_json_size(cd, hdr)) { + log_dbg(cd, "Not enough space in header json area for new %s token.", type); + json_object_object_del_by_uint(jobj_tokens, token); + return -ENOSPC; + } + + if (commit) + return LUKS2_hdr_write(cd, hdr) ?: token; + + return token; +} + +static int LUKS2_token_open(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + char **buffer, + size_t *buffer_len, + void *usrptr) +{ + const char *json; + const crypt_token_handler *h; + int r; + + if (!(h = LUKS2_token_handler(cd, token))) + return -ENOENT; + + if (h->validate) { + if (LUKS2_token_json_get(cd, hdr, token, &json)) + return -EINVAL; + + if (h->validate(cd, json)) { + log_dbg(cd, "Token %d (%s) validation failed.", token, h->name); + return -EINVAL; + } + } + + r = h->open(cd, token, buffer, buffer_len, usrptr); + if (r < 0) + log_dbg(cd, "Token %d (%s) open failed with %d.", token, h->name, r); + + return r; +} + +static void LUKS2_token_buffer_free(struct crypt_device *cd, + int token, + void *buffer, + size_t buffer_len) +{ + const crypt_token_handler *h = LUKS2_token_handler(cd, token); + + if (h->buffer_free) + h->buffer_free(buffer, buffer_len); + else { + crypt_memzero(buffer, buffer_len); + free(buffer); + } +} + +static int LUKS2_keyslot_open_by_token(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + int segment, + const char *buffer, + size_t buffer_len, + struct volume_key **vk) +{ + const crypt_token_handler *h; + json_object *jobj_token, *jobj_token_keyslots, *jobj; + const char *num = NULL; + int i, r; + + if (!(h = LUKS2_token_handler(cd, token))) + return -ENOENT; + + jobj_token = LUKS2_get_token_jobj(hdr, token); + if (!jobj_token) + return -EINVAL; + + json_object_object_get_ex(jobj_token, "keyslots", &jobj_token_keyslots); + if (!jobj_token_keyslots) + return -EINVAL; + + /* Try to open keyslot referenced in token */ + r = -EINVAL; + for (i = 0; i < (int) json_object_array_length(jobj_token_keyslots) && r < 0; i++) { + jobj = json_object_array_get_idx(jobj_token_keyslots, i); + num = json_object_get_string(jobj); + log_dbg(cd, "Trying to open keyslot %s with token %d (type %s).", num, token, h->name); + r = LUKS2_keyslot_open(cd, atoi(num), segment, buffer, buffer_len, vk); + } + + if (r >= 0 && num) + return atoi(num); + + return r; +} + +int LUKS2_token_open_and_activate(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + const char *name, + uint32_t flags, + void *usrptr) +{ + int keyslot, r; + char *buffer; + size_t buffer_len; + struct volume_key *vk = NULL; + + r = LUKS2_token_open(cd, hdr, token, &buffer, &buffer_len, usrptr); + if (r < 0) + return r; + + r = LUKS2_keyslot_open_by_token(cd, hdr, token, + (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) ? + CRYPT_ANY_SEGMENT : CRYPT_DEFAULT_SEGMENT, + buffer, buffer_len, &vk); + + LUKS2_token_buffer_free(cd, token, buffer, buffer_len); + + if (r < 0) + return r; + + keyslot = r; + + if ((name || (flags & CRYPT_ACTIVATE_KEYRING_KEY)) && crypt_use_keyring_for_vk(cd)) + r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, hdr, vk, keyslot); + + if (r >= 0 && name) + r = LUKS2_activate(cd, name, vk, flags); + + if (r < 0 && vk) + crypt_drop_keyring_key(cd, vk->key_description); + crypt_free_volume_key(vk); + + return r < 0 ? r : keyslot; +} + +int LUKS2_token_open_and_activate_any(struct crypt_device *cd, + struct luks2_hdr *hdr, + const char *name, + uint32_t flags) +{ + char *buffer; + json_object *tokens_jobj; + size_t buffer_len; + int keyslot, token, r = -EINVAL; + struct volume_key *vk = NULL; + + json_object_object_get_ex(hdr->jobj, "tokens", &tokens_jobj); + + json_object_object_foreach(tokens_jobj, slot, val) { + UNUSED(val); + token = atoi(slot); + + r = LUKS2_token_open(cd, hdr, token, &buffer, &buffer_len, NULL); + if (r < 0) + continue; + + r = LUKS2_keyslot_open_by_token(cd, hdr, token, + (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) ? + CRYPT_ANY_SEGMENT : CRYPT_DEFAULT_SEGMENT, + buffer, buffer_len, &vk); + LUKS2_token_buffer_free(cd, token, buffer, buffer_len); + if (r >= 0) + break; + } + + keyslot = r; + + if (r >= 0 && (name || (flags & CRYPT_ACTIVATE_KEYRING_KEY)) && crypt_use_keyring_for_vk(cd)) + r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, hdr, vk, keyslot); + + if (r >= 0 && name) + r = LUKS2_activate(cd, name, vk, flags); + + if (r < 0 && vk) + crypt_drop_keyring_key(cd, vk->key_description); + crypt_free_volume_key(vk); + + return r < 0 ? r : keyslot; +} + +void LUKS2_token_dump(struct crypt_device *cd, int token) +{ + const crypt_token_handler *h; + json_object *jobj_token; + + h = LUKS2_token_handler(cd, token); + if (h && h->dump) { + jobj_token = LUKS2_get_token_jobj(crypt_get_hdr(cd, CRYPT_LUKS2), token); + if (jobj_token) + h->dump(cd, json_object_to_json_string_ext(jobj_token, + JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE)); + } +} + +int LUKS2_token_json_get(struct crypt_device *cd, struct luks2_hdr *hdr, + int token, const char **json) +{ + json_object *jobj_token; + + jobj_token = LUKS2_get_token_jobj(hdr, token); + if (!jobj_token) + return -EINVAL; + + *json = json_object_to_json_string_ext(jobj_token, + JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE); + return 0; +} + +static int assign_one_keyslot(struct crypt_device *cd, struct luks2_hdr *hdr, + int token, int keyslot, int assign) +{ + json_object *jobj1, *jobj_token, *jobj_token_keyslots; + char num[16]; + + log_dbg(cd, "Keyslot %i %s token %i.", keyslot, assign ? "assigned to" : "unassigned from", token); + + jobj_token = LUKS2_get_token_jobj(hdr, token); + if (!jobj_token) + return -EINVAL; + + json_object_object_get_ex(jobj_token, "keyslots", &jobj_token_keyslots); + if (!jobj_token_keyslots) + return -EINVAL; + + snprintf(num, sizeof(num), "%d", keyslot); + if (assign) { + jobj1 = LUKS2_array_jobj(jobj_token_keyslots, num); + if (!jobj1) + json_object_array_add(jobj_token_keyslots, json_object_new_string(num)); + } else { + jobj1 = LUKS2_array_remove(jobj_token_keyslots, num); + if (jobj1) + json_object_object_add(jobj_token, "keyslots", jobj1); + } + + return 0; +} + +static int assign_one_token(struct crypt_device *cd, struct luks2_hdr *hdr, + int keyslot, int token, int assign) +{ + json_object *jobj_keyslots; + int r = 0; + + if (!LUKS2_get_token_jobj(hdr, token)) + return -EINVAL; + + if (keyslot == CRYPT_ANY_SLOT) { + json_object_object_get_ex(hdr->jobj, "keyslots", &jobj_keyslots); + + json_object_object_foreach(jobj_keyslots, key, val) { + UNUSED(val); + r = assign_one_keyslot(cd, hdr, token, atoi(key), assign); + if (r < 0) + break; + } + } else + r = assign_one_keyslot(cd, hdr, token, keyslot, assign); + + return r; +} + +int LUKS2_token_assign(struct crypt_device *cd, struct luks2_hdr *hdr, + int keyslot, int token, int assign, int commit) +{ + json_object *jobj_tokens; + int r = 0; + + if (token == CRYPT_ANY_TOKEN) { + json_object_object_get_ex(hdr->jobj, "tokens", &jobj_tokens); + + json_object_object_foreach(jobj_tokens, key, val) { + UNUSED(val); + r = assign_one_token(cd, hdr, keyslot, atoi(key), assign); + if (r < 0) + break; + } + } else + r = assign_one_token(cd, hdr, keyslot, token, assign); + + if (r < 0) + return r; + + // FIXME: do not write header in nothing changed + if (commit) + return LUKS2_hdr_write(cd, hdr) ?: token; + + return token; +} + +int LUKS2_token_is_assigned(struct crypt_device *cd, struct luks2_hdr *hdr, + int keyslot, int token) +{ + int i; + json_object *jobj_token, *jobj_token_keyslots, *jobj; + + if (keyslot < 0 || keyslot >= LUKS2_KEYSLOTS_MAX || token < 0 || token >= LUKS2_TOKENS_MAX) + return -EINVAL; + + jobj_token = LUKS2_get_token_jobj(hdr, token); + if (!jobj_token) + return -ENOENT; + + json_object_object_get_ex(jobj_token, "keyslots", &jobj_token_keyslots); + + for (i = 0; i < (int) json_object_array_length(jobj_token_keyslots); i++) { + jobj = json_object_array_get_idx(jobj_token_keyslots, i); + if (keyslot == atoi(json_object_get_string(jobj))) + return 0; + } + + return -ENOENT; +} + +int LUKS2_tokens_count(struct luks2_hdr *hdr) +{ + json_object *jobj_tokens = LUKS2_get_tokens_jobj(hdr); + if (!jobj_tokens) + return -EINVAL; + + return json_object_object_length(jobj_tokens); +} diff --git a/lib/luks2/luks2_token_keyring.c b/lib/luks2/luks2_token_keyring.c new file mode 100644 index 0000000..7f28607 --- /dev/null +++ b/lib/luks2/luks2_token_keyring.c @@ -0,0 +1,170 @@ +/* + * LUKS - Linux Unified Key Setup v2, kernel keyring token + * + * Copyright (C) 2016-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2016-2019 Ondrej Kozina + * + * 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 <assert.h> + +#include "luks2_internal.h" + +static int keyring_open(struct crypt_device *cd, + int token, + char **buffer, + size_t *buffer_len, + void *usrptr __attribute__((unused))) +{ + json_object *jobj_token, *jobj_key; + struct luks2_hdr *hdr; + int r; + + if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2))) + return -EINVAL; + + jobj_token = LUKS2_get_token_jobj(hdr, token); + if (!jobj_token) + return -EINVAL; + + json_object_object_get_ex(jobj_token, "key_description", &jobj_key); + + r = keyring_get_passphrase(json_object_get_string(jobj_key), buffer, buffer_len); + if (r == -ENOTSUP) { + log_dbg(cd, "Kernel keyring features disabled."); + return -EINVAL; + } else if (r < 0) { + log_dbg(cd, "keyring_get_passphrase failed (error %d)", r); + return -EINVAL; + } + + return 0; +} + +static int keyring_validate(struct crypt_device *cd __attribute__((unused)), + const char *json) +{ + enum json_tokener_error jerr; + json_object *jobj_token, *jobj_key; + int r = 1; + + log_dbg(cd, "Validating keyring token json"); + + jobj_token = json_tokener_parse_verbose(json, &jerr); + if (!jobj_token) { + log_dbg(cd, "Keyring token JSON parse failed."); + return r; + } + + if (json_object_object_length(jobj_token) != 3) { + log_dbg(cd, "Keyring token is expected to have exactly 3 fields."); + goto out; + } + + if (!json_object_object_get_ex(jobj_token, "key_description", &jobj_key)) { + log_dbg(cd, "missing key_description field."); + goto out; + } + + if (!json_object_is_type(jobj_key, json_type_string)) { + log_dbg(cd, "key_description is not a string."); + goto out; + } + + /* TODO: perhaps check that key description is in '%s:%s' + * format where both strings are not empty */ + r = !strlen(json_object_get_string(jobj_key)); +out: + json_object_put(jobj_token); + return r; +} + +static void keyring_dump(struct crypt_device *cd, const char *json) +{ + enum json_tokener_error jerr; + json_object *jobj_token, *jobj_key; + + jobj_token = json_tokener_parse_verbose(json, &jerr); + if (!jobj_token) + return; + + if (!json_object_object_get_ex(jobj_token, "key_description", &jobj_key)) { + json_object_put(jobj_token); + return; + } + + log_std(cd, "\tKey description: %s\n", json_object_get_string(jobj_key)); + + json_object_put(jobj_token); +} + +int token_keyring_set(json_object **jobj_builtin_token, + const void *params) +{ + json_object *jobj_token, *jobj; + const struct crypt_token_params_luks2_keyring *keyring_params = (const struct crypt_token_params_luks2_keyring *) params; + + jobj_token = json_object_new_object(); + if (!jobj_token) + return -ENOMEM; + + jobj = json_object_new_string(LUKS2_TOKEN_KEYRING); + if (!jobj) { + json_object_put(jobj_token); + return -ENOMEM; + } + json_object_object_add(jobj_token, "type", jobj); + + jobj = json_object_new_array(); + if (!jobj) { + json_object_put(jobj_token); + return -ENOMEM; + } + json_object_object_add(jobj_token, "keyslots", jobj); + + jobj = json_object_new_string(keyring_params->key_description); + if (!jobj) { + json_object_put(jobj_token); + return -ENOMEM; + } + json_object_object_add(jobj_token, "key_description", jobj); + + *jobj_builtin_token = jobj_token; + return 0; +} + +int token_keyring_get(json_object *jobj_token, + void *params) +{ + json_object *jobj; + struct crypt_token_params_luks2_keyring *keyring_params = (struct crypt_token_params_luks2_keyring *) params; + + json_object_object_get_ex(jobj_token, "type", &jobj); + assert(!strcmp(json_object_get_string(jobj), LUKS2_TOKEN_KEYRING)); + + json_object_object_get_ex(jobj_token, "key_description", &jobj); + + keyring_params->key_description = json_object_get_string(jobj); + + return 0; +} + +const crypt_token_handler keyring_handler = { + .name = LUKS2_TOKEN_KEYRING, + .open = keyring_open, + .validate = keyring_validate, + .dump = keyring_dump +}; |