From c56bdee177e75784ca2ab5f3a55483c292ff3317 Mon Sep 17 00:00:00 2001 From: Milan Broz Date: Sun, 24 Sep 2017 12:00:05 +0200 Subject: [PATCH] Add backend support for new device-mapper kernel options. This patch adds support for using keyring for volume key and support for new integrity fields for dm-crypt. Also helpers for searching disk by id. To be used later. --- lib/internal.h | 11 ++ lib/libcryptsetup.h | 49 +++++++ lib/libcryptsetup.sym | 2 + lib/libdevmapper.c | 270 +++++++++++++++++++++++++++++++++----- lib/loopaes/loopaes.c | 1 + lib/luks1/keyencryption.c | 2 + lib/luks1/keymanage.c | 1 + lib/setup.c | 159 +++++++++++++++++++++- lib/tcrypt/tcrypt.c | 1 + lib/utils_crypt.c | 34 +++++ lib/utils_crypt.h | 2 + lib/utils_devpath.c | 85 ++++++++++++ lib/utils_dm.h | 8 +- lib/volumekey.c | 18 +++ 14 files changed, 606 insertions(+), 37 deletions(-) diff --git a/lib/internal.h b/lib/internal.h index 4f81642c..a34b01e8 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -36,6 +36,7 @@ #include "utils_loop.h" #include "utils_dm.h" #include "utils_fips.h" +#include "utils_keyring.h" #include "crypto_backend.h" #include "libcryptsetup.h" @@ -55,16 +56,21 @@ #define at_least(a, b) ({ __typeof__(a) __at_least = (a); (__at_least >= (b))?__at_least:(b); }) +#define CRYPT_DEFAULT_SEGMENT 0 + struct crypt_device; struct volume_key { size_t keylength; + const char *key_description; char key[]; }; struct volume_key *crypt_alloc_volume_key(size_t keylength, const char *key); struct volume_key *crypt_generate_volume_key(struct crypt_device *cd, size_t keylength); void crypt_free_volume_key(struct volume_key *vk); +void crypt_volume_key_set_description(struct volume_key *key, const char *key_description); +const char *crypt_volume_key_get_description(const struct volume_key *key); struct crypt_pbkdf_type *crypt_get_pbkdf(struct crypt_device *cd); int init_pbkdf_type(struct crypt_device *cd, @@ -120,6 +126,8 @@ int crypt_dev_is_partition(const char *dev_path); char *crypt_get_partition_device(const char *dev_path, uint64_t offset, uint64_t size); char *crypt_get_base_device(const char *dev_path); uint64_t crypt_dev_partition_offset(const char *dev_path); +int lookup_by_disk_id(const char *dm_uuid); +int lookup_by_sysfs_uuid_field(const char *dm_uuid, size_t max_len); ssize_t write_buffer(int fd, const void *buf, size_t count); ssize_t read_buffer(int fd, void *buf, size_t count); @@ -175,4 +183,7 @@ const char *crypt_get_integrity(struct crypt_device *cd); int crypt_get_integrity_key_size(struct crypt_device *cd); int crypt_get_integrity_tag_size(struct crypt_device *cd); +int crypt_key_in_keyring(struct crypt_device *cd); +void crypt_set_key_in_keyring(struct crypt_device *cd, unsigned key_in_keyring); + #endif /* INTERNAL_H */ diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index 5a0c9036..514b3909 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -282,6 +282,24 @@ int crypt_memory_lock(struct crypt_device *cd, int lock); * to on-disk format types */ +/** + * Set or unset loading of volume keys via kernel keyring. When set to 'enabled' + * library loads key in kernel keyring first and pass the key description to + * dm-crypt instead of binary key copy. If set to 'disabled' library fall backs + * to classical method loading volume key directly in dm-crypt target. + * + * @param cd crypt device handle, can be @e NULL + * @param enable 0 to disable loading of volume keys via kernel keyring + * (classical method) otherwise enable it (default) + * + * @returns @e 0 on success or negative errno value otherwise. + * + * @note Currently loading of volume keys via kernel keyring is supported + * (and enabled by default) only for LUKS2 devices. + * @note The switch is global on the library level. + */ +int crypt_volume_key_keyring(struct crypt_device *cd, int enable); + /** * @addtogroup crypt_type * @{ @@ -527,6 +545,12 @@ int crypt_repair(struct crypt_device *cd, * @param new_size - new device size in sectors or @e 0 to use all of the underlying device size * * @return @e 0 on success or negative errno value otherwise. + * + * @note Most notably it returns -EPERM when device was activated with volume key + * in kernel keyring and current device handle doesn't have verified key + * in kernel keyring in the same time. Perform any crypt_activate_*() + * operation with device name set to NULL to load verified volume key in + * the keyring. */ int crypt_resize(struct crypt_device *cd, const char *name, @@ -760,6 +784,8 @@ int crypt_keyslot_destroy(struct crypt_device *cd, int keyslot); #define CRYPT_ACTIVATE_RESTART_ON_CORRUPTION (1 << 9) /** dm-verity: ignore_zero_blocks - do not verify zero blocks */ #define CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS (1 << 10) +/** key loaded in kernel keyring instead directly in dm-crypt */ +#define CRYPT_ACTIVATE_KEYRING_KEY (1 << 11) /** dm-integrity: direct writes, do not use journal */ #define CRYPT_ACTIVATE_NO_JOURNAL (1 << 12) /** dm-integrity: recovery mode - no journal, no integrity checks */ @@ -872,6 +898,29 @@ int crypt_activate_by_volume_key(struct crypt_device *cd, /** force deactivation - if the device is busy, it is replaced by error device */ #define CRYPT_DEACTIVATE_FORCE (1 << 1) +/** + * Activate device using passphrase stored in kernel keyring. + * + * + * @param cd crypt device handle + * @param name name of device to create, if @e NULL only check passphrase in keyring + * @param key_description kernel keyring key description library should look + * for passphrase in + * @param keyslot requested keyslot to check or CRYPT_ANY_SLOT + * @param flags activation flags + * + * @return @e unlocked keyslot number on success or negative errno value otherwise. + * + * @note Keyslot passphrase must be stored in 'user' key type + * and the key has to be reachable for process context + * on behalf of which this function is called. + */ +int crypt_activate_by_keyring(struct crypt_device *cd, + const char *name, + const char *key_description, + int keyslot, + uint32_t flags); + /** * Deactivate crypt device. This function tries to remove active device-mapper * mapping from kernel. Also, sensitive data like the volume key are removed from diff --git a/lib/libcryptsetup.sym b/lib/libcryptsetup.sym index 66a5b54c..d25ceecd 100644 --- a/lib/libcryptsetup.sym +++ b/lib/libcryptsetup.sym @@ -31,10 +31,12 @@ CRYPTSETUP_2.0 { crypt_activate_by_keyfile; crypt_activate_by_keyfile_offset; crypt_activate_by_volume_key; + crypt_activate_by_keyring; crypt_deactivate; crypt_deactivate_by_name; crypt_volume_key_get; crypt_volume_key_verify; + crypt_volume_key_keyring; crypt_status; crypt_dump; crypt_benchmark; diff --git a/lib/libdevmapper.c b/lib/libdevmapper.c index 56497be2..92451b86 100644 --- a/lib/libdevmapper.c +++ b/lib/libdevmapper.c @@ -35,6 +35,8 @@ #include "internal.h" #define DM_UUID_LEN 129 +#define DM_BY_ID_PREFIX "dm-uuid-" +#define DM_BY_ID_PREFIX_LEN 8 #define DM_UUID_PREFIX "CRYPT-" #define DM_UUID_PREFIX_LEN 6 #define DM_CRYPT_TARGET "crypt" @@ -382,36 +384,179 @@ static size_t get_key_size_strlen(size_t key_size) return ret; } +#define CLEN 64 /* 2*MAX_CIPHER_LEN */ +#define CLENS "63" /* for sscanf length + '\0' */ +#define CAPIL 144 /* should be enough to fit whole capi string */ +#define CAPIS "143" /* for sscanf of crypto API string + 16 + \0 */ + +static int cipher_c2dm(const char *org_c, const char *org_i, unsigned tag_size, + char *c_dm, int c_dm_size, + char *i_dm, int i_dm_size) +{ + int c_size = 0, i_size = 0, i; + char cipher[CLEN], mode[CLEN], iv[CLEN], tmp[CLEN]; + char capi[CAPIL]; + + if (!c_dm || !c_dm_size || !i_dm || !i_dm_size) + return -EINVAL; + + i = sscanf(org_c, "%" CLENS "[^-]-%" CLENS "s", cipher, tmp); + if (i != 2) + return -EINVAL; + + i = sscanf(tmp, "%" CLENS "[^-]-%" CLENS "s", mode, iv); + if (i == 1) { + strncpy(iv, mode, CLEN); + *mode = '\0'; + if (snprintf(capi, sizeof(capi), "%s", cipher) < 0) + return -EINVAL; + } else if (i == 2) { + if (snprintf(capi, sizeof(capi), "%s(%s)", mode, cipher) < 0) + return -EINVAL; + } else + return -EINVAL; + + if (!org_i) { + /* legacy mode: CIPHER-MODE-IV*/ + i_size = snprintf(i_dm, i_dm_size, "%s", ""); + c_size = snprintf(c_dm, c_dm_size, "%s", org_c); + } else if (!strcmp(org_i, "none")) { + /* IV only: capi:MODE(CIPHER)-IV */ + i_size = snprintf(i_dm, i_dm_size, " integrity:%u:none", tag_size); + c_size = snprintf(c_dm, c_dm_size, "capi:%s-%s", capi, iv); + } else if (!strcmp(org_i, "aead") && !strcmp(mode, "ccm")) { + /* CCM AEAD: capi:rfc4309(MODE(CIPHER))-IV */ + i_size = snprintf(i_dm, i_dm_size, " integrity:%u:aead", tag_size); + c_size = snprintf(c_dm, c_dm_size, "capi:rfc4309(%s)-%s", capi, iv); + } else if (!strcmp(org_i, "aead")) { + /* AEAD: capi:MODE(CIPHER))-IV */ + i_size = snprintf(i_dm, i_dm_size, " integrity:%u:aead", tag_size); + c_size = snprintf(c_dm, c_dm_size, "capi:%s-%s", capi, iv); + } else if (!strcmp(org_i, "poly1305")) { + /* POLY1305 AEAD: capi:rfc7539(MODE(CIPHER),POLY1305)-IV */ + i_size = snprintf(i_dm, i_dm_size, " integrity:%u:aead", tag_size); + c_size = snprintf(c_dm, c_dm_size, "capi:rfc7539(%s,poly1305)-%s", capi, iv); + } else { + /* other AEAD: capi:authenc(,MODE(CIPHER))-IV */ + i_size = snprintf(i_dm, i_dm_size, " integrity:%u:aead", tag_size); + c_size = snprintf(c_dm, c_dm_size, "capi:authenc(%s,%s)-%s", org_i, capi, iv); + } + + if (c_size < 0 || c_size == c_dm_size) + return -EINVAL; + if (i_size < 0 || i_size == i_dm_size) + return -EINVAL; + + return 0; +} + +static int cipher_dm2c(char **org_c, char **org_i, const char *c_dm, const char *i_dm) +{ + char cipher[CLEN], mode[CLEN], iv[CLEN], auth[CLEN]; + char tmp[CAPIL], capi[CAPIL]; + size_t len; + int i; + + if (!c_dm) + return -EINVAL; + + /* legacy mode */ + if (strncmp(c_dm, "capi:", 4)) { + if (!(*org_c = strdup(c_dm))) + return -ENOMEM; + *org_i = NULL; + return 0; + } + + /* modes with capi: prefix */ + i = sscanf(c_dm, "capi:%" CAPIS "[^-]-%" CLENS "s", tmp, iv); + if (i != 2) + return -EINVAL; + + len = strlen(tmp); + if (len < 2) + return -EINVAL; + + if (tmp[len-1] == ')') + tmp[len-1] = '\0'; + + if (sscanf(tmp, "rfc4309(%" CAPIS "s", capi) == 1) { + if (!(*org_i = strdup("aead"))) + return -ENOMEM; + } else if (sscanf(tmp, "rfc7539(%" CAPIS "[^,],%" CLENS "s", capi, auth) == 2) { + if (!(*org_i = strdup(auth))) + return -ENOMEM; + } else if (sscanf(tmp, "authenc(%" CLENS "[^,],%" CAPIS "s", auth, capi) == 2) { + if (!(*org_i = strdup(auth))) + return -ENOMEM; + } else { + if (i_dm) { + if (!(*org_i = strdup(i_dm))) + return -ENOMEM; + } else + *org_i = NULL; + strncpy(capi, tmp, sizeof(capi)); + } + + i = sscanf(capi, "%" CLENS "[^(](%" CLENS "[^)])", mode, cipher); + if (i == 2) + snprintf(tmp, sizeof(tmp), "%s-%s-%s", cipher, mode, iv); + else + snprintf(tmp, sizeof(tmp), "%s-%s", capi, iv); + + if (!(*org_c = strdup(tmp))) { + free(*org_i); + *org_i = NULL; + return -ENOMEM; + } + + return 0; +} + /* https://gitlab.com/cryptsetup/cryptsetup/wikis/DMCrypt */ static char *get_dm_crypt_params(struct crypt_dm_active_device *dmd, uint32_t flags) { int r, max_size, null_cipher = 0, num_options = 0, keystr_len = 0; char *params, *hexkey; - char features[256]; + char sector_feature[32], features[256], integrity_dm[256], cipher_dm[256]; if (!dmd) return NULL; + r = cipher_c2dm(dmd->u.crypt.cipher, dmd->u.crypt.integrity, dmd->u.crypt.tag_size, + cipher_dm, sizeof(cipher_dm), integrity_dm, sizeof(integrity_dm)); + if (r < 0) + return NULL; + if (flags & CRYPT_ACTIVATE_ALLOW_DISCARDS) num_options++; if (flags & CRYPT_ACTIVATE_SAME_CPU_CRYPT) num_options++; if (flags & CRYPT_ACTIVATE_SUBMIT_FROM_CRYPT_CPUS) num_options++; + if (dmd->u.crypt.integrity) + num_options++; - if (num_options) - snprintf(features, sizeof(features)-1, " %d%s%s%s", num_options, + if (dmd->u.crypt.sector_size != SECTOR_SIZE) { + num_options++; + snprintf(sector_feature, sizeof(sector_feature), " sector_size:%u", dmd->u.crypt.sector_size); + } else + *sector_feature = '\0'; + + if (num_options) { + snprintf(features, sizeof(features)-1, " %d%s%s%s%s%s", num_options, (flags & CRYPT_ACTIVATE_ALLOW_DISCARDS) ? " allow_discards" : "", (flags & CRYPT_ACTIVATE_SAME_CPU_CRYPT) ? " same_cpu_crypt" : "", - (flags & CRYPT_ACTIVATE_SUBMIT_FROM_CRYPT_CPUS) ? " submit_from_crypt_cpus" : ""); - else + (flags & CRYPT_ACTIVATE_SUBMIT_FROM_CRYPT_CPUS) ? " submit_from_crypt_cpus" : "", + sector_feature, integrity_dm); + } else *features = '\0'; - if (!strncmp(dmd->u.crypt.cipher, "cipher_null-", 12)) + if (!strncmp(cipher_dm, "cipher_null-", 12)) null_cipher = 1; - if (dmd->u.crypt.key_in_keyring) { - keystr_len = strlen(dmd->u.crypt.key_description) + get_key_size_strlen(dmd->u.crypt.vk->keylength) + 9; + if (flags & CRYPT_ACTIVATE_KEYRING_KEY) { + keystr_len = strlen(dmd->u.crypt.vk->key_description) + get_key_size_strlen(dmd->u.crypt.vk->keylength) + 9; hexkey = crypt_safe_alloc(keystr_len); } else hexkey = crypt_safe_alloc(null_cipher ? 2 : (dmd->u.crypt.vk->keylength * 2 + 1)); @@ -421,8 +566,8 @@ static char *get_dm_crypt_params(struct crypt_dm_active_device *dmd, uint32_t fl if (null_cipher) strncpy(hexkey, "-", 2); - else if (dmd->u.crypt.key_in_keyring) { - r = snprintf(hexkey, keystr_len, ":%zu:logon:%s", dmd->u.crypt.vk->keylength, dmd->u.crypt.key_description); + else if (flags & CRYPT_ACTIVATE_KEYRING_KEY) { + r = snprintf(hexkey, keystr_len, ":%zu:logon:%s", dmd->u.crypt.vk->keylength, dmd->u.crypt.vk->key_description); if (r < 0 || r >= keystr_len) { params = NULL; goto out; @@ -430,7 +575,7 @@ static char *get_dm_crypt_params(struct crypt_dm_active_device *dmd, uint32_t fl } else hex_key(hexkey, dmd->u.crypt.vk->keylength, dmd->u.crypt.vk->key); - max_size = strlen(hexkey) + strlen(dmd->u.crypt.cipher) + + max_size = strlen(hexkey) + strlen(cipher_dm) + strlen(device_block_path(dmd->data_device)) + strlen(features) + 64; params = crypt_safe_alloc(max_size); @@ -438,7 +583,7 @@ static char *get_dm_crypt_params(struct crypt_dm_active_device *dmd, uint32_t fl goto out; r = snprintf(params, max_size, "%s %s %" PRIu64 " %s %" PRIu64 "%s", - dmd->u.crypt.cipher, hexkey, dmd->u.crypt.iv_offset, + cipher_dm, hexkey, dmd->u.crypt.iv_offset, device_block_path(dmd->data_device), dmd->u.crypt.offset, features); if (r < 0 || r >= max_size) { @@ -852,6 +997,31 @@ static int dm_prepare_uuid(const char *name, const char *type, const char *uuid, return 1; } +int lookup_dm_dev_by_uuid(const char *uuid, const char *type) +{ + int r; + char *c; + char dev_uuid[DM_UUID_LEN + DM_BY_ID_PREFIX_LEN] = DM_BY_ID_PREFIX; + + if (!dm_prepare_uuid("", type, uuid, dev_uuid + DM_BY_ID_PREFIX_LEN, DM_UUID_LEN)) + return -EINVAL; + + c = strrchr(dev_uuid, '-'); + if (!c) + return -EINVAL; + + /* cut of dm name */ + *c = '\0'; + + r = lookup_by_disk_id(dev_uuid); + if (r == -ENOENT) { + log_dbg("Search by disk id not available. Using sysfs instead."); + r = lookup_by_sysfs_uuid_field(dev_uuid + DM_BY_ID_PREFIX_LEN, DM_UUID_LEN); + } + + return r; +} + static int _dm_create_device(const char *name, const char *type, struct device *device, uint32_t flags, const char *uuid, uint64_t size, @@ -980,6 +1150,14 @@ static int check_retry(uint32_t *dmd_flags, uint32_t dmt_flags) ret = 1; } + /* If kernel keyring is not supported load key directly in dm-crypt */ + if ((*dmd_flags & CRYPT_ACTIVATE_KEYRING_KEY) && + !(dmt_flags & DM_KERNEL_KEYRING_SUPPORTED)) { + log_dbg("dm-crypt doesn't suport kernel keyring"); + *dmd_flags = *dmd_flags & ~CRYPT_ACTIVATE_KEYRING_KEY; + ret = 1; + } + return ret; } @@ -1037,6 +1215,13 @@ int dm_create_device(struct crypt_device *cd, const char *name, if (r == -EINVAL && dmd->target == DM_VERITY && dmd->u.verity.fec_device && !(dmt_flags & DM_VERITY_FEC_SUPPORTED)) log_err(cd, _("Requested dm-verity FEC options are not supported.\n")); + + if (r == -EINVAL && dmd->target == DM_CRYPT) { + if (dmd->u.crypt.integrity && !(dmt_flags & DM_INTEGRITY_SUPPORTED)) + log_err(cd, _("Requested data integrity options are not supported.\n")); + if (dmd->u.crypt.sector_size != SECTOR_SIZE && !(dmt_flags & DM_SECTOR_SIZE_SUPPORTED)) + log_err(cd, _("Requested sector_size option is not supported.\n")); + } out: crypt_safe_free(table_params); dm_exit_context(); @@ -1169,23 +1354,22 @@ static int _dm_query_crypt(uint32_t get_flags, struct crypt_dm_active_device *dmd) { uint64_t val64; - char *rcipher, *key_, *rdevice, *endp, buffer[3], *arg; - unsigned int i; + char *rcipher, *rintegrity, *key_, *rdevice, *endp, buffer[3], *arg, *key_desc; + unsigned int i, val; int r; size_t key_size; struct device *data_device = NULL; - char *cipher = NULL; + char *cipher = NULL, *integrity = NULL; struct volume_key *vk = NULL; memset(dmd, 0, sizeof(*dmd)); dmd->target = DM_CRYPT; + dmd->u.crypt.sector_size = SECTOR_SIZE; r = -EINVAL; rcipher = strsep(¶ms, " "); - /* cipher */ - if (get_flags & DM_ACTIVE_CRYPT_CIPHER) - cipher = strdup(rcipher); + rintegrity = NULL; /* skip */ key_ = strsep(¶ms, " "); @@ -1216,6 +1400,8 @@ static int _dm_query_crypt(uint32_t get_flags, val64 = strtoull(params, ¶ms, 10); dmd->u.crypt.offset = val64; + dmd->u.crypt.tag_size = 0; + /* Features section, available since crypt target version 1.11 */ if (*params) { if (*params != ' ') @@ -1238,7 +1424,14 @@ static int _dm_query_crypt(uint32_t get_flags, dmd->flags |= CRYPT_ACTIVATE_SAME_CPU_CRYPT; else if (!strcasecmp(arg, "submit_from_crypt_cpus")) dmd->flags |= CRYPT_ACTIVATE_SUBMIT_FROM_CRYPT_CPUS; - else /* unknown option */ + else if (sscanf(arg, "integrity:%u:", &val) == 1) { + dmd->u.crypt.tag_size = val; + rintegrity = strchr(arg + strlen("integrity:"), ':') + 1; + if (!rintegrity) + goto err; + } else if (sscanf(arg, "sector_size:%u", &val) == 1) { + dmd->u.crypt.sector_size = val; + } else /* unknown option */ goto err; } @@ -1247,6 +1440,17 @@ static int _dm_query_crypt(uint32_t get_flags, goto err; } + /* cipher */ + if (get_flags & DM_ACTIVE_CRYPT_CIPHER) { + r = cipher_dm2c(CONST_CAST(char**)&cipher, + CONST_CAST(char**)&integrity, + rcipher, rintegrity); + if (r < 0) + goto err; + } + + r = -EINVAL; + /* Never allow to return empty key */ if ((get_flags & DM_ACTIVE_CRYPT_KEY) && dmi->suspended) { log_dbg("Cannot read volume key while suspended."); @@ -1254,7 +1458,7 @@ static int _dm_query_crypt(uint32_t get_flags, } if (key_[0] == ':') - dmd->u.crypt.key_in_keyring = 1; + dmd->flags |= CRYPT_ACTIVATE_KEYRING_KEY; if (get_flags & DM_ACTIVE_CRYPT_KEYSIZE) { /* we will trust kernel the key_string is in expected format */ @@ -1272,11 +1476,12 @@ static int _dm_query_crypt(uint32_t get_flags, if (get_flags & DM_ACTIVE_CRYPT_KEY) { if (key_[0] == ':') { - dmd->u.crypt.key_description = strdup(strpbrk(strpbrk(key_ + 1, ":") + 1, ":") + 1); - if (!dmd->u.crypt.key_description) { + key_desc = strdup(strpbrk(strpbrk(key_ + 1, ":") + 1, ":") + 1); + if (!key_desc) { r = -ENOMEM; goto err; } + crypt_volume_key_set_description(vk, key_desc); } else { buffer[2] = '\0'; for(i = 0; i < vk->keylength; i++) { @@ -1294,6 +1499,8 @@ static int _dm_query_crypt(uint32_t get_flags, if (cipher) dmd->u.crypt.cipher = cipher; + if (integrity) + dmd->u.crypt.integrity = integrity; if (data_device) dmd->data_device = data_device; if (vk) @@ -1301,6 +1508,7 @@ static int _dm_query_crypt(uint32_t get_flags, return 0; err: free(cipher); + free(integrity); device_free(data_device); crypt_free_volume_key(vk); return r; @@ -1543,9 +1751,9 @@ static int _dm_query_integrity(uint32_t get_flags, uint32_t val32; uint64_t val64; char c, *str, *str2, *arg; - unsigned int features, val; + unsigned int i, features, val; ssize_t len; - int i, r; + int r; struct device *data_device = NULL; char *integrity = NULL, *journal_crypt = NULL, *journal_integrity = NULL; struct volume_key *vk = NULL; @@ -1830,7 +2038,7 @@ out: } int dm_resume_and_reinstate_key(struct crypt_device *cd, const char *name, - size_t key_size, const char *key, unsigned key_in_keyring) + const struct volume_key *vk) { uint32_t dmt_flags; int msg_size; @@ -1843,10 +2051,10 @@ int dm_resume_and_reinstate_key(struct crypt_device *cd, const char *name, if (!(dmt_flags & DM_KEY_WIPE_SUPPORTED)) goto out; - if (key_in_keyring) - msg_size = strlen(key) + get_key_size_strlen(key_size) + 17; + if (vk->key_description) + msg_size = strlen(vk->key_description) + get_key_size_strlen(vk->keylength) + 17; else - msg_size = key_size * 2 + 10; // key set + msg_size = vk->keylength * 2 + 10; // key set msg = crypt_safe_alloc(msg_size); if (!msg) { @@ -1855,10 +2063,10 @@ int dm_resume_and_reinstate_key(struct crypt_device *cd, const char *name, } strcpy(msg, "key set "); - if (key_in_keyring) - snprintf(msg + 8, msg_size - 8, ":%zu:logon:%s", key_size, key); + if (vk->key_description) + snprintf(msg + 8, msg_size - 8, ":%zu:logon:%s", vk->keylength, vk->key_description); else - hex_key(&msg[8], key_size, key); + hex_key(&msg[8], vk->keylength, vk->key); if (!_dm_message(name, msg, dmt_flags) || !_dm_simple(DM_DEVICE_RESUME, name, 1)) { diff --git a/lib/loopaes/loopaes.c b/lib/loopaes/loopaes.c index 3d5468d4..7437daff 100644 --- a/lib/loopaes/loopaes.c +++ b/lib/loopaes/loopaes.c @@ -216,6 +216,7 @@ int LOOPAES_activate(struct crypt_device *cd, .vk = vk, .offset = crypt_get_data_offset(cd), .iv_offset = crypt_get_iv_offset(cd), + .sector_size = crypt_get_sector_size(cd), } }; diff --git a/lib/luks1/keyencryption.c b/lib/luks1/keyencryption.c index 79f16499..64896b17 100644 --- a/lib/luks1/keyencryption.c +++ b/lib/luks1/keyencryption.c @@ -24,6 +24,7 @@ #include #include #include "luks.h" +#include "af.h" #include "internal.h" static void _error_hint(struct crypt_device *ctx, const char *device, @@ -62,6 +63,7 @@ static int LUKS_endec_template(char *src, size_t srcLength, .vk = vk, .offset = sector, .iv_offset = 0, + .sector_size = SECTOR_SIZE, } }; int r, devfd = -1; diff --git a/lib/luks1/keymanage.c b/lib/luks1/keymanage.c index a691c5c6..f316bb75 100644 --- a/lib/luks1/keymanage.c +++ b/lib/luks1/keymanage.c @@ -1183,6 +1183,7 @@ int LUKS1_activate(struct crypt_device *cd, .vk = vk, .offset = crypt_get_data_offset(cd), .iv_offset = 0, + .sector_size = crypt_get_sector_size(cd), } }; diff --git a/lib/setup.c b/lib/setup.c index 71e2064f..9cd4e654 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -48,6 +48,7 @@ struct crypt_device { struct crypt_pbkdf_type pbkdf; /* global context scope settings */ + unsigned key_in_keyring:1; // FIXME: private binary headers and access it properly // through sub-library (LUKS1, TCRYPT) @@ -108,6 +109,12 @@ static int _crypto_logged = 0; static void (*_default_log)(int level, const char *msg, void *usrptr) = NULL; static int _debug_level = 0; +/* Library scope detection for kernel keyring support */ +static int _kernel_keyring_supported; + +/* Library allowed to use kernel keyring for loading VK in kernel crypto layer */ +static int _vk_via_keyring = 1; + void crypt_set_debug_level(int level) { _debug_level = level; @@ -124,6 +131,8 @@ void crypt_log(struct crypt_device *cd, int level, const char *msg) cd->log(level, msg, cd->log_usrptr); else if (_default_log) _default_log(level, msg, NULL); + else if (_debug_level) + printf("%s", msg); } __attribute__((format(printf, 5, 6))) @@ -400,6 +409,7 @@ int PLAIN_activate(struct crypt_device *cd, .vk = vk, .offset = crypt_get_data_offset(cd), .iv_offset = crypt_get_iv_offset(cd), + .sector_size = crypt_get_sector_size(cd), } }; @@ -1514,9 +1524,36 @@ int crypt_repair(struct crypt_device *cd, return r; } +static int crypt_get_segment_key_description(struct crypt_device *cd, char **segment_key_desc, int segment) +{ + char *key_desc; + int r; + size_t len; + + if (!crypt_get_uuid(cd) || segment > 9 || segment < 0) + return -EINVAL; + + len = strlen(crypt_get_uuid(cd)) + 14; + + key_desc = malloc(len); + if (!key_desc) + return -ENOMEM; + + r = snprintf(key_desc, len, "%s:%s-%u", "cryptsetup", crypt_get_uuid(cd), segment); + if (r < 0 || (size_t)r >= len) { + free(key_desc); + return -EINVAL; + } + + *segment_key_desc = key_desc; + + return 0; +} + int crypt_resize(struct crypt_device *cd, const char *name, uint64_t new_size) { - struct crypt_dm_active_device dmd; + char *key_desc; + struct crypt_dm_active_device dmd = {}; int r; /* Device context type must be initialised */ @@ -1538,6 +1575,20 @@ int crypt_resize(struct crypt_device *cd, const char *name, uint64_t new_size) goto out; } + if ((dmd.flags & CRYPT_ACTIVATE_KEYRING_KEY) && !crypt_key_in_keyring(cd)) { + r = -EPERM; + goto out; + } + + if (crypt_key_in_keyring(cd)) { + r = crypt_get_segment_key_description(cd, &key_desc, CRYPT_DEFAULT_SEGMENT); + if (r) + goto out; + + crypt_volume_key_set_description(dmd.u.crypt.vk, key_desc); + dmd.flags |= CRYPT_ACTIVATE_KEYRING_KEY; + } + if (crypt_loop_device(crypt_get_device_name(cd))) { log_dbg("Trying to resize underlying loop device %s.", crypt_get_device_name(cd)); @@ -1690,6 +1741,56 @@ void crypt_free(struct crypt_device *cd) } } +/* internal only */ +int crypt_key_in_keyring(struct crypt_device *cd) +{ + return cd ? cd->key_in_keyring : 0; +} + +/* internal only */ +void crypt_set_key_in_keyring(struct crypt_device *cd, unsigned key_in_keyring) +{ + if (!cd) + return; + + cd->key_in_keyring = key_in_keyring; +} + +static void crypt_drop_keyring_key(struct crypt_device *cd, const char *active_key_desc) +{ + char *seg_key_desc; + + /* drop key detected in active dm-crypt table */ + if (active_key_desc) + keyring_revoke_and_unlink_key(active_key_desc); + + if (!crypt_key_in_keyring(cd) || + crypt_get_segment_key_description(cd, &seg_key_desc, CRYPT_DEFAULT_SEGMENT)) + return; + + /* drop segment key */ + if (!keyring_revoke_and_unlink_key(seg_key_desc)) + crypt_set_key_in_keyring(cd, 0); + + free(seg_key_desc); +} + +static char *crypt_get_device_key_description(const char *name) +{ + char *tmp = NULL; + struct crypt_dm_active_device dmd; + + if (dm_query_device(NULL, name, DM_ACTIVE_CRYPT_KEY | DM_ACTIVE_CRYPT_KEYSIZE, &dmd) < 0 || dmd.target != DM_CRYPT) + return NULL; + + if (dmd.flags & CRYPT_ACTIVATE_KEYRING_KEY) + tmp = strdup(crypt_volume_key_get_description(dmd.u.crypt.vk)); + + crypt_free_volume_key(dmd.u.crypt.vk); + + return tmp; +} + int crypt_suspend(struct crypt_device *cd, const char *name) { @@ -1771,7 +1872,7 @@ int crypt_resume_by_passphrase(struct crypt_device *cd, &cd->u.luks1.hdr, &vk, cd); if (r >= 0) { keyslot = r; - r = dm_resume_and_reinstate_key(cd, name, vk->keylength, vk->key, 0); + r = dm_resume_and_reinstate_key(cd, name, vk); if (r == -ENOTSUP) log_err(cd, _("Resume is not supported for device %s.\n"), name); else if (r) @@ -1828,7 +1929,7 @@ int crypt_resume_by_keyfile_offset(struct crypt_device *cd, goto out; keyslot = r; - r = dm_resume_and_reinstate_key(cd, name, vk->keylength, vk->key, 0); + r = dm_resume_and_reinstate_key(cd, name, vk); if (r) log_err(cd, _("Error during resuming device %s.\n"), name); out: @@ -2110,6 +2211,29 @@ int crypt_keyslot_destroy(struct crypt_device *cd, int keyslot) return LUKS_del_key(keyslot, &cd->u.luks1.hdr, cd); } +int crypt_volume_key_load_in_keyring(struct crypt_device *cd, struct volume_key *vk) +{ + char *seg_key_desc = NULL; + int r; + + if (!vk) + return -EINVAL; + + r = crypt_get_segment_key_description(cd, &seg_key_desc, CRYPT_DEFAULT_SEGMENT); + if (!r) + r = keyring_add_key_in_thread_keyring(seg_key_desc, vk->key, vk->keylength); + + if (r) { + free(seg_key_desc); + log_err(cd, _("Failed to load key in kernel keyring.\n")); + } else { + crypt_volume_key_set_description(vk, seg_key_desc); + crypt_set_key_in_keyring(cd, 1); + } + + return r; +} + /* * Activation/deactivation of a device */ @@ -2909,6 +3033,35 @@ int crypt_get_integrity_info(struct crypt_device *cd, return -ENOTSUP; } +static int kernel_keyring_support(void) +{ + static unsigned _checked = 0; + + if (!_checked) { + _kernel_keyring_supported = keyring_check(); + _checked = 1; + } + + return _kernel_keyring_supported; +} + +int crypt_use_keyring_for_vk(const struct crypt_device *cd) +{ + uint32_t dmc_flags; + + /* dm backend must be initialised */ + if (!cd) + return 0; + + if (!_vk_via_keyring || !kernel_keyring_support()) + return 0; + + if (dm_flags(DM_CRYPT, &dmc_flags)) + return 1; + + return (dmc_flags & DM_KERNEL_KEYRING_SUPPORTED); +} + static void __attribute__((destructor)) libcryptsetup_exit(void) { crypt_backend_destroy(); diff --git a/lib/tcrypt/tcrypt.c b/lib/tcrypt/tcrypt.c index c953e089..0ffa00a9 100644 --- a/lib/tcrypt/tcrypt.c +++ b/lib/tcrypt/tcrypt.c @@ -704,6 +704,7 @@ int TCRYPT_activate(struct crypt_device *cd, .cipher = cipher, .offset = crypt_get_data_offset(cd), .iv_offset = crypt_get_iv_offset(cd), + .sector_size = crypt_get_sector_size(cd), } }; diff --git a/lib/utils_crypt.c b/lib/utils_crypt.c index 8b9f7eb7..fba042bf 100644 --- a/lib/utils_crypt.c +++ b/lib/utils_crypt.c @@ -89,6 +89,40 @@ int crypt_parse_hash_integrity_mode(const char *s, char *integrity) return 0; } +int crypt_parse_integrity_mode(const char *s, char *integrity, + int *integrity_key_size) +{ + int ks = 0, r = 0; + + if (!s || !integrity) + return -EINVAL; + + // FIXME: do not hardcode it here + + /* AEAD modes */ + if (!strcmp(s, "aead") || + !strcmp(s, "poly1305") || + !strcmp(s, "none")) { + strncpy(integrity, s, MAX_CIPHER_LEN); + ks = 0; + } else if (!strcmp(s, "hmac-sha256")) { + strncpy(integrity, "hmac(sha256)", MAX_CIPHER_LEN); + ks = 32; + } else if (!strcmp(s, "hmac-sha512")) { + ks = 64; + strncpy(integrity, "hmac(sha512)", MAX_CIPHER_LEN); + } else if (!strcmp(s, "cmac-aes")) { + ks = 16; + strncpy(integrity, "cmac(aes)", MAX_CIPHER_LEN); + } else + r = -EINVAL; + + if (integrity_key_size) + *integrity_key_size = ks; + + return r; +} + int crypt_parse_pbkdf(const char *s, const char **pbkdf) { const char *tmp = NULL; diff --git a/lib/utils_crypt.h b/lib/utils_crypt.h index 21336208..ed3d6461 100644 --- a/lib/utils_crypt.h +++ b/lib/utils_crypt.h @@ -40,6 +40,8 @@ struct safe_allocation { int crypt_parse_name_and_mode(const char *s, char *cipher, int *key_nums, char *cipher_mode); int crypt_parse_hash_integrity_mode(const char *s, char *integrity); +int crypt_parse_integrity_mode(const char *s, char *integrity, + int *integrity_key_size); int crypt_parse_pbkdf(const char *s, const char **pbkdf); void *crypt_safe_alloc(size_t size); diff --git a/lib/utils_devpath.c b/lib/utils_devpath.c index 257aa4db..fd44b28e 100644 --- a/lib/utils_devpath.c +++ b/lib/utils_devpath.c @@ -371,3 +371,88 @@ char *crypt_get_base_device(const char *dev_path) snprintf(part_path, sizeof(part_path), "/dev/%s", devname); return strdup(part_path); } + +int lookup_by_disk_id(const char *dm_uuid) +{ + struct dirent *entry; + struct stat st; + int r = 0; /* not found */ + DIR *dir = opendir("/dev/disk/by-id"); + + if (!dir) + /* map ENOTDIR to ENOENT we'll handle both errors same */ + return errno == ENOTDIR ? -ENOENT : -errno; + + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.' || + !strncmp(entry->d_name, "..", 2)) + continue; + + if (fstatat(dirfd(dir), entry->d_name, &st, AT_SYMLINK_NOFOLLOW)) { + r = -EINVAL; + break; + } + + if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) + continue; + + if (!strncmp(entry->d_name, dm_uuid, strlen(dm_uuid))) { + r = 1; + break; + } + } + + closedir(dir); + + return r; +} + +int lookup_by_sysfs_uuid_field(const char *dm_uuid, size_t max_len) +{ + struct dirent *entry; + char subpath[PATH_MAX], uuid[max_len]; + ssize_t s; + struct stat st; + int fd, len, r = 0; /* not found */ + DIR *dir = opendir("/sys/block/"); + + if (!dir) + /* map ENOTDIR to ENOENT we'll handle both errors same */ + return errno == ENOTDIR ? -ENOENT : -errno; + + while (r != 1 && (entry = readdir(dir))) { + if (entry->d_name[0] == '.' || + !strncmp(entry->d_name, "..", 2)) + continue; + + len = snprintf(subpath, PATH_MAX, "%s/%s", entry->d_name, "dm/uuid"); + if (len < 0 || len >= PATH_MAX) { + r = -EINVAL; + break; + } + + /* looking for dm-X/dm/uuid file, symlinks are fine */ + fd = openat(dirfd(dir), subpath, O_RDONLY | O_CLOEXEC); + if (fd < 0) + continue; + + if (fstat(fd, &st) || !S_ISREG(st.st_mode)) { + close(fd); + continue; + } + + /* reads binary data */ + s = read_buffer(fd, uuid, max_len - 1); + if (s > 0) { + uuid[s] = '\0'; + if (!strncmp(uuid, dm_uuid, strlen(dm_uuid))) + r = 1; + } + + close(fd); + } + + closedir(dir); + + return r; +} diff --git a/lib/utils_dm.h b/lib/utils_dm.h index 96d9d20e..d1329f0b 100644 --- a/lib/utils_dm.h +++ b/lib/utils_dm.h @@ -83,8 +83,8 @@ struct crypt_dm_active_device { /* struct crypt_active_device */ uint64_t offset; /* offset in sectors */ uint64_t iv_offset; /* IV initilisation sector */ - - unsigned key_in_keyring:1; /* status detected key loaded via kernel keyring */ + uint32_t tag_size; /* additional on-disk tag size */ + uint32_t sector_size; /* encryption sector size */ } crypt; struct { struct device *hash_device; @@ -136,10 +136,12 @@ int dm_create_device(struct crypt_device *cd, const char *name, int reload); int dm_suspend_and_wipe_key(struct crypt_device *cd, const char *name); int dm_resume_and_reinstate_key(struct crypt_device *cd, const char *name, - size_t key_size, const char *key, unsigned key_in_keyring); + const struct volume_key *vk); const char *dm_get_dir(void); +int lookup_dm_dev_by_uuid(const char *uuid, const char *type); + /* These are DM helpers used only by utils_devpath file */ int dm_is_dm_device(int major, int minor); int dm_is_dm_kernel_name(const char *name); diff --git a/lib/volumekey.c b/lib/volumekey.c index 6a6cd2a3..dcb20568 100644 --- a/lib/volumekey.c +++ b/lib/volumekey.c @@ -36,6 +36,7 @@ struct volume_key *crypt_alloc_volume_key(size_t keylength, const char *key) if (!vk) return NULL; + vk->key_description = NULL; vk->keylength = keylength; /* keylength 0 is valid => no key */ @@ -49,11 +50,28 @@ struct volume_key *crypt_alloc_volume_key(size_t keylength, const char *key) return vk; } +void crypt_volume_key_set_description(struct volume_key *vk, const char *key_description) +{ + if (vk) { + free(CONST_CAST(void*)vk->key_description); + vk->key_description = key_description; + } +} + +const char *crypt_volume_key_get_description(const struct volume_key *vk) +{ + if (!vk) + return NULL; + + return vk->key_description; +} + void crypt_free_volume_key(struct volume_key *vk) { if (vk) { crypt_memzero(vk->key, vk->keylength); vk->keylength = 0; + free(CONST_CAST(void*)vk->key_description); free(vk); } }