zkey: Add 'zkey kms refresh' command to update a KMS-bound key

Allow to refresh or update a KMS-bound key in the repository.
The secure key is re-imported from the KMS, and optionally also
its properties are updates from the KMS.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
Ingo Franzki
2020-06-22 13:31:32 +02:00
committed by Jan Höppner
parent cd8a733c82
commit 91b1692b16
6 changed files with 615 additions and 0 deletions
+223
View File
@@ -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, &sector_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 <device> "
"--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
*
+6
View File
@@ -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);
+196
View File
@@ -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;
}
+5
View File
@@ -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
+84
View File
@@ -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
+101
View File
@@ -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;