Subject: zkey: Add keystore implementation From: Philipp Rudo Summary: zkey: Add support of protected key crypto for dm-crypt. Description: Support the usage of protected key crypto for dm-crypt disks in plain format by providing a tool to manage a key repository allowing to associate secure keys with disk partitions or logical volumes. Upstream-ID: c944f23d7e1f983499b4c5fcf04430dc49902f04 Problem-ID: SEC1800 Upstream-Description: zkey: Add keystore implementation Add a keystore implementation that stores secure AES keys in a key repository, located in a directory, e.g. '/etc/zkey/repository'. The keystore allows you to generate, validate, re-encipher, modify, list, delete, etc secure keys. Signed-off-by: Ingo Franzki Reviewed-by: Hendrik Brueckner Signed-off-by: Jan Höppner Signed-off-by: Philipp Rudo --- zkey/Makefile | 3 zkey/keystore.c | 3299 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ zkey/keystore.h | 77 + 3 files changed, 3378 insertions(+), 1 deletion(-) --- a/zkey/Makefile +++ b/zkey/Makefile @@ -27,8 +27,9 @@ libs = $(rootdir)/libutil/libutil.a zkey.o: zkey.c pkey.h misc.h pkey.o: pkey.c pkey.h properties.o: properties.c properties.h +keystore.o: keystore.c keystore.h properties.h -zkey: zkey.o pkey.o properties.o $(libs) +zkey: zkey.o pkey.o properties.o keystore.o $(libs) install: all $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) --- /dev/null +++ b/zkey/keystore.c @@ -0,0 +1,3299 @@ +/* + * zkey - Generate, re-encipher, and validate secure keys + * + * Keystore handling functions + * + * Copyright IBM Corp. 2018 + * + * 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 "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 "properties.h" + +struct key_filenames { + char *skey_filename; + char *info_filename; + char *renc_filename; +}; + +#define FILE_EXTENSION_LEN 5 +#define SKEY_FILE_EXTENSION ".skey" +#define INFO_FILE_EXTENSION ".info" +#define RENC_FILE_EXTENSION ".renc" + +#define LOCK_FILE_NAME ".lock" + +#define PROP_NAME_KEY_TYPE "key-type" +#define PROP_NAME_CIPHER "cipher" +#define PROP_NAME_IV_MODE "iv-mode" +#define PROP_NAME_DESCRIPTION "description" +#define PROP_NAME_VOLUMES "volumes" +#define PROP_NAME_APQNS "apqns" +#define PROP_NAME_SECTOR_SIZE "sector-size" +#define PROP_NAME_CREATION_TIME "creation-time" +#define PROP_NAME_CHANGE_TIME "update-time" +#define PROP_NAME_REENC_TIME "reencipher-time" + +#define IS_XTS(secure_key_size) (secure_key_size > SECURE_KEY_SIZE ? 1 : 0) + +#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_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 "Encrypted with" +#define REC_CREATION_TIME "Created" +#define REC_CHANGE_TIME "Changed" +#define REC_REENC_TIME "Re-enciphered" + +#define pr_verbose(keystore, fmt...) do { \ + if (keystore->verbose) \ + warnx(fmt); \ + } while (0) + +/** + * 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); + + 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) +{ + struct stat sb; + int rc; + + rc = stat(file_names->renc_filename, &sb); + if (rc == 0 && !S_ISREG(sb.st_mode)) + rc = 1; + + return !rc; +} + +/** + * 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) +{ + struct stat sb_skey, sb_info; + int rc_skey, rc_info; + + rc_skey = stat(file_names->skey_filename, &sb_skey); + if (rc_skey == 0 && !S_ISREG(sb_skey.st_mode)) + rc_skey = 1; + + rc_info = stat(file_names->info_filename, &sb_info); + if (rc_info == 0 && !S_ISREG(sb_info.st_mode)) + rc_info = 1; + + if (rc_skey == 0 && rc_info == 0) + return 1; + if (rc_skey != 0 && rc_info != 0 && + _keystore_reencipher_key_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); +} + +/** + * 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 rc; + + if (chmod(filename, keystore->mode) != 0) { + rc = -errno; + warnx("chmod faild on file '%s': %s", filename, strerror(-rc)); + return rc; + } + + if (chown(filename, geteuid(), keystore->owner) != 0) { + rc = -errno; + warnx("chown faild on file '%s': %s", filename, strerror(-rc)); + return rc; + } + + return 0; +} + +/** + * Checks if the sector size is power of two and in range 512 - 4096 bytes. + * + * @param[in] sector_size the sector size + * + * @returns 1 if the sector size is valid, 0 otherwise + */ +static int _keystore_valid_sector_size(size_t sector_size) +{ + if (sector_size == 0) + return 1; + if (sector_size < 512 || sector_size > 4096) + return 0; + if (sector_size & (sector_size - 1)) + return 0; + return 1; +} + +typedef int (*check_association_t)(const char *value, bool remove, + 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, &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, &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, &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)) +{ + char *modified; + char *pattern_domain; + char *pattern_card; + char *copy; + int card, domain; + 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 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) + 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 + * mutliple 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 + * mutliple APQN filters separated by commas. + * NULL means no APQN filter. + * @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, + process_key_t process_func, + void *process_private) +{ + struct key_filenames file_names = { NULL, NULL, NULL }; + 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, + volume_filter, apqn_filter); + + 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 = 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; +} + +/** + * Checks if the specified APQN is of type CCA and is online + * + * @param[in] card card number + * @param[in] domain the domain + * + * @returns 1 if its a CCA card and is online, 0 if offline and -1 if its + * not a CCA card. + */ +static int _keystore_is_apqn_online(int card, int domain) +{ + long int online; + char *dev_path; + char type[20]; + int rc = 1; + + dev_path = util_path_sysfs("bus/ap/devices/card%02x", card); + if (!util_path_is_dir(dev_path)) { + rc = 0; + goto out; + } + if (util_file_read_l(&online, 10, "%s/online", dev_path) != 0) { + rc = 0; + goto out; + } + if (online == 0) { + rc = 0; + goto out; + } + if (util_file_read_line(type, sizeof(type), "%s/type", dev_path) != 0) { + rc = 0; + goto out; + } + if (strncmp(type, "CEX", 3) != 0 || strlen(type) < 5) { + rc = 0; + goto out; + } + if (type[4] != 'C') { + rc = -1; + goto out; + } + free(dev_path); + + dev_path = util_path_sysfs("bus/ap/devices/card%02x/%02x.%04x", card, + card, domain); + if (!util_path_is_dir(dev_path)) { + rc = 0; + goto out; + } + if (util_file_read_l(&online, 10, "%s/online", dev_path) != 0) { + rc = 0; + goto out; + } + if (online == 0) { + rc = 0; + goto out; + } + +out: + free(dev_path); + return rc; +} + +/** + * 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[out] normalized normalized value on return or NULL if no change + * @param[in] private private data (not used here) + * + * @returns 0 if successful, a negative errno value otherwise + */ +static int _keystore_apqn_check(const char *apqn, bool remove, + char **normalized, void *UNUSED(private)) +{ + int rc, card, domain; + regmatch_t pmatch[1]; + regex_t reg_buf; + + *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); + return -EINVAL; + } + + if (sscanf(apqn, "%x.%x", &card, &domain) != 2) + return -EINVAL; + + util_asprintf(normalized, "%02x.%04x", card, domain); + + if (remove) + return 0; + + rc = _keystore_is_apqn_online(card, domain); + if (rc != 1) { + warnx("The APQN %02x.%04x is %s", card, domain, + rc == -1 ? "not a CCA card" : "not online"); + return -EIO; + } + + return 0; +} + + +struct volume_check { + struct keystore *keystore; + const char *name; + const char *volume; +}; + +/** + * 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 *UNUSED(properties), + struct key_filenames + *UNUSED(file_names), + void *private) +{ + struct volume_check *info = (struct volume_check *)private; + + warnx("Key '%s' is already associated with volume '%s'", name, + info->volume); + return -EINVAL; +} + +/** + * 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[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, + 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 = 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) { + 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; + } + + rc = _keystore_process_filtered(info->keystore, NULL, info->volume, + NULL, _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 (stat(lock_file_name, &sb) == 0) { + keystore->lock_fd = open(lock_file_name, O_RDONLY); + 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, + 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 faild 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] verbose if true, verbose messages are printed + * + * @returns a new keystore object + */ +struct keystore *keystore_new(const char *directory, bool verbose) +{ + struct keystore *keystore; + struct stat sb; + int rc; + + util_assert(directory != NULL, "Internal error: directory is NULL"); + + if (stat(directory, &sb) != 0) { + warnx("'%s' does not exist", directory); + return NULL; + } + if (!(sb.st_mode & S_IFDIR)) { + 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'; + + 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; +} + +/** + * 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 a key, such as key-type, cipher-name, and + * IV-mode + * + * @param[in] key_props the properties object + */ +static int _keystore_set_default_properties(struct properties *key_props) +{ + int rc; + + rc = properties_set(key_props, PROP_NAME_KEY_TYPE, "CCA-AESDATA"); + if (rc != 0) + return 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; +} + +/** + * Creates an initial .info file for a key + * + * @param[in] keystore the key store + * @param[in] name the name of the key + * @param[in] info_filename the file name of the key info file + * @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] 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. + */ +static int _keystore_create_info_file(struct keystore *keystore, + const char *name, + const char *info_filename, + const char *description, + const char *volumes, const char *apqns, + size_t sector_size) +{ + struct volume_check vol_check = { .keystore = keystore, .name = name }; + struct properties *key_props; + char temp[10]; + int rc; + + key_props = properties_new(); + rc = _keystore_set_default_properties(key_props); + 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 = _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, NULL); + if (rc != 0) + goto out; + + if (!_keystore_valid_sector_size(sector_size)) { + 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_save(key_props, info_filename, 1); + if (rc != 0) { + pr_verbose(keystore, + "Key info file '%s' could not be written: %s", + info_filename, strerror(-rc)); + goto out; + } + + rc = _keystore_set_file_permission(keystore, info_filename); + if (rc != 0) { + remove(info_filename); + goto out; + } + +out: + properties_free(key_props); + return rc; +} + +/** + * Extracts a card/domain pair from the specified APQns, or uses AUTOSELECT + * if no APQNs are specified. + */ +static int _keystore_get_card_domain(const char *apqns, unsigned int *card, + unsigned int *domain) +{ + char **apqn_list; + char *normalized = NULL; + int rc = 0; + + *card = AUTOSELECT; + *domain = AUTOSELECT; + + if (apqns == NULL) + return 0; + + apqn_list = str_list_split(apqns); + if (apqn_list[0] == NULL) + goto out; + + rc = _keystore_apqn_check(apqn_list[0], 0, &normalized, NULL); + if (normalized != NULL) + free(normalized); + if (rc != 0) + goto out; + + if (sscanf(apqn_list[0], "%x.%x", card, domain) != 2) { + rc = -EINVAL; + goto out; + } + +out: + str_list_free_string_array(apqn_list); + 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] 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] 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, size_t sector_size, + size_t keybits, bool xts, const char *clear_key_file, + int pkey_fd) +{ + struct key_filenames file_names = { NULL, NULL, NULL }; + struct properties *key_props = NULL; + unsigned int card, domain; + 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_free_key_filenames; + + rc = _keystore_ensure_keyfiles_not_exist(&file_names, name); + if (rc != 0) + goto out_free_key_filenames; + + rc = _keystore_get_card_domain(apqns, &card, &domain); + if (rc != 0) + goto out_free_key_filenames; + + if (clear_key_file == NULL) + rc = generate_secure_key_random(pkey_fd, + file_names.skey_filename, + keybits, xts, card, domain, + keystore->verbose); + else + rc = generate_secure_key_clear(pkey_fd, + file_names.skey_filename, + keybits, xts, clear_key_file, + card, domain, + 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.info_filename, + description, volumes, apqns, + sector_size); + 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 (key_props != NULL) + properties_free(key_props); + if (rc != 0 && rc != -EEXIST) + 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; +} + +/** + * 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] 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] import_file The name of a secure key containing the kley to import + * + * @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, size_t sector_size, + const char *import_file) +{ + struct key_filenames file_names = { NULL, NULL, NULL }; + struct properties *key_props = NULL; + 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(import_file != NULL, "Internal error: import_file 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; + + secure_key = read_secure_key(import_file, &secure_key_size, + keystore->verbose); + if (secure_key == NULL) { + rc = -ENOENT; + goto out_free_key_filenames; + } + + rc = write_secure_key(file_names.skey_filename, secure_key, + secure_key_size, keystore->verbose); + free(secure_key); + 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.info_filename, + description, volumes, apqns, + sector_size); + 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 && rc != -EEXIST) + 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; +} + + +/** + * 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. IfNULL then the APQNs + * are not changed. + * @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. + * + * @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, long int sector_size) +{ + struct volume_check vol_check = { .keystore = keystore, .name = name }; + struct key_filenames file_names = { NULL, NULL, NULL }; + struct properties *key_props = NULL; + char temp[10]; + 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; + + 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 (description != NULL) { + rc = properties_set(key_props, PROP_NAME_DESCRIPTION, + description); + if (rc != 0) { + warnx("Invalid characters in description"); + goto out; + } + } + + if (volumes != NULL) { + rc = _keystore_change_association(key_props, PROP_NAME_VOLUMES, + volumes, "volume", + _keystore_volume_check, + &vol_check); + if (rc != 0) + goto out; + } + + if (apqns != NULL) { + rc = _keystore_change_association(key_props, PROP_NAME_APQNS, + apqns, "APQN", + _keystore_apqn_check, NULL); + if (rc != 0) + goto out; + } + + if (sector_size >= 0) { + if (!_keystore_valid_sector_size(sector_size)) { + 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 = _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; + } + + rc = _keystore_set_file_permission(keystore, file_names.info_filename); + if (rc != 0) + goto out; + + pr_verbose(keystore, "Successfully changed 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 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 = { NULL, NULL, NULL }; + struct key_filenames new_names = { NULL, NULL, NULL }; + 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; + + 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)); + rename(new_names.skey_filename, file_names.skey_filename); + } + if (_keystore_reencipher_key_exists(&file_names)) { + 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)); + rename(new_names.skey_filename, + file_names.skey_filename); + rename(new_names.info_filename, + file_names.info_filename); + } + } + + pr_verbose(keystore, "Successfully renamed key '%s' to '%s'", name, + newname); + +out: + _keystore_free_key_filenames(&file_names); + _keystore_free_key_filenames(&new_names); + + 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, 23, 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); + 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_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, + size_t clear_key_bitsize, bool valid, + bool is_old_mk, bool reenc_pending) +{ + char *volumes_argz = NULL; + size_t volumes_argz_len; + char *apqns_argz = NULL; + size_t sector_size = 0; + size_t apqns_argz_len; + char *description; + char *reencipher; + char *creation; + char *volumes; + char *change; + char *apqns; + char *temp; + + 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); + + 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) + 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(secure_key_size) ? "Yes" : "No"); + if (validation) { + if (valid) + util_rec_set(rec, REC_MASTERKEY, + is_old_mk ? "OLD CCA master key" : + "CURRENT CCA master key"); + else + util_rec_set(rec, REC_MASTERKEY, "(unknown)"); + } + 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_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); +} + +struct validate_info { + struct util_rec *rec; + int pkey_fd; + unsigned long int num_valid; + unsigned long int num_invalid; + unsigned long int num_warnings; +}; + +/** + * Displays the status of the associated APQNs. + * + * @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 APQNs is not + * available + */ +static int _keystore_display_apqn_status(struct properties *properties, + const char *name) +{ + int i, rc, card, domain, warning = 0; + char **apqn_list; + char *apqns; + + apqns = properties_get(properties, PROP_NAME_APQNS); + if (apqns == NULL) + return 0; + apqn_list = str_list_split(apqns); + + for (i = 0; apqn_list[i] != NULL; i++) { + + if (sscanf(apqn_list[i], "%x.%x", &card, &domain) != 2) + continue; + + rc = _keystore_is_apqn_online(card, domain); + if (rc != 1) { + printf("WARNING: The APQN %02x.%04x associated with " + "key '%s' is %s\n", card, domain, name, + rc == -1 ? "not a CCA card" : "not online"); + warning = 1; + } + } + + if (warning) + printf("\n"); + + free(apqns); + str_list_free_string_array(apqn_list); + 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; + size_t clear_key_bitsize; + size_t secure_key_size; + u8 *secure_key; + 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; + } + + rc = validate_secure_key(info->pkey_fd, secure_key, secure_key_size, + &clear_key_bitsize, &is_old_mk, + keystore->verbose); + if (rc != 0) { + valid = 0; + info->num_invalid++; + rc = 0; + } else { + info->num_valid++; + valid = 1; + } + free(secure_key); + + _keystore_print_record(info->rec, name, properties, 1, + file_names->skey_filename, secure_key_size, + clear_key_bitsize, valid, is_old_mk, + _keystore_reencipher_key_exists(file_names)); + + if (valid && is_old_mk) { + util_print_indented("WARNING: The secure key is currently " + "enciphered with the OLD CCA master key " + "and should be re-enciphered with the " + "CURRENT CCA master key as soon as " + "possible to avoid data loss\n", 0); + info->num_warnings++; + } + if (_keystore_display_apqn_status(properties, name) != 0) + info->num_warnings++; + if (_keystore_display_volume_status(properties, name) != 0) + info->num_warnings++; + +out: + 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) + * + * @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, 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.rec = rec; + info.num_valid = 0; + info.num_invalid = 0; + info.num_warnings = 0; + + rc = _keystore_process_filtered(keystore, name_filter, NULL, + apqn_filter, + _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; + t_CSNBKTC dll_CSNBKTC; + 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] dll_CSNBKTC the CCA key token change function + * @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 + * @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, + t_CSNBKTC dll_CSNBKTC, + struct reencipher_params *params, + u8 *secure_key, size_t secure_key_size, + bool is_old_mk) +{ + 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 CCA " + "master key and is being " + "re-enciphered with the CURRENT " + "CCA master key\n", 0); + } else { + params->to_new = 1; + util_print_indented("The secure key is currently " + "enciphered with the CURRENT CCA " + "master key and is being " + "re-enciphered with the NEW CCA " + "master key\n", 0); + } + } + + if (params->from_old) { + if (!is_old_mk) { + printf("The secure key '%s' is already enciphered " + "with the CURRENT CCA master key\n", name); + return 1; + } + + if (params->inplace == -1) + params->inplace = 1; + + pr_verbose(keystore, + "Secure key '%s' will be re-enciphered from OLD " + "to the CURRENT CCA master key", name); + + rc = key_token_change(dll_CSNBKTC, + secure_key, secure_key_size, + METHOD_OLD_TO_CURRENT, + keystore->verbose); + if (rc != 0) { + warnx("Failed to re-encipher '%s' from OLD to " + "CURRENT CCA master key", name); + return rc; + } + } + if (params->to_new) { + pr_verbose(keystore, + "Secure key '%s' will be re-enciphered from " + "CURRENT to the NEW CCA master key", name); + + if (params->inplace == -1) + params->inplace = 0; + + rc = key_token_change(dll_CSNBKTC, + secure_key, secure_key_size, + METHOD_CURRENT_TO_NEW, + keystore->verbose); + if (rc != 0) { + warnx("Failed to re-encipher '%s' from CURRENT to " + "NEW CCA master key", name); + 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; + u8 *secure_key = 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 in 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; + } + + rc = validate_secure_key(info->pkey_fd, secure_key, secure_key_size, + &clear_key_bitsize, &is_old_mk, + keystore->verbose); + if (rc != 0) { + if (params.complete) { + warnx("Key '%s' is not valid, re-enciphering is not " + "completed", name); + warnx("Possibly the CCA master key not yet been set?"); + } 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->dll_CSNBKTC, ¶ms, + secure_key, secure_key_size, + is_old_mk); + 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; + + rc = _keystore_set_file_permission(keystore, out_file); + 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 = 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 = _keystore_set_file_permission(keystore, + file_names->info_filename); + if (rc != 0) + goto out; + } + + 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 has completed for " + "key '%s'. Run 'zkey reencipher' with option " + "'--complete' when the NEW CCA master key has " + "been set (moved to the CURRENT master key " + "register) to complete the re-enciphering " + "process", name); + util_print_indented(temp, 0); + free(temp); + } + + info->num_reenciphered++; + +out: + 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 CCA master key. + * @param[in] to_new If true the key is reenciphered from the CURRENT to + * the OLD CCA 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 + * Note: if both from Old 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 CCA master key. + * Note: if both inplace and staged are FLASE, 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, + t_CSNBKTC dll_CSNBKTC) +{ + 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.dll_CSNBKTC = dll_CSNBKTC; + info.num_failed = 0; + info.num_reenciphered = 0; + info.num_skipped = 0; + + rc = _keystore_process_filtered(keystore, name_filter, NULL, + apqn_filter, + _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) + * + * @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) +{ + struct volume_check vol_check = { .keystore = keystore, + .name = newname }; + struct key_filenames file_names = { NULL, NULL, NULL }; + struct key_filenames new_names = { NULL, NULL, NULL }; + struct properties *key_prop = NULL; + 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(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; + + 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; + + 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); + remove(file_names.skey_filename); + 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) { + 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; + + 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)); + remove(new_names.skey_filename); + goto out; + } + + rc = _keystore_set_file_permission(keystore, new_names.info_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); + } + + _keystore_free_key_filenames(&file_names); + _keystore_free_key_filenames(&new_names); + if (key_prop != NULL) + properties_free(key_prop); + + 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 = { NULL, NULL, NULL }; + 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 + * + * @returnd 0 if the user confirmed the deletion, a negative errno value + * otherwise + */ +static int _keystore_propmp_for_remove(struct keystore *keystore, + const char *name, + struct key_filenames *file_names) +{ + struct properties *key_prop; + char *volumes = NULL; + char **volume_list = NULL; + char str[20]; + int rc, i; + + 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; + } + + volumes = properties_get(key_prop, PROP_NAME_VOLUMES); + if (volumes != NULL && strlen(volumes) > 0) { + volume_list = str_list_split(volumes); + + warnx("When you remove key '%s' the following volumes will " + "no longer be usable:", name); + for (i = 0; volume_list[i] != NULL; i++) + fprintf(stderr, "%s\n", volume_list[i]); + } + + printf("%s: Remove key '%s'? ", program_invocation_short_name, name); + if (fgets(str, sizeof(str), stdin) == NULL) { + rc = -EIO; + goto out; + } + if (str[strlen(str) - 1] == '\n') + str[strlen(str) - 1] = '\0'; + pr_verbose(keystore, "Prompt reply: '%s'", str); + if (strcasecmp(str, "y") != 0 && strcasecmp(str, "yes") != 0) { + rc = -ECANCELED; + goto out; + } + +out: + properties_free(key_prop); + if (volume_list != NULL) + str_list_free_string_array(volume_list); + + 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 + * + * @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 key_filenames file_names = { NULL, NULL, 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_propmp_for_remove(keystore, name, + &file_names) != 0) + 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)); + } + } + pr_verbose(keystore, "Successfully removed key '%s'", name); + +out: + _keystore_free_key_filenames(&file_names); + + 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; + struct secaeskeytoken *secure_key; + size_t secure_key_size; + int rc = 0; + + secure_key = (struct secaeskeytoken *) + read_secure_key(file_names->skey_filename, + &secure_key_size, keystore->verbose); + if (secure_key == NULL) + return -EIO; + + if (secure_key_size < SECURE_KEY_SIZE) { + pr_verbose(keystore, + "Size of secure key is too small: %lu expected %lu", + secure_key_size, SECURE_KEY_SIZE); + rc = -EIO; + goto out; + } + + _keystore_print_record(rec, name, properties, 0, + file_names->skey_filename, secure_key_size, + IS_XTS(secure_key_size) ? secure_key->bitsize * 2 + : secure_key->bitsize, + 0, 0, + _keystore_reencipher_key_exists(file_names)); + +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 + * mutliple 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 + * mutliple APQN filters separated by commas. + * NULL means no APQN filter. + * + * @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) +{ + struct util_rec *rec; + int rc; + + util_assert(keystore != NULL, "Internal error: keystore is NULL"); + + rec = _keystore_setup_record(0); + + rc = _keystore_process_filtered(keystore, name_filter, volume_filter, + apqn_filter, _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:/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 crypt_info { + bool execute; + 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, + 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 debice 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 sector_size the sector size in bytes or 0 if not specified + * @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, + struct crypt_info *info) +{ + char temp[100]; + int rc = 0; + char *cmd; + + sprintf(temp, "--sector-size %lu ", sector_size); + util_asprintf(&cmd, + "cryptsetup plainOpen %s--key-file '%s' --key-size %lu " + "--cipher %s %s%s %s", + 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); + } + + 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 debice 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 sector_size the sector size in bytes or 0 if not specified + * @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, + struct crypt_info *UNUSED(info)) +{ + char temp[1000]; + + if (sector_size > 0) { + sprintf(temp, + "WARNING: volume '%s' is using a sector size of %lu. " + "At the time this utility was developed, systemd's " + "support of crypttab did not support to specify a " + "sector size with plain dm-crypt devices. The generated " + "crypttab entry may or may not work, and may need " + "manual adoptions.", volume, sector_size); + util_print_indented(temp, 0); + } + + sprintf(temp, ",sector-size=%lu", sector_size); + printf("%s\t%s\t%s\tplain,cipher=%s,size=%lu,hash=plain%s\n", + dmname, volume, key_file_name, cipher_spec, key_file_size * 8, + sector_size > 0 ? temp : ""); + + 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; +} + +/** + * Returns the size of the secure key file + * + * @param[in] keystore the keystore + * @param[in] skey_filename the file name of the secure key + * + * @returns the size of the secure key, or -1 in case of an error + */ +static size_t _keystore_get_key_file_size(struct keystore *keystore, + const char *skey_filename) +{ + size_t secure_key_size; + struct stat sb; + + if (stat(skey_filename, &sb)) { + pr_verbose(keystore, "Key file '%s': %s", + skey_filename, strerror(errno)); + return -1; + } + + secure_key_size = sb.st_size; + if (secure_key_size < SECURE_KEY_SIZE) { + pr_verbose(keystore, + "Size of secure key is too small: %lu expected %lu", + secure_key_size, SECURE_KEY_SIZE); + return -1; + } + + return secure_key_size; +} + +/** + * 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; + size_t secure_key_size; + size_t sector_size = 0; + char *volumes = NULL; + char *dmname; + char *temp; + int rc = 0; + char *vol; + char *ch; + int i; + + secure_key_size = _keystore_get_key_file_size(keystore, + file_names->skey_filename); + if (secure_key_size < SECURE_KEY_SIZE) { + pr_verbose(keystore, + "Size of secure key is too small: %lu expected %lu", + secure_key_size, SECURE_KEY_SIZE); + rc = -EIO; + goto out; + } + + cipher_spec = _keystore_build_cipher_spec(properties, + IS_XTS(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); + } + + 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, 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); + 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 + * mutliple 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 + * + * @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) +{ + struct crypt_info info = { 0 }; + int rc; + + util_assert(keystore != NULL, "Internal error: keystore is NULL"); + + if (volume_filter == NULL) + volume_filter = "*"; + info.execute = execute; + info.volume_filter = str_list_split(volume_filter); + info.process_func = _keystore_process_cryptsetup; + + rc = _keystore_process_filtered(keystore, NULL, volume_filter, NULL, + _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 + * mutliple 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_crypttab(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.volume_filter = str_list_split(volume_filter); + info.process_func = _keystore_process_crypttab; + + rc = _keystore_process_filtered(keystore, NULL, volume_filter, NULL, + _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; +} + +/** + * 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); +} --- /dev/null +++ b/zkey/keystore.h @@ -0,0 +1,77 @@ +/* + * zkey - Generate, re-encipher, and validate secure keys + * + * Keystore handling functions + * + * Copyright IBM Corp. 2018 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef KEYSTORE_H +#define KEYSTORE_H + +#include + +#include "pkey.h" + +struct keystore { + bool verbose; + char *directory; + int lock_fd; + mode_t mode; + gid_t owner; +}; + +struct keystore *keystore_new(const char *directory, bool verbose); + +int keystore_generate_key(struct keystore *keystore, const char *name, + const char *description, const char *volumes, + const char *apqns, size_t sector_size, + size_t keybits, bool xts, const char *clear_key_file, + int pkey_fd); + +int keystore_import_key(struct keystore *keystore, const char *name, + const char *description, const char *volumes, + const char *apqns, size_t sector_size, + const char *import_file); + +int keystore_change_key(struct keystore *keystore, const char *name, + const char *description, const char *volumes, + const char *apqns, long int sector_size); + +int keystore_rename_key(struct keystore *keystore, const char *name, + const char *newname); + +int keystore_validate_key(struct keystore *keystore, const char *name_filter, + const char *apqn_filter, int pkey_fd); + +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, + t_CSNBKTC dll_CSNBKTC); + +int keystore_copy_key(struct keystore *keystore, const char *name, + const char *newname, const char *volumes); + +int keystore_export_key(struct keystore *keystore, const char *name, + const char *export_file); + +int keystore_remove_key(struct keystore *keystore, const char *name, + bool quiet); + +int keystore_list_keys(struct keystore *keystore, const char *name_filter, + const char *volume_filter, const char *apqn_filter); + +int keystore_cryptsetup(struct keystore *keystore, const char *volume_filter, + bool execute); + +int keystore_crypttab(struct keystore *keystore, const char *volume_filter); + +void keystore_free(struct keystore *keystore); + + + +#endif