/* * zkey - Generate, re-encipher, and validate secure keys * * Keystore handling functions * * Copyright IBM Corp. 2018, 2024 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/util_base.h" #include "lib/util_file.h" #include "lib/util_libc.h" #include "lib/util_panic.h" #include "lib/util_path.h" #include "lib/util_rec.h" #include "keystore.h" #include "pkey.h" #include "cca.h" #include "properties.h" #include "utils.h" struct key_filenames { char *skey_filename; char *info_filename; char *renc_filename; char *pass_filename; }; #define FILE_EXTENSION_LEN 5 #define SKEY_FILE_EXTENSION ".skey" #define INFO_FILE_EXTENSION ".info" #define RENC_FILE_EXTENSION ".renc" #define PASS_FILE_EXTENSION ".pass" #define DUMMY_PASSPHRASE_LEN 16 #define LOCK_FILE_NAME ".lock" #define VOLUME_TYPE_PLAIN "plain" #define VOLUME_TYPE_LUKS2 "luks2" #define VOLUME_TYPE_INTEGRITY "integrity" #define DEFAULT_VOLUME_TYPE VOLUME_TYPE_LUKS2 #define DEFAULT_INTERITY_VOLUME_TYPE VOLUME_TYPE_INTEGRITY #define REC_KEY "Key" #define REC_DESCRIPTION "Description" #define REC_SEC_KEY_SIZE "Secure key size" #define REC_CLR_KEY_SIZE "Clear key size" #define REC_XTS "XTS type key" #define REC_KEY_TYPE "Key type" #define REC_VOLUMES "Volumes" #define REC_APQNS "APQNs" #define REC_KEY_FILE "Key file name" #define REC_SECTOR_SIZE "Sector size" #define REC_STATUS "Status" #define REC_MASTERKEY "Enciphered with" #define REC_CREATION_TIME "Created" #define REC_CHANGE_TIME "Changed" #define REC_REENC_TIME "Re-enciphered" #define REC_KEY_VP "Verification pattern" #define REC_VOLUME_TYPE "Volume type" #define REC_KMS "KMS" #define REC_KMS_KEY_LABEL "KMS key label" #define REC_PASSPHRASE_FILE "Dummy passphrase" #define pr_verbose(keystore, fmt...) do { \ if (keystore->verbose) \ warnx(fmt); \ } while (0) static int _keystore_kms_key_unbind(struct keystore *keystore, struct properties *properties); static char *_keystore_build_hmac_spec(struct properties *properties, size_t bitsize); /** * Gets the file names of the .skey and .info and .renc files for a named * key in the key strore's directory * * @param[in] keystore the key store * @param[in] name the name of the key * @param[out] names is filled with the names of the files * * @returns 0 for success or a negative errno in case of an error* */ static int _keystore_get_key_filenames(struct keystore *keystore, const char *name, struct key_filenames *names) { if (strpbrk(name, "/\\ *?'\"")) { warnx("Key name '%s' contains invalid characters", name); return -EINVAL; } util_asprintf(&names->skey_filename, "%s/%s%s", keystore->directory, name, SKEY_FILE_EXTENSION); util_asprintf(&names->info_filename, "%s/%s%s", keystore->directory, name, INFO_FILE_EXTENSION); util_asprintf(&names->renc_filename, "%s/%s%s", keystore->directory, name, RENC_FILE_EXTENSION); util_asprintf(&names->pass_filename, "%s/%s%s", keystore->directory, name, PASS_FILE_EXTENSION); pr_verbose(keystore, "File names for key '%s': '%s' and '%s'", name, names->skey_filename, names->info_filename); return 0; } /** * Checks if the .renc file exists. * * @param[in] file_names names of the files * * @returns 1 if the file exist, 0 if the file do not exist */ static int _keystore_reencipher_key_exists(struct key_filenames *file_names) { return util_path_is_reg_file("%s", file_names->renc_filename); } /** * Checks if the .pass file exists. * * @param[in] file_names names of the files * * @returns 1 if the file exist, 0 if the file do not exist */ static int _keystore_passphrase_file_exists(struct key_filenames *file_names) { return util_path_is_reg_file("%s", file_names->pass_filename); } /** * Checks if both, the .skey and the .info (and .renc) files exist. * * @param[in] file_names names of the files * * @returns 1 if all files exist, 0 if all files do not exist, -1 if one * file exists but other one does not exist (inconsistent state) */ static int _keystore_exists_keyfiles(struct key_filenames *file_names) { bool rc_skey, rc_info; rc_skey = util_path_is_reg_file("%s", file_names->skey_filename); rc_info = util_path_is_reg_file("%s", file_names->info_filename); if (rc_skey && rc_info) return 1; if (!rc_skey && !rc_info && _keystore_reencipher_key_exists(file_names) == 0 && _keystore_passphrase_file_exists(file_names) == 0) return 0; return -1; } /** * Checks if the files belonging to a key exist. If not an appropriate error * message is issued. * * @param[in] file_names names of the files * @param[in] name name of the key * * @returns 0 if the files exist, -ENOENT if the files do not exist, -EPERM if * one file exists but the other does not exist (inconsistent state) */ static int _keystore_ensure_keyfiles_exist(struct key_filenames *file_names, const char *name) { int rc; rc = _keystore_exists_keyfiles(file_names); if (rc == 0) { warnx("Key '%s' does not exist", name); return -ENOENT; } if (rc == -1) { warnx("Key '%s' is in an inconsistent state", name); return -EPERM; } return 0; } /** * Checks if the files belonging to a key do not exist. If they files exist, * an appropriate error message is issued. * * @param[in] file_names names of the files * @param[in] name name of the key * * @returns 0 if the files exist, -EEXIST if the files exist already, -EPERM if * one file exists but the other does not exist (inconsistent state) */ static int _keystore_ensure_keyfiles_not_exist(struct key_filenames *file_names, const char *name) { int rc; rc = _keystore_exists_keyfiles(file_names); if (rc == 1) { warnx("Key '%s' exists already", name); return -EEXIST; } if (rc == -1) { warnx("Key '%s' is in an inconsistent state", name); return -EPERM; } return 0; } /** * Frees the file names stored inside the struct key_filenames * * @param[in] names names of the files */ static void _keystore_free_key_filenames(struct key_filenames *names) { if (names->skey_filename) free(names->skey_filename); if (names->info_filename) free(names->info_filename); if (names->renc_filename) free(names->renc_filename); if (names->pass_filename) free(names->pass_filename); } /** * Sets the file permissions of the file to the permissions and the group * of the repository directory * * @param[in] keystroe the keystore * @param[in] filename the name of the file to set permissions for * * @returns 0 on success, or a negative errno value on failure */ static int _keystore_set_file_permission(struct keystore *keystore, const char *filename) { int fd, rc = 0; fd = open(filename, O_RDONLY | O_NOFOLLOW); if (fd < 0) { rc = -errno; warnx("Failed to open '%s': %s", filename, strerror(-rc)); return rc; } if (fchmod(fd, keystore->mode) != 0) { rc = -errno; warnx("chmod failed on file '%s': %s", filename, strerror(-rc)); goto out; } if (fchown(fd, geteuid(), keystore->owner) != 0) { rc = -errno; warnx("chown failed on file '%s': %s", filename, strerror(-rc)); goto out; } out: close(fd); return rc; } /** * Checks if the sector size is power of two and in range 512 - 4096 bytes. * * @param[in] sector_size the sector size * @param[in] key_type the type of the key, or NULL if the sector size is * to be checked independent of the key type. * @param[in] volume_type the type of associated volume, or NULL if the * sector size is to be checked independent of the * volume type. * * @returns 1 if the sector size is valid, 0 otherwise */ static int _keystore_valid_sector_size(size_t sector_size, const char *key_type, const char *volume_type) { if (sector_size == 0) return 1; if (sector_size < 512 || sector_size > 4096) return 0; if (sector_size & (sector_size - 1)) return 0; /* Can not set the sector size for volume type LUKS2 on a HMAC key */ if (sector_size != 0 && key_type != NULL && is_hmac_key_type(key_type) && volume_type != NULL && strcasecmp(volume_type, VOLUME_TYPE_LUKS2) == 0) return 0; return 1; } /** * Checks if the volume type is supported. * * @param[in] key_type the key type of the key associated to the volume, * or NULL if the volume type is to be checked * independent of the key type. * @param[in] volume_type the volume type * * @returns 1 if the volume type is valid, 0 otherwise */ static int _keystore_valid_volume_type(const char *key_type, const char *volume_type) { if (key_type == NULL || is_aes_key_type(key_type)) { if (strcasecmp(volume_type, VOLUME_TYPE_PLAIN) == 0) return 1; if (strcasecmp(volume_type, VOLUME_TYPE_LUKS2) == 0) return 1; }; if (key_type == NULL || is_hmac_key_type(key_type)) { if (strcasecmp(volume_type, VOLUME_TYPE_INTEGRITY) == 0) return 1; if (strcasecmp(volume_type, VOLUME_TYPE_LUKS2) == 0) return 1; } return 0; } /** * Returns the volume type contained in the properties. If no volume type * property is contained, then 'plain' is assumed (for backward compatibility). * * @returns a string containing the volume type. Must be freed by the caller. */ static char *_keystore_get_volume_type(struct properties *properties) { char *type; type = properties_get(properties, PROP_NAME_VOLUME_TYPE); if (type == NULL) type = util_strdup(VOLUME_TYPE_PLAIN); return type; } /** * Returns the key type contained in the properties. If no key type * property is contained, then 'CCA-AESDATA' is assumed (for backward * compatibility). * * @returns a string containing the key type. Must be freed by the caller. */ static char *_keystore_get_key_type(struct properties *properties) { char *type; type = properties_get(properties, PROP_NAME_KEY_TYPE); if (type == NULL) type = util_strdup(KEY_TYPE_CCA_AESDATA); return type; } /** * Checks if the key type is supported. * * @param[in] key_type the key type * * @returns 1 if the key type is valid, 0 otherwise */ static int _keystore_valid_key_type(const char *key_type) { if (strcasecmp(key_type, KEY_TYPE_CCA_AESDATA) == 0) return 1; if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0) return 1; if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0) return 1; if (strcasecmp(key_type, KEY_TYPE_PVSECRET_AES) == 0) return 1; if (strcasecmp(key_type, KEY_TYPE_PVSECRET_HMAC) == 0) return 1; return 0; } /** * Checks if the keys is KMS-bound * * @param[in] key_props the key properties * @param[out] kms_name the name of the KMS plugin, if KMS-bound * * @return true if the key is KMS bound, false otherwise */ static bool _keystore_is_kms_bound_key(struct properties *key_props, char **kms_name) { bool ret = false; char *kms; if (kms_name != NULL) *kms_name = NULL; kms = properties_get(key_props, PROP_NAME_KMS); if (kms != NULL && strcasecmp(kms, "LOCAL") != 0) ret = true; if (kms_name != NULL && ret == true) *kms_name = kms; else if (kms != NULL) free(kms); return ret; } /** * Prints a message followed by a list of associated volumes, if volumes are * associated and the volume-type matches (if specified) * * @param[in] msg the message to display * @param[in] properties the properties * @param[in] volume_type the volume type to display the message for (or NULL) * * @returns always zero */ static int _keystore_msg_for_volumes(const char *msg, struct properties *properties, const char *volume_type) { char *volumes = NULL; char **volume_list; char *type = NULL; int i; if (volume_type != NULL) { type = _keystore_get_volume_type(properties); if (strcasecmp(type, volume_type) != 0) goto out; } volumes = properties_get(properties, PROP_NAME_VOLUMES); if (volumes != NULL && strlen(volumes) > 0) { volume_list = str_list_split(volumes); util_print_indented(msg, 0); for (i = 0; volume_list[i] != NULL; i++) printf(" %s\n", volume_list[i]); str_list_free_string_array(volume_list); } out: if (volumes != NULL) free(volumes); if (type != NULL) free(type); return 0; } typedef int (*check_association_t)(const char *value, bool remove, bool set, char **normalized, void *private); /** * Set an association property. For each object set function check_func is * called (if not NULL). * * @param[in/out] key_props the properties object to modify * @param[in] property the name of the property to modify * @param[in] newvalue the new value(s) to add, remove or set * @param[in] msg_obj the name of the object for error messages * @param[in] check_func a function to call on each object before it is * added, removed or set to the property * @param[in] check_private a private pointer passed to check_func * * @returns 0 for success, or a negative errno value in case of an error, or * whatever check_func returns if check_func returns a non-zero value. */ static int _keystore_set_association(struct properties *key_props, const char *property, const char *newvalue, const char *msg_obj, check_association_t check_func, void *check_private) { char *normalized = NULL; char **newvals = NULL; char *value = NULL; char *changedval; char *newval; int i, rc = 0; newvals = str_list_split(newvalue); if (newvals == NULL) return -EINVAL; for (i = 0; newvals[i] != NULL; i++) { if (check_func != NULL) { rc = check_func(newvals[i], 0, 1, &normalized, check_private); if (rc != 0) goto out; } newval = normalized != NULL ? normalized : newvals[i]; if (value == NULL) changedval = str_list_add("", newval); else changedval = str_list_add(value, newval); if (changedval == NULL) { warnx("The %s '%s' is already specified or contains " "invalid characters", msg_obj, newval); rc = -EEXIST; goto out; } if (normalized != NULL) free(normalized); normalized = NULL; free(value); value = changedval; } rc = properties_set(key_props, property, value != NULL ? value : ""); if (rc != 0) warnx("Invalid characters in %ss", msg_obj); out: if (newvals != NULL) str_list_free_string_array(newvals); if (value != NULL) free(value); if (normalized != NULL) free(normalized); return rc; } /** * Add a value to an association property. For each object added function * check_func is called (if not NULL). * * @param[in/out] key_props the properties object to modify * @param[in] property the name of the property to modify * @param[in] newvalue the new value(s) to add, remove or set * @param[in] msg_obj the name of the object for error messages * @param[in] check_func a function to call on each object before it is * added, removed or set to the property * @param[in] check_private a private pointer passed to check_func * * @returns 0 for success, or a negative errno value in case of an error, or * whatever check_func returns if check_func returns a non-zero value. */ static int _keystore_add_association(struct properties *key_props, const char *property, const char *newvalue, const char *msg_obj, check_association_t check_func, void *check_private) { char *normalized = NULL; char **newvals = NULL; char *changedval; char *newval; int i, rc = 0; char *value; value = properties_get(key_props, property); if (value == NULL) return _keystore_set_association(key_props, property, newvalue, msg_obj, check_func, check_private); newvals = str_list_split(newvalue); if (newvals == NULL) { rc = -EINVAL; goto out; } for (i = 0; newvals[i] != NULL; i++) { if (check_func != NULL) { rc = check_func(newvals[i], 0, 0, &normalized, check_private); if (rc != 0) goto out; } newval = normalized != NULL ? normalized : newvals[i]; changedval = str_list_add(value, newval); if (changedval == NULL) { warnx("The %s '%s' is already associated with this key " "or contains invalid characters", msg_obj, newval); rc = -EEXIST; goto out; } if (normalized != NULL) free(normalized); normalized = NULL; free(value); value = changedval; } rc = properties_set(key_props, property, value); if (rc != 0) warnx("Invalid characters in %ss", msg_obj); out: if (newvals != NULL) str_list_free_string_array(newvals); if (value != NULL) free(value); if (normalized != NULL) free(normalized); return rc; } /** * Removes a value from an association property. For each object removed * function check_func is called (if not NULL). * * @param[in/out] key_props the properties object to modify * @param[in] property the name of the property to modify * @param[in] delvalue the value(s) to remove * @param[in] msg_obj the name of the object for error messages * @param[in] check_func a function to call on each object before it is * added, removed or set to the property * @param[in] check_private a private pointer passed to check_func * * @returns 0 for success, or a negative errno value in case of an error, or * whatever check_func returns if check_func returns a non-zero value. */ static int _keystore_remove_association(struct properties *key_props, const char *property, const char *delvalue, const char *msg_obj, check_association_t check_func, void *check_private) { char *normalized = NULL; char **delvals = NULL; char *changedval; char *delval; int i, rc = 0; char *value; value = properties_get(key_props, property); if (value == NULL) { warnx("No %ss are currently associated with this key", msg_obj); return -ENOENT; } delvals = str_list_split(delvalue); if (delvals == NULL) { rc = -EINVAL; goto out; } for (i = 0; delvals[i] != NULL; i++) { if (check_func != NULL) { rc = check_func(delvals[i], 1, 0, &normalized, check_private); if (rc != 0) goto out; } delval = normalized != NULL ? normalized : delvals[i]; changedval = str_list_remove(value, delval); if (changedval == NULL) { warnx("%s '%s' is not associated with this key", msg_obj, delval); rc = -ENOENT; goto out; } if (normalized != NULL) free(normalized); normalized = NULL; free(value); value = changedval; } rc = properties_set(key_props, property, value); if (rc != 0) warnx("Invalid characters in %ss", msg_obj); out: if (delvals != NULL) str_list_free_string_array(delvals); if (value != NULL) free(value); if (normalized != NULL) free(normalized); return rc; } /** * Change an association property. This function adds the objects in the * comma separated string when newvalue begins with a '+'. It removes the * objects when newvalue begins with a '-', or it sets the property to * newvalue when newvalue does not begin with '+' or '-'. For each object * added, Removed or set function check_func is called (if not NULL). * * @param[in/out] key_props the properties object to modify * @param[in] property the name of the property to modify * @param[in] newvalue the new value(s) to add, remove or set * @param[in] msg_obj the name of the object for error messages * @param[in] check_func a function to call on each object before it is * added, removed or set to the property * @param[in] check_private a private pointer passed to check_func * * @returns 0 for success, or a negative errno value in case of an error, or * whatever check_func returns if check_func returns a non-zero value. */ static int _keystore_change_association(struct properties *key_props, const char *property, const char *newvalue, const char *msg_obj, check_association_t check_func, void *check_private) { switch (*newvalue) { case '+': return _keystore_add_association(key_props, property, &newvalue[1], msg_obj, check_func, check_private); case '-': return _keystore_remove_association(key_props, property, &newvalue[1], msg_obj, check_func, check_private); default: return _keystore_set_association(key_props, property, newvalue, msg_obj, check_func, check_private); } } /** * Filter match function for APQNs * * @param[in] pattern the pattern to match * @param[in] apqn the apqn to match * @param[in] flags Not used here * * @returns Zero if string matches pattern, FNM_NOMATCH if there is no match * or another nonzero value if there is an error. */ static int _keystore_apqn_match(const char *pattern, const char *apqn, int UNUSED(flags)) { unsigned int card, domain; char *pattern_domain; char *pattern_card; char *modified; char *copy; size_t i; char *ch; int rc; if (sscanf(pattern, "%x.%x", &card, &domain) == 2) { util_asprintf(&modified, "%02x.%04x", card, domain); goto match; } copy = util_strdup(pattern); ch = strchr(copy, '.'); if (ch != NULL) { *ch = '\0'; pattern_card = copy; pattern_domain = ch + 1; modified = NULL; if (strchr(pattern_card, '*') == NULL && strlen(pattern_card) < 2) { for (i = 0; i < 2 - strlen(pattern_card); i++) modified = util_strcat_realloc(modified, "0"); } modified = util_strcat_realloc(modified, pattern_card); modified = util_strcat_realloc(modified, "."); if (strchr(pattern_domain, '*') == NULL && strlen(pattern_domain) < 4) { for (i = 0; i < 4 - strlen(pattern_domain); i++) modified = util_strcat_realloc(modified, "0"); } modified = util_strcat_realloc(modified, pattern_domain); } else { modified = util_strdup(copy); } free(copy); match: rc = fnmatch(modified, apqn, FNM_CASEFOLD); free(modified); return rc; } typedef int (*filter_match_t)(const char *pattern, const char *string, int flags); /* * Checks if the value matches the filter list. The value can be a comma * separated string. * * If the filter values contain a second part separated by a colon (':'), then * the filter matches only if both parts match. If the filter values do not * contain a second part,then only the first part is checked, and the second * parts of the values are ignored. * * @param[in] value the value to check * @param[in] filter_list a list of filter strings to match the value with * @param[in] match_func the filter match function. If NULL fnmatch() is used. * * @returns 1 for a match, 0 for not matched */ static int _keystore_match_filter(const char *value, char **filter_list, filter_match_t match_func) { char **value_list; int i, k, rc = 0; char *ch; if (filter_list == NULL) return 1; if (match_func == NULL) match_func = fnmatch; value_list = str_list_split(value); for (i = 0; filter_list[i] != NULL && rc == 0; i++) { for (k = 0; value_list[k] != NULL; k++) { /* * Ignore part after ':' of value if filter does * not also contain a ':' part. */ if (strchr(filter_list[i], ':') == NULL) { ch = strchr(value_list[k], ':'); if (ch != NULL) *ch = '\0'; } if (match_func(filter_list[i], value_list[k], 0) == 0) { rc = 1; break; } } } str_list_free_string_array(value_list); return rc; } /* * Checks if the property value matches the filter list. The property value * can be a comma separated string. * * If the filter values contain a second part separated by a colon (':'), then * the filter matches only if both parts match. If the filter values do not * contain a second part,then only the first part is checked, and the second * parts of the values are ignored. * * @param[in] properties a properties object * @param[in] property the name of the property to check * @param[in] filter_list a list of filter strings to match the value with * @param[in] match_func the filter match function. If NULL fnmatch() is used. * * @returns 1 for a match, 0 for not matched */ static int _keystore_match_filter_property(struct properties *properties, const char *property, char **filter_list, filter_match_t match_func) { char *value; int rc; if (filter_list == NULL) return 1; value = properties_get(properties, property); if (value == NULL) return 0; rc = _keystore_match_filter(value, filter_list, match_func); free(value); return rc; } /** * Checks if the volume type property matches the specified volume type. * If the properties do not contain a volume type property, then the default * volume type is assumed. * * @param[in] properties a properties object * @param[in] volume_type the volume type to match. Can be NULL. In this case * it always matches. * * @returns 1 for a match, 0 for not matched */ static int _keystore_match_volume_type_property(struct properties *properties, const char *volume_type) { char *type; int rc = 0; if (volume_type == NULL) return 1; type = _keystore_get_volume_type(properties); if (strcasecmp(type, volume_type) == 0) rc = 1; free(type); return rc; } /** * Checks if the key type property matches the specified key type. * If the properties do not contain a key type property, then the default * key type is assumed. * * @param[in] properties a properties object * @param[in] key_type the key type to match. Can be NULL. In this case * it always matches. * * @returns 1 for a match, 0 for not matched */ static int _keystore_match_key_type_property(struct properties *properties, const char *key_type) { char *type; int rc = 0; if (key_type == NULL) return 1; type = _keystore_get_key_type(properties); if (strcasecmp(type, key_type) == 0) rc = 1; else if (strcasecmp(key_type, KEY_TYPE_FILTER_AES) == 0 && is_aes_key_type(type)) rc = 1; else if (strcasecmp(key_type, KEY_TYPE_FILTER_HMAC) == 0 && is_hmac_key_type(type)) rc = 1; free(type); return rc; } /** * Checks if a key name matches a name filter * * @param[in] name the name to check * @param[in] name_filter the name filter to match against * * @returns 1 if the filter matches, 0 otherwise */ static int _keystore_match_name_filter(const char *name, const char *name_filter) { if (name_filter == NULL) return 1; if (fnmatch(name_filter, name, 0) != 0) return 0; return 1; } /** * Filters directory entries for scanfile(). Only entries that are regular * files and who's name ends with '.info' are matched. */ static int _keystore_info_file_filter(const struct dirent *dirent) { size_t len; if (dirent->d_type != DT_REG && dirent->d_type != DT_UNKNOWN) return 0; len = strlen(dirent->d_name); if (len > FILE_EXTENSION_LEN && strcmp(&dirent->d_name[len - FILE_EXTENSION_LEN], INFO_FILE_EXTENSION) == 0) return 1; return 0; } typedef int (*process_key_t)(struct keystore *keystore, const char *name, struct properties *properties, struct key_filenames *file_names, void *private); /** * Iterates over all keys stored in the keystore. For every key that matches * the specified filter process_func is called. * * @param[in] keystore the key store * @param[in] name_filter the name filter. Can contain wild cards. * NULL means no name filter. * @param[in] volume_filter the volume filter. Can contain wild cards, and * multiple volume filters separated by commas. * If the filter does not contain the ':dm-name' part, * then the volumes are matched without the dm-name * part. If the filter contains the ':dm-name' part, * then the filter is matched including the dm-name * part. * NULL means no volume filter. * specification is ignored for filter matching. * @param[in] apqn_filter the APQN filter. Can contain wild cards, and * multiple APQN filters separated by commas. * NULL means no APQN filter. * @param[in] volume_type If not NULL, specifies the volume type. * @param[in] key_type The key type. NULL means no key type filter. * In addition to the KEY_TYPE_nnn types, the special * filter types KEY_TYPE_FILTER_nnn can be specified. * This matches to the respective group of key types. * @param[in] local if true, only local keys are processed * @param[in] kms_bound if true, only KMS-bound keys are processed * @param[in] process_func the callback function called for a matching key * @param[in/out] process_private private data passed to the process_func * * @returns 0 for success, or a negative errno value in case of an error, or * whatever process_func returns if process_func returns a non-zero * value. */ static int _keystore_process_filtered(struct keystore *keystore, const char *name_filter, const char *volume_filter, const char *apqn_filter, const char *volume_type, const char *key_type, bool local, bool kms_bound, process_key_t process_func, void *process_private) { struct key_filenames file_names = { 0 }; char **apqn_filter_list = NULL; char **vol_filter_list = NULL; struct properties *key_props; struct dirent **namelist; int n, i, rc = 0; bool skip = 0; char *name; int len; pr_verbose(keystore, "Process_filtered: name_filter = '%s', " "volume_filter = '%s', apqn_filter = '%s'", name_filter ? name_filter : "(null)", volume_filter ? volume_filter : "(null)", apqn_filter ? apqn_filter : "(null)"); if (volume_filter != NULL) vol_filter_list = str_list_split(volume_filter); if (apqn_filter != NULL) apqn_filter_list = str_list_split(apqn_filter); n = scandir(keystore->directory, &namelist, _keystore_info_file_filter, alphasort); if (n == -1) { rc = -errno; pr_verbose(keystore, "scandir failed with: %s", strerror(-rc)); return rc; } for (i = 0; i < n ; i++) { if (skip) goto free; name = namelist[i]->d_name; len = strlen(name); if (len > FILE_EXTENSION_LEN) name[len - FILE_EXTENSION_LEN] = '\0'; if (_keystore_match_name_filter(name, name_filter) == 0) { pr_verbose(keystore, "Key '%s' filtered out due to name filter", name); goto free; } rc = _keystore_get_key_filenames(keystore, name, &file_names); if (rc != 0) goto free; rc = _keystore_ensure_keyfiles_exist(&file_names, name); if (rc != 0) goto free_names; key_props = properties_new(); rc = properties_load(key_props, file_names.info_filename, 1); if (rc != 0) { warnx("Key '%s' does not exist or is invalid", name); goto free_prop; } rc = _keystore_match_filter_property(key_props, PROP_NAME_VOLUMES, vol_filter_list, NULL); if (rc == 0) { pr_verbose(keystore, "Key '%s' filtered out due to volumes filter", name); goto free_prop; } rc = _keystore_match_filter_property(key_props, PROP_NAME_APQNS, apqn_filter_list, _keystore_apqn_match); if (rc == 0) { pr_verbose(keystore, "Key '%s' filtered out due to APQN filter", name); goto free_prop; } rc = _keystore_match_volume_type_property(key_props, volume_type); if (rc == 0) { pr_verbose(keystore, "Key '%s' filtered out due to volume type", name); goto free_prop; } rc = _keystore_match_key_type_property(key_props, key_type); if (rc == 0) { pr_verbose(keystore, "Key '%s' filtered out due to key type", name); goto free_prop; } if (local && _keystore_is_kms_bound_key(key_props, NULL)) { pr_verbose(keystore, "Key '%s' filtered out because it is KMS " "bound", name); rc = 0; goto free_prop; } if (kms_bound && !_keystore_is_kms_bound_key(key_props, NULL)) { pr_verbose(keystore, "Key '%s' filtered out because it is not " "KMS bound", name); rc = 0; goto free_prop; } rc = process_func(keystore, name, key_props, &file_names, process_private); if (rc != 0) { pr_verbose(keystore, "Process function returned %d", rc); skip = 1; } free_prop: properties_free(key_props); free_names: _keystore_free_key_filenames(&file_names); free: free(namelist[i]); } free(namelist); if (vol_filter_list) str_list_free_string_array(vol_filter_list); if (apqn_filter_list) str_list_free_string_array(apqn_filter_list); pr_verbose(keystore, "Process_filtered rc = %d", rc); return rc; } struct apqn_check { bool noonlinecheck; bool nomsg; enum card_type cardtype; }; /** * Checks an APQN value for its syntax. This is a callback function for * function _keystore_change_association(). * * @param[in] apqn the APQN value to check * @param[in] remove if true the apqn is removed * @param[in] set if true the apqn is set (not used here) * @param[out] normalized normalized value on return or NULL if no change * @param[in] private private data (struct apqn_check) * * @returns 0 if successful, a negative errno value otherwise */ static int _keystore_apqn_check(const char *apqn, bool remove, bool UNUSED(set), char **normalized, void *private) { struct apqn_check *info = (struct apqn_check *)private; unsigned int card, domain; regmatch_t pmatch[1]; unsigned int num; regex_t reg_buf; int rc; *normalized = NULL; rc = regcomp(®_buf, "[[:xdigit:]]+\\.[[:xdigit:]]", REG_EXTENDED); if (rc != 0) return -EIO; rc = regexec(®_buf, apqn, (size_t) 1, pmatch, 0); if (rc != 0) { warnx("the APQN '%s' is not valid", apqn); rc = -EINVAL; goto out; } if (sscanf(apqn, "%x.%x%n", &card, &domain, (int *)&num) != 2 || num != strlen(apqn) || card > 0xff || domain > 0xFFFF) { warnx("the APQN '%s' is not valid", apqn); rc = -EINVAL; goto out; } util_asprintf(normalized, "%02x.%04x", card, domain); if (remove || info->noonlinecheck) { rc = 0; goto out; } rc = sysfs_is_apqn_online(card, domain, info->cardtype); if (rc != 1) { if (info->nomsg == 0) warnx("The APQN %02x.%04x is %s", card, domain, rc == -1 ? "not the correct type" : "not online"); rc = -EIO; goto out; } else { rc = 0; } out: regfree(®_buf); return rc; } struct volume_check { struct keystore *keystore; const char *name; const char *volume; const char *key_type; const char *volume_type; bool set; bool nocheck; unsigned int num_combined; }; /** * Processing callback function for the volume association check function. * * @param[in] keystore the keystore (not used here) * @param[in] name the name of the key * @param[in] properties the properties object of the key (not used here) * @param[in] file_names the file names used by this key (not used here) * @param[in] private private data: struct volume_check * * @returns 0 if the key name is equal to the key we are checking the volume * associations for, -EINVAL otherwise (i.e. to indicate duplicate * volume association) */ static int _keystore_volume_check_process(struct keystore *UNUSED(keystore), const char *name, struct properties *properties, struct key_filenames *UNUSED(file_names), void *private) { struct volume_check *info = (struct volume_check *)private; char *key_type, *volume_type; int rc = 0; key_type = _keystore_get_key_type(properties); volume_type = _keystore_get_volume_type(properties); if (info->set) { if (strcmp(name, info->name) == 0) goto out; } if (is_hmac_key_type(info->key_type) && strcasecmp(info->volume_type, VOLUME_TYPE_LUKS2) == 0) { /* HMAC key with LUKS2: accept up to one AES key with LUKS2 */ if (!is_aes_key_type(key_type)) { warnx("Key '%s' is already associated with volume '%s'", name, info->volume); rc = -EINVAL; goto out; } if (strcasecmp(volume_type, VOLUME_TYPE_LUKS2) != 0) { warnx("Key '%s' is already associated with volume " "'%s' but the volume type not LUKS2", name, info->volume); rc = -EINVAL; goto out; } if (info->num_combined > 0) { warnx("More than one AES key is already associated " "with volume '%s'", info->volume); rc = -EINVAL; goto out; } info->num_combined++; goto out; } if (is_aes_key_type(info->key_type) && strcasecmp(info->volume_type, VOLUME_TYPE_LUKS2) == 0) { /* AES key with LUKS2: accept up to one HMAC key with LUKS2 */ if (!is_hmac_key_type(key_type)) { warnx("Key '%s' is already associated with volume '%s'", name, info->volume); rc = -EINVAL; goto out; } if (strcasecmp(volume_type, VOLUME_TYPE_LUKS2) != 0) { warnx("Key '%s' is already associated with volume " "'%s' but the volume type not LUKS2", name, info->volume); rc = -EINVAL; goto out; } if (info->num_combined > 0) { warnx("More than one HMAC key is already associated " "with volume '%s'", info->volume); rc = -EINVAL; goto out; } info->num_combined++; goto out; } warnx("Key '%s' is already associated with volume '%s'", name, info->volume); rc = -EINVAL; out: if (key_type != NULL) free(key_type); if (volume_type != NULL) free(volume_type); return rc; } /** * Checks if the volume is a block device * * @param[in] volume the volume to check * * @return 1 if the volume is a block device, 0 otherwise */ static int _keystore_is_block_device(const char *volume) { struct stat sb; if (stat(volume, &sb)) return 0; if (!S_ISBLK(sb.st_mode)) return 0; return 1; } /** * Checks an Volume value for its syntax and if it is already associated with * another key. This is a callback function for function * _keystore_change_association(). * * @param[in] volume the Volume value to check * @param[in] remove if true the volume is removed * @param[in] set if true the volume is set * @param[out] normalized normalized value on return or NULL if no change * @param[in] private private data: struct volume_check * * @returns 0 if successful, a negative errno value otherwise */ static int _keystore_volume_check(const char *volume, bool remove, bool set, char **normalized, void *private) { struct volume_check *info = (struct volume_check *)private; char *ch; int rc; *normalized = NULL; if (strpbrk(volume, "*?") != NULL) { warnx("Volume name can not contain '*' or '?'"); return -EINVAL; } info->volume = util_strdup(volume); ch = (char *)strchr(info->volume, ':'); if (ch == NULL || strlen(ch + 1) == 0) { warnx("Volume specification must contain a dm-crypt mapping " "name separated by a colon"); rc = -EINVAL; goto out; } if (remove || info->nocheck) { rc = 0; goto out; } /* * Strip off the ':dm-name' part, so that the volume filter only * matches the volume part. */ *ch = '\0'; if (!_keystore_is_block_device(info->volume)) { warnx("Volume '%s' is not a block device or is not available", info->volume); rc = -EINVAL; goto out; } info->set = set; rc = _keystore_process_filtered(info->keystore, NULL, info->volume, NULL, NULL, NULL, false, false, _keystore_volume_check_process, info); out: free((void *)info->volume); info->volume = NULL; return rc; } /** * Locks the repository against other processes. * * @param[in] keystore the keystore * * @returns 0 if successful, a negative errno value otherwise */ static int _keystore_lock_repository(struct keystore *keystore) { char *lock_file_name; struct stat sb; int rc; util_asprintf(&lock_file_name, "%s/%s", keystore->directory, LOCK_FILE_NAME); if (lstat(lock_file_name, &sb) == 0) { keystore->lock_fd = open(lock_file_name, O_RDONLY | O_NOFOLLOW); if (keystore->lock_fd == -1) { rc = -errno; warnx("Failed to open lock file '%s': %s", lock_file_name, strerror(-rc)); goto out; } } else { keystore->lock_fd = open(lock_file_name, O_CREAT | O_RDONLY | O_NOFOLLOW, keystore->mode); if (keystore->lock_fd == -1) { rc = -errno; warnx("Failed to create lock file '%s': %s", lock_file_name, strerror(-rc)); goto out; } if (fchown(keystore->lock_fd, geteuid(), keystore->owner) != 0) { rc = -errno; warnx("chown failed on file '%s': %s", lock_file_name, strerror(-rc)); return rc; } } rc = flock(keystore->lock_fd, LOCK_EX); if (rc == -1) { rc = -errno; warnx("Failed to obtain the file lock on '%s': %s", lock_file_name, strerror((-rc))); } out: free(lock_file_name); return rc; } /** * Unlocks the repository * * @param[in] keystore the keystore * * @returns 0 if successful, a negative errno value otherwise */ static int _keystore_unlock_repository(struct keystore *keystore) { int rc; if (keystore->lock_fd == -1) return 0; rc = flock(keystore->lock_fd, LOCK_UN); if (rc == -1) { rc = -errno; warnx("Failed to release the file lock: %s", strerror((-rc))); } close(keystore->lock_fd); keystore->lock_fd = -1; return rc; } /** * Allocates new keystore object * * @param[in] directory the directory where the keystore resides * @param[in] kms_info KMS plugin info * @param[in] verbose if true, verbose messages are printed * * @returns a new keystore object */ struct keystore *keystore_new(const char *directory, struct kms_info *kms_info, bool verbose) { struct keystore *keystore; struct stat sb; int rc; util_assert(directory != NULL, "Internal error: directory is NULL"); if (lstat(directory, &sb) != 0) { warnx("Can not access '%s': %s", directory, strerror(errno)); return NULL; } if (!S_ISDIR(sb.st_mode)) { warnx("'%s' is not a directory", directory); return NULL; } if (!util_path_is_readable(directory) || !util_path_is_writable(directory)) { warnx("Permission denied for '%s'", directory); return NULL; } if (sb.st_mode & S_IWOTH) { warnx("Directory '%s' is writable for others, this is not " "accepted", directory); return NULL; } keystore = util_zalloc(sizeof(struct keystore)); keystore->owner = sb.st_gid; keystore->mode = sb.st_mode & (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); keystore->lock_fd = -1; keystore->verbose = verbose; keystore->directory = util_strdup(directory); if (keystore->directory[strlen(keystore->directory)-1] == '/') keystore->directory[strlen(keystore->directory)-1] = '\0'; keystore->kms_info = kms_info; rc = _keystore_lock_repository(keystore); if (rc != 0) { keystore_free(keystore); return NULL; } pr_verbose(keystore, "Keystore in directory '%s' opened successfully", keystore->directory); return keystore; } /** * Generate the key verification pattern from the specified secure key file * * @param[in] keystore the key store * @param[in} keyfile the key file * @param[in] vp buffer filled with the verification pattern * @param[in] vp_len length of the buffer. Must be at * least VERIFICATION_PATTERN_LEN bytes in size. * @param[in] pkey_fd the pkey file descriptor * * @returns 0 for success or a negative errno in case of an error */ static int _keystore_generate_verification_pattern(struct keystore *keystore, const char *keyfile, char *vp, size_t vp_len, int pkey_fd) { size_t key_size; u8 *key; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(keyfile != NULL, "Internal error: keyfile is NULL"); util_assert(vp != NULL, "Internal error: vp is NULL"); util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); key = read_secure_key(keyfile, &key_size, keystore->verbose); if (key == NULL) return -EIO; rc = generate_key_verification_pattern(pkey_fd, key, key_size, vp, vp_len, keystore->verbose); free(key); return rc; } /** * Checks if the key verification pattern property exists. If not, then it is * created from the secure key. * * @param[in] keystore the key store * @param[in] file_names the file names of the key * @param[in] key_props the properties of the key * @param[in] pkey_fd the pkey file descriptor * * @returns 0 for success or a negative errno in case of an error */ static int _keystore_ensure_vp_exists(struct keystore *keystore, const struct key_filenames *file_names, struct properties *key_props, int pkey_fd) { char vp[VERIFICATION_PATTERN_LEN]; char *temp; int rc; temp = properties_get(key_props, PROP_NAME_KEY_VP); if (temp != NULL) { free(temp); return 0; } rc = _keystore_generate_verification_pattern(keystore, file_names->skey_filename, vp, sizeof(vp), pkey_fd); if (rc != 0) return rc; rc = properties_set(key_props, PROP_NAME_KEY_VP, vp); if (rc != 0) return rc; return 0; } /** * Sets a timestamp to be used as creation/update/reencipher time into * the specified property * * @param[in] properties the properties object * @param[in] property the name of the property to set * * @returns 0 on success, or a negative errno value on error */ static int _keystore_set_timestamp_property(struct properties *properties, const char *property) { char *time_str; struct tm *tm; time_t t; int rc; t = time(NULL); tm = localtime(&t); util_assert(tm != NULL, "Internal error: tm is NULL"); time_str = util_zalloc(200); rc = strftime(time_str, 200, "%F %T", tm); util_assert(rc > 0, "Internal error: strftime failed"); rc = properties_set(properties, property, time_str); free(time_str); return rc; } /** * Sets the default properties of an AES key, such as cipher-name, IV-mode, * and timestamps * * @param[in] key_props the properties object */ static int _keystore_set_default_aes_properties(struct properties *key_props) { int rc; rc = properties_set(key_props, PROP_NAME_CIPHER, "paes"); if (rc != 0) return rc; rc = properties_set(key_props, PROP_NAME_IV_MODE, "plain64"); if (rc != 0) return rc; rc = _keystore_set_timestamp_property(key_props, PROP_NAME_CREATION_TIME); if (rc != 0) return rc; return 0; } /** * Sets the default properties of an HMAC key, such as cipher-name and * timestmmps * * @param[in] key_props the properties object */ static int _keystore_set_default_hmac_properties(struct properties *key_props) { int rc; rc = properties_set(key_props, PROP_NAME_CIPHER, "phmac"); if (rc != 0) return rc; rc = properties_set(key_props, PROP_NAME_DIGEST, "sha"); if (rc != 0) return rc; rc = _keystore_set_timestamp_property(key_props, PROP_NAME_CREATION_TIME); if (rc != 0) return rc; return 0; } /** * Generate, Set or remove a dummy LUKS2 passphrase of a key. * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] file_names the file names of the key * @param[in] properties the properties of the key * @param[in] prompt if true, prompt for removal (if passphrase exists) * * @returns 0 on success, or a negative errno value on error */ static int _keystore_remove_passphrase(struct keystore *keystore, const char *name, const struct key_filenames *filenames, struct properties *properties, bool prompt) { int rc; if (_keystore_passphrase_file_exists((struct key_filenames *)filenames) && prompt) { util_print_indented("ATTENTION: When you remove the LUKS2 " "dummy passphrase of a key, you might no " "longer be able to open the LUKS2 volumes " "associated with the key, unless you still " "know a passphrase of one of the key slots " "of these volumes!", 0); _keystore_msg_for_volumes("The following volumes are encrypted " "with this key:", properties, NULL); printf("%s: Remove passphrase for key '%s' [y/N]? ", program_invocation_short_name, name); if (!prompt_for_yes(keystore->verbose)) { warnx("Operation aborted"); return -ECANCELED; } } rc = remove(filenames->pass_filename); if (rc != 0 && errno != ENOENT) { rc = -errno; warnx("Failed to remove file '%s': %s", filenames->pass_filename, strerror(-rc)); return rc; } return 0; } /** * Generate, Set or remove a dummy LUKS2 passphrase of a key. * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] passphrase_file the file name of a file containing a passphrase * for LUKS2. If NULKL, the passphrase is generated by * random. * @param[in] file_names the file names of the key * @param[in] properties the properties of the key * @param[in] prompt if true, prompt for change, if passphrase exists * already * * @returns 0 on success, or a negative errno value on error */ static int _keystore_set_passphrase(struct keystore *keystore, const char *name, const char *passphrase_file, const struct key_filenames *filenames, struct properties *properties, bool prompt) { char *volume_type, *key_type; int rc; if (_keystore_passphrase_file_exists((struct key_filenames *)filenames) && prompt) { warnx("There is already a LUKS2 dummy passphrase associated " "with key '%s'.", name); util_print_indented("To change a dummy passphrase of a key, " "first remove the currently associated " "passphrase with command 'zkey change " "--name --remove-dummy-passphrase' " "and then set the new dummy passphrase for " "the key.", 0); return -EEXIST; } volume_type = _keystore_get_volume_type(properties); if (volume_type == NULL) { pr_verbose(keystore, "No volume type available"); return -EINVAL; } if (strcasecmp(volume_type, VOLUME_TYPE_LUKS2) != 0) { warnx("The LUKS2 dummy passphrase can only be set for keys " "with a volume type of LUKS2."); free(volume_type); return -EINVAL; } free(volume_type); key_type = _keystore_get_key_type(properties); if (key_type == NULL) { pr_verbose(keystore, "No key type available"); return -EINVAL; } if (!is_aes_key_type(key_type)) { warnx("The LUKS2 dummy passphrase can only be set for AES-type " "keys"); free(key_type); return -EINVAL; } free(key_type); if (passphrase_file != NULL) { rc = copy_file(passphrase_file, filenames->pass_filename, 0); if (rc != 0) { warnx("Failed to copy the passphrase phase '%s': %s", passphrase_file, strerror(-rc)); return rc; } } else { rc = copy_file("/dev/urandom", filenames->pass_filename, DUMMY_PASSPHRASE_LEN); if (rc != 0) { warnx("Failed to generate the dummy passphrase: %s", strerror(-rc)); return rc; } } rc = _keystore_set_file_permission(keystore, filenames->pass_filename); if (rc != 0) return rc; return 0; } /** * Creates the key properties for a key * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] description textual description of the key (optional, can be NULL) * @param[in] volumes a comma separated list of volumes associated with this * key (optional, can be NULL) * @param[in] apqns a comma separated list of APQNs associated with this * key (optional, can be NULL) * @param[in] noapqncheck if true, the specified APQN(s) are not checked for * existence and type. * @param[i] novolscheck if true, the specified Volume(s) are not checked for * existence or duplicate use * @param[in] sector_size the sector size to use with dm-crypt. It must be power * of two and in range 512 - 4096 bytes. 0 means that * the sector size is not specified and the system * default is used. * @param[in] volume_type the type of volume * @param[in] key_type the type of the key * @param[in] kms the name of the KMS plugin, or NULL if no KMS is bound * @param[out] props the properties object is allocated and returned * * @returns 0 on success, or a negative errno value on error */ static int _keystore_create_info_props(struct keystore *keystore, const char *name, const char *description, const char *volumes, const char *apqns, bool noapqncheck, bool novolcheck, size_t sector_size, const char *volume_type, const char *key_type, const char *kms, struct properties **props) { struct volume_check vol_check = { .keystore = keystore, .name = name, .set = 0, .nocheck = novolcheck }; struct apqn_check apqn_check = { .noonlinecheck = noapqncheck, .nomsg = 0, .cardtype = get_card_type_for_keytype( key_type), }; struct properties *key_props; char temp[10]; int rc; *props = NULL; if (volume_type == NULL) volume_type = is_hmac_key_type(key_type) ? DEFAULT_INTERITY_VOLUME_TYPE : DEFAULT_VOLUME_TYPE; if (!_keystore_valid_volume_type(key_type, volume_type)) { warnx("Invalid volume-type specified"); return -EINVAL; } key_props = properties_new(); if (is_aes_key_type(key_type)) rc = _keystore_set_default_aes_properties(key_props); else if (is_hmac_key_type(key_type)) rc = _keystore_set_default_hmac_properties(key_props); else rc = -EINVAL; if (rc != 0) goto out; rc = properties_set(key_props, PROP_NAME_DESCRIPTION, description != NULL ? description : ""); if (rc != 0) { warnx("Invalid characters in description"); goto out; } rc = properties_set2(key_props, PROP_NAME_KEY_TYPE, key_type, true); if (rc != 0) { warnx("Invalid characters in key-type"); goto out; } vol_check.key_type = key_type; vol_check.volume_type = volume_type; rc = _keystore_change_association(key_props, PROP_NAME_VOLUMES, volumes != NULL ? volumes : "", "volume", _keystore_volume_check, &vol_check); if (rc != 0) goto out; rc = _keystore_change_association(key_props, PROP_NAME_APQNS, apqns != NULL ? apqns : "", "APQN", _keystore_apqn_check, &apqn_check); if (rc != 0) goto out; if (!_keystore_valid_sector_size(sector_size, key_type, volume_type)) { warnx("Invalid sector-size specified"); rc = -EINVAL; goto out; } sprintf(temp, "%lu", sector_size); rc = properties_set(key_props, PROP_NAME_SECTOR_SIZE, temp); if (rc != 0) { warnx("Invalid characters in sector-size"); goto out; } rc = properties_set2(key_props, PROP_NAME_VOLUME_TYPE, volume_type, true); if (rc != 0) { warnx("Invalid characters in volume-type"); goto out; } if (kms != NULL) { rc = properties_set(key_props, PROP_NAME_KMS, kms); if (rc != 0) { warnx("Invalid characters in KMS"); goto out; } } out: if (rc == 0) *props = key_props; else properties_free(key_props); return rc; } /** * Creates an initial .info file for a key * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] filenames the file names of the key files * @param[in] description textual description of the key (optional, can be NULL) * @param[in] volumes a comma separated list of volumes associated with this * key (optional, can be NULL) * @param[in] apqns a comma separated list of APQNs associated with this * key (optional, can be NULL) * @param[in] noapqncheck if true, the specified APQN(s) are not checked for * existence and type. * @param[in] sector_size the sector size to use with dm-crypt. It must be power * of two and in range 512 - 4096 bytes. 0 means that * the sector size is not specified and the system * default is used. * @param[in] volume_type the type of volume * @param[in] key_type the type of the key * @param[in] gen_passphrase if true, generate a (dummy) passphrase for LUKS2 * @param[in] passphrase_file the file name of a file containing a passphrase * for LUKS2 (optional, can be NULL) * @param[in] kms the name of the KMS plugin, or NULL if no KMS is bound * @param[in] pkey_fd the pkey file descriptor * * @returns 0 on success, or a negative errno value on error */ static int _keystore_create_info_file(struct keystore *keystore, const char *name, const struct key_filenames *filenames, const char *description, const char *volumes, const char *apqns, bool noapqncheck, size_t sector_size, const char *volume_type, const char *key_type, bool gen_passphrase, const char *passphrase_file, const char *kms, int pkey_fd) { struct properties *key_props = NULL; int rc; rc = _keystore_create_info_props(keystore, name, description, volumes, apqns, noapqncheck, false, sector_size, volume_type, key_type, kms, &key_props); if (rc != 0) return rc; if (gen_passphrase || passphrase_file != NULL) { rc = _keystore_set_passphrase(keystore, name, gen_passphrase ? NULL : passphrase_file, filenames, key_props, true); if (rc != 0) { pr_verbose(keystore, "Failed to set the passphrase: %s", strerror(-rc)); goto out; } } rc = _keystore_ensure_vp_exists(keystore, filenames, key_props, pkey_fd); if (rc != 0) { warnx("Failed to generate the key verification pattern: %s", strerror(-rc)); remove(filenames->pass_filename); goto out; } rc = properties_save(key_props, filenames->info_filename, 1); if (rc != 0) { pr_verbose(keystore, "Key info file '%s' could not be written: %s", filenames->info_filename, strerror(-rc)); remove(filenames->pass_filename); goto out; } rc = _keystore_set_file_permission(keystore, filenames->info_filename); if (rc != 0) { remove(filenames->info_filename); remove(filenames->pass_filename); goto out; } out: properties_free(key_props); return rc; } /** * Generates a secure key by random and adds it to the key store * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] description textual description of the key (optional, can be NULL) * @param[in] volumes a comma separated list of volumes associated with this * key (optional, can be NULL) * @param[in] apqns a comma separated list of APQNs associated with this * key (optional, can be NULL) * @param[in] noapqncheck if true, the specified APQN(s) are not checked for * existence and type. * @param[in] sector_size the sector size to use with dm-crypt. It must be power * of two and in range 512 - 4096 bytes. 0 means that * the sector size is not specified and the system * default is used. * @param[in] keybits cryptographical size of the key in bits * @param[in] xts if true, an XTS key is generated * @param[in] clear_key_file if not NULL the secure key is generated from the * clear key contained in the file denoted here. * if NULL, the secure key is generated by random. * @param[in] volume_type the type of volume * @param[in] key_type the type of the key * @param[in] gen_passphrase if true, generate a (dummy) passphrase for LUKS2 * @param[in] passphrase_file the file name of a file containing a passphrase * for LUKS2 (optional, can be NULL) * @param[in] exportable if true the key shall be exportable * @param[in] wrap_with_trusted if true the key shall be wrapable with a * trusted KEK only * @param[in] pkey_fd the file descriptor of /dev/pkey * * @returns 0 for success or a negative errno in case of an error */ int keystore_generate_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, const char *apqns, bool noapqncheck, size_t sector_size, size_t keybits, bool xts, const char *clear_key_file, const char *volume_type, const char *key_type, bool gen_passphrase, const char *passphrase_file, bool exportable, bool wrap_with_trusted, int pkey_fd) { struct key_filenames file_names = { 0 }; struct properties *key_props = NULL; char **apqn_list = NULL; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(name != NULL, "Internal error: name is NULL"); util_assert(key_type != NULL, "Internal error: key_type is NULL"); if (!_keystore_valid_key_type(key_type)) { warnx("Invalid key-type specified"); return -EINVAL; } if (!is_secure_key_type(key_type)) { warnx("Keys of type %s can not be generated. Use 'zkey " "pvsecret import' instead", key_type); return -EINVAL; } rc = _keystore_get_key_filenames(keystore, name, &file_names); if (rc != 0) goto out_free_key_filenames; rc = _keystore_ensure_keyfiles_not_exist(&file_names, name); if (rc != 0) goto out_free_key_filenames; rc = cross_check_apqns(apqns, NULL, get_min_card_level_for_keytype(key_type), get_min_fw_version_for_keytype(key_type), get_card_type_for_keytype(key_type), true, keystore->verbose); if (rc == -EINVAL) goto out_free_key_filenames; if (rc != 0 && rc != -ENOTSUP && noapqncheck == 0) { warnx("Your master key setup is improper"); goto out_free_key_filenames; } if (apqns != NULL) apqn_list = str_list_split(apqns); if (clear_key_file == NULL) rc = generate_secure_key_random(pkey_fd, file_names.skey_filename, keybits, xts, key_type, (const char **)apqn_list, exportable, wrap_with_trusted, keystore->verbose); else rc = generate_secure_key_clear(pkey_fd, file_names.skey_filename, keybits, xts, clear_key_file, key_type, (const char **)apqn_list, exportable, wrap_with_trusted, keystore->verbose); if (rc != 0) goto out_free_props; rc = _keystore_set_file_permission(keystore, file_names.skey_filename); if (rc != 0) goto out_free_props; rc = _keystore_create_info_file(keystore, name, &file_names, description, volumes, apqns, noapqncheck, sector_size, volume_type, key_type, gen_passphrase, passphrase_file, NULL, pkey_fd); if (rc != 0) goto out_free_props; pr_verbose(keystore, "Successfully generated a secure key in '%s' and key info " "in '%s'", file_names.skey_filename, file_names.info_filename); out_free_props: if (apqn_list != NULL) str_list_free_string_array(apqn_list); if (key_props != NULL) properties_free(key_props); if (rc != 0) remove(file_names.skey_filename); out_free_key_filenames: _keystore_free_key_filenames(&file_names); if (rc != 0) pr_verbose(keystore, "Failed to generate key '%s': %s", name, strerror(-rc)); return rc; } /** * Generates a secure key by using a KMS plugin and adds it to the key store * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] description textual description of the key (optional, can be NULL) * @param[in] volumes a comma separated list of volumes associated with this * key (optional, can be NULL) * @param[in] sector_size the sector size to use with dm-crypt. It must be power * of two and in range 512 - 4096 bytes. 0 means that * the sector size is not specified and the system * default is used. * @param[in] keybits cryptographical size of the key in bits * @param[in] xts if true, an XTS key is generated * @param[in] volume_type the type of volume * @param[in] key_type the type of the key (can be NULL) * @param[in] gen_passphrase if true, generate a (dummy) passphrase for LUKS2 * @param[in] passphrase_file the file name of a file containing a passphrase * for LUKS2 (optional, can be NULL) * @param[in] kms_options an array of KMS options specified, or NULL if no * KMS options have been specified * @param[in] num_kms_options the number of options in above array * @param[in] pkey_fd the pkey file descriptor * * @returns 0 for success or a negative errno in case of an error */ int keystore_generate_key_kms(struct keystore *keystore, const char *name, const char *description, const char *volumes, size_t sector_size, size_t keybits, bool xts, const char *volume_type, const char *key_type, bool gen_passphrase, const char *passphrase_file, struct kms_option *kms_options, size_t num_kms_options, int pkey_fd) { struct key_filenames file_names = { 0 }; struct properties *key_props = NULL; struct kms_info *kms_info; char *apqns = NULL; int rc, i; static const char * const key_types[] = { KEY_TYPE_CCA_AESDATA, KEY_TYPE_CCA_AESCIPHER, KEY_TYPE_EP11_AES, NULL }; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(name != NULL, "Internal error: name is NULL"); util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); kms_info = keystore->kms_info; if (kms_info->plugin_lib == NULL) { warnx("The repository is not bound to a KMS plugin"); return -ENOENT; } if (key_type == NULL) { for (i = 0; kms_info->funcs->kms_supports_key_type != NULL && key_types[i] != NULL; i++) { if (kms_info->funcs->kms_supports_key_type( kms_info->handle, key_types[i])) { key_type = key_types[i]; break; } } if (key_type == NULL) key_type = KEY_TYPE_CCA_AESDATA; } if (!_keystore_valid_key_type(key_type)) { warnx("Invalid key-type specified"); return -EINVAL; } rc = _keystore_get_key_filenames(keystore, name, &file_names); if (rc != 0) goto out_free_key_filenames; rc = _keystore_ensure_keyfiles_not_exist(&file_names, name); if (rc != 0) goto out_free_key_filenames; rc = get_kms_apqns_for_key_type(kms_info, key_type, true, &apqns, keystore->verbose); if (rc != 0) { if (rc == -ENOTSUP) warnx("Key-type not supported by the KMS plugin '%s'", kms_info->plugin_name); goto out_free_key_filenames; } pr_verbose(keystore, "APQNs for keytype %s: '%s'", key_type, apqns); rc = _keystore_create_info_props(keystore, name, description, volumes, apqns, false, false, sector_size, volume_type, key_type, kms_info->plugin_name, &key_props); if (rc != 0) goto out_free_key_filenames; if (gen_passphrase || passphrase_file != NULL) { rc = _keystore_set_passphrase(keystore, name, gen_passphrase ? NULL : passphrase_file, &file_names, key_props, true); if (rc != 0) { pr_verbose(keystore, "Failed to set the passphrase: %s", strerror(-rc)); goto out_free_key_filenames; } } rc = generate_kms_key(kms_info, name, key_type, key_props, xts, keybits, file_names.skey_filename, _keystore_passphrase_file_exists(&file_names) ? file_names.pass_filename : NULL, kms_options, num_kms_options, keystore->verbose, pkey_fd); if (rc != 0) { warnx("KMS plugin '%s' failed to generate key '%s': %s", kms_info->plugin_name, name, strerror(-rc)); print_last_kms_error(kms_info); goto out_free_props; } rc = _keystore_set_file_permission(keystore, file_names.skey_filename); if (rc != 0) goto out_free_props; rc = _keystore_ensure_vp_exists(keystore, &file_names, key_props, pkey_fd); if (rc != 0) { warnx("Failed to generate the key verification pattern: %s", strerror(-rc)); goto out_free_props; } rc = properties_save(key_props, file_names.info_filename, 1); if (rc != 0) { pr_verbose(keystore, "Key info file '%s' could not be written: %s", file_names.info_filename, strerror(-rc)); goto out_del_info_file; } rc = _keystore_set_file_permission(keystore, file_names.info_filename); if (rc != 0) { remove(file_names.info_filename); goto out_del_info_file; } pr_verbose(keystore, "Successfully generated a secure key with KMS plugin '%s' " "in '%s' and key info in '%s'", kms_info->plugin_name, file_names.skey_filename, file_names.info_filename); out_del_info_file: if (rc != 0) remove(file_names.info_filename); out_free_props: if (key_props != NULL) properties_free(key_props); if (rc != 0) { remove(file_names.skey_filename); remove(file_names.pass_filename); } out_free_key_filenames: _keystore_free_key_filenames(&file_names); if (apqns != NULL) free(apqns); if (rc != 0) pr_verbose(keystore, "Failed to generate key '%s' with KMS " "plugin '%s': %s", name, kms_info->plugin_name, strerror(-rc)); return rc; } /** * Imports a secure key from a buffer and adds it to the key store * * @param[in] keystore the key store * @param[in] secure_key the buffer containing the key * @param[in] secure_key_size the size of the key * @param[in] name the name of the key * @param[in] description textual description of the key (optional, can be NULL) * @param[in] volumes a comma separated list of volumes associated with this * key (optional, can be NULL) * @param[in] apqns a comma separated list of APQNs associated with this * key (optional, can be NULL) * @param[in] noapqncheck if true, the specified APQN(s) are not checked for * existence and type. * @param[in] sector_size the sector size to use with dm-crypt. It must be power * of two and in range 512 - 4096 bytes. 0 means that * the sector size is not specified and the system * default is used. * @param[in] volume_type the type of volume * @param[in] gen_passphrase if true, generate a (dummy) passphrase for LUKS2 * @param[in] passphrase_file the file name of a file containing a passphrase * for LUKS2 (optional, can be NULL) * @param[in] exportable if true the key shall be exportable * @param[in] lib the external library struct * @param[in] pkey_fd the pkey file descriptor * * @returns 0 for success or a negative errno in case of an error */ int keystore_import(struct keystore *keystore, unsigned char *secure_key, size_t secure_key_size, const char *name, const char *description, const char *volumes, const char *apqns, bool noapqncheck, size_t sector_size, const char *volume_type, bool gen_passphrase, const char *passphrase_file, bool exportable, struct ext_lib *lib, int pkey_fd) { struct key_filenames file_names = { 0 }; struct properties *key_props = NULL; const char *key_type; u8 mkvp[MKVP_LENGTH]; int selected = 1; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(name != NULL, "Internal error: name is NULL"); util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); rc = _keystore_get_key_filenames(keystore, name, &file_names); if (rc != 0) goto out_free_key_filenames; rc = _keystore_ensure_keyfiles_not_exist(&file_names, name); if (rc != 0) goto out_free_key_filenames; key_type = get_key_type(secure_key, secure_key_size); if (key_type == NULL) { warnx("Key '%s' is not a valid secure key", name); rc = -EINVAL; goto out_free_key_filenames; } if (!is_secure_key(secure_key, secure_key_size)) { if (apqns != NULL) { warnx("No APQNs can be associated with keys of type %s", key_type); rc = -EINVAL; goto out_free_props; } goto write_key; } rc = get_master_key_verification_pattern(secure_key, secure_key_size, mkvp, keystore->verbose); if (rc != 0) { warnx("Failed to get the master key verification pattern: %s", strerror(-rc)); goto out_free_props; } rc = cross_check_apqns(apqns, mkvp, get_min_card_level_for_keytype(key_type), get_min_fw_version_for_keytype(key_type), get_card_type_for_keytype(key_type), true, keystore->verbose); if (rc == -EINVAL) goto out_free_props; if (rc != 0 && rc != -ENOTSUP && noapqncheck == 0) { warnx("Your master key setup is improper"); goto out_free_props; } if (is_cca_aes_cipher_key(secure_key, secure_key_size)) { if (lib->cca->lib_csulcca == NULL) { rc = load_cca_library(lib->cca, keystore->verbose); if (rc != 0) goto out_free_props; } rc = select_cca_adapter_by_mkvp(lib->cca, mkvp, apqns, FLAG_SEL_CCA_MATCH_CUR_MKVP | FLAG_SEL_CCA_MATCH_OLD_MKVP, keystore->verbose); if (rc == -ENOTSUP) { rc = 0; selected = 0; } if (rc != 0) { warnx("No APQN found that is suitable for " "working with the secure AES key '%s'", name); rc = 0; goto out_free_props; } if (!exportable) { rc = restrict_key_export(lib->cca, secure_key, secure_key_size, keystore->verbose); if (rc != 0) { warnx("Failed to export-restrict the imported " "secure key: %s", strerror(-rc)); if (!selected) print_msg_for_cca_envvars("secure AES " "key"); goto out_free_props; } } rc = check_aes_cipher_key(secure_key, secure_key_size, exportable); if (rc != 0) { warnx("The secure key to import might not be secure"); printf("%s: Do you want to import it anyway [y/N]? ", program_invocation_short_name); if (!prompt_for_yes(keystore->verbose)) { warnx("Operation aborted"); rc = -ECANCELED; goto out_free_props; } } } write_key: rc = write_secure_key(file_names.skey_filename, secure_key, secure_key_size, keystore->verbose); if (rc != 0) goto out_free_props; rc = _keystore_set_file_permission(keystore, file_names.skey_filename); if (rc != 0) goto out_free_props; rc = _keystore_create_info_file(keystore, name, &file_names, description, volumes, apqns, noapqncheck, sector_size, volume_type, key_type, gen_passphrase, passphrase_file, NULL, pkey_fd); if (rc != 0) goto out_free_props; pr_verbose(keystore, "Successfully imported a secure key in '%s' and key info in '%s'", file_names.skey_filename, file_names.info_filename); out_free_props: if (key_props != NULL) properties_free(key_props); if (rc != 0) remove(file_names.skey_filename); out_free_key_filenames: _keystore_free_key_filenames(&file_names); if (rc != 0) pr_verbose(keystore, "Failed to import key '%s': %s", name, strerror(-rc)); return rc; } /** * Imports a secure key from a file and adds it to the key store * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] description textual description of the key (optional, can be NULL) * @param[in] volumes a comma separated list of volumes associated with this * key (optional, can be NULL) * @param[in] apqns a comma separated list of APQNs associated with this * key (optional, can be NULL) * @param[in] noapqncheck if true, the specified APQN(s) are not checked for * existence and type. * @param[in] sector_size the sector size to use with dm-crypt. It must be a * power of two and in range 512 - 4096 bytes. 0 means * that the sector size is not specified and the system * default is used. * @param[in] import_file The name of a secure key containing the key to import * @param[in] volume_type the type of volume * @param[in] gen_passphrase if true, generate a (dummy) passphrase for LUKS2 * @param[in] passphrase_file the file name of a file containing a passphrase * for LUKS2 (optional, can be NULL) * @param[in] exportable if true the key shall be exportable * @param[in] lib the external library struct * @param[in] pkey_fd the pkey file descriptor * * @returns 0 for success or a negative errno in case of an error */ int keystore_import_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, const char *apqns, bool noapqncheck, size_t sector_size, const char *import_file, const char *volume_type, bool gen_passphrase, const char *passphrase_file, bool exportable, struct ext_lib *lib, int pkey_fd) { size_t secure_key_size; u8 *secure_key; int rc; util_assert(import_file != NULL, "Internal error: import_file is NULL"); secure_key = read_secure_key(import_file, &secure_key_size, keystore->verbose); if (secure_key == NULL) return -ENOENT; rc = keystore_import(keystore, secure_key, secure_key_size, name, description, volumes, apqns, noapqncheck, sector_size, volume_type, gen_passphrase, passphrase_file, exportable, lib, pkey_fd); if (secure_key != NULL) free(secure_key); return rc; } /** * Changes properties of a key in the keystore. * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] description textual description of the key. If NULL then the * description is not changed. * @param[in] volumes a comma separated list of volumes associated with this * key, or a volume prefixed with '+' or '-' to add or * remove that volume respectively. If NULL then the * volumes are not changed. * @param[in] apqns a comma separated list of APQNs associated with this * key, or an APQN prefixed with '+' or '-' to add or * remove that APQN respectively. If NULL then the APQNs * are not changed. * @param[in] noapqncheck if true, the specified APQN(s) are not checked for * existence and type. * @param[in] sector_size the sector size to use with dm-crypt. It must be power * of two and in range 512 - 4096 bytes. 0 means that * the sector size is not specified and the system * default is used. Specify -1 if this property should * not be changed. * @param[in] volume_type the type of volume. If NULL then the volume type is * not changed. * @param[in] gen_passphrase if true, generate a (dummy) passphrase for LUKS2 * @param[in] passphrase_file the file name of a file containing a passphrase * for LUKS2 (optional, can be NULL) * @param[in] remove_passphrase if true, remove the (dummy) passphrase * @param[in] quiet if true no confirmation prompt is shown when removing * a (dummy) passphrase * @param[in] pkey_fd the pkey file descriptor * * @returns 0 for success or a negative errno in case of an error * */ int keystore_change_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, const char *apqns, bool noapqncheck, long int sector_size, const char *volume_type, bool gen_passphrase, const char *passphrase_file, bool remove_passphrase, bool quiet, int pkey_fd) { struct volume_check vol_check = { .keystore = keystore, .name = name, .set = 0, .nocheck = 0 }; struct apqn_check apqn_check = { .noonlinecheck = noapqncheck, .nomsg = 0 }; struct key_filenames file_names = { 0 }; struct properties *key_props = NULL; const char **passphrase_upd = NULL; char *apqns_prop, *key_type = NULL; char *upd_volume_type = NULL; char *old_volume_type = NULL; const char *null_ptr = NULL; char *prop_volumes = NULL; char *upd_volumes = NULL; size_t secure_key_size; u8 *secure_key = NULL; u8 mkvp[MKVP_LENGTH]; char sect_size[30]; bool kms_bound; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(name != NULL, "Internal error: name is NULL"); util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); rc = _keystore_get_key_filenames(keystore, name, &file_names); if (rc != 0) goto out; rc = _keystore_ensure_keyfiles_exist(&file_names, name); if (rc != 0) goto out; key_props = properties_new(); rc = properties_load(key_props, file_names.info_filename, 1); if (rc != 0) { warnx("Key '%s' does not exist or is invalid", name); goto out; } kms_bound = _keystore_is_kms_bound_key(key_props, NULL); if (kms_bound) { rc = perform_kms_login(keystore->kms_info, keystore->verbose); if (rc != 0) goto out; } key_type = _keystore_get_key_type(key_props); old_volume_type = _keystore_get_volume_type(key_props); if (volume_type != NULL) { if (!_keystore_valid_volume_type(key_type, volume_type)) { warnx("Invalid volume-type specified"); rc = -EINVAL; goto out; } } if (description != NULL) { rc = properties_set(key_props, PROP_NAME_DESCRIPTION, description); if (rc != 0) { warnx("Invalid characters in description"); goto out; } } if (volumes != NULL) { vol_check.key_type = key_type; vol_check.volume_type = volume_type != NULL ? volume_type : old_volume_type; rc = _keystore_change_association(key_props, PROP_NAME_VOLUMES, volumes, "volume", _keystore_volume_check, &vol_check); if (rc != 0) goto out; upd_volumes = properties_get(key_props, PROP_NAME_VOLUMES); } if (apqns != NULL) { if (kms_bound) { rc = -EINVAL; warnx("The APQN association of a KMS-bound key can not " "be changed"); goto out; } secure_key = read_secure_key(file_names.skey_filename, &secure_key_size, keystore->verbose); if (secure_key == NULL) { rc = -ENOENT; goto out; } if (!is_secure_key(secure_key, secure_key_size)) { warnx("No APQNs can be associated with keys of type %s", get_key_type(secure_key, secure_key_size)); rc = -EINVAL; goto out; } rc = _keystore_change_association(key_props, PROP_NAME_APQNS, apqns, "APQN", _keystore_apqn_check, &apqn_check); if (rc != 0) goto out; rc = get_master_key_verification_pattern(secure_key, secure_key_size, mkvp, keystore->verbose); if (rc) goto out; apqns_prop = properties_get(key_props, PROP_NAME_APQNS); rc = cross_check_apqns(apqns_prop, mkvp, get_min_card_level_for_keytype(key_type), get_min_fw_version_for_keytype(key_type), get_card_type_for_keytype(key_type), true, keystore->verbose); free(apqns_prop); if (rc == -ENOTSUP) rc = 0; if (rc != 0 && noapqncheck == 0) { warnx("Your master key setup is improper"); goto out; } } if (sector_size >= 0) { if (!_keystore_valid_sector_size(sector_size, key_type, volume_type != NULL ? volume_type : old_volume_type)) { warnx("Invalid sector-size specified"); rc = -EINVAL; goto out; } sprintf(sect_size, "%lu", sector_size); rc = properties_set(key_props, PROP_NAME_SECTOR_SIZE, sect_size); if (rc != 0) { warnx("Invalid characters in sector-size"); goto out; } } if (volume_type != NULL) { if (!_keystore_valid_volume_type(key_type, volume_type)) { warnx("Invalid volume-type specified"); rc = -EINVAL; goto out; } prop_volumes = properties_get(key_props, PROP_NAME_VOLUMES); vol_check.volume = prop_volumes; vol_check.key_type = key_type; vol_check.volume_type = volume_type; vol_check.set = true; vol_check.nocheck = false; vol_check.num_combined = 0; rc = _keystore_process_filtered(keystore, NULL, prop_volumes, NULL, NULL, NULL, false, false, _keystore_volume_check_process, &vol_check); free(prop_volumes); if (rc != 0) goto out; rc = properties_set2(key_props, PROP_NAME_VOLUME_TYPE, volume_type, true); if (rc != 0) { warnx("Invalid characters in volume-type"); goto out; } upd_volume_type = properties_get(key_props, PROP_NAME_VOLUME_TYPE); /* Remove dummy passphrase if change to PLAIN volume type */ if (strcasecmp(volume_type, VOLUME_TYPE_LUKS2) != 0 && _keystore_passphrase_file_exists(&file_names)) { rc = _keystore_remove_passphrase(keystore, name, &file_names, key_props, false); if (rc != 0) goto out; passphrase_upd = &null_ptr; } /* * Reset sector-size to zero if changed to anything but * INTEGTRITY on a HMAC key. */ if (strcasecmp(volume_type, VOLUME_TYPE_INTEGRITY) != 0 && is_hmac_key_type(key_type)) { sector_size = 0; rc = properties_set(key_props, PROP_NAME_SECTOR_SIZE, "0"); if (rc != 0) goto out; } } if (gen_passphrase || passphrase_file != NULL) { rc = _keystore_set_passphrase(keystore, name, gen_passphrase ? NULL : passphrase_file, &file_names, key_props, true); if (rc != 0) goto out; passphrase_upd = (const char **)&file_names.pass_filename; } if (remove_passphrase) { if (_keystore_passphrase_file_exists(&file_names)) passphrase_upd = &null_ptr; rc = _keystore_remove_passphrase(keystore, name, &file_names, key_props, !quiet); if (rc != 0) goto out; } if (kms_bound) { rc = perform_kms_login(keystore->kms_info, keystore->verbose); if (rc != 0) goto out; rc = set_kms_key_properties(keystore->kms_info, key_props, NULL, description, upd_volumes, upd_volume_type, sector_size >= 0 ? sect_size : NULL, passphrase_upd, keystore->verbose); if (rc != 0) { warnx("KMS plugin '%s' failed to set key properties " "for key '%s': %s", keystore->kms_info->plugin_name, name, strerror(-rc)); print_last_kms_error(keystore->kms_info); goto out; } } rc = _keystore_ensure_vp_exists(keystore, &file_names, key_props, pkey_fd); /* ignore return code, vp generation might fail if key is not valid */ rc = _keystore_set_timestamp_property(key_props, PROP_NAME_CHANGE_TIME); if (rc != 0) goto out; rc = properties_save(key_props, file_names.info_filename, 1); if (rc != 0) { pr_verbose(keystore, "Key info file '%s' could not be written: %s", file_names.info_filename, strerror(-rc)); goto out; } pr_verbose(keystore, "Successfully changed key '%s'", name); out: if (rc != 0 && passphrase_upd != NULL && *passphrase_upd != NULL) remove(*passphrase_upd); _keystore_free_key_filenames(&file_names); if (key_props != NULL) properties_free(key_props); if (upd_volumes != NULL) free(upd_volumes); if (upd_volume_type != NULL) free(upd_volume_type); if (secure_key != NULL) free(secure_key); if (key_type != NULL) free(key_type); if (old_volume_type != NULL) free(old_volume_type); if (rc != 0) pr_verbose(keystore, "Failed to change key '%s': %s", name, strerror(-rc)); return rc; } /** * Renames a key in the keystore * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] newname the new name of the key * * @returns 0 for success or a negative errno in case of an error */ int keystore_rename_key(struct keystore *keystore, const char *name, const char *newname) { struct key_filenames file_names = { 0 }; struct key_filenames new_names = { 0 }; struct properties *key_props = NULL; bool reenc_exists = false; bool pass_exists = false; bool kms_bound; char *msg; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(name != NULL, "Internal error: name is NULL"); util_assert(newname != NULL, "Internal error: newname is NULL"); rc = _keystore_get_key_filenames(keystore, name, &file_names); if (rc != 0) goto out; rc = _keystore_ensure_keyfiles_exist(&file_names, name); if (rc != 0) goto out; rc = _keystore_get_key_filenames(keystore, newname, &new_names); if (rc != 0) goto out; rc = _keystore_ensure_keyfiles_not_exist(&new_names, newname); if (rc != 0) goto out; key_props = properties_new(); rc = properties_load(key_props, file_names.info_filename, 1); if (rc != 0) { warnx("Key '%s' does not exist or is invalid", name); goto out; } kms_bound = _keystore_is_kms_bound_key(key_props, NULL); if (kms_bound) { rc = perform_kms_login(keystore->kms_info, keystore->verbose); if (rc != 0) goto out; } if (rename(file_names.skey_filename, new_names.skey_filename) != 0) { rc = -errno; pr_verbose(keystore, "Failed to rename '%s': %s", file_names.skey_filename, strerror(-rc)); goto out; } if (rename(file_names.info_filename, new_names.info_filename) != 0) { rc = -errno; pr_verbose(keystore, "Failed to rename '%s': %s", file_names.info_filename, strerror(-rc)); goto out_rename_skey; } if (_keystore_reencipher_key_exists(&file_names)) { reenc_exists = true; if (rename(file_names.renc_filename, new_names.renc_filename) != 0) { rc = -errno; pr_verbose(keystore, "Failed to rename '%s': %s", file_names.renc_filename, strerror(-rc)); goto out_rename_info; } } if (_keystore_passphrase_file_exists(&file_names)) { pass_exists = true; if (rename(file_names.pass_filename, new_names.pass_filename) != 0) { rc = -errno; pr_verbose(keystore, "Failed to rename '%s': %s", file_names.pass_filename, strerror(-rc)); goto out_rename_info; } } if (kms_bound) { rc = set_kms_key_properties(keystore->kms_info, key_props, newname, NULL, NULL, NULL, NULL, NULL, keystore->verbose); if (rc != 0) { warnx("KMS plugin '%s' failed to set key properties " "for key '%s': %s", keystore->kms_info->plugin_name, name, strerror(-rc)); print_last_kms_error(keystore->kms_info); goto out_rename_info; } } util_asprintf(&msg, "The following volumes are associated with the " "renamed key '%s'. You should adjust the corresponding " "crypttab entries and 'cryptsetup plainOpen' commands to " "use the new name.", newname); _keystore_msg_for_volumes(msg, key_props, VOLUME_TYPE_PLAIN); free(msg); util_asprintf(&msg, "The following volumes are associated with the " "renamed key '%s'. You should adjust the corresponding " "integritytab entries and 'integritysetup open' commands " "to use the new name.", newname); _keystore_msg_for_volumes(msg, key_props, VOLUME_TYPE_INTEGRITY); free(msg); if (_keystore_passphrase_file_exists(&new_names)) { util_asprintf(&msg, "The following volumes are associated with " "the renamed key '%s'. You should adjust the " "corresponding crypttab entries to use the new " "dummy passphrase file name '%s'.", newname, new_names.pass_filename); _keystore_msg_for_volumes(msg, key_props, VOLUME_TYPE_LUKS2); free(msg); } pr_verbose(keystore, "Successfully renamed key '%s' to '%s'", name, newname); goto out; out_rename_info: if (reenc_exists) rename(file_names.renc_filename, new_names.renc_filename); if (pass_exists) rename(file_names.pass_filename, new_names.pass_filename); rename(new_names.info_filename, file_names.info_filename); out_rename_skey: rename(new_names.skey_filename, file_names.skey_filename); out: _keystore_free_key_filenames(&file_names); _keystore_free_key_filenames(&new_names); if (key_props != NULL) properties_free(key_props); if (rc != 0) pr_verbose(keystore, "Failed to rename key '%s'to '%s': %s", name, newname, strerror(-rc)); return rc; } /** * Sets up a util_rec used for displaying key information * * @param[in] validation if true the record is used for validate, else it is * used for display * * @returns a pointer to a set up struct util_rec. */ static struct util_rec *_keystore_setup_record(bool validation) { struct util_rec *rec; rec = util_rec_new_long("-", ":", REC_KEY, 28, 54); util_rec_def(rec, REC_KEY, UTIL_REC_ALIGN_LEFT, 54, REC_KEY); if (validation) util_rec_def(rec, REC_STATUS, UTIL_REC_ALIGN_LEFT, 54, REC_STATUS); util_rec_def(rec, REC_DESCRIPTION, UTIL_REC_ALIGN_LEFT, 54, REC_DESCRIPTION); util_rec_def(rec, REC_SEC_KEY_SIZE, UTIL_REC_ALIGN_LEFT, 20, REC_SEC_KEY_SIZE); util_rec_def(rec, REC_CLR_KEY_SIZE, UTIL_REC_ALIGN_LEFT, 20, REC_CLR_KEY_SIZE); util_rec_def(rec, REC_XTS, UTIL_REC_ALIGN_LEFT, 3, REC_XTS); util_rec_def(rec, REC_KEY_TYPE, UTIL_REC_ALIGN_LEFT, 54, REC_KEY_TYPE); if (validation) util_rec_def(rec, REC_MASTERKEY, UTIL_REC_ALIGN_LEFT, 54, REC_MASTERKEY); util_rec_def(rec, REC_VOLUMES, UTIL_REC_ALIGN_LEFT, 54, REC_VOLUMES); util_rec_def(rec, REC_APQNS, UTIL_REC_ALIGN_LEFT, 54, REC_APQNS); util_rec_def(rec, REC_KEY_FILE, UTIL_REC_ALIGN_LEFT, 54, REC_KEY_FILE); util_rec_def(rec, REC_SECTOR_SIZE, UTIL_REC_ALIGN_LEFT, 54, REC_SECTOR_SIZE); util_rec_def(rec, REC_VOLUME_TYPE, UTIL_REC_ALIGN_LEFT, 54, REC_VOLUME_TYPE); util_rec_def(rec, REC_KEY_VP, UTIL_REC_ALIGN_LEFT, 54, REC_KEY_VP); util_rec_def(rec, REC_KMS, UTIL_REC_ALIGN_LEFT, 54, REC_KMS); util_rec_def(rec, REC_KMS_KEY_LABEL, UTIL_REC_ALIGN_LEFT, 54, REC_KMS_KEY_LABEL); util_rec_def(rec, REC_PASSPHRASE_FILE, UTIL_REC_ALIGN_LEFT, 54, REC_PASSPHRASE_FILE); util_rec_def(rec, REC_CREATION_TIME, UTIL_REC_ALIGN_LEFT, 54, REC_CREATION_TIME); util_rec_def(rec, REC_CHANGE_TIME, UTIL_REC_ALIGN_LEFT, 54, REC_CHANGE_TIME); util_rec_def(rec, REC_REENC_TIME, UTIL_REC_ALIGN_LEFT, 54, REC_REENC_TIME); return rec; } static void _keystore_print_record(struct util_rec *rec, const char *name, struct properties *properties, bool validation, const char *skey_filename, size_t secure_key_size, bool is_xts, size_t clear_key_bitsize, bool valid, bool is_old_mk, bool reenc_pending, u8 *mkvp, const char *pass_filename) { char temp_vp[VERIFICATION_PATTERN_LEN + 2]; char *kms_xts_key1_label = NULL; char *kms_xts_key2_label = NULL; char *kms_key_label = NULL; char *volumes_argz = NULL; size_t label_argz_len = 0; size_t volumes_argz_len; char *apqns_argz = NULL; char *label_argz = NULL; size_t sector_size = 0; size_t apqns_argz_len; char *description; char *volume_type; char *reencipher; char *key_type; char *creation; char *volumes; char *change; char *apqns; char *temp; char *kms; char *vp; int len; description = properties_get(properties, PROP_NAME_DESCRIPTION); volumes = properties_get(properties, PROP_NAME_VOLUMES); if (volumes != NULL) util_assert(argz_create_sep(volumes, ',', &volumes_argz, &volumes_argz_len) == 0, "Internal error: argz_create_sep failed"); apqns = properties_get(properties, PROP_NAME_APQNS); if (apqns != NULL) util_assert(argz_create_sep(apqns, ',', &apqns_argz, &apqns_argz_len) == 0, "Internal error: argz_create_sep failed"); temp = properties_get(properties, PROP_NAME_SECTOR_SIZE); if (temp != NULL) { util_assert(sscanf(temp, "%lu", §or_size) == 1, "Internal error: sscanf failed"); free(temp); } creation = properties_get(properties, PROP_NAME_CREATION_TIME); change = properties_get(properties, PROP_NAME_CHANGE_TIME); reencipher = properties_get(properties, PROP_NAME_REENC_TIME); vp = properties_get(properties, PROP_NAME_KEY_VP); volume_type = _keystore_get_volume_type(properties); key_type = properties_get(properties, PROP_NAME_KEY_TYPE); if (_keystore_is_kms_bound_key(properties, &kms)) { if (is_xts) { kms_xts_key1_label = properties_get(properties, PROP_NAME_KMS_XTS_KEY1_LABEL); kms_xts_key2_label = properties_get(properties, PROP_NAME_KMS_XTS_KEY2_LABEL); if (kms_xts_key1_label != NULL && kms_xts_key2_label != NULL) { label_argz_len = util_asprintf(&label_argz, "%s%c%s", kms_xts_key1_label, '\0', kms_xts_key2_label) + 1; } } else { kms_key_label = properties_get(properties, PROP_NAME_KMS_KEY_LABEL); if (kms_key_label != NULL) { label_argz = kms_key_label; label_argz_len = strlen(label_argz) + 1; kms_key_label = NULL; } } } util_rec_set(rec, REC_KEY, name); if (validation) util_rec_set(rec, REC_STATUS, valid ? "Valid" : "Invalid"); util_rec_set(rec, REC_DESCRIPTION, description != NULL ? description : ""); util_rec_set(rec, REC_SEC_KEY_SIZE, "%lu bytes", secure_key_size); if ((!validation || valid) && clear_key_bitsize != 0) util_rec_set(rec, REC_CLR_KEY_SIZE, "%lu bits", clear_key_bitsize); else util_rec_set(rec, REC_CLR_KEY_SIZE, "(unknown)"); util_rec_set(rec, REC_XTS, is_xts ? "Yes" : "No"); util_rec_set(rec, REC_KEY_TYPE, key_type); if (validation) { if (mkvp != NULL) { if (valid) util_rec_set(rec, REC_MASTERKEY, "%s master key (MKVP: %s)", is_old_mk ? "OLD" : "CURRENT", printable_mkvp( get_card_type_for_keytype( key_type), mkvp)); else util_rec_set(rec, REC_MASTERKEY, "(unknown, MKVP: %s)", printable_mkvp( get_card_type_for_keytype( key_type), mkvp)); } else { util_rec_set(rec, REC_MASTERKEY, "(none)"); } } if (volumes_argz != NULL) util_rec_set_argz(rec, REC_VOLUMES, volumes_argz, volumes_argz_len); else util_rec_set(rec, REC_VOLUMES, "(none)"); if (apqns_argz != NULL) util_rec_set_argz(rec, REC_APQNS, apqns_argz, apqns_argz_len); else util_rec_set(rec, REC_APQNS, "(none)"); util_rec_set(rec, REC_KEY_FILE, skey_filename); if (sector_size == 0) util_rec_set(rec, REC_SECTOR_SIZE, "(system default)"); else util_rec_set(rec, REC_SECTOR_SIZE, "%lu bytes", sector_size); util_rec_set(rec, REC_VOLUME_TYPE, volume_type); if (vp != NULL) { len = sprintf(temp_vp, "%.*s%c%.*s", VERIFICATION_PATTERN_LEN / 2, vp, '\0', VERIFICATION_PATTERN_LEN / 2, &vp[VERIFICATION_PATTERN_LEN / 2]); util_rec_set_argz(rec, REC_KEY_VP, temp_vp, len + 1); } else { util_rec_set(rec, REC_KEY_VP, "(not available)"); } util_rec_set(rec, REC_KMS, kms != NULL ? kms : "(local)"); if (kms != NULL && label_argz != NULL) util_rec_set_argz(rec, REC_KMS_KEY_LABEL, label_argz, label_argz_len); else util_rec_set(rec, REC_KMS_KEY_LABEL, "(local)"); if (pass_filename != NULL) util_rec_set(rec, REC_PASSPHRASE_FILE, pass_filename); else util_rec_set(rec, REC_PASSPHRASE_FILE, "(none)"); util_rec_set(rec, REC_CREATION_TIME, creation); util_rec_set(rec, REC_CHANGE_TIME, change != NULL ? change : "(never)"); util_rec_set(rec, REC_REENC_TIME, "%s %s", reencipher != NULL ? reencipher : "(never)", reenc_pending ? "(re-enciphering pending)" : ""); util_rec_print(rec); if (description != NULL) free(description); if (volumes != NULL) free(volumes); if (volumes_argz != NULL) free(volumes_argz); if (apqns != NULL) free(apqns); if (apqns_argz != NULL) free(apqns_argz); if (creation != NULL) free(creation); if (change != NULL) free(change); if (reencipher != NULL) free(reencipher); if (vp != NULL) free(vp); if (volume_type != NULL) free(volume_type); if (key_type != NULL) free(key_type); if (kms != NULL) free(kms); if (kms_key_label != NULL) free(kms_key_label); if (kms_xts_key1_label != NULL) free(kms_xts_key1_label); if (kms_xts_key2_label != NULL) free(kms_xts_key2_label); if (label_argz != NULL) free(label_argz); } struct validate_info { struct util_rec *rec; int pkey_fd; bool noapqncheck; unsigned long int num_valid; unsigned long int num_invalid; unsigned long int num_warnings; }; /** * Displays the status of the associated APQNs. * * @param[in] keystore the key store * @param[in] properties the properties of the key * @param[in] mkvp the master key verification pattern of the key * * @returns 0 in case of success, 1 if at least one of the APQNs is not * available or has a master key mismatch */ static int _keystore_display_apqn_status(struct keystore *keystore, struct properties *properties, u8 *mkvp) { int rc, warning = 0; char *apqns; char *key_type; apqns = properties_get(properties, PROP_NAME_APQNS); if (apqns == NULL) return 0; key_type = properties_get(properties, PROP_NAME_KEY_TYPE); rc = cross_check_apqns(apqns, mkvp, get_min_card_level_for_keytype(key_type), get_min_fw_version_for_keytype(key_type), get_card_type_for_keytype(key_type), true, keystore->verbose); if (rc != 0 && rc != -ENOTSUP) warning = 1; if (warning) printf("\n"); free(apqns); free(key_type); return warning; } /** * Displays the status of the associated volumes. * * @param[in] properties the properties of the key * @param[in] name the name of the key * * @returns 0 in case of success, 1 if at least one of the volumes is not * available */ static int _keystore_display_volume_status(struct properties *properties, const char *name) { int i, warning = 0; char **volume_list; char *volumes; char *ch; volumes = properties_get(properties, PROP_NAME_VOLUMES); if (volumes == NULL) return 0; volume_list = str_list_split(volumes); for (i = 0; volume_list[i] != NULL; i++) { ch = strchr(volume_list[i], ':'); if (ch != NULL) *ch = '\0'; if (!_keystore_is_block_device(volume_list[i])) { printf("WARNING: The volume '%s' associated with " "key '%s' is not available\n", volume_list[i], name); warning = 1; } } if (warning) printf("\n"); free(volumes); str_list_free_string_array(volume_list); return warning; } /** * Processing function for the key validate function. Prints validation * information for the key to be validated. * * @param[in] keystore the keystore * @param[in] name the name of the key * @param[in] properties the properties object of the key * @param[in] file_names the file names used by this key * @param[in] private private data: struct validate_info * * @returns 0 if the validation is successful, a negative errno value otherwise */ static int _keystore_process_validate(struct keystore *keystore, const char *name, struct properties *properties, struct key_filenames *file_names, void *private) { struct validate_info *info = (struct validate_info *)private; char **apqn_list = NULL; size_t clear_key_bitsize; size_t secure_key_size; u8 mkvp[MKVP_LENGTH]; char *apqns = NULL; u8 *secure_key = NULL; int is_old_mk; int rc, valid; rc = _keystore_ensure_keyfiles_exist(file_names, name); if (rc != 0) goto out; secure_key = read_secure_key(file_names->skey_filename, &secure_key_size, keystore->verbose); if (secure_key == NULL) { rc = -ENOENT; goto out; } apqns = properties_get(properties, PROP_NAME_APQNS); if (apqns != NULL) apqn_list = str_list_split(apqns); rc = validate_secure_key(info->pkey_fd, secure_key, secure_key_size, &clear_key_bitsize, &is_old_mk, (const char **)apqn_list, keystore->verbose); if (rc != 0) { valid = 0; info->num_invalid++; rc = 0; } else { info->num_valid++; valid = 1; } if (is_secure_key(secure_key, secure_key_size)) { rc = get_master_key_verification_pattern(secure_key, secure_key_size, mkvp, keystore->verbose); if (rc != 0) goto out; } _keystore_print_record(info->rec, name, properties, 1, file_names->skey_filename, secure_key_size, is_xts_key(secure_key, secure_key_size), clear_key_bitsize, valid, is_old_mk, _keystore_reencipher_key_exists(file_names), is_secure_key(secure_key, secure_key_size) ? mkvp : NULL, _keystore_passphrase_file_exists(file_names) ? file_names->pass_filename : NULL); if (valid && is_old_mk) { util_print_indented("WARNING: The secure key is currently " "enciphered with the OLD master key. " "To mitigate the danger of data loss " "re-encipher it with the CURRENT " "master key\n", 0); info->num_warnings++; } if (info->noapqncheck == 0 && is_secure_key(secure_key, secure_key_size)) if (_keystore_display_apqn_status(keystore, properties, mkvp) != 0) info->num_warnings++; if (_keystore_display_volume_status(properties, name) != 0) info->num_warnings++; out: if (secure_key != NULL) free(secure_key); if (apqns != NULL) free(apqns); if (apqn_list != NULL) str_list_free_string_array(apqn_list); if (rc != 0) pr_verbose(keystore, "Failed to validate key '%s': %s", name, strerror(-rc)); return rc; } /** * Validates one or multiple keys in the keystore * * @param[in] keystore the key store * @param[in] name_filter the name filter to select the key (can be NULL) * @param[in] apqn_filter the APQN filter to select the key (can be NULL) * @param[in] noapqncheck if true, the specified APQN(s) are not checked for * existence and type. * @param[in] pkey_fd the file descriptor of /dev/pkey * * @returns 0 for success or a negative errno in case of an error */ int keystore_validate_key(struct keystore *keystore, const char *name_filter, const char *apqn_filter, bool noapqncheck, int pkey_fd) { struct validate_info info; struct util_rec *rec; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); rec = _keystore_setup_record(1); info.pkey_fd = pkey_fd; info.noapqncheck = noapqncheck; info.rec = rec; info.num_valid = 0; info.num_invalid = 0; info.num_warnings = 0; rc = _keystore_process_filtered(keystore, name_filter, NULL, apqn_filter, NULL, NULL, false, false, _keystore_process_validate, &info); util_rec_free(rec); if (rc != 0) { pr_verbose(keystore, "Failed to validate keys: %s", strerror(-rc)); } else { pr_verbose(keystore, "Successfully validated keys"); printf("%lu keys are valid, %lu keys are invalid, %lu " "warnings\n", info.num_valid, info.num_invalid, info.num_warnings); } return rc; } struct reencipher_params { bool from_old; bool to_new; bool complete; int inplace; /* -1 = autodetect, 0 = not in-place, 1 = in-place */ }; struct reencipher_info { struct reencipher_params params; int pkey_fd; struct ext_lib *lib; unsigned long num_reenciphered; unsigned long num_failed; unsigned long num_skipped; }; /** * Perform the reencipherment of a key * * @param[in] keystore the keystore * @param[in] name the name of the key * @param[in] lib the external library struct * @param[in] params reenciphering parameters * @param[in] secure_key a buffer containing the secure key * @param[in] secure_key_size the size of the secure key * @param[in] is_old_mk if true the key is currently re-enciphered with the * OLD master key * @param[in] apqns the associated APQNs (or NULL if none) * @returns 0 if the re-enciphering is successful, a negative errno value * otherwise, 1 if it was skipped */ static int _keystore_perform_reencipher(struct keystore *keystore, const char *name, struct ext_lib *lib, struct reencipher_params *params, u8 *secure_key, size_t secure_key_size, bool is_old_mk, const char *apqns) { bool selected; int rc; if (!params->from_old && !params->to_new) { /* Autodetect reencipher mode */ if (is_old_mk) { params->from_old = 1; util_print_indented("The secure key is currently " "enciphered with the OLD " "master key and is being " "re-enciphered with the CURRENT " "master key\n", 0); } else { params->to_new = 1; util_print_indented("The secure key is currently " "enciphered with the CURRENT " "master key and is being " "re-enciphered with the NEW " "master key\n", 0); } } if (params->from_old) { if (params->inplace == -1) params->inplace = 1; pr_verbose(keystore, "Secure key '%s' will be re-enciphered from OLD " "to the CURRENT master key", name); rc = reencipher_secure_key(lib, secure_key, secure_key_size, apqns, REENCIPHER_OLD_TO_CURRENT, &selected, keystore->verbose); if (rc != 0) { if (rc == -ENODEV) { warnx("No APQN found that is suitable for " "re-enciphering this secure AES key"); } else { warnx("Failed to re-encipher '%s' from OLD to " "CURRENT master key", name); if (!selected && !is_ep11_aes_key(secure_key, secure_key_size) && !is_ep11_aes_key_with_header(secure_key, secure_key_size)) print_msg_for_cca_envvars( "secure AES key"); } return rc; } } if (params->to_new) { pr_verbose(keystore, "Secure key '%s' will be re-enciphered from " "CURRENT to the NEW master key", name); if (params->inplace == -1) params->inplace = 0; rc = reencipher_secure_key(lib, secure_key, secure_key_size, apqns, REENCIPHER_CURRENT_TO_NEW, &selected, keystore->verbose); if (rc != 0) { if (rc == -ENODEV) { warnx("No APQN found that is suitable for " "re-enciphering this secure AES key and " "has the NEW master key loaded"); } else { warnx("Failed to re-encipher '%s' from CURRENT " "to NEW master key", name); if (!selected && !is_ep11_aes_key(secure_key, secure_key_size)) print_msg_for_cca_envvars( "secure AES key"); } return rc; } } return 0; } /** * Processing function for the key re-enciphering function. * * @param[in] keystore the keystore * @param[in] name the name of the key * @param[in] properties the properties object of the key (not used here) * @param[in] file_names the file names used by this key * @param[in] private private data: struct reencipher_info * * @returns 0 if the re-enciphering is successful, a negative errno value * otherwise */ static int _keystore_process_reencipher(struct keystore *keystore, const char *name, struct properties *properties, struct key_filenames *file_names, void *private) { struct reencipher_info *info = (struct reencipher_info *)private; struct reencipher_params params = info->params; size_t clear_key_bitsize; size_t secure_key_size; char **apqn_list = NULL; u8 *secure_key = NULL; char *apqns = NULL; char *out_file; int is_old_mk; char *temp; int rc; rc = _keystore_ensure_keyfiles_exist(file_names, name); if (rc != 0) goto out; pr_verbose(keystore, "Complete reencipher: %d", params.complete); pr_verbose(keystore, "In-place reencipher: %d", params.inplace); if (params.complete) { if (!_keystore_reencipher_key_exists(file_names)) { warnx("Staged re-enciphering is not pending for key " "'%s', skipping", name); info->num_skipped++; rc = 0; goto out; } printf("Completing re-enciphering for key '%s'\n", name); params.inplace = 1; } secure_key = read_secure_key(params.complete ? file_names->renc_filename : file_names->skey_filename, &secure_key_size, keystore->verbose); if (secure_key == NULL) { rc = -ENOENT; goto out; } if (!is_secure_key(secure_key, secure_key_size)) { warnx("Key '%s' is of type %s and can not be re-enciphered, " "skipping", name, get_key_type(secure_key, secure_key_size)); info->num_skipped++; rc = 0; goto out; } apqns = properties_get(properties, PROP_NAME_APQNS); if (apqns != NULL) apqn_list = str_list_split(apqns); rc = validate_secure_key(info->pkey_fd, secure_key, secure_key_size, &clear_key_bitsize, &is_old_mk, (const char **)apqn_list, keystore->verbose); if (rc != 0) { if (params.complete) { warnx("Key '%s' is not valid, re-enciphering is not " "completed", name); warnx("The new master key might yet have to be set " "as the CURRENT master key."); } else { warnx("Key '%s' is not valid, it is not re-enciphered", name); info->num_skipped++; rc = 0; } goto out; } if (!params.complete) { printf("Re-enciphering key '%s'\n", name); rc = _keystore_perform_reencipher(keystore, name, info->lib, ¶ms, secure_key, secure_key_size, is_old_mk, apqns); if (rc < 0) goto out; if (rc > 0) { info->num_skipped++; rc = 0; goto out; } } pr_verbose(keystore, "In-place reencipher: %d", params.inplace); out_file = params.inplace == 1 ? file_names->skey_filename : file_names->renc_filename; rc = write_secure_key(out_file, secure_key, secure_key_size, keystore->verbose); if (rc != 0) goto out; if (params.complete || params.inplace == 1) { rc = _keystore_set_timestamp_property(properties, PROP_NAME_REENC_TIME); if (rc != 0) goto out; rc = _keystore_ensure_vp_exists(keystore, file_names, properties, info->pkey_fd); if (rc != 0) { warnx("Failed to generate the key verification pattern " "for key '%s': %s", file_names->skey_filename, strerror(-rc)); goto out; } rc = properties_save(properties, file_names->info_filename, 1); if (rc != 0) { pr_verbose(keystore, "Failed to write key info file '%s': %s", file_names->info_filename, strerror(-rc)); goto out; } util_asprintf(&temp, "The following LUKS2 volumes are " "encrypted with key '%s'. You should also " "re-encipher the volume key of those volumes " "using command 'zkey-cryptsetup reencipher " "':", name); _keystore_msg_for_volumes(temp, properties, VOLUME_TYPE_LUKS2); free(temp); } if (params.complete || (params.inplace && _keystore_reencipher_key_exists(file_names))) { if (remove(file_names->renc_filename) != 0) { rc = -errno; pr_verbose(keystore, "Failed to remove '%s': %s", file_names->renc_filename, strerror(-rc)); goto out; } } if (params.inplace != 1) { util_asprintf(&temp, "Staged re-enciphering is initiated for " "key '%s'. After the NEW master key has been " "set to become the CURRENT master key run " "'zkey reencipher' with option '--complete' to " "complete the re-enciphering process", name); util_print_indented(temp, 0); free(temp); } info->num_reenciphered++; out: if (apqns != NULL) free(apqns); if (apqn_list != NULL) str_list_free_string_array(apqn_list); if (secure_key != NULL) free(secure_key); printf("\n"); if (rc != 0) { info->num_failed++; pr_verbose(keystore, "Failed to re-encipher key '%s': %s", name, strerror(-rc)); rc = 0; } return rc; } /** * Reenciphers a key in the keystore * * @param[in] keystore the key store * @param[in] name_filter the name filter to select the key (can be NULL) * @param[in] apqn_filter the APQN filter to seletc the key (can be NULL) * @param[in] from_old If true the key is reenciphered from the OLD to the * CURRENT master key. * @param[in] to_new If true the key is reenciphered from the CURRENT to * the OLD master key. * @param[in] inplace if true, the key will be re-enciphere in-place * @param[in] staged if true, the key will be re-enciphere not in-place * @param[in] complete if true, a pending re-encipherment is completed * @param[in] pkey_fd the file descriptor of /dev/pkey * @param[in] lib the external library struct * Note: if both fromOld and toNew are FALSE, then the reencipherement mode is * detected automatically. If both are TRUE then the key is reenciphered * from the OLD to the NEW master key. * Note: if both inplace and staged are FALSE, then the key is re-enciphered * inplace when for OLD-to-CURRENT, and is reenciphered staged for * CURRENT-to-NEW. * @returns 0 for success or a negative errno in case of an error */ int keystore_reencipher_key(struct keystore *keystore, const char *name_filter, const char *apqn_filter, bool from_old, bool to_new, bool inplace, bool staged, bool complete, int pkey_fd, struct ext_lib *lib) { struct reencipher_info info; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); info.params.from_old = from_old; info.params.to_new = to_new; info.params.inplace = -1; if (inplace) info.params.inplace = 1; if (staged) info.params.inplace = 0; info.params.complete = complete; info.pkey_fd = pkey_fd; info.lib = lib; info.num_failed = 0; info.num_reenciphered = 0; info.num_skipped = 0; rc = _keystore_process_filtered(keystore, name_filter, NULL, apqn_filter, NULL, NULL, false, false, _keystore_process_reencipher, &info); if (rc != 0) { pr_verbose(keystore, "Failed to re-encipher keys: %s", strerror(-rc)); } else { pr_verbose(keystore, "Successfully re-enciphered keys"); printf("%lu keys re-enciphered, %lu keys skipped, %lu keys " "failed to re-encipher\n", info.num_reenciphered, info.num_skipped, info.num_failed); if (info.num_failed > 0) rc = -EIO; } return rc; } /** * Copies (duplicates) a key in the keystore. Any existing volume associations * are removed from the copy, because a volume can only be associated to one * key. However, you can set new volume associations using the volumes * parameter. * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] newname the new name of the key * @param[in] volumes a comma separated list of volumes associated with this * key (optional, can be NULL) * @param[in] local if true copy a KMS-bound key to a local one * * @returns 0 for success or a negative errno in case of an error */ int keystore_copy_key(struct keystore *keystore, const char *name, const char *newname, const char *volumes, bool local) { struct volume_check vol_check = { .keystore = keystore, .name = newname, .set = 0, .nocheck = 0 }; struct key_filenames file_names = { 0 }; struct key_filenames new_names = { 0 }; struct properties *key_prop = NULL; char *volume_type = NULL; size_t secure_key_size; bool kms_bound = false; char *key_type = NULL; u8 *secure_key; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(name != NULL, "Internal error: name is NULL"); util_assert(newname != NULL, "Internal error: newname is NULL"); rc = _keystore_get_key_filenames(keystore, name, &file_names); if (rc != 0) goto out; rc = _keystore_ensure_keyfiles_exist(&file_names, name); if (rc != 0) goto out; rc = _keystore_get_key_filenames(keystore, newname, &new_names); if (rc != 0) goto out; rc = _keystore_ensure_keyfiles_not_exist(&new_names, newname); if (rc != 0) goto out; key_prop = properties_new(); rc = properties_load(key_prop, file_names.info_filename, 1); if (rc != 0) { warnx("Key '%s' does not exist or is invalid", name); goto out; } kms_bound = _keystore_is_kms_bound_key(key_prop, NULL); if (kms_bound && !local) { rc = -EINVAL; warnx("Copying a KMS-bound key requires the " "'--local|-L' option"); goto out; } key_type = _keystore_get_key_type(key_prop); volume_type = _keystore_get_volume_type(key_prop); secure_key = read_secure_key(file_names.skey_filename, &secure_key_size, keystore->verbose); if (secure_key == NULL) { rc = -ENOENT; goto out; } rc = write_secure_key(new_names.skey_filename, secure_key, secure_key_size, keystore->verbose); free(secure_key); if (rc != 0) goto out; rc = _keystore_set_file_permission(keystore, new_names.skey_filename); if (rc != 0) goto out; /* * Remove any volume association, since a volume can only be associated * with one key */ rc = properties_set(key_prop, PROP_NAME_VOLUMES, ""); if (rc != 0) goto out; if (volumes != NULL) { vol_check.key_type = key_type; vol_check.volume_type = volume_type; rc = _keystore_change_association(key_prop, PROP_NAME_VOLUMES, volumes, "volume", _keystore_volume_check, &vol_check); if (rc != 0) goto out; } rc = properties_remove(key_prop, PROP_NAME_CHANGE_TIME); if (rc != 0 && rc != -ENOENT) goto out; rc = properties_remove(key_prop, PROP_NAME_REENC_TIME); if (rc != 0 && rc != -ENOENT) goto out; rc = _keystore_set_timestamp_property(key_prop, PROP_NAME_CREATION_TIME); if (rc != 0) goto out; if (kms_bound) { rc = _keystore_kms_key_unbind(keystore, key_prop); if (rc != 0) goto out; } rc = properties_save(key_prop, new_names.info_filename, 1); if (rc != 0) { pr_verbose(keystore, "Key info file '%s' could not be written: %s", new_names.info_filename, strerror(-rc)); goto out; } rc = _keystore_set_file_permission(keystore, new_names.info_filename); if (rc != 0) goto out; if (_keystore_passphrase_file_exists(&file_names)) { rc = copy_file(file_names.skey_filename, new_names.pass_filename, 0); if (rc != 0) { pr_verbose(keystore, "Passphrase file '%s' could not be copied: " "%s", new_names.pass_filename, strerror(-rc)); goto out; } rc = _keystore_set_file_permission(keystore, file_names.pass_filename); if (rc != 0) goto out; } pr_verbose(keystore, "Successfully copied key '%s' to '%s'", name, newname); out: if (rc != 0) { remove(new_names.skey_filename); remove(new_names.info_filename); remove(new_names.pass_filename); } _keystore_free_key_filenames(&file_names); _keystore_free_key_filenames(&new_names); if (key_prop != NULL) properties_free(key_prop); if (key_type != NULL) free(key_type); if (volume_type != NULL) free(volume_type); if (rc != 0) pr_verbose(keystore, "Failed to copy key '%s'to '%s': %s", name, newname, strerror(-rc)); return rc; } /** * Exports a key from the keystore to a file * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] export_file the name of the file to export the key to * * @returns 0 for success or a negative errno in case of an error */ int keystore_export_key(struct keystore *keystore, const char *name, const char *export_file) { struct key_filenames file_names = { 0 }; size_t secure_key_size; u8 *secure_key; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(name != NULL, "Internal error: name is NULL"); util_assert(export_file != NULL, "Internal error: export_file is NULL"); rc = _keystore_get_key_filenames(keystore, name, &file_names); if (rc != 0) goto out; rc = _keystore_ensure_keyfiles_exist(&file_names, name); if (rc != 0) goto out; secure_key = read_secure_key(file_names.skey_filename, &secure_key_size, keystore->verbose); if (secure_key == NULL) { rc = -ENOENT; goto out; } rc = write_secure_key(export_file, secure_key, secure_key_size, keystore->verbose); free(secure_key); pr_verbose(keystore, "Successfully exported key '%s' to '%s'", name, export_file); out: _keystore_free_key_filenames(&file_names); if (rc != 0) pr_verbose(keystore, "Failed to export key '%s': %s", name, strerror(-rc)); return rc; } /** * Prompts the user to confirm deletion of a key * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] file_names the file names of the key * * @returns 0 if the user confirmed the deletion, a negative errno value * otherwise */ static int _keystore_prompt_for_remove(struct keystore *keystore, const char *name, struct key_filenames *file_names) { struct properties *key_prop; char *msg; int rc; key_prop = properties_new(); rc = properties_load(key_prop, file_names->info_filename, 1); if (rc != 0) { warnx("Key '%s' does not exist or is invalid", name); goto out; } util_asprintf(&msg, "When you remove key '%s' the following volumes " "will no longer be usable:", name); _keystore_msg_for_volumes(msg, key_prop, VOLUME_TYPE_PLAIN); _keystore_msg_for_volumes(msg, key_prop, VOLUME_TYPE_INTEGRITY); free(msg); printf("%s: Remove key '%s' [y/N]? ", program_invocation_short_name, name); if (!prompt_for_yes(keystore->verbose)) { warnx("Operation aborted"); rc = -ECANCELED; goto out; } out: properties_free(key_prop); return rc; } /** * Removes a key from the keystore * * @param[in] keystore the key store * @param[in] name the name of the key * @param[in] quiet if true no confirmation prompt is shown * @param[in] kms_options an array of KMS options specified, or NULL if no * KMS options have been specified * @param[in] num_kms_options the number of options in above array * * @returns 0 for success or a negative errno in case of an error */ int keystore_remove_key(struct keystore *keystore, const char *name, bool quiet, struct kms_option *kms_options, size_t num_kms_options) { struct key_filenames file_names = { 0 }; struct properties *key_props = NULL; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(name != NULL, "Internal error: name is NULL"); rc = _keystore_get_key_filenames(keystore, name, &file_names); if (rc != 0) goto out; rc = _keystore_ensure_keyfiles_exist(&file_names, name); if (rc != 0) goto out; if (!quiet) { if (_keystore_prompt_for_remove(keystore, name, &file_names) != 0) goto out; } key_props = properties_new(); rc = properties_load(key_props, file_names.info_filename, 1); if (rc != 0) { warnx("Key '%s' does not exist or is invalid", name); goto out; } if (_keystore_is_kms_bound_key(key_props, NULL)) { rc = perform_kms_login(keystore->kms_info, keystore->verbose); if (rc != 0) goto out; rc = remove_kms_key(keystore->kms_info, key_props, kms_options, num_kms_options, keystore->verbose); if (rc != 0) { warnx("KMS plugin '%s' failed to remove key '%s': %s", keystore->kms_info->plugin_name, name, strerror(-rc)); print_last_kms_error(keystore->kms_info); goto out; } } if (remove(file_names.skey_filename) != 0) { rc = -errno; pr_verbose(keystore, "Failed to remove '%s': %s", file_names.skey_filename, strerror(-rc)); goto out; } if (remove(file_names.info_filename) != 0) { rc = -errno; pr_verbose(keystore, "Failed to remove '%s': %s", file_names.info_filename, strerror(-rc)); } if (_keystore_reencipher_key_exists(&file_names)) { if (remove(file_names.renc_filename) != 0) { rc = -errno; pr_verbose(keystore, "Failed to remove '%s': %s", file_names.renc_filename, strerror(-rc)); } } if (_keystore_passphrase_file_exists(&file_names)) { if (remove(file_names.pass_filename) != 0) { rc = -errno; pr_verbose(keystore, "Failed to remove '%s': %s", file_names.pass_filename, strerror(-rc)); } } pr_verbose(keystore, "Successfully removed key '%s'", name); out: _keystore_free_key_filenames(&file_names); if (key_props != NULL) properties_free(key_props); if (rc != 0) pr_verbose(keystore, "Failed to remove key '%s': %s", name, strerror(-rc)); return rc; } /** * Processing function for the key display function. * * @param[in] keystore the keystore * @param[in] name the name of the key * @param[in] properties the properties object of the key * @param[in] file_names the file names used by this key * @param[in] private private data: struct reencipher_info * * @returns 0 if the display is successful, a negative errno value otherwise */ static int _keystore_display_key(struct keystore *keystore, const char *name, struct properties *properties, struct key_filenames *file_names, void *private) { struct util_rec *rec = (struct util_rec *)private; u8 *secure_key; size_t secure_key_size, clear_key_bitsize = 0; int rc = 0; secure_key = read_secure_key(file_names->skey_filename, &secure_key_size, keystore->verbose); if (secure_key == NULL) return -EIO; if (secure_key_size < MIN_SECURE_KEY_SIZE) { pr_verbose(keystore, "Size of secure key is too small: %lu expected %lu", secure_key_size, MIN_SECURE_KEY_SIZE); rc = -EIO; goto out; } get_key_bit_size(secure_key, secure_key_size, &clear_key_bitsize); _keystore_print_record(rec, name, properties, 0, file_names->skey_filename, secure_key_size, is_xts_key(secure_key, secure_key_size), clear_key_bitsize, 0, 0, _keystore_reencipher_key_exists(file_names), NULL, _keystore_passphrase_file_exists(file_names) ? file_names->pass_filename : NULL); out: free(secure_key); return rc; } /** * Lists keys in the keystore that matches the filters * * @param[in] keystore the key store * @param[in] name_filter the name filter. Can contain wild cards. * NULL means no name filter. * @param[in] volume_filter the volume filter. Can contain wild cards, and * multiple volume filters separated by commas. * The ':dm-name' part of the volume is optional * for the volume filter. If not specified, the filter * checks the volume part only. * NULL means no volume filter. * @param[in] apqn_filter the APQN filter. Can contain wild cards, and * multiple APQN filters separated by commas. * NULL means no APQN filter. * @param[in] volume_type The volume type. NULL means no volume type filter. * @param[in] key_type The key type. NULL means no key type filter. * @param[in] local if true, only local keys are listed * @param[in] kms_bound if true, only KMS-bound keys are listed * * @returns 0 for success or a negative errno in case of an error */ int keystore_list_keys(struct keystore *keystore, const char *name_filter, const char *volume_filter, const char *apqn_filter, const char *volume_type, const char *key_type, bool local, bool kms_bound) { struct util_rec *rec; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); if (volume_type != NULL && !_keystore_valid_volume_type(NULL, volume_type)) { warnx("Invalid volume-type specified"); return -EINVAL; } if (key_type != NULL && !_keystore_valid_key_type(key_type)) { warnx("Invalid key-type specified"); return -EINVAL; } rec = _keystore_setup_record(0); rc = _keystore_process_filtered(keystore, name_filter, volume_filter, apqn_filter, volume_type, key_type, local, kms_bound, _keystore_display_key, rec); util_rec_free(rec); if (rc != 0) pr_verbose(keystore, "Failed to list keys: %s", strerror(-rc)); else pr_verbose(keystore, "Successfully listed keys"); return rc; } /** * Executes a command via system(). * * @param[in] cmd the command to execute * @param[in] msg_cmd the short command name (for messages) * * @returns the exit code of the command execution, or -1 in case of an error */ static int _keystore_execute_cmd(const char *cmd, const char *msg_cmd) { int rc; rc = setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin", 1); if (rc < 0) return rc; rc = system(cmd); if (WIFEXITED(rc)) { rc = WEXITSTATUS(rc); if (rc != 0) printf("%s exit code: %d\n", msg_cmd, rc); } else { rc = -EIO; warnx("%s terminated abnormally", msg_cmd); } return rc; } struct find_integrity_key_info { bool found; char *integrity_spec; char *integrity_key_name; size_t integrity_key_size; }; static int _keystore_find_integrity_key_cb(struct keystore *keystore, const char *UNUSED(name), struct properties *properties, struct key_filenames *file_names, void *private) { struct find_integrity_key_info *find_info = private; size_t secure_key_size; u8 *secure_key = NULL; size_t bitsize = 0; int rc; if (find_info->found) return 0; secure_key = read_secure_key(file_names->skey_filename, &secure_key_size, keystore->verbose); if (secure_key == NULL) return -EIO; rc = get_key_bit_size(secure_key, secure_key_size, &bitsize); if (rc != 0) goto out; find_info->integrity_spec = _keystore_build_hmac_spec(properties, bitsize); if (find_info->integrity_spec == NULL) { rc = -EINVAL; goto out; } find_info->integrity_key_name = util_strdup(file_names->skey_filename); find_info->integrity_key_size = secure_key_size; find_info->found = true; out: if (secure_key != NULL) free(secure_key); return rc; } /** * Check if all characters of the string are within [A-Za-z0-9._/:-], and thus * are safe to be used in generated commands that might get executed. * * @param[in] str the string to check * * @returns true if the string is valid, false otherwise */ static bool is_valid_characters_for_command(const char *str) { const char *chars = "._/:-"; size_t i; for (i = 0; i < strlen(str); i++) { if (isalnum(str[i])) continue; if (strchr(chars, str[i]) != NULL) continue; warnx("'%s' contains invalid characters", str); return false; } return true; } struct crypt_info { bool integrity; bool execute; bool batch_mode; const char *keyfile; size_t keyfile_offset; size_t keyfile_size; size_t tries; bool open; bool format; int fips; char **volume_filter; int (*process_func)(struct keystore *keystore, const char *volume, const char *dmname, const char *cipher_spec, const char *key_file_name, size_t key_file_size, size_t sector_size, const char *volume_type, const char *passphrase_file, struct crypt_info *info); }; /** * Processing function for the cryptsetup function. Builds a cryptsetup command * line and optionally executes it. * * @param[in] keystore the keystore (not used here) * @param[in] volume the volume to mount * @param[in] dmname the device mapper name * @param[in] cipher_spec the cipher specification * @param[in] key_file_name the key file name * @param[in] key_file_size the size of the key file in bytes * @param[in] sector_size the sector size in bytes or 0 if not specified * @param[in] volume_type the volume type * @param[in] passphrase_file the passphrase file name (can be NULL) * @param[in] info processing info * * @returns 0 if successful, a negative errno value otherwise */ static int _keystore_process_cryptsetup(struct keystore *keystore, const char *volume, const char *dmname, const char *cipher_spec, const char *key_file_name, size_t key_file_size, size_t sector_size, const char *volume_type, const char *passphrase_file, struct crypt_info *info) { struct find_integrity_key_info find_info = { 0 }; char *keyfile_opt = NULL, *offset_opt = NULL; char *size_opt = NULL, *tries_opt = NULL; char *common_passphrase_options; char *integrity_opts = NULL; char *combined_key = NULL; size_t common_len; char *cmd = NULL; char temp[100]; char *tmpdir; int rc = 0; if (!is_valid_characters_for_command(cipher_spec)) return -EINVAL; if (!is_valid_characters_for_command(key_file_name)) return -EINVAL; if (!is_valid_characters_for_command(volume)) return -EINVAL; if (!is_valid_characters_for_command(dmname)) return -EINVAL; sprintf(temp, "--sector-size %lu ", sector_size); if (info->keyfile) { if (!is_valid_characters_for_command(info->keyfile)) return -EINVAL; util_asprintf(&keyfile_opt, "--key-file '%s' ", info->keyfile); if (info->keyfile_offset > 0) util_asprintf(&offset_opt, "--keyfile-offset %lu ", info->keyfile_offset); if (info->keyfile_size > 0) util_asprintf(&size_opt, "--keyfile-size %lu ", info->keyfile_size); } else if (passphrase_file != NULL) { if (!is_valid_characters_for_command(passphrase_file)) return -EINVAL; util_asprintf(&keyfile_opt, "--key-file '%s' ", passphrase_file); } if (info->tries > 0) util_asprintf(&tries_opt, "--tries %lu ", info->tries); util_asprintf(&common_passphrase_options, "%s%s%s%s", keyfile_opt != NULL ? keyfile_opt : "", offset_opt != NULL ? offset_opt : "", size_opt != NULL ? size_opt : "", tries_opt != NULL ? tries_opt : ""); common_len = strlen(common_passphrase_options); free(keyfile_opt); free(offset_opt); free(size_opt); free(tries_opt); if (strcasecmp(volume_type, VOLUME_TYPE_PLAIN) == 0) { if (info->format) return 0; util_asprintf(&cmd, "cryptsetup plainOpen %s%s--key-file '%s' " "--key-size %lu --cipher %s %s%s %s", info->batch_mode ? "-q " : "", keystore->verbose ? "-v " : "", key_file_name, key_file_size * 8, cipher_spec, sector_size > 0 ? temp : "", volume, dmname); if (info->execute) { printf("Executing: %s\n", cmd); rc = _keystore_execute_cmd(cmd, "cryptsetup"); } else { printf("%s\n", cmd); } } else if (strcasecmp(volume_type, VOLUME_TYPE_LUKS2) == 0) { if (info->open) { util_asprintf(&cmd, "cryptsetup luksOpen %s%s%s%s %s", info->batch_mode ? "-q " : "", keystore->verbose ? "-v " : "", common_len > 0 ? common_passphrase_options : "", volume, dmname); if (info->execute) { printf("Executing: %s\n", cmd); rc = _keystore_execute_cmd(cmd, "cryptsetup"); } else { printf("%s\n", cmd); } } else { rc = _keystore_process_filtered(keystore, NULL, volume, NULL, VOLUME_TYPE_LUKS2, KEY_TYPE_FILTER_HMAC, false, false, _keystore_find_integrity_key_cb, &find_info); if (rc != 0) goto out; if (find_info.found) { if (!is_valid_characters_for_command( find_info.integrity_spec)) { rc = -EINVAL; goto out; } if (!is_valid_characters_for_command( find_info.integrity_key_name)) { rc = -EINVAL; goto out; } util_asprintf(&integrity_opts, "--integrity %s " "--integrity-key-size %lu ", find_info.integrity_spec, find_info.integrity_key_size * 8); /* * The volume key for a volume with combined * encryption and integrity protection is built * by concatenating the integrity key to the * encryption key. The size of the volume key * specified by --key-size is however just the * encryption key size in bits. */ tmpdir = secure_getenv("TMPDIR"); util_asprintf(&combined_key, "%s/luks2-integrity-%s.skey", tmpdir != NULL ? tmpdir : "/tmp", dmname); if (!is_valid_characters_for_command( combined_key)) { rc = -EINVAL; goto out; } util_asprintf(&cmd, "cat '%s' '%s' > '%s'", key_file_name, find_info.integrity_key_name, combined_key); if (info->execute) { printf("Executing: %s\n", cmd); rc = _keystore_execute_cmd(cmd, "cat"); } else { printf("%s\n", cmd); } free(cmd); cmd = NULL; if (rc != 0) goto out; key_file_name = combined_key; } /* * Use Argon2i as key derivation function for LUKS2 * volumes, but with options for low memory and time requirements. * Using the default Argon2i options might cause out-of-memory * errors when multiple LUKS2 volumes are opened automatically * via /etc/crypttab * In case the system runs in FIPS mode, use PBKDF2 * instead, because Argon2i might be disabled by a * policy when FIPS mode is active. */ util_asprintf(&cmd, "cryptsetup luksFormat %s%s--type luks2 " "--volume-key-file '%s' --key-size %lu " "--cipher %s --pbkdf %s %s%s%s%s", info->batch_mode ? "-q " : "", keystore->verbose ? "-v " : "", key_file_name, key_file_size * 8, cipher_spec, info->fips ? "pbkdf2" : "argon2i --pbkdf-memory 32 " "--pbkdf-force-iterations 4", integrity_opts != NULL ? integrity_opts : "", common_len > 0 ? common_passphrase_options : "", sector_size > 0 ? temp : "", volume); if (info->execute) { printf("Executing: %s\n", cmd); rc = _keystore_execute_cmd(cmd, "cryptsetup"); } else { printf("%s\n", cmd); } free(cmd); cmd = NULL; if (rc != 0) goto out; if (combined_key != NULL && !info->execute) { util_asprintf(&cmd, "rm '%s' -f", combined_key); printf("%s\n", cmd); free(cmd); cmd = NULL; } util_asprintf(&cmd, "zkey-cryptsetup setvp %s %s%s", volume, common_len > 0 ? common_passphrase_options : "", keystore->verbose ? "-V" : ""); if (info->execute) { printf("Executing: %s\n", cmd); rc = _keystore_execute_cmd(cmd, "zkey-cryptsetup"); } else { printf("%s\n", cmd); } } } else { rc = -EINVAL; } out: if (find_info.integrity_spec != NULL) free(find_info.integrity_spec); if (find_info.integrity_key_name != NULL) free(find_info.integrity_key_name); if (integrity_opts != NULL) free(integrity_opts); if (combined_key != NULL) { if (info->execute) remove(combined_key); free(combined_key); } free(common_passphrase_options); free(cmd); return rc; } /** * Processing function for the crypttab function. Builds a crypttab entry * and prints it. * * @param[in] keystore the keystore (not used here) * @param[in] volume the volume to mount * @param[in] dmname the device mapper name * @param[in] cipher_spec the cipher specification * @param[in] key_file_name the key file name * @param[in] key_file_size the size of the key file in bytes * @param[in] sector_size the sector size in bytes or 0 if not specified * @param[in] volume_type the volume type * @param[in] passphrase_file the passphrase file name (can be NULL) * @param[in] info processing info (not used here) * * @returns 0 if successful, a negative errno value otherwise */ static int _keystore_process_crypttab(struct keystore *UNUSED(keystore), const char *volume, const char *dmname, const char *cipher_spec, const char *key_file_name, size_t key_file_size, size_t sector_size, const char *volume_type, const char *passphrase_file, struct crypt_info *info) { char temp[1000]; if (!is_valid_characters_for_command(cipher_spec)) return -EINVAL; if (!is_valid_characters_for_command(key_file_name)) return -EINVAL; if (!is_valid_characters_for_command(volume)) return -EINVAL; if (!is_valid_characters_for_command(dmname)) return -EINVAL; if (strcasecmp(volume_type, VOLUME_TYPE_PLAIN) == 0) { sprintf(temp, ",sector-size=%lu", sector_size); printf("%s\t%s\t%s\tplain,cipher=%s,size=%lu%s\n", dmname, volume, key_file_name, cipher_spec, key_file_size * 8, sector_size > 0 ? temp : ""); } else if (strcasecmp(volume_type, VOLUME_TYPE_LUKS2) == 0) { if (info->keyfile != NULL) { if (!is_valid_characters_for_command(info->keyfile)) return -EINVAL; printf("%s\t%s\t%s\tluks", dmname, volume, info->keyfile); if (info->keyfile_offset > 0) printf(",keyfile-offset=%lu", info->keyfile_offset); if (info->keyfile_size > 0) printf(",keyfile-size=%lu", info->keyfile_size); } else if (passphrase_file != NULL) { if (!is_valid_characters_for_command(passphrase_file)) return -EINVAL; printf("%s\t%s\t%s\tluks", dmname, volume, passphrase_file); } else { printf("%s\t%s\tnone\tluks", dmname, volume); } if (info->tries > 0) printf(",tries=%lu", info->tries); printf("\n"); } else { return -EINVAL; } return 0; } /** * Processing function for the integritysetup function. Builds a integritysetup * command line and optionally executes it. * * @param[in] keystore the keystore (not used here) * @param[in] volume the volume to mount * @param[in] dmname the device mapper name * @param[in] cipher_spec the cipher specification * @param[in] key_file_name the key file name * @param[in] key_file_size the size of the key file in bytes * @param[in] sector_size the sector size in bytes or 0 if not specified * @param[in] volume_type the volume type * @param[in] passphrase_file the passphrase file name (can be NULL) * @param[in] info processing info * * @returns 0 if successful, a negative errno value otherwise */ static int _keystore_process_integritysetup(struct keystore *keystore, const char *volume, const char *dmname, const char *cipher_spec, const char *key_file_name, size_t key_file_size, size_t sector_size, const char *volume_type, const char *UNUSED(passphrase_file), struct crypt_info *info) { char temp[100]; int rc = 0; char *cmd; if (strcasecmp(volume_type, VOLUME_TYPE_INTEGRITY) != 0) return 0; sprintf(temp, "--sector-size %lu ", sector_size); if (!is_valid_characters_for_command(cipher_spec)) return -EINVAL; if (!is_valid_characters_for_command(key_file_name)) return -EINVAL; if (!is_valid_characters_for_command(volume)) return -EINVAL; if (!is_valid_characters_for_command(dmname)) return -EINVAL; util_asprintf(&cmd, "integritysetup %s %s%s--integrity %s " "--integrity-key-file '%s' --integrity-key-size %lu " "%s%s%s%s", info->open ? "open" : "format", info->batch_mode ? "-q " : "", keystore->verbose ? "-v " : "", cipher_spec, key_file_name, key_file_size, sector_size > 0 && !info->open ? temp : "", volume, info->open ? " " : "", info->open ? dmname : ""); if (info->execute) { printf("Executing: %s\n", cmd); rc = _keystore_execute_cmd(cmd, "integritysetup"); } else { printf("%s\n", cmd); } free(cmd); return rc; } /** * Processing function for the integritytab function. Builds a integritytab * entry and prints it. * * @param[in] keystore the keystore (not used here) * @param[in] volume the volume to mount * @param[in] dmname the device mapper name * @param[in] cipher_spec the cipher specification * @param[in] key_file_name the key file name * @param[in] key_file_size the size of the key file in bytes * @param[in] sector_size the sector size in bytes or 0 if not specified * @param[in] volume_type the volume type * @param[in] passphrase_file the passphrase file name (can be NULL) * @param[in] info processing info (not used here) * * @returns 0 if successful, a negative errno value otherwise */ static int _keystore_process_integritytab(struct keystore *UNUSED(keystore), const char *volume, const char *dmname, const char *cipher_spec, const char *key_file_name, size_t UNUSED(key_file_size), size_t UNUSED(sector_size), const char *volume_type, const char *UNUSED(passphrase_file), struct crypt_info *UNUSED(info)) { if (strcasecmp(volume_type, VOLUME_TYPE_INTEGRITY) != 0) return 0; if (!is_valid_characters_for_command(cipher_spec)) return -EINVAL; if (!is_valid_characters_for_command(key_file_name)) return -EINVAL; if (!is_valid_characters_for_command(volume)) return -EINVAL; if (!is_valid_characters_for_command(dmname)) return -EINVAL; printf("%s\t%s\t%s\tintegrity-algorithm=%s\n", volume, dmname, key_file_name, cipher_spec); return 0; } /** * Builds a cipher specification for cryptsetup/crypttab * * @param properties the key properties * @param is_xts if true, the key is an XTS key * * @returns the cipher spec string (must be freed by the caller) */ static char *_keystore_build_cipher_spec(struct properties *properties, bool is_xts) { char *cipher_spec = NULL; char *cipher = NULL; char *ivmode = NULL; cipher = properties_get(properties, PROP_NAME_CIPHER); if (cipher == NULL) goto out; ivmode = properties_get(properties, PROP_NAME_IV_MODE); if (ivmode == NULL) goto out; util_asprintf(&cipher_spec, "%s-%s-%s", cipher, is_xts ? "xts" : "cbc", ivmode); out: if (cipher != NULL) free(cipher); if (ivmode != NULL) free(ivmode); return cipher_spec; } /** * Builds a hmac specification for integritysetup/integritytab * * @param properties the key properties * * @returns the hmac spec string (must be freed by the caller) */ static char *_keystore_build_hmac_spec(struct properties *properties, size_t bitsize) { char *cipher_spec = NULL; char *cipher = NULL; char *digest = NULL; cipher = properties_get(properties, PROP_NAME_CIPHER); if (cipher == NULL) goto out; digest = properties_get(properties, PROP_NAME_DIGEST); if (digest == NULL) goto out; util_asprintf(&cipher_spec, "%s-%s%u", cipher, digest, bitsize / 2); out: if (cipher != NULL) free(cipher); if (digest != NULL) free(digest); return cipher_spec; } /** * Processing function for the cryptsetup and crypttab functions. * Extracts the required information and calls the secondary processing function * contained in struct crypt_info. * * @param[in] keystore the keystore * @param[in] name the name of the key * @param[in] properties the properties object of the key * @param[in] file_names the file names used by this key * @param[in] private private data: struct crypt_info * * @returns 0 if the validation is successful, a negative errno value otherwise */ static int _keystore_process_crypt(struct keystore *keystore, const char *name, struct properties *properties, struct key_filenames *file_names, void *private) { struct crypt_info *info = (struct crypt_info *)private; char **volume_list = NULL; char *cipher_spec = NULL; char *volume_type = NULL; size_t secure_key_size; size_t sector_size = 0; char *volumes = NULL; u8 *secure_key = NULL; size_t bitsize = 0; char *dmname; char *temp; int rc = 0; char *vol; char *ch; int i; secure_key = read_secure_key(file_names->skey_filename, &secure_key_size, keystore->verbose); if (secure_key == NULL) return -EIO; if (info->integrity) { rc = get_key_bit_size(secure_key, secure_key_size, &bitsize); if (rc != 0) goto out; cipher_spec = _keystore_build_hmac_spec(properties, bitsize); } else { cipher_spec = _keystore_build_cipher_spec(properties, is_xts_key(secure_key, secure_key_size)); } if (cipher_spec == NULL) { rc = -EINVAL; goto out; } volumes = properties_get(properties, PROP_NAME_VOLUMES); if (volumes == NULL) return -EINVAL; volume_list = str_list_split(volumes); temp = properties_get(properties, PROP_NAME_SECTOR_SIZE); if (temp != NULL) { util_assert(sscanf(temp, "%lu", §or_size) == 1, "Internal error: sscanf failed"); free(temp); } volume_type = _keystore_get_volume_type(properties); for (i = 0; volume_list[i] != NULL && rc == 0; i++) { vol = volume_list[i]; if (_keystore_match_filter(vol, info->volume_filter, NULL) != 0) { ch = strchr(vol, ':'); if (ch == NULL) { warnx("Volume does not contain a dm-name part." " Key: '%s'", name); rc = -EINVAL; break; } *ch = '\0'; dmname = ch + 1; rc = info->process_func(keystore, vol, dmname, cipher_spec, file_names->skey_filename, secure_key_size, sector_size, volume_type, _keystore_passphrase_file_exists(file_names) ? file_names->pass_filename : NULL, info); if (rc != 0) break; } } out: if (volumes != NULL) free(volumes); if (volume_list != NULL) str_list_free_string_array(volume_list); if (cipher_spec != NULL) free(cipher_spec); if (volume_type != NULL) free(volume_type); if (secure_key != NULL) free(secure_key); return rc; } /** * Generates cryptsetup commands for one or multiple volumes. * * @param[in] keystore the key store * @param[in] volume_filter the volume filter. Can contain wild cards, and * multiple volume filters separated by commas. * The ':dm-name' part of the volume is optional * for the volume filter. If not specified, the filter * checks the volume part only. * @param[in] execute If TRUE the cryptsetup command is executed, * otherwise it is printed to stdout * @param[in] volume_type the type of volume to generate cryptsetup cmds for * @param[in] keyfile If non-NULL, specifies the name of the file to * read the passphrase from. * @param[in] keyfile_offset the offset in bytes for reading from keyfile * @param[in] keyfile_size the size in bytes for reading from keyfile * @param[in] tries the number of tries for passphrase entry * @param[in] batch_mode If TRUE, suppress cryptsetup confirmation questions * @param[in] open If TRUE, generate luksOpen/plainOpen commands * @param[in] format If TRUE, generate luksFormat commands * @returns 0 for success or a negative errno in case of an error */ int keystore_cryptsetup(struct keystore *keystore, const char *volume_filter, bool execute, const char *volume_type, const char *keyfile, size_t keyfile_offset, size_t keyfile_size, size_t tries, bool batch_mode, bool open, bool format) { struct crypt_info info = { 0 }; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); if (volume_filter == NULL) volume_filter = "*"; if (volume_type != NULL && !_keystore_valid_volume_type(NULL, volume_type)) { warnx("Invalid volume-type specified"); return -EINVAL; } util_file_read_i(&info.fips, 10, "/proc/sys/crypto/fips_enabled"); info.integrity = false; info.execute = execute; info.open = open; info.format = format; info.batch_mode = batch_mode; info.keyfile = keyfile; info.keyfile_offset = keyfile_offset; info.keyfile_size = keyfile_size; info.tries = tries; info.volume_filter = str_list_split(volume_filter); info.process_func = _keystore_process_cryptsetup; rc = _keystore_process_filtered(keystore, NULL, volume_filter, NULL, volume_type, KEY_TYPE_FILTER_AES, false, false, _keystore_process_crypt, &info); str_list_free_string_array(info.volume_filter); if (rc < 0) pr_verbose(keystore, "Cryptsetup failed with: %s", strerror(-rc)); else if (rc > 0) pr_verbose(keystore, "Cryptsetup failed with: %d", rc); else pr_verbose(keystore, "Successfully generated cryptsetup commands"); return rc; } /** * Generates crypttab entries for one or multiple volumes. * * @param[in] keystore the key store * @param[in] volume_filter the volume filter. Can contain wild cards, and * multiple volume filters separated by commas. * The ':dm-name' part of the volume is optional * for the volume filter. If not specified, the filter * checks the volume part only. * @param[in] volume_type the type of volume to generate crypttab entries for * @param[in] keyfile If non-NULL, specifies the name of the file to * read the passphrase from. * @param[in] keyfile_offset the offset in bytes for reading from keyfile * @param[in] keyfile_size the size in bytes for reading from keyfile * @param[in] tries the number of tries for passphrase entry * * @returns 0 for success or a negative errno in case of an error */ int keystore_crypttab(struct keystore *keystore, const char *volume_filter, const char *volume_type, const char *keyfile, size_t keyfile_offset, size_t keyfile_size, size_t tries) { struct crypt_info info = { 0 }; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); if (volume_filter == NULL) volume_filter = "*"; if (volume_type != NULL && !_keystore_valid_volume_type(NULL, volume_type)) { warnx("Invalid volume-type specified"); return -EINVAL; } info.integrity = false; info.keyfile = keyfile; info.keyfile_offset = keyfile_offset; info.keyfile_size = keyfile_size; info.tries = tries; info.volume_filter = str_list_split(volume_filter); info.process_func = _keystore_process_crypttab; rc = _keystore_process_filtered(keystore, NULL, volume_filter, NULL, volume_type, KEY_TYPE_FILTER_AES, false, false, _keystore_process_crypt, &info); str_list_free_string_array(info.volume_filter); if (rc != 0) pr_verbose(keystore, "Cryptsetup failed with: %s", strerror(-rc)); else pr_verbose(keystore, "Successfully generated crypttab entries"); return rc; } /** * Generates integritysetup commands for one or multiple volumes. * * @param[in] keystore the key store * @param[in] volume_filter the volume filter. Can contain wild cards, and * multiple volume filters separated by commas. * The ':dm-name' part of the volume is optional * for the volume filter. If not specified, the filter * checks the volume part only. * @param[in] execute If TRUE the integritysetup command is executed, * otherwise it is printed to stdout * @param[in] batch_mode If TRUE, suppress integritysetup confirmation * questions * @param[in] open If TRUE, generate integritysetup open commands * @returns 0 for success or a negative errno in case of an error */ int keystore_integritysetup(struct keystore *keystore, const char *volume_filter, bool execute, bool batch_mode, bool open) { struct crypt_info info = { 0 }; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); if (volume_filter == NULL) volume_filter = "*"; info.integrity = true; info.execute = execute; info.open = open; info.batch_mode = batch_mode; info.volume_filter = str_list_split(volume_filter); info.process_func = _keystore_process_integritysetup; rc = _keystore_process_filtered(keystore, NULL, volume_filter, NULL, VOLUME_TYPE_INTEGRITY, KEY_TYPE_FILTER_HMAC, false, false, _keystore_process_crypt, &info); str_list_free_string_array(info.volume_filter); if (rc < 0) pr_verbose(keystore, "Integritysetup failed with: %s", strerror(-rc)); else if (rc > 0) pr_verbose(keystore, "Integritysetup failed with: %d", rc); else pr_verbose(keystore, "Successfully generated integritysetup commands"); return rc; } /** * Generates integritytab entries for one or multiple volumes. * * @param[in] keystore the key store * @param[in] volume_filter the volume filter. Can contain wild cards, and * multiple volume filters separated by commas. * The ':dm-name' part of the volume is optional * for the volume filter. If not specified, the filter * checks the volume part only. * * @returns 0 for success or a negative errno in case of an error */ int keystore_integritytab(struct keystore *keystore, const char *volume_filter) { struct crypt_info info = { 0 }; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); if (volume_filter == NULL) volume_filter = "*"; info.integrity = true; info.volume_filter = str_list_split(volume_filter); info.process_func = _keystore_process_integritytab; rc = _keystore_process_filtered(keystore, NULL, volume_filter, NULL, VOLUME_TYPE_INTEGRITY, KEY_TYPE_FILTER_HMAC, false, false, _keystore_process_crypt, &info); str_list_free_string_array(info.volume_filter); if (rc != 0) pr_verbose(keystore, "Integritysetup failed with: %s", strerror(-rc)); else pr_verbose(keystore, "Successfully generated integritysetup entries"); return rc; } /** * Converts a secure keys in the keystore * * @param[in] keystore the key store * @param[in] name the name of the key to convert * @param[in] key_type the type of the key to convert it to * @param[in] noapqncheck if true, the specified APQN(s) are not checked for * existence and type. * @param[in] exportable if true the key shall be exportable * @param[in] pkey_fd the file descriptor of /dev/pkey * @param[in] lib the external library struct * * @returns 0 for success or a negative errno in case of an error */ int keystore_convert_key(struct keystore *keystore, const char *name, const char *key_type, bool noapqncheck, bool quiet, bool exportable, int pkey_fd, struct ext_lib *lib) { struct key_filenames file_names = { 0 }; u8 output_key[2 * MAX_SECURE_KEY_SIZE]; struct properties *properties = NULL; int rc, min_level, selected = 1; unsigned int output_key_size; char *cur_key_type = NULL; char **apqn_list = NULL; size_t secure_key_size; u8 *secure_key = NULL; u8 mkvp[MKVP_LENGTH]; char *apqns = NULL; char *temp; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(name != NULL, "Internal error: name is NULL"); rc = _keystore_get_key_filenames(keystore, name, &file_names); if (rc != 0) goto out; rc = _keystore_ensure_keyfiles_exist(&file_names, name); if (rc != 0) goto out; properties = properties_new(); rc = properties_load(properties, file_names.info_filename, 1); if (rc != 0) { warnx("Key '%s' does not exist or is invalid", name); goto out; } if (_keystore_is_kms_bound_key(properties, NULL)) { rc = -EINVAL; warnx("A KMS-bound key can not be converted"); goto out; } cur_key_type = _keystore_get_key_type(properties); if (strcasecmp(cur_key_type, key_type) == 0) { warnx("The secure key '%s' is already of type %s", name, cur_key_type); rc = 0; goto out; } if (strcasecmp(cur_key_type, KEY_TYPE_CCA_AESDATA) != 0) { warnx("Only secure keys of type %s can " "be converted. The secure key '%s' is of type %s", KEY_TYPE_CCA_AESDATA, name, cur_key_type); rc = 0; goto out; } secure_key = read_secure_key(file_names.skey_filename, &secure_key_size, keystore->verbose); if (secure_key == NULL) { rc = -ENOENT; goto out; } min_level = get_min_card_level_for_keytype(key_type); if (min_level < 0) { warnx("Invalid key-type specified: %s", key_type); rc = -EINVAL; goto out; } apqns = properties_get(properties, PROP_NAME_APQNS); if (apqns != NULL) apqn_list = str_list_split(apqns); rc = cross_check_apqns(apqns, NULL, min_level, get_min_fw_version_for_keytype(key_type), get_card_type_for_keytype(key_type), true, keystore->verbose); if (rc == -EINVAL) goto out; if (rc != 0 && rc != -ENOTSUP && !noapqncheck) { warnx("Your master key setup is improper for converting key " "'%s'", name); goto out; } rc = validate_secure_key(pkey_fd, secure_key, secure_key_size, NULL, NULL, (const char **)apqn_list, keystore->verbose); if (rc != 0) goto out; rc = get_master_key_verification_pattern(secure_key, secure_key_size, mkvp, keystore->verbose); if (rc) goto out; rc = select_cca_adapter_by_mkvp(lib->cca, mkvp, apqns, FLAG_SEL_CCA_MATCH_CUR_MKVP, keystore->verbose); if (rc == -ENOTSUP) { rc = 0; selected = 0; } if (rc != 0) { warnx("No APQN found that is suitable for " "converting the secure AES key '%s'", name); goto out; } if (!quiet) { util_print_indented("ATTENTION: Converting a secure key is " "irreversible, and might have an effect " "on the volumes encrypted with it!", 0); _keystore_msg_for_volumes("The following volumes are encrypted " "with this key:", properties, NULL); printf("%s: Convert key '%s [y/N]'? ", program_invocation_short_name, name); if (!prompt_for_yes(keystore->verbose)) { warnx("Operation aborted"); rc = -ECANCELED; goto out; } } memset(output_key, 0, sizeof(output_key)); output_key_size = sizeof(output_key); rc = convert_aes_data_to_cipher_key(lib->cca, secure_key, secure_key_size, output_key, &output_key_size, keystore->verbose); if (rc != 0) { warnx("Converting the secure key '%s' from %s to %s has failed", name, KEY_TYPE_CCA_AESDATA, key_type); if (!selected) print_msg_for_cca_envvars("secure AES key"); goto out; } if (!exportable) { rc = restrict_key_export(lib->cca, output_key, output_key_size, keystore->verbose); if (rc != 0) { warnx("Export restricting the converted secure key " "'%s' has failed", name); if (!selected) print_msg_for_cca_envvars("secure AES key"); goto out; } } rc = properties_set2(properties, PROP_NAME_KEY_TYPE, key_type, true); if (rc != 0) { warnx("Invalid characters in key-type"); goto out; } rc = properties_save(properties, file_names.info_filename, 1); if (rc != 0) { pr_verbose(keystore, "Failed to write key info file '%s': %s", file_names.info_filename, strerror(-rc)); goto out; } rc = write_secure_key(file_names.skey_filename, output_key, output_key_size, keystore->verbose); if (rc != 0) goto out; pr_verbose(keystore, "Secure key '%s' was converted successfully", name); util_asprintf(&temp, "The following LUKS2 volumes are " "encrypted with key '%s'. These volumes still contain " "the secure AES volume key of type CCA-AESDATA. To " "change the secure AES volume key in the LUKS2 header, " "run command 'zkey-cryptsetup setkey " "--volume-key-file %s':", name, file_names.skey_filename); _keystore_msg_for_volumes(temp, properties, VOLUME_TYPE_LUKS2); free(temp); util_asprintf(&temp, "The following plain mode volumes are " "encrypted with key '%s'. You must adapt the crypttab " "entries for this volumes and change the key size " "parameter to 'size=%u' or run command 'zkey crypttab " "--volumes ' for each volume to re-generate the " "crypttab entries:", name, output_key_size * 8, name); _keystore_msg_for_volumes(temp, properties, VOLUME_TYPE_PLAIN); free(temp); out: _keystore_free_key_filenames(&file_names); if (properties != NULL) properties_free(properties); if (secure_key != NULL) free(secure_key); if (apqns != NULL) free(apqns); if (apqn_list != NULL) str_list_free_string_array(apqn_list); if (cur_key_type != NULL) free(cur_key_type); if (rc != 0) pr_verbose(keystore, "Failed to convert key '%s': %s", name, strerror(-rc)); return rc; } struct kms_process { process_key_t process_func; void *process_private; }; struct kms_set_prop { const char *prop_name; const char *prop_value; unsigned long num_keys; }; /** * Processing function for setting properties of KMS-bound keys * * @param[in] keystore the keystore * @param[in] name the name of the key * @param[in] properties the properties object of the key (not used here) * @param[in] file_names the file names used by this key * @param[in] private private data: struct reencipher_info * * @returns 0 for success, or a negative errno value in case of an error */ static int _keystore_process_kms_key_set_prop(struct keystore *keystore, const char *name, struct properties *properties, struct key_filenames *file_names, void *private) { struct kms_set_prop *set_prop = private; int rc; pr_verbose(keystore, "Setting property for KMS-bound key '%s'", name); if (set_prop->prop_value != NULL) { rc = properties_set(properties, set_prop->prop_name, set_prop->prop_value); if (rc != 0) { pr_verbose(keystore, "Invalid characters in property: " "%s: %s", set_prop->prop_name, set_prop->prop_value); goto out; } } else { rc = properties_remove(properties, set_prop->prop_name); if (rc != 0 && rc != -ENOENT) { pr_verbose(keystore, "Failed to remove property: " "%s: %s", set_prop->prop_name, strerror(-rc)); goto out; } } rc = properties_save(properties, file_names->info_filename, 1); if (rc != 0) { pr_verbose(keystore, "Key info file '%s' could not be written: %s", file_names->info_filename, strerror(-rc)); goto out; } set_prop->num_keys++; out: return rc; } /** * Iterates over all keys stored in the keystore. For every key that is bound * to a KMS plugin the specified property is set to the specified value. * If value is NULL, then the property is removed from the key. * * @param[in] keystore the keystore * @param[in] key_type the key type. NULL means no key type filter. * @param[in] prop_name the name of the property to set * @param[in] prop_value the value of the property to set or NULL if the property * is to be removed. * * @returns 0 for success, or a negative errno value in case of an error */ int keystore_kms_keys_set_property(struct keystore *keystore, const char *key_type, const char *prop_name, const char *prop_value) { struct kms_set_prop set_prop; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(prop_name != NULL, "Internal error: prop_name is NULL"); set_prop.prop_name = prop_name; set_prop.prop_value = prop_value; set_prop.num_keys = 0; rc = _keystore_process_filtered(keystore, NULL, NULL, NULL, NULL, key_type, false, true, _keystore_process_kms_key_set_prop, &set_prop); if (rc != 0) pr_verbose(keystore, "Failed to set properties of kms keys: %s", strerror(-rc)); else pr_verbose(keystore, "Successfully set properties of %lu kms " "keys", set_prop.num_keys); return rc; } static const char * const kms_props[] = { PROP_NAME_KMS, PROP_NAME_KMS_KEY_ID, PROP_NAME_KMS_KEY_LABEL, PROP_NAME_KMS_XTS_KEY1_ID, PROP_NAME_KMS_XTS_KEY1_LABEL, PROP_NAME_KMS_XTS_KEY2_ID, PROP_NAME_KMS_XTS_KEY2_LABEL, NULL, }; /** * Unbinds the key by removing the KMS specific properties * * @param[in] keystore the keystore * @param[in] properties the properties object of the key * * @returns 0 for success, or a negative errno value in case of an error */ static int _keystore_kms_key_unbind(struct keystore *keystore, struct properties *properties) { int i, rc; for (i = 0; kms_props[i] != NULL; i++) { rc = properties_remove(properties, kms_props[i]); if (rc != 0 && rc != -ENOENT) { pr_verbose(keystore, "Failed to remove property: " "%s: %s", kms_props[i], strerror(-rc)); return rc; } } return 0; } /** * Processing function for unbinding of KMS-bound keys * * @param[in] keystore the keystore * @param[in] name the name of the key * @param[in] properties the properties object of the key * @param[in] file_names the file names used by this key * @param[in] private private data: struct reencipher_info * * @returns 0 for success, or a negative errno value in case of an error */ static int _keystore_process_kms_key_unbind(struct keystore *keystore, const char *name, struct properties *properties, struct key_filenames *file_names, void *private) { unsigned long *num_keys = private; int rc; pr_verbose(keystore, "Unbinding KMS-bound key '%s'", name); rc = _keystore_kms_key_unbind(keystore, properties); if (rc != 0) goto out; rc = properties_save(properties, file_names->info_filename, 1); if (rc != 0) { pr_verbose(keystore, "Key info file '%s' could not be written: %s", file_names->info_filename, strerror(-rc)); goto out; } (*num_keys)++; out: return rc; } /** * Iterates over all keys stored in the keystore. For every key that is bound * to a KMS plugin the KMS specific properties are deleted, and thus these keys * are unbound from the KMS plugin. * * @param[in] keystore the keystore * * @returns 0 for success, or a negative errno value in case of an error */ int keystore_kms_keys_unbind(struct keystore *keystore) { unsigned long num_keys = 0; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); rc = _keystore_process_filtered(keystore, NULL, NULL, NULL, NULL, NULL, false, true, _keystore_process_kms_key_unbind, &num_keys); if (rc != 0) pr_verbose(keystore, "Failed to unbinds kms keys: %s", strerror(-rc)); else pr_verbose(keystore, "Successfully unbound of %lu kms keys", num_keys); return rc; } struct kms_msg_for_key { const char *msg; unsigned long num_keys; }; /** * Processing function for issuing a message for KMS-bound keys * * @param[in] keystore the keystore * @param[in] name the name of the key * @param[in] properties the properties object of the key (not used here) * @param[in] file_names the file names used by this key * @param[in] private private data: struct reencipher_info * * @returns 0 for success, or a negative errno value in case of an error */ static int _keystore_process_kms_msg_for_key(struct keystore *UNUSED(keystore), const char *name, struct properties * UNUSED(properties), struct key_filenames * UNUSED(file_names), void *private) { struct kms_msg_for_key *msg_for_key = private; if (msg_for_key->num_keys == 0) util_print_indented(msg_for_key->msg, 0); printf(" %s\n", name); msg_for_key->num_keys++; return 0; } /** * Iterates over all keys stored in the keystore. Every key that is bound * to a KMS plugin and has the specified key type is listed together with the * message. If no KMS-bound keys with the specified key type exist in the * keystore, then no message is printed, and -ENOENT is returned. * * @param[in] keystore the keystore * @param[in] key_type the key type. NULL means no key type filter. * @param[in] msg the message to print * * @returns 0 for success, or a negative errno value in case of an error. * -ENOENT if no key matched */ int keystore_msg_for_kms_key(struct keystore *keystore, const char *key_type, const char *msg) { struct kms_msg_for_key msg_for_key; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(msg != NULL, "Internal error: msg is NULL"); msg_for_key.msg = msg; msg_for_key.num_keys = 0; rc = _keystore_process_filtered(keystore, NULL, NULL, NULL, NULL, key_type, false, true, _keystore_process_kms_msg_for_key, &msg_for_key); if (rc != 0) return rc; return msg_for_key.num_keys == 0 ? -ENOENT : 0; } struct kms_import { struct keystore *keystore; bool batch_mode; bool novolcheck; unsigned long num_imported; unsigned long num_skipped; unsigned long num_failed; int pkey_fd; }; /** * Callback used with the keystore_import_kms_keys() function. Called for each * key. * * @param[in] key1_id the key-ID of the key (1st key of an XTS key) * @param[in] key1_label the label of the key (1st key of an XTS key) * @param[in] key2_id the key-ID of the 2nd XTS key, NULL if not XTS * @param[in] key2_label the label of the 2nd XTS key, NULL if not XTS * @param[in] xts if true, this is an XTS key pair * @param[in] name the zkey name of the key * @param[in] key_type the type of the key (CCA-AESDATA, etc) * @param[in] key_bits the key size in bits * @param[in] description the description of the key (can be NULL) * @param[in] cipher the cipher of the key (can be NULL) * @param[in] iv_mode the IV-mode of the key (can be NULL) * @param[in] volumes the associated volumes of the key (can be NULL) * @param[in] volume_type the volume type of the volume (can be NULL) * @param[in] sector_size the sector size of the volume (0 means default) * @param[in] passphrase the passphrase of the key (can be NULL) * @param[in] addl_info_argz an argz string containing additional KMS plugin * specific infos to be displayed, or NULL if none. * @param[in] addl_info_len length of the argz string in addl_info_argz * @param[in] private_data the private data pointer * * @returns 0 on success, or a negative errno in case of an error. */ static int _keystore_process_kms_import(const char *key1_id, const char *key1_label, const char *key2_id, const char *key2_label, bool xts, const char *name, const char *key_type, size_t UNUSED(key_bits), const char *description, const char *UNUSED(cipher), const char *UNUSED(iv_mode), const char *volumes, const char *volume_type, size_t sector_size, const char *passphrase, const char *UNUSED(addl_info_argz), size_t UNUSED(addl_info_len), void *private_data) { struct kms_import *import_data = private_data; struct key_filenames file_names = { 0 }; u8 secure_key[2 * MAX_SECURE_KEY_SIZE]; struct properties *key_props = NULL; char vp[VERIFICATION_PATTERN_LEN]; const char *key_name = name; struct keystore *keystore; size_t alt_name_len = 0; size_t secure_key_size; bool fatal_err = false; char *alt_name = NULL; char *apqns = NULL; int rc; keystore = import_data->keystore; rc = _keystore_get_key_filenames(keystore, key_name, &file_names); if (rc != 0) goto out; check_duplicate_key: rc = _keystore_ensure_keyfiles_not_exist(&file_names, key_name); if (rc == -EEXIST) { if (import_data->batch_mode) { rc = 1; goto out; } printf("%s: Do you want to enter an alternate name [y/N]? ", program_invocation_short_name); if (!prompt_for_yes(keystore->verbose)) { rc = 1; goto out; } prompt_alt_name: printf("%s: Alternate name: ", program_invocation_short_name); rc = getline(&alt_name, &alt_name_len, stdin); if (rc <= 1) { rc = 1; goto out; } if (alt_name[strlen(alt_name) - 1] == '\n') alt_name[strlen(alt_name) - 1] = '\0'; key_name = alt_name; _keystore_free_key_filenames(&file_names); rc = _keystore_get_key_filenames(keystore, key_name, &file_names); if (rc != 0) goto prompt_alt_name; goto check_duplicate_key; } else if (rc != 0) { goto out; } secure_key_size = sizeof(secure_key); rc = import_kms_key(keystore->kms_info, key1_id, key2_id, xts, key_name, secure_key, &secure_key_size, key_type, keystore->verbose); if (rc != 0) { warnx("KMS plugin '%s' failed to import key '%s': %s", keystore->kms_info->plugin_name, key_name, strerror(-rc)); print_last_kms_error(keystore->kms_info); if (rc == -ENOTSUP) fatal_err = true; goto out; } key_type = get_key_type(secure_key, secure_key_size); if (key_type == NULL) { warnx("Key '%s' is not a valid secure key", key_name); rc = -EINVAL; goto out; } rc = get_kms_apqns_for_key_type(keystore->kms_info, key_type, true, &apqns, keystore->verbose); if (rc != 0) { if (rc == -ENOTSUP) warnx("Key-type not supported by the KMS plugin '%s'", keystore->kms_info->plugin_name); goto out; } pr_verbose(keystore, "APQNs for keytype %s: '%s'", key_type, apqns); rc = _keystore_create_info_props(keystore, key_name, description, volumes, apqns, false, import_data->novolcheck, sector_size, volume_type, key_type, keystore->kms_info->plugin_name, &key_props); if (rc != 0) goto out; if (passphrase != NULL && volume_type != NULL && strcasecmp(volume_type, VOLUME_TYPE_LUKS2) == 0) { rc = store_passphrase_from_base64(passphrase, file_names.pass_filename, keystore->verbose); if (rc != 0) { pr_verbose(keystore, "Failed to parse passphrase: %s", strerror(-rc)); goto out; } rc = _keystore_set_file_permission(keystore, file_names.pass_filename); if (rc != 0) goto out_remove; } rc = properties_set(key_props, xts ? PROP_NAME_KMS_XTS_KEY1_ID : PROP_NAME_KMS_KEY_ID, key1_id); if (rc != 0) { pr_verbose(keystore, "Failed to set key id of key #1: %s", strerror(-rc)); goto out; } rc = properties_set(key_props, xts ? PROP_NAME_KMS_XTS_KEY1_LABEL : PROP_NAME_KMS_KEY_LABEL, key1_label); if (rc != 0) { pr_verbose(keystore, "Failed to set key label of key #1: %s", strerror(-rc)); goto out; } if (xts) { rc = properties_set(key_props, PROP_NAME_KMS_XTS_KEY2_ID, key2_id); if (rc != 0) { pr_verbose(keystore, "Failed to set key id of key #2: " "%s", strerror(-rc)); goto out; } rc = properties_set(key_props, PROP_NAME_KMS_XTS_KEY2_LABEL, key2_label); if (rc != 0) { pr_verbose(keystore, "Failed to set key label of key " "#2: %s", strerror(-rc)); goto out; } } rc = write_secure_key(file_names.skey_filename, secure_key, secure_key_size, keystore->verbose); if (rc != 0) goto out; rc = _keystore_set_file_permission(keystore, file_names.skey_filename); if (rc != 0) goto out_remove; rc = generate_key_verification_pattern(import_data->pkey_fd, secure_key, secure_key_size, vp, sizeof(vp), keystore->verbose); if (rc != 0) { warnx("Failed to generate the key verification pattern: %s", strerror(-rc)); fatal_err = true; goto out_remove; } rc = properties_set(key_props, PROP_NAME_KEY_VP, vp); if (rc != 0) { pr_verbose(keystore, "Failed to set verification pattern of " "key: %s", strerror(-rc)); goto out_remove; } rc = properties_save(key_props, file_names.info_filename, 1); if (rc != 0) { pr_verbose(keystore, "Key info file '%s' could not be written: %s", file_names.info_filename, strerror(-rc)); goto out; } rc = _keystore_set_file_permission(keystore, file_names.info_filename); if (rc != 0) { remove(file_names.info_filename); goto out_remove; } out_remove: if (rc != 0) { remove(file_names.skey_filename); remove(file_names.info_filename); } out: if (rc == 0) { printf("Successfully imported key '%s'\n", key_name); import_data->num_imported++; } else if (rc < 0) { warnx("Failed to import key '%s': %s", key_name, strerror(-rc)); import_data->num_failed++; } else { warnx("Skipping key '%s'", key_name); import_data->num_skipped++; } _keystore_free_key_filenames(&file_names); if (alt_name != NULL) free(alt_name); if (apqns != NULL) free(apqns); if (key_props != NULL) properties_free(key_props); return fatal_err ? rc : 0; } /** * Imports secure keys from the KMS and adds it to the key store * * @param[in] keystore the key store * @param[in] label_filter the KMS label filter. Can contain wild cards. * NULL means no name filter. * @param[in] name_filter the name filter. Can contain wild cards. * NULL means no name filter. * @param[in] volume_filter the volume filter. Can contain wild cards, and * multiple volume filters separated by commas. * If the filter does not contain the ':dm-name' * part, then the volumes are matched without the * dm-name part. If the filter contains the * ':dm-name' part, then the filter is matched * including the dm-name part. * NULL means no volume filter. * @param[in] volume_type If not NULL, specifies the volume type. * @param[in] kms_options an array of KMS options specified, or NULL if no * KMS options have been specified * @param[in] num_kms_options the number of options in above array * @param[in] batch_mode if true, suppress alternate name prompts if a key * with an already existing name is to be imported. * @param[in] novolcheck if true, do not check the associated volumes for * existence and duplicate use * @param[in] pkey_fd the pkey file descriptor * * @returns 0 for success or a negative errno in case of an error */ int keystore_import_kms_keys(struct keystore *keystore, const char *label_filter, const char *name_filter, const char *volume_filter, const char *volume_type, struct kms_option *kms_options, size_t num_kms_options, bool batch_mode, bool novolcheck, int pkey_fd) { struct kms_import import_data = { 0 }; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); if (keystore->kms_info->plugin_lib == NULL) { warnx("The repository is not bound to a KMS plugin"); return -ENOENT; } if (volume_type != NULL && !_keystore_valid_volume_type(NULL, volume_type)) { warnx("Invalid volume-type specified"); return -EINVAL; } import_data.keystore = keystore; import_data.batch_mode = batch_mode; import_data.novolcheck = novolcheck; import_data.num_imported = 0; import_data.num_skipped = 0; import_data.num_failed = 0; import_data.pkey_fd = pkey_fd; rc = process_kms_keys(keystore->kms_info, label_filter, name_filter, volume_filter, volume_type, kms_options, num_kms_options, _keystore_process_kms_import, &import_data, keystore->verbose); if (rc != 0) { pr_verbose(keystore, "Failed to import kms keys: %s", strerror(-rc)); } else { printf("%lu keys imported, %lu keys skipped, %lu keys " "failed to import\n", import_data.num_imported, import_data.num_skipped, import_data.num_failed); if (import_data.num_failed > 0) rc = -EIO; } return rc; } struct kms_refresh { bool refresh_properties; bool novolcheck; unsigned long num_refreshed; unsigned long num_failed; int pkey_fd; }; /** * Processing function for the key refresh function. * * @param[in] keystore the keystore * @param[in] name the name of the key * @param[in] properties the properties object of the key * @param[in] file_names the file names used by this key * @param[in] private private data: struct reencipher_info * * @returns 0 if the display is successful, a negative errno value otherwise */ static int _keystore_refresh_kms_key(struct keystore *keystore, const char *name, struct properties *properties, struct key_filenames *file_names, void *private) { struct volume_check vol_check = { .keystore = keystore, .name = name, .set = 1, .nocheck = 0 }; char *description = NULL, *cipher = NULL, *iv_mode = NULL; struct kms_refresh *refresh_data = private; char *volumes = NULL, *volume_type = NULL; ssize_t sector_size = -1; bool fatal_err = false; char *key_type = NULL; char sect_size[30]; char *msg; int rc; vol_check.nocheck = refresh_data->novolcheck; key_type = _keystore_get_key_type(properties); rc = refresh_kms_key(keystore->kms_info, properties, &description, &cipher, &iv_mode, &volumes, &volume_type, §or_size, file_names->skey_filename, file_names->pass_filename, key_type, keystore->verbose, refresh_data->pkey_fd); if (rc != 0) { warnx("KMS plugin '%s' failed to refresh key '%s': %s", keystore->kms_info->plugin_name, name, strerror(-rc)); print_last_kms_error(keystore->kms_info); if (rc == -ENOTSUP) fatal_err = true; goto out; } rc = _keystore_set_file_permission(keystore, file_names->skey_filename); if (rc != 0) goto out; if (_keystore_passphrase_file_exists(file_names)) { rc = _keystore_set_file_permission(keystore, file_names->pass_filename); if (rc != 0) goto out; } if (!refresh_data->refresh_properties) goto save_props; if (description != NULL) { rc = properties_set(properties, PROP_NAME_DESCRIPTION, description); if (rc != 0) { warnx("Invalid characters in description"); goto out; } } if (volumes != NULL) { vol_check.key_type = key_type; vol_check.volume_type = volume_type; rc = _keystore_change_association(properties, PROP_NAME_VOLUMES, volumes, "volume", _keystore_volume_check, &vol_check); if (rc != 0) goto out; } if (sector_size >= 0) { if (!_keystore_valid_sector_size(sector_size, key_type, volume_type)) { warnx("Invalid sector-size specified"); rc = -EINVAL; goto out; } sprintf(sect_size, "%lu", sector_size); rc = properties_set(properties, PROP_NAME_SECTOR_SIZE, sect_size); if (rc != 0) { warnx("Invalid characters in sector-size"); goto out; } } if (volume_type != NULL) { if (!_keystore_valid_volume_type(key_type, volume_type)) { warnx("Invalid volume-type specified"); rc = -EINVAL; goto out; } rc = properties_set2(properties, PROP_NAME_VOLUME_TYPE, volume_type, true); if (rc != 0) { warnx("Invalid characters in volume-type"); goto out; } } save_props: rc = _keystore_set_timestamp_property(properties, PROP_NAME_CHANGE_TIME); if (rc != 0) { warnx("Failed to set the update timestamp property"); goto out; } rc = properties_save(properties, file_names->info_filename, 1); if (rc != 0) { pr_verbose(keystore, "Key info file '%s' could not be written: %s", file_names->info_filename, strerror(-rc)); goto out; } rc = _keystore_set_file_permission(keystore, file_names->info_filename); if (rc != 0) goto out; out: if (rc == 0) { printf("Successfully refreshed key '%s'\n", name); refresh_data->num_refreshed++; util_asprintf(&msg, "The following LUKS2 volumes are " "encrypted with key '%s'. To update the secure " "AES volume key in the LUKS2 header, run command " "'zkey-cryptsetup setkey " "--volume-key-file %s':", name, file_names->skey_filename); _keystore_msg_for_volumes(msg, properties, VOLUME_TYPE_LUKS2); free(msg); } else { warnx("Failed to refresh key '%s': %s", name, strerror(-rc)); refresh_data->num_failed++; } if (description != NULL) free(description); if (cipher != NULL) free(cipher); if (iv_mode != NULL) free(iv_mode); if (volumes != NULL) free(volumes); if (volume_type != NULL) free(volume_type); if (key_type != NULL) free(key_type); return fatal_err ? rc : 0; } /** * Refreshes secure KMS-bound secure key and updates them from the KMS * * @param[in] keystore the key store * @param[in] name_filter the name filter. Can contain wild cards. * NULL means no name filter. * @param[in] volume_filter the volume filter. Can contain wild cards, and * multiple volume filters separated by commas. * If the filter does not contain the ':dm-name' * part, then the volumes are matched without the * dm-name part. If the filter contains the * ':dm-name' part, then the filter is matched * including the dm-name part. * NULL means no volume filter. * @param[in] volume_type If not NULL, specifies the volume type. * @param[in] key_type The key type. NULL means no key type filter. * @param[in] refresh_properties if true, also refresh the key's properties * @param[in] novolcheck if true, do not check the associated volumes for * existence and duplicate use * @param[in] pkey_fd the pkey file descriptor * * @returns 0 for success or a negative errno in case of an error */ int keystore_refresh_kms_keys(struct keystore *keystore, const char *name_filter, const char *volume_filter, const char *volume_type, const char *key_type, bool refresh_properties, bool novolcheck, int pkey_fd) { struct kms_refresh refresh_data = { 0 }; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); if (keystore->kms_info->plugin_lib == NULL) { warnx("The repository is not bound to a KMS plugin"); return -ENOENT; } if (volume_type != NULL && !_keystore_valid_volume_type(NULL, volume_type)) { warnx("Invalid volume-type specified"); return -EINVAL; } if (key_type != NULL && !_keystore_valid_key_type(key_type)) { warnx("Invalid key-type specified"); return -EINVAL; } refresh_data.refresh_properties = refresh_properties; refresh_data.novolcheck = novolcheck; refresh_data.num_refreshed = 0; refresh_data.num_failed = 0; refresh_data.pkey_fd = pkey_fd; rc = _keystore_process_filtered(keystore, name_filter, volume_filter, NULL, volume_type, key_type, false, true, _keystore_refresh_kms_key, &refresh_data); if (rc != 0) { pr_verbose(keystore, "Failed to refresh kms keys: %s", strerror(-rc)); } else { printf("%lu keys refreshed, %lu keys failed to refresh\n", refresh_data.num_refreshed, refresh_data.num_failed); if (refresh_data.num_failed > 0) rc = -EIO; } return rc; } /** * Frees a keystore object * * @param[in] keystore the key store */ void keystore_free(struct keystore *keystore) { util_assert(keystore != NULL, "Internal error: keystore is NULL"); _keystore_unlock_repository(keystore); free(keystore->directory); free(keystore); }