Files
s390-tools/zkey/keystore.c
Ingo Franzki 4d598ade86 zkey: Add support for generating and importing exportable secure keys
Normally, secure keys generated by zkey are intentionally export
restricted. Export restricted keys can not be wrapped with a key
encrypting key (KEK).

However, keys that are generated inside a Secure Execution for Linux
guest that shall also be used outside of the Secure Execution for Linux
guest can only be transported to outside the Secure Execution for Linux
guest by wrapping them with a KEK and unwrapping them outside of the
Secure Execution for Linux guest. For that such keys must be exportable.

Add an option to generate and import exportable secure keys, which then
can be wrapped, and thus transported to outside of a Secure Execution for
Linux guest.

This applies to keys of type 'CCA-AESCIPHER' and 'EP11-AES'. Keys of type
'CCA-AESDATA' are always exportable, and can not be export restricted.

For keys of type 'EP11-AES' additionally allow to set the
'wrap-with-trusted' attribute. This restricts the key so that it only can
be wrapped with a trusted key encrypting key.

For keys of type 'EP11-AES' to be exportable, the access control point
(ACP) XCP_CPB_ALLOW_COMBINED_EXTRACT must be 'ON' on all APQNs used. This
access control point is only supported on newer EP11 firmware levels. If
the access control point is 'OFF' or not supported by the EP11 firmware,
then the IOCTL to generate the key fails with a generic error
(Input/output error - EIO). The zkey tool prints an appropriate error
message in that case.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-09-15 11:47:32 +02:00

5928 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 (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;
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;
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;
}
}
key_props = properties_new();
rc = properties_load(key_props, new_names.info_filename, 1);
if (rc != 0) {
warnx("Key '%s' does not exist or is invalid", newname);
goto out_rename_info;
}
if (_keystore_is_kms_bound_key(key_props, NULL)) {
rc = perform_kms_login(keystore->kms_info, keystore->verbose);
if (rc != 0)
goto out_rename_info;
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);
}