diff --git a/zkey/keystore.c b/zkey/keystore.c index a0566ce4..d3ea3db6 100644 --- a/zkey/keystore.c +++ b/zkey/keystore.c @@ -5225,6 +5225,229 @@ int keystore_import_kms_keys(struct keystore *keystore, return rc; } +struct kms_refresh { + bool refresh_properties; + bool novolcheck; + unsigned long num_refreshed; + unsigned long num_failed; +}; + +/** + * Processing function for the key refresh function. + * + * @param[in] keystore the keystore + * @param[in] name the name of the key + * @param[in] properties the properties object of the key + * @param[in] file_names the file names used by this key + * @param[in] private private data: struct reencipher_info + * + * @returns 0 if the display is successful, a negative errno value otherwise + */ +static int _keystore_refresh_kms_key(struct keystore *keystore, + const char *name, + struct properties *properties, + struct key_filenames *file_names, + void *private) +{ + struct volume_check vol_check = { .keystore = keystore, .name = name, + .set = 1, .nocheck = 0 }; + char *description = NULL, *cipher = NULL, *iv_mode = NULL; + struct kms_refresh *refresh_data = private; + char *volumes = NULL, *volume_type = NULL; + ssize_t sector_size = -1; + bool fatal_err = false; + char sect_size[30]; + char *msg; + int rc; + + vol_check.nocheck = refresh_data->novolcheck; + + rc = refresh_kms_key(keystore->kms_info, properties, + &description, &cipher, &iv_mode, &volumes, + &volume_type, §or_size, + file_names->skey_filename, keystore->verbose); + if (rc != 0) { + warnx("KMS plugin '%s' failed to refresh key '%s': %s", + keystore->kms_info->plugin_name, name, strerror(-rc)); + print_last_kms_error(keystore->kms_info); + if (rc == -ENOTSUP) + fatal_err = true; + goto out; + } + + if (!refresh_data->refresh_properties) + goto save_props; + + if (description != NULL) { + rc = properties_set(properties, PROP_NAME_DESCRIPTION, + description); + if (rc != 0) { + warnx("Invalid characters in description"); + goto out; + } + } + + if (volumes != NULL) { + rc = _keystore_change_association(properties, PROP_NAME_VOLUMES, + volumes, "volume", + _keystore_volume_check, + &vol_check); + if (rc != 0) + goto out; + } + + if (sector_size >= 0) { + if (!_keystore_valid_sector_size(sector_size)) { + warnx("Invalid sector-size specified"); + rc = -EINVAL; + goto out; + } + + sprintf(sect_size, "%lu", sector_size); + rc = properties_set(properties, PROP_NAME_SECTOR_SIZE, + sect_size); + if (rc != 0) { + warnx("Invalid characters in sector-size"); + goto out; + } + } + + if (volume_type != NULL) { + if (!_keystore_valid_volume_type(volume_type)) { + warnx("Invalid volume-type specified"); + rc = -EINVAL; + goto out; + } + + rc = properties_set2(properties, PROP_NAME_VOLUME_TYPE, + volume_type, true); + if (rc != 0) { + warnx("Invalid characters in volume-type"); + goto out; + } + } + +save_props: + rc = _keystore_set_timestamp_property(properties, + PROP_NAME_CHANGE_TIME); + if (rc != 0) { + warnx("Failed to set the update timestamp property"); + goto out; + } + + rc = properties_save(properties, file_names->info_filename, 1); + if (rc != 0) { + pr_verbose(keystore, + "Key info file '%s' could not be written: %s", + file_names->info_filename, strerror(-rc)); + goto out; + } + +out: + if (rc == 0) { + printf("Successfully refreshed key '%s'\n", name); + refresh_data->num_refreshed++; + + util_asprintf(&msg, "The following LUKS2 volumes are " + "encrypted with key '%s'. To update the secure " + "AES volume key in the LUKS2 header, run command " + "'zkey-cryptsetup setkey " + "--master-key-file %s':", name, + file_names->skey_filename); + _keystore_msg_for_volumes(msg, properties, VOLUME_TYPE_LUKS2); + free(msg); + } else { + warnx("Failed to refresh key '%s': %s", name, strerror(-rc)); + refresh_data->num_failed++; + } + + if (description != NULL) + free(description); + if (cipher != NULL) + free(cipher); + if (iv_mode != NULL) + free(iv_mode); + if (volumes != NULL) + free(volumes); + if (volume_type != NULL) + free(volume_type); + + return fatal_err ? rc : 0; +} + +/** + * Refreshes secure KMS-bound secure key and updates them from the KMS + * + * @param[in] keystore the key store + * @param[in] name_filter the name filter. Can contain wild cards. + * NULL means no name filter. + * @param[in] volume_filter the volume filter. Can contain wild cards, and + * 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. + * @param[in] volume_type If not NULL, specifies the volume type. + * @param[in] key_type The key type. NULL means no key type filter. + * @param[in] refresh_properties if true, also refresh the key's properties + * @param[in] novolcheck if true, do not check the associated volumes for + * existence and duplicate use + * + * @returns 0 for success or a negative errno in case of an error + */ +int keystore_refresh_kms_keys(struct keystore *keystore, + const char *name_filter, + const char *volume_filter, + const char *volume_type, const char *key_type, + bool refresh_properties, bool novolcheck) +{ + struct kms_refresh refresh_data = { 0 }; + int rc; + + util_assert(keystore != NULL, "Internal error: keystore is NULL"); + + if (keystore->kms_info->plugin_lib == NULL) { + warnx("The repository is not bound to a KMS plugin"); + return -ENOENT; + } + + if (volume_type != NULL && + !_keystore_valid_volume_type(volume_type)) { + warnx("Invalid volume-type specified"); + return -EINVAL; + } + + if (key_type != NULL && + !_keystore_valid_key_type(key_type)) { + warnx("Invalid key-type specified"); + return -EINVAL; + } + + refresh_data.refresh_properties = refresh_properties; + refresh_data.novolcheck = novolcheck; + refresh_data.num_refreshed = 0; + refresh_data.num_failed = 0; + + rc = _keystore_process_filtered(keystore, name_filter, volume_filter, + NULL, volume_type, key_type, false, + true, _keystore_refresh_kms_key, + &refresh_data); + + if (rc != 0) { + pr_verbose(keystore, "Failed to refresh kms keys: %s", + strerror(-rc)); + } else { + printf("%lu keys refreshed, %lu keys failed to refresh\n", + refresh_data.num_refreshed, refresh_data.num_failed); + if (refresh_data.num_failed > 0) + rc = -EIO; + } + + return rc; +} + /** * Frees a keystore object * diff --git a/zkey/keystore.h b/zkey/keystore.h index d143e743..944cc91d 100644 --- a/zkey/keystore.h +++ b/zkey/keystore.h @@ -135,6 +135,12 @@ int keystore_import_kms_keys(struct keystore *keystore, size_t num_kms_options, bool batch_mode, bool novolcheck); +int keystore_refresh_kms_keys(struct keystore *keystore, + const char *name_filter, + const char *volume_filter, + const char *volume_type, const char *key_type, + bool refres_properties, bool novolcheck); + void keystore_free(struct keystore *keystore); diff --git a/zkey/kms.c b/zkey/kms.c index 2cd6493d..aeb2ce30 100644 --- a/zkey/kms.c +++ b/zkey/kms.c @@ -3113,3 +3113,199 @@ out: return rc; } + +/** + * Refreshes KMS managed keys. + * + * @param[in] kms_info information of the currently bound plugin. + * @param[in] key_props the key properties + * @param[out] description on return: the description property + * @param[out] cipher on return: the cipher property + * @param[out] iv_mode on return: the iv_mode property + * @param[out] volumes on return: the volumes property + * @param[out] volume_type on return: the volume_type property + * @param[out] sector_size on return: the sector_size property + * @param[in] filename the file name to store the refreshed key blob in + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 for success or a negative errno in case of an error. + */ +int refresh_kms_key(struct kms_info *kms_info, struct properties *key_props, + char **description, char **cipher, char **iv_mode, + char **volumes, char **volume_type, ssize_t *sector_size, + const char *filename, bool verbose) +{ + struct kms_property *properties = NULL; + u8 key_blob[2 * MAX_SECURE_KEY_SIZE]; + char *key1_id = NULL, *key2_id = NULL; + size_t key_blob_size, key_size = 0; + char vp[VERIFICATION_PATTERN_LEN]; + size_t i, num_properties = 0; + char *sys_volumes = NULL; + char *orig_vp = NULL; + bool xts = false; + const char *str; + int rc = 0; + + util_assert(kms_info != NULL, "Internal error: kms_info is NULL"); + + if (kms_info->plugin_lib == NULL) { + warnx("The repository is not bound to a KMS plugin"); + return -ENOENT; + } + + if (kms_info->funcs->kms_import_key == NULL || + kms_info->funcs->kms_get_key_properties == NULL) { + pr_verbose(verbose, "The KMS plugin does not support to " + "import keys or get properties"); + return -ENOTSUP; + } + + key1_id = properties_get(key_props, PROP_NAME_KMS_KEY_ID); + if (key1_id == NULL) { + key1_id = properties_get(key_props, PROP_NAME_KMS_XTS_KEY1_ID); + key2_id = properties_get(key_props, PROP_NAME_KMS_XTS_KEY2_ID); + if (key1_id == NULL || key2_id == NULL) { + pr_verbose(verbose, "Failed to get key-id(s)"); + rc = -ENOENT; + goto out; + } + xts = true; + } + + rc = kms_info->funcs->kms_get_key_properties(kms_info->handle, key1_id, + &properties, + &num_properties); + if (rc != 0) { + pr_verbose(verbose, "KMS plugin failed to get attributes of " + "key '%s': %s", key1_id, strerror(-rc)); + goto out; + } + + sys_volumes = _get_system_specific_prop_name(KMS_KEY_PROP_VOLUMES); + if (sys_volumes == NULL) + return -ENOMEM; + + if (description != NULL) { + str = _find_property(properties, num_properties, + KMS_KEY_PROP_DESCRIPTION); + *description = (str != NULL) ? util_strdup(str) : NULL; + } + + if (cipher != NULL) { + str = _find_property(properties, num_properties, + KMS_KEY_PROP_CIPHER); + *cipher = (str != NULL) ? util_strdup(str) : NULL; + } + + if (iv_mode != NULL) { + str = _find_property(properties, num_properties, + KMS_KEY_PROP_IV_MODE); + *iv_mode = (str != NULL) ? util_strdup(str) : NULL; + } + + if (volumes != NULL) { + str = _find_property(properties, num_properties, + sys_volumes); + if (str == NULL) + str = _find_property(properties, num_properties, + KMS_KEY_PROP_VOLUMES); + *volumes = (str != NULL) ? util_strdup(str) : NULL; + } + + if (volume_type != NULL) { + str = _find_property(properties, num_properties, + KMS_KEY_PROP_VOLUME_TYPE); + *volume_type = (str != NULL) ? util_strdup(str) : NULL; + } + + if (sector_size != NULL) { + *sector_size = -1; + str = _find_property(properties, num_properties, + KMS_KEY_PROP_SECTOR_SIZE); + if (str != NULL) + sscanf(str, "%lu", sector_size); + } + + key_blob_size = sizeof(key_blob); + memset(key_blob, 0, key_blob_size); + + rc = kms_info->funcs->kms_import_key(kms_info->handle, key1_id, + key_blob, &key_blob_size); + if (rc != 0) { + pr_verbose(verbose, "KMS plugin failed to import key '%s': %s", + key1_id, strerror(-rc)); + goto out; + } + + if (is_cca_aes_data_key(key_blob, key_blob_size)) + key_size = AESDATA_KEY_SIZE; + else if (is_cca_aes_cipher_key(key_blob, key_blob_size)) + key_size = AESCIPHER_KEY_SIZE; + else if (is_ep11_aes_key(key_blob, key_blob_size)) + key_size = EP11_KEY_SIZE; + + if (key_size == 0 || key_blob_size > key_size) { + pr_verbose(verbose, "Key '%s' has an unknown or unsupported " + "key type", key1_id); + rc = -EIO; + goto out; + } + + if (xts) { + key_blob_size = key_size; + rc = kms_info->funcs->kms_import_key(kms_info->handle, key2_id, + key_blob + key_size, + &key_blob_size); + if (rc != 0) { + pr_verbose(verbose, "KMS plugin failed to import key #2" + "'%s': %s", key2_id, strerror(-rc)); + goto out; + } + } + + key_blob_size = xts ? key_size * 2 : key_size; + + orig_vp = properties_get(key_props, PROP_NAME_KEY_VP); + if (orig_vp != NULL) { + rc = generate_key_verification_pattern(key_blob, key_blob_size, + vp, sizeof(vp), verbose); + if (rc != 0) { + warnx("Failed to generate the verification pattern: %s", + strerror(-rc)); + warnx("Make sure that kernel module 'paes_s390' is " + "loaded and that the 'paes' cipher is available"); + goto out; + } + + if (strcmp(vp, orig_vp) != 0) { + warnx("The key verification pattern of the refreshed " + "secure key does not match the current one."); + rc = -EIO; + goto out; + } + } + + rc = write_secure_key(filename, key_blob, key_blob_size, verbose); + if (rc != 0) + goto out; + +out: + if (key1_id != NULL) + free(key1_id); + if (key2_id != NULL) + free(key2_id); + if (sys_volumes != NULL) + free(sys_volumes); + if (orig_vp != NULL) + free(orig_vp); + if (properties != NULL) { + for (i = 0; i < num_properties; i++) { + free((void *)properties[i].name); + free((void *)properties[i].value); + } + free(properties); + } + + return rc; +} diff --git a/zkey/kms.h b/zkey/kms.h index 75088032..b415df45 100644 --- a/zkey/kms.h +++ b/zkey/kms.h @@ -115,4 +115,9 @@ int import_kms_key(struct kms_info *kms_info, const char *key1_id, unsigned char *key_blob, size_t *key_blob_length, bool verbose); +int refresh_kms_key(struct kms_info *kms_info, struct properties *key_props, + char **description, char **cipher, char **iv_mode, + char **volumes, char **volume_type, ssize_t *sector_size, + const char *filename, bool verbose); + #endif diff --git a/zkey/zkey.1 b/zkey/zkey.1 index 84f4af8f..0b398c2d 100644 --- a/zkey/zkey.1 +++ b/zkey/zkey.1 @@ -1109,6 +1109,45 @@ not available, or are already associated with another secure key, the import fails. Use option \fB\-\-no\-volume\-check\fP to omit the volume check, and import the keys even if the associated volume(s) do not exist. . +.SS "Refresh secure keys that are bound to a key management system" +. +.B zkey kms +.BR refresh | ref +.RB [ \-\-name | \-N +.IR key-name ] +.RB [ \-\-volumes | \-l +.IR volume1[:dmname1][,volume2[:dmname2][,...]] ] +.RB [ \-\-volume-type | \-t +.IR type ] +.RB [ \-\-key-type | \-K +.IR type ] +.RB [ \-\-refresh\-properties | \-P ] +.RB [ \-\-no\-volume\-check ] +.RB [ \-\-verbose | \-V ] +. +.PP +Use the +.B kms refresh +command to refresh secure keys that are bound to a key management system (KMS). +Refreshing a key updates the secure key by re-importing it from the key +management system. Use option \fB\-\-refresh\-properties\fP to also update the +associated information, such as the textual description, associated volumes, +volume type, and sector size, with the information stored in the key management +system. +.PP +You can filter the list of keys to be refreshed by key name, associated volumes, +volume type, and key type. You can use wildcards for the key name, and +associated volumes. The device-mapper name of an associated volume can be +omitted; if it is specified then only those keys are listed that are associated +with the specified volume and device-mapper name. +.PP +If a refreshed key is associated with one or multiple volumes, it is +verified that the volumes are available, and are not already associated with +another secure key in the repository. If one of the volumes or all of them are +not available, or are already associated with another secure key, the refresh +fails. Use option \fB\-\-no\-volume\-check\fP to omit the volume check, and +refresh the keys even if the associated volume(s) do not exist. +. . . .SH OPTIONS @@ -1846,6 +1885,51 @@ repository. . . . +.SS "Options for the kms refresh command" +.TP +.BR \-N ", " \-\-name\~\fIkey-name\fP +Specifies the name of the secure key in the secure key repository. You can +use wildcards to select multiple secure keys in the secure key repository. +When wildcards are used you must quote the value. +Only keys with names that match the pattern are refreshed. +.TP +.BR \-l ", " \-\-volumes\~\fIvolume1[:dmname1][,volume2[:dmname2][,...]]\fP +Specifies a comma-separated list of volumes (block devices) which are +associated with the secure AES key in the repository. Only those keys are +refreshed, which are associated with the specified volumes. +The volume association also contains the device-mapper name, separated by a +colon, used with dm-crypt. You can omit the device-mapper name; if it is +specified then only those keys are listed that are associated with the +specified volume and device-mapper name. You can use wildcards to specify +the volumes and device-mapper names. +When wildcards are used you must quote the value. +.TP +.BR \-t ", " \-\-volume-type\~\fItype\fP +Specifies the volume type of the associated volumes used with dm-crypt. Possible +values are \fBplain\fP and \fBluks2\fP. Only keys with the specified volume +type are refreshed. +This option is only available if +.B zkey +has been compiled with LUKS2 support enabled. +.TP +.BR \-K ", " \-\-key-type\~\fItype\fP +Specifies the key type of the secure key. Possible values are \fBCCA-AESDATA\fP, +\fBCCA-AESCIPHER\fP, and \fBEP11-AES\fP. Only keys with the specified key type +are refreshed. +.TP +.BR \-q ", " \-\-refresh\-properties +Also update the associated information, such as the textual description, +associated volumes, volume type, and sector size, with the information stored in +the key management system. +.TP +.BR \-\-no\-volume\-check +Do not check if the volume(s) associated with the secure key(s) to be refreshed +are available, or are already associated with other secure keys in the +repository. This option only has an effect when specified together with option +\fB\-\-refresh\-properties\fP. +. +. +. .SS "General options" .TP .BR \-V ", " \-\-verbose diff --git a/zkey/zkey.c b/zkey/zkey.c index 4a73f251..3a1aee75 100644 --- a/zkey/zkey.c +++ b/zkey/zkey.c @@ -89,6 +89,7 @@ static struct zkey_globals { bool force; bool open; bool format; + bool refresh_properties; struct ext_lib lib; struct cca_lib cca; struct ep11_lib ep11; @@ -130,6 +131,7 @@ static struct zkey_globals { #define COMMAND_KMS_REENCIPHER "reencipher" #define COMMAND_KMS_LIST "list" #define COMMAND_KMS_IMPORT "import" +#define COMMAND_KMS_REFRESH "refresh" #define OPT_COMMAND_PLACEHOLDER "PLACEHOLDER" @@ -153,6 +155,7 @@ static struct zkey_globals { #define OPT_CRYPTSETUP_FORMAT 261 #define OPT_NO_APQN_CHECK 262 #define OPT_NO_VOLUME_CHECK 263 +#define OPT_REFRESH_PROPERTIES 264 /* * Configuration of command line options @@ -1025,6 +1028,65 @@ static struct util_opt opt_vec[] = { .flags = UTIL_OPT_FLAG_NOSHORT, }, /***********************************************************/ + { + .flags = UTIL_OPT_FLAG_SECTION, + .desc = "OPTIONS", + .command = COMMAND_KMS " " COMMAND_KMS_REFRESH, + }, + { + .option = { "name", required_argument, NULL, 'N'}, + .argument = "NAME", + .desc = "Name of the secure AES keys in the repository that " + "are to be refreshed. You can use wildcards to select " + "the keys to be refreshed.", + .command = COMMAND_KMS " " COMMAND_KMS_REFRESH, + }, + { + .option = { "volumes", required_argument, NULL, 'l'}, + .argument = "VOLUME[:DMNAME][,...]", + .desc = "Comma-separated pairs of volume and device-mapper " + "names that are associated with the secure AES key in " + "the repository. Use this option to refresh all keys " + "associated with specific volumes. The device-mapper " + "name (DMNAME) is optional. If specified, only those " + "keys are refreshed where both, the volume and the " + "device-mapper name matches", + .command = COMMAND_KMS " " COMMAND_KMS_REFRESH, + }, +#ifdef HAVE_LUKS2_SUPPORT + { + .option = { "volume-type", required_argument, NULL, 't'}, + .argument = "type", + .desc = "The type of the associated volume(s). Possible values " + "are 'plain' and 'luks2'. Use this option to refresh " + "all keys with the specified volumes type.", + .command = COMMAND_KMS " " COMMAND_KMS_REFRESH, + }, +#endif + { + .option = { "key-type", required_argument, NULL, 'K'}, + .argument = "type", + .desc = "The type of the key. Possible values are '" + KEY_TYPE_CCA_AESDATA"', '"KEY_TYPE_CCA_AESCIPHER"' " + "and '"KEY_TYPE_EP11_AES"'. Use this option to refresh " + "all keys with the specified key type.", + .command = COMMAND_KMS " " COMMAND_KMS_REFRESH, + }, + { + .option = {"refresh-properties", 0, NULL, 'P'}, + .desc = "Also refresh the properties of the secure AES key " + "and update them with the values from the KMS.", + .command = COMMAND_KMS " " COMMAND_KMS_REFRESH, + }, + { + .option = {"no-volume-check", 0, NULL, OPT_NO_VOLUME_CHECK}, + .desc = "Do not check if the volume(s) associated with the " + "secure key(s) to be refreshed are available, or are " + "already associated with other secure keys.", + .command = COMMAND_KMS " " COMMAND_KMS_REFRESH, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + /***********************************************************/ OPT_PLACEHOLDER, OPT_PLACEHOLDER, OPT_PLACEHOLDER, @@ -1127,6 +1189,7 @@ static int command_kms_configure(void); static int command_kms_reencipher(void); static int command_kms_list(void); static int command_kms_import(void); +static int command_kms_refresh(void); static struct zkey_command zkey_kms_commands[] = { { @@ -1228,6 +1291,19 @@ static struct zkey_command zkey_kms_commands[] = { .need_kms_login = 1, .kms_plugin_opts_cmd = KMS_COMMAND_LIST_IMPORT, }, + { + .command = COMMAND_KMS_REFRESH, + .abbrev_len = 3, + .function = command_kms_refresh, + .short_desc = "Refreshes secure keys that are bound to a key " + "management system", + .long_desc = "Refreshes secure keys that are bound to a key " + "management system (KMS)", + .need_keystore = 1, + .has_options = 1, + .use_kms_plugin = 1, + .need_kms_login = 1, + }, { .command = NULL } }; @@ -2632,6 +2708,28 @@ static int command_kms_import(void) return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } +/* + * Command handler for 'kms refresh'. + * + * Refreshes secure keys managed by a KMS + */ +static int command_kms_refresh(void) +{ + int rc; + + if (g.kms_info.plugin_lib == NULL) { + rc = -ENOENT; + warnx("The repository is not bound to a KMS plugin"); + return EXIT_FAILURE; + } + + rc = keystore_refresh_kms_keys(g.keystore, g.name, g.volumes, + g.volume_type, g.key_type, + g.refresh_properties, g.novolcheck); + + return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + /** * Opens the keystore. The keystore directory is either the * default directory or as specified in an environment variable @@ -2955,6 +3053,9 @@ int main(int argc, char *argv[]) case 'B': g.label = optarg; break; + case 'P': + g.refresh_properties = 1; + break; case 'h': print_help(command, sub_command); return EXIT_SUCCESS;