Files
s390-tools/zkey/keystore.c
Ingo Franzki 8be51ad454 zkey: Perform KMS login before making any permanent changes to a key
A KMS login may prompt the user for its credentials. Users may abort that
prompt by Cntl-C. Do not make any permanent changes to a key before the
KMS login, because those changes can not be undone when the user has
aborted during KMS login, leaving the key in a potentially inconsistent
state.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-11-07 14:33:26 +01:00

5937 lines
170 KiB
C

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