Files
s390-tools/zkey/keystore.c
Ingo Franzki f97d048643 zkey: Allow to associate non-existing APQNs with a key
Add option --no-apqn-check to the generate, import, change, and
validate commands to disable checking of the specified APQNs.

With this option a currently non-existing APQN can be associated
with a key. This is useful to associate APQNs that exist only on
other systems, such as disaster recovery systems, but not on the
current system. When generating keys, at least one of the specified
APQNs must be available to generate the key.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2019-05-21 14:16:53 +02:00

3833 lines
106 KiB
C

/*
* zkey - Generate, re-encipher, and validate secure keys
*
* Keystore handling functions
*
* Copyright IBM Corp. 2018
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <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 "properties.h"
struct key_filenames {
char *skey_filename;
char *info_filename;
char *renc_filename;
};
#define FILE_EXTENSION_LEN 5
#define SKEY_FILE_EXTENSION ".skey"
#define INFO_FILE_EXTENSION ".info"
#define RENC_FILE_EXTENSION ".renc"
#define LOCK_FILE_NAME ".lock"
#define PROP_NAME_KEY_TYPE "key-type"
#define PROP_NAME_CIPHER "cipher"
#define PROP_NAME_IV_MODE "iv-mode"
#define PROP_NAME_DESCRIPTION "description"
#define PROP_NAME_VOLUMES "volumes"
#define PROP_NAME_APQNS "apqns"
#define PROP_NAME_SECTOR_SIZE "sector-size"
#define PROP_NAME_CREATION_TIME "creation-time"
#define PROP_NAME_CHANGE_TIME "update-time"
#define PROP_NAME_REENC_TIME "reencipher-time"
#define PROP_NAME_KEY_VP "verification-pattern"
#define PROP_NAME_VOLUME_TYPE "volume-type"
#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 IS_XTS(secure_key_size) (secure_key_size > SECURE_KEY_SIZE ? 1 : 0)
#define REC_KEY "Key"
#define REC_DESCRIPTION "Description"
#define REC_SEC_KEY_SIZE "Secure key size"
#define REC_CLR_KEY_SIZE "Clear key size"
#define REC_XTS "XTS type key"
#define REC_VOLUMES "Volumes"
#define REC_APQNS "APQNs"
#define REC_KEY_FILE "Key file name"
#define REC_SECTOR_SIZE "Sector size"
#define REC_STATUS "Status"
#define REC_MASTERKEY "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 pr_verbose(keystore, fmt...) do { \
if (keystore->verbose) \
warnx(fmt); \
} while (0)
/**
* Gets the file names of the .skey and .info and .renc files for a named
* key in the key strore's directory
*
* @param[in] keystore the key store
* @param[in] name the name of the key
* @param[out] names is filled with the names of the files
*
* @returns 0 for success or a negative errno in case of an error*
*/
static int _keystore_get_key_filenames(struct keystore *keystore,
const char *name,
struct key_filenames *names)
{
if (strpbrk(name, "/\\ *?'\"")) {
warnx("Key name '%s' contains invalid characters", name);
return -EINVAL;
}
util_asprintf(&names->skey_filename, "%s/%s%s", keystore->directory,
name, SKEY_FILE_EXTENSION);
util_asprintf(&names->info_filename, "%s/%s%s", keystore->directory,
name, INFO_FILE_EXTENSION);
util_asprintf(&names->renc_filename, "%s/%s%s", keystore->directory,
name, RENC_FILE_EXTENSION);
pr_verbose(keystore, "File names for key '%s': '%s' and '%s'", name,
names->skey_filename, names->info_filename);
return 0;
}
/**
* Checks if the .renc file exists.
*
* @param[in] file_names names of the files
*
* @returns 1 if the file exist, 0 if the file do not exist
*/
static int _keystore_reencipher_key_exists(struct key_filenames *file_names)
{
struct stat sb;
int rc;
rc = stat(file_names->renc_filename, &sb);
if (rc == 0 && !S_ISREG(sb.st_mode))
rc = 1;
return !rc;
}
/**
* Checks if both, the .skey and the .info (and .renc) files exist.
*
* @param[in] file_names names of the files
*
* @returns 1 if all files exist, 0 if all files do not exist, -1 if one
* file exists but other one does not exist (inconsistent state)
*/
static int _keystore_exists_keyfiles(struct key_filenames *file_names)
{
struct stat sb_skey, sb_info;
int rc_skey, rc_info;
rc_skey = stat(file_names->skey_filename, &sb_skey);
if (rc_skey == 0 && !S_ISREG(sb_skey.st_mode))
rc_skey = 1;
rc_info = stat(file_names->info_filename, &sb_info);
if (rc_info == 0 && !S_ISREG(sb_info.st_mode))
rc_info = 1;
if (rc_skey == 0 && rc_info == 0)
return 1;
if (rc_skey != 0 && rc_info != 0 &&
_keystore_reencipher_key_exists(file_names) == 0)
return 0;
return -1;
}
/**
* Checks if the files belonging to a key exist. If not an appropriate error
* message is issued.
*
* @param[in] file_names names of the files
* @param[in] name name of the key
*
* @returns 0 if the files exist, -ENOENT if the files do not exist, -EPERM if
* one file exists but the other does not exist (inconsistent state)
*/
static int _keystore_ensure_keyfiles_exist(struct key_filenames *file_names,
const char *name)
{
int rc;
rc = _keystore_exists_keyfiles(file_names);
if (rc == 0) {
warnx("Key '%s' does not exist", name);
return -ENOENT;
}
if (rc == -1) {
warnx("Key '%s' is in an inconsistent state", name);
return -EPERM;
}
return 0;
}
/**
* Checks if the files belonging to a key do not exist. If they files exist,
* an appropriate error message is issued.
*
* @param[in] file_names names of the files
* @param[in] name name of the key
*
* @returns 0 if the files exist, -EEXIST if the files exist already, -EPERM if
* one file exists but the other does not exist (inconsistent state)
*/
static int _keystore_ensure_keyfiles_not_exist(struct key_filenames *file_names,
const char *name)
{
int rc;
rc = _keystore_exists_keyfiles(file_names);
if (rc == 1) {
warnx("Key '%s' exists already", name);
return -EEXIST;
}
if (rc == -1) {
warnx("Key '%s' is in an inconsistent state", name);
return -EPERM;
}
return 0;
}
/**
* Frees the file names stored inside the struct key_filenames
*
* @param[in] names names of the files
*/
static void _keystore_free_key_filenames(struct key_filenames *names)
{
if (names->skey_filename)
free(names->skey_filename);
if (names->info_filename)
free(names->info_filename);
if (names->renc_filename)
free(names->renc_filename);
}
/**
* Sets the file permissions of the file to the permissions and the group
* of the repository directory
*
* @param[in] keystroe the keystore
* @param[in] filename the name of the file to set permissions for
*
* @returns 0 on success, or a negative errno value on failure
*/
static int _keystore_set_file_permission(struct keystore *keystore,
const char *filename)
{
int rc;
if (chmod(filename, keystore->mode) != 0) {
rc = -errno;
warnx("chmod 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;
}
/**
* 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))
{
char *modified;
char *pattern_domain;
char *pattern_card;
char *copy;
int card, domain;
size_t i;
char *ch;
int rc;
if (sscanf(pattern, "%x.%x", &card, &domain) == 2) {
util_asprintf(&modified, "%02x.%04x", card, domain);
goto match;
}
copy = util_strdup(pattern);
ch = strchr(copy, '.');
if (ch != NULL) {
*ch = '\0';
pattern_card = copy;
pattern_domain = ch + 1;
modified = NULL;
if (strchr(pattern_card, '*') == NULL &&
strlen(pattern_card) < 2) {
for (i = 0; i < 2 - strlen(pattern_card); i++)
modified = util_strcat_realloc(modified, "0");
}
modified = util_strcat_realloc(modified, pattern_card);
modified = util_strcat_realloc(modified, ".");
if (strchr(pattern_domain, '*') == NULL &&
strlen(pattern_domain) < 4) {
for (i = 0; i < 4 - strlen(pattern_domain); i++)
modified = util_strcat_realloc(modified, "0");
}
modified = util_strcat_realloc(modified, pattern_domain);
} else {
modified = util_strdup(copy);
}
free(copy);
match:
rc = fnmatch(modified, apqn, FNM_CASEFOLD);
free(modified);
return rc;
}
typedef int (*filter_match_t)(const char *pattern, const char *string,
int flags);
/*
* Checks if the value matches the filter list. The value can be a comma
* separated string.
*
* If the filter values contain a second part separated by a colon (':'), then
* the filter matches only if both parts match. If the filter values do not
* contain a second part,then only the first part is checked, and the second
* parts of the values are ignored.
*
* @param[in] value the value to check
* @param[in] filter_list a list of filter strings to match the value with
* @param[in] match_func the filter match function. If NULL fnmatch() is used.
*
* @returns 1 for a match, 0 for not matched
*/
static int _keystore_match_filter(const char *value,
char **filter_list,
filter_match_t match_func)
{
char **value_list;
int i, k, rc = 0;
char *ch;
if (filter_list == NULL)
return 1;
if (match_func == NULL)
match_func = fnmatch;
value_list = str_list_split(value);
for (i = 0; filter_list[i] != NULL && rc == 0; i++) {
for (k = 0; value_list[k] != NULL; k++) {
/*
* Ignore part after ':' of value if filter does
* not also contain a ':' part.
*/
if (strchr(filter_list[i], ':') == NULL) {
ch = strchr(value_list[k], ':');
if (ch != NULL)
*ch = '\0';
}
if (match_func(filter_list[i], value_list[k], 0) == 0) {
rc = 1;
break;
}
}
}
str_list_free_string_array(value_list);
return rc;
}
/*
* Checks if the property value matches the filter list. The property value
* can be a comma separated string.
*
* If the filter values contain a second part separated by a colon (':'), then
* the filter matches only if both parts match. If the filter values do not
* contain a second part,then only the first part is checked, and the second
* parts of the values are ignored.
*
* @param[in] properties a properties object
* @param[in] property the name of the property to check
* @param[in] filter_list a list of filter strings to match the value with
* @param[in] match_func the filter match function. If NULL fnmatch() is used.
*
* @returns 1 for a match, 0 for not matched
*/
static int _keystore_match_filter_property(struct properties *properties,
const char *property,
char **filter_list,
filter_match_t match_func)
{
char *value;
int rc;
if (filter_list == NULL)
return 1;
value = properties_get(properties, property);
if (value == NULL)
return 0;
rc = _keystore_match_filter(value, filter_list, match_func);
free(value);
return rc;
}
/**
* Checks if 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 a key name matches a name filter
*
* @param[in] name the name to check
* @param[in] name_filter the name filter to match against
*
* @returns 1 if the filter matches, 0 otherwise
*/
static int _keystore_match_name_filter(const char *name,
const char *name_filter)
{
if (name_filter == NULL)
return 1;
if (fnmatch(name_filter, name, 0) != 0)
return 0;
return 1;
}
/**
* Filters directory entries for scanfile(). Only entries that are regular
* files and who's name ends with '.info' are matched.
*/
static int _keystore_info_file_filter(const struct dirent *dirent)
{
size_t len;
if (dirent->d_type != DT_REG)
return 0;
len = strlen(dirent->d_name);
if (len > FILE_EXTENSION_LEN &&
strcmp(&dirent->d_name[len - FILE_EXTENSION_LEN],
INFO_FILE_EXTENSION) == 0)
return 1;
return 0;
}
typedef int (*process_key_t)(struct keystore *keystore,
const char *name, struct properties *properties,
struct key_filenames *file_names, void *private);
/**
* Iterates over all keys stored in the keystore. For every key that matches
* the specified filter process_func is called.
*
* @param[in] keystore the key store
* @param[in] name_filter the name filter. Can contain wild cards.
* NULL means no name filter.
* @param[in] volume_filter the volume filter. Can contain wild cards, and
* mutliple volume filters separated by commas.
* If the filter does not contain the ':dm-name' part,
* then the volumes are matched without the dm-name
* part. If the filter contains the ':dm-name' part,
* then the filter is matched including the dm-name
* part.
* NULL means no volume filter.
* specification is ignored for filter matching.
* @param[in] apqn_filter the APQN filter. Can contain wild cards, and
* mutliple APQN filters separated by commas.
* NULL means no APQN filter.
* @param[in] volume_type If not NULL, specifies the volume type.
* @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,
process_key_t process_func,
void *process_private)
{
struct key_filenames file_names = { NULL, NULL, NULL };
char **apqn_filter_list = NULL;
char **vol_filter_list = NULL;
struct properties *key_props;
struct dirent **namelist;
int n, i, rc = 0;
bool skip = 0;
char *name;
int len;
pr_verbose(keystore, "Process_filtered: name_filter = '%s', "
"volume_filter = '%s', apqn_filter = '%s'", name_filter,
volume_filter, apqn_filter);
if (volume_filter != NULL)
vol_filter_list = str_list_split(volume_filter);
if (apqn_filter != NULL)
apqn_filter_list = str_list_split(apqn_filter);
n = scandir(keystore->directory, &namelist, _keystore_info_file_filter,
alphasort);
if (n == -1) {
rc = -errno;
pr_verbose(keystore, "scandir failed with: %s", strerror(-rc));
return rc;
}
for (i = 0; i < n ; i++) {
if (skip)
goto free;
name = namelist[i]->d_name;
len = strlen(name);
if (len > FILE_EXTENSION_LEN)
name[len - FILE_EXTENSION_LEN] = '\0';
if (_keystore_match_name_filter(name, name_filter) == 0) {
pr_verbose(keystore,
"Key '%s' filtered out due to name filter",
name);
goto free;
}
rc = _keystore_get_key_filenames(keystore, name, &file_names);
if (rc != 0)
goto free;
rc = _keystore_ensure_keyfiles_exist(&file_names, name);
if (rc != 0)
goto free_names;
key_props = properties_new();
rc = properties_load(key_props, file_names.info_filename, 1);
if (rc != 0) {
warnx("Key '%s' does not exist or is invalid", name);
goto free_prop;
}
rc = _keystore_match_filter_property(key_props,
PROP_NAME_VOLUMES,
vol_filter_list, NULL);
if (rc == 0) {
pr_verbose(keystore,
"Key '%s' filtered out due to volumes filter",
name);
goto free_prop;
}
rc = _keystore_match_filter_property(key_props,
PROP_NAME_APQNS,
apqn_filter_list,
_keystore_apqn_match);
if (rc == 0) {
pr_verbose(keystore,
"Key '%s' filtered out due to APQN filter",
name);
goto free_prop;
}
rc = _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 = process_func(keystore, name, key_props, &file_names,
process_private);
if (rc != 0) {
pr_verbose(keystore, "Process function returned %d",
rc);
skip = 1;
}
free_prop:
properties_free(key_props);
free_names:
_keystore_free_key_filenames(&file_names);
free:
free(namelist[i]);
}
free(namelist);
if (vol_filter_list)
str_list_free_string_array(vol_filter_list);
if (apqn_filter_list)
str_list_free_string_array(apqn_filter_list);
pr_verbose(keystore, "Process_filtered rc = %d", rc);
return rc;
}
/**
* Checks if the specified APQN is of type CCA and is online
*
* @param[in] card card number
* @param[in] domain the domain
*
* @returns 1 if its a CCA card and is online, 0 if offline and -1 if its
* not a CCA card.
*/
static int _keystore_is_apqn_online(int card, int domain)
{
long int online;
char *dev_path;
char type[20];
int rc = 1;
dev_path = util_path_sysfs("bus/ap/devices/card%02x", card);
if (!util_path_is_dir(dev_path)) {
rc = 0;
goto out;
}
if (util_file_read_l(&online, 10, "%s/online", dev_path) != 0) {
rc = 0;
goto out;
}
if (online == 0) {
rc = 0;
goto out;
}
if (util_file_read_line(type, sizeof(type), "%s/type", dev_path) != 0) {
rc = 0;
goto out;
}
if (strncmp(type, "CEX", 3) != 0 || strlen(type) < 5) {
rc = 0;
goto out;
}
if (type[4] != 'C') {
rc = -1;
goto out;
}
free(dev_path);
dev_path = util_path_sysfs("bus/ap/devices/card%02x/%02x.%04x", card,
card, domain);
if (!util_path_is_dir(dev_path)) {
rc = 0;
goto out;
}
if (util_file_read_l(&online, 10, "%s/online", dev_path) != 0) {
rc = 0;
goto out;
}
if (online == 0) {
rc = 0;
goto out;
}
out:
free(dev_path);
return rc;
}
struct apqn_check {
bool noonlinecheck;
bool nomsg;
};
/**
* 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;
int rc, card, domain;
regmatch_t pmatch[1];
regex_t reg_buf;
*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", &card, &domain) != 2) {
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 = _keystore_is_apqn_online(card, domain);
if (rc != 1) {
if (info->nomsg == 0)
warnx("The APQN %02x.%04x is %s", card, domain,
rc == -1 ? "not a CCA card" : "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;
};
/**
* 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) {
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,
_keystore_volume_check_process, info);
out:
free((void *)info->volume);
info->volume = NULL;
return rc;
}
/**
* Locks the repository against other processes.
*
* @param[in] keystore the keystore
*
* @returns 0 if successful, a negative errno value otherwise
*/
static int _keystore_lock_repository(struct keystore *keystore)
{
char *lock_file_name;
struct stat sb;
int rc;
util_asprintf(&lock_file_name, "%s/%s", keystore->directory,
LOCK_FILE_NAME);
if (stat(lock_file_name, &sb) == 0) {
keystore->lock_fd = open(lock_file_name, O_RDONLY);
if (keystore->lock_fd == -1) {
rc = -errno;
warnx("Failed to open lock file '%s': %s",
lock_file_name,
strerror(-rc));
goto out;
}
} else {
keystore->lock_fd = open(lock_file_name, O_CREAT | O_RDONLY,
keystore->mode);
if (keystore->lock_fd == -1) {
rc = -errno;
warnx("Failed to create lock file '%s': %s",
lock_file_name,
strerror(-rc));
goto out;
}
if (fchown(keystore->lock_fd, geteuid(),
keystore->owner) != 0) {
rc = -errno;
warnx("chown faild on file '%s': %s", lock_file_name,
strerror(-rc));
return rc;
}
}
rc = flock(keystore->lock_fd, LOCK_EX);
if (rc == -1) {
rc = -errno;
warnx("Failed to obtain the file lock on '%s': %s",
lock_file_name, strerror((-rc)));
}
out:
free(lock_file_name);
return rc;
}
/**
* Unlocks the repository
*
* @param[in] keystore the keystore
*
* @returns 0 if successful, a negative errno value otherwise
*/
static int _keystore_unlock_repository(struct keystore *keystore)
{
int rc;
if (keystore->lock_fd == -1)
return 0;
rc = flock(keystore->lock_fd, LOCK_UN);
if (rc == -1) {
rc = -errno;
warnx("Failed to release the file lock: %s", strerror((-rc)));
}
close(keystore->lock_fd);
keystore->lock_fd = -1;
return rc;
}
/**
* Allocates new keystore object
*
* @param[in] directory the directory where the keystore resides
* @param[in] verbose if true, verbose messages are printed
*
* @returns a new keystore object
*/
struct keystore *keystore_new(const char *directory, bool verbose)
{
struct keystore *keystore;
struct stat sb;
int rc;
util_assert(directory != NULL, "Internal error: directory is NULL");
if (stat(directory, &sb) != 0) {
warnx("Can not access '%s': %s", directory, strerror(errno));
return NULL;
}
if (!(sb.st_mode & S_IFDIR)) {
warnx("'%s' is not a directory", directory);
return NULL;
}
if (!util_path_is_readable(directory) ||
!util_path_is_writable(directory)) {
warnx("Permission denied for '%s'", directory);
return NULL;
}
if (sb.st_mode & S_IWOTH) {
warnx("Directory '%s' is writable for others, this is not "
"accepted", directory);
return NULL;
}
keystore = util_zalloc(sizeof(struct keystore));
keystore->owner = sb.st_gid;
keystore->mode = sb.st_mode & (S_IRUSR | S_IWUSR |
S_IRGRP | S_IWGRP |
S_IROTH);
keystore->lock_fd = -1;
keystore->verbose = verbose;
keystore->directory = util_strdup(directory);
if (keystore->directory[strlen(keystore->directory)-1] == '/')
keystore->directory[strlen(keystore->directory)-1] = '\0';
rc = _keystore_lock_repository(keystore);
if (rc != 0) {
keystore_free(keystore);
return NULL;
}
pr_verbose(keystore, "Keystore in directory '%s' opened successfully",
keystore->directory);
return keystore;
}
/**
* 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((const char *)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_KEY_TYPE, "CCA-AESDATA");
if (rc != 0)
return rc;
rc = properties_set(key_props, PROP_NAME_CIPHER, "paes");
if (rc != 0)
return rc;
rc = properties_set(key_props, PROP_NAME_IV_MODE, "plain64");
if (rc != 0)
return rc;
rc = _keystore_set_timestamp_property(key_props,
PROP_NAME_CREATION_TIME);
if (rc != 0)
return rc;
return 0;
}
/**
* Creates an initial .info file for a key
*
* @param[in] keystore the key store
* @param[in] name the name of the key
* @param[in] info_filename the file name of the key info file
* @param[in] description textual description of the key (optional, can be NULL)
* @param[in] volumes a comma separated list of volumes associated with this
* key (optional, can be NULL)
* @param[in] apqns a comma separated list of APQNs associated with this
* key (optional, can be NULL)
* @param[in] 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
*/
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)
{
struct volume_check vol_check = { .keystore = keystore, .name = name,
.set = 0 };
struct apqn_check apqn_check = { .noonlinecheck = noapqncheck,
.nomsg = 0 };
struct properties *key_props;
char temp[10];
int rc;
key_props = properties_new();
rc = _keystore_set_default_properties(key_props);
if (rc != 0)
goto out;
rc = properties_set(key_props, PROP_NAME_DESCRIPTION,
description != NULL ? description : "");
if (rc != 0) {
warnx("Invalid characters in description");
goto out;
}
rc = _keystore_change_association(key_props, PROP_NAME_VOLUMES,
volumes != NULL ? volumes : "",
"volume", _keystore_volume_check,
&vol_check);
if (rc != 0)
goto out;
rc = _keystore_change_association(key_props, PROP_NAME_APQNS,
apqns != NULL ? apqns : "",
"APQN", _keystore_apqn_check,
&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_set(key_props, PROP_NAME_VOLUME_TYPE, volume_type);
if (rc != 0) {
warnx("Invalid characters in volume-type");
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");
return rc;
}
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));
goto out;
}
rc = _keystore_set_file_permission(keystore, filenames->info_filename);
if (rc != 0) {
remove(filenames->info_filename);
goto out;
}
out:
properties_free(key_props);
return rc;
}
/**
* Extracts an online card/domain pair from the specified APQns. If none of the
* specified APQNs are online, then -ENODEV is returned.
* If no APQNs are specified at all, then it uses AUTOSELECT and returns zero.
*/
static int _keystore_get_card_domain(const char *apqns, unsigned int *card,
unsigned int *domain)
{
struct apqn_check apqn_check = { .noonlinecheck = 0, .nomsg = 1 };
char **apqn_list;
char *normalized = NULL;
int rc = 0;
int i;
*card = AUTOSELECT;
*domain = AUTOSELECT;
if (apqns == NULL)
return 0;
apqn_list = str_list_split(apqns);
if (apqn_list[0] == NULL)
goto out;
for (i = 0; apqn_list[i] != NULL; i++) {
rc = _keystore_apqn_check(apqn_list[i], 0, 0, &normalized,
&apqn_check);
if (normalized != NULL)
free(normalized);
if (rc == -EINVAL)
goto out;
if (rc != 0)
continue;
if (sscanf(apqn_list[i], "%x.%x", card, domain) == 2)
goto found;
}
warnx("None of the specified APQNs is online or of type CCA");
rc = -ENODEV;
found:
out:
str_list_free_string_array(apqn_list);
return rc;
}
/**
* Generates a secure key by random and adds it to the key store
*
* @param[in] keystore the key store
* @param[in] name the name of the key
* @param[in] description textual description of the key (optional, can be NULL)
* @param[in] volumes a comma separated list of volumes associated with this
* key (optional, can be NULL)
* @param[in] apqns a comma separated list of APQNs associated with this
* key (optional, can be NULL)
* @param[in] 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] 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,
int pkey_fd)
{
struct key_filenames file_names = { NULL, NULL, NULL };
struct properties *key_props = NULL;
unsigned int card, domain;
int rc;
util_assert(keystore != NULL, "Internal error: keystore is NULL");
util_assert(name != NULL, "Internal error: name is NULL");
rc = _keystore_get_key_filenames(keystore, name, &file_names);
if (rc != 0)
goto out_free_key_filenames;
rc = _keystore_ensure_keyfiles_not_exist(&file_names, name);
if (rc != 0)
goto out_free_key_filenames;
rc = _keystore_get_card_domain(apqns, &card, &domain);
if (rc != 0)
goto out_free_key_filenames;
if (clear_key_file == NULL)
rc = generate_secure_key_random(pkey_fd,
file_names.skey_filename,
keybits, xts, card, domain,
keystore->verbose);
else
rc = generate_secure_key_clear(pkey_fd,
file_names.skey_filename,
keybits, xts, clear_key_file,
card, domain,
keystore->verbose);
if (rc != 0)
goto out_free_props;
rc = _keystore_set_file_permission(keystore, file_names.skey_filename);
if (rc != 0)
goto out_free_props;
rc = _keystore_create_info_file(keystore, name, &file_names,
description, volumes, apqns,
noapqncheck, sector_size, volume_type);
if (rc != 0)
goto out_free_props;
pr_verbose(keystore,
"Successfully generated a secure key in '%s' and key info "
"in '%s'", file_names.skey_filename,
file_names.info_filename);
out_free_props:
if (key_props != NULL)
properties_free(key_props);
if (rc != 0)
remove(file_names.skey_filename);
out_free_key_filenames:
_keystore_free_key_filenames(&file_names);
if (rc != 0)
pr_verbose(keystore, "Failed to generate key '%s': %s",
name, strerror(-rc));
return rc;
}
/**
* Imports a secure key from a file and adds it to the key store
*
* @param[in] keystore the key store
* @param[in] name the name of the key
* @param[in] description textual description of the key (optional, can be NULL)
* @param[in] volumes a comma separated list of volumes associated with this
* key (optional, can be NULL)
* @param[in] apqns a comma separated list of APQNs associated with this
* key (optional, can be NULL)
* @param[in] 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] import_file The name of a secure key containing the key to import
* @param[in] volume_type the type of volume
*
* @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)
{
struct key_filenames file_names = { NULL, NULL, NULL };
struct properties *key_props = NULL;
size_t secure_key_size;
u8 *secure_key;
int rc;
util_assert(keystore != NULL, "Internal error: keystore is NULL");
util_assert(name != NULL, "Internal error: name is NULL");
util_assert(import_file != NULL, "Internal error: import_file is NULL");
rc = _keystore_get_key_filenames(keystore, name, &file_names);
if (rc != 0)
goto out_free_key_filenames;
rc = _keystore_ensure_keyfiles_not_exist(&file_names, name);
if (rc != 0)
goto out_free_key_filenames;
secure_key = read_secure_key(import_file, &secure_key_size,
keystore->verbose);
if (secure_key == NULL) {
rc = -ENOENT;
goto out_free_key_filenames;
}
rc = write_secure_key(file_names.skey_filename, secure_key,
secure_key_size, keystore->verbose);
free(secure_key);
if (rc != 0)
goto out_free_props;
rc = _keystore_set_file_permission(keystore, file_names.skey_filename);
if (rc != 0)
goto out_free_props;
rc = _keystore_create_info_file(keystore, name, &file_names,
description, volumes, apqns,
noapqncheck, sector_size, volume_type);
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;
}
/**
* 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.
* *
* @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)
{
struct volume_check vol_check = { .keystore = keystore, .name = name,
.set = 0 };
struct apqn_check apqn_check = { .noonlinecheck = noapqncheck,
.nomsg = 0 };
struct key_filenames file_names = { NULL, NULL, NULL };
struct properties *key_props = NULL;
char temp[30];
int rc;
util_assert(keystore != NULL, "Internal error: keystore is NULL");
util_assert(name != NULL, "Internal error: name is NULL");
rc = _keystore_get_key_filenames(keystore, name, &file_names);
if (rc != 0)
goto out;
rc = _keystore_ensure_keyfiles_exist(&file_names, name);
if (rc != 0)
goto out;
key_props = properties_new();
rc = properties_load(key_props, file_names.info_filename, 1);
if (rc != 0) {
warnx("Key '%s' does not exist or is invalid", name);
goto out;
}
if (description != NULL) {
rc = properties_set(key_props, PROP_NAME_DESCRIPTION,
description);
if (rc != 0) {
warnx("Invalid characters in description");
goto out;
}
}
if (volumes != NULL) {
rc = _keystore_change_association(key_props, PROP_NAME_VOLUMES,
volumes, "volume",
_keystore_volume_check,
&vol_check);
if (rc != 0)
goto out;
}
if (apqns != NULL) {
rc = _keystore_change_association(key_props, PROP_NAME_APQNS,
apqns, "APQN",
_keystore_apqn_check,
&apqn_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(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) {
if (!_keystore_valid_volume_type(volume_type)) {
warnx("Invalid volume-type specified");
rc = -EINVAL;
goto out;
}
rc = properties_set(key_props, PROP_NAME_VOLUME_TYPE,
volume_type);
if (rc != 0) {
warnx("Invalid characters in volume-type");
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:
_keystore_free_key_filenames(&file_names);
if (key_props != NULL)
properties_free(key_props);
if (rc != 0)
pr_verbose(keystore, "Failed to change key '%s': %s",
name, strerror(-rc));
return rc;
}
/**
* Renames a key in the keystore
*
* @param[in] keystore the key store
* @param[in] name the name of the key
* @param[in] newname the new name of the key
*
* @returns 0 for success or a negative errno in case of an error
*/
int keystore_rename_key(struct keystore *keystore, const char *name,
const char *newname)
{
struct key_filenames file_names = { NULL, NULL, NULL };
struct key_filenames new_names = { NULL, NULL, NULL };
struct properties *key_props = NULL;
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));
rename(new_names.skey_filename, file_names.skey_filename);
}
if (_keystore_reencipher_key_exists(&file_names)) {
if (rename(file_names.renc_filename,
new_names.renc_filename) != 0) {
rc = -errno;
pr_verbose(keystore, "Failed to rename '%s': %s",
file_names.renc_filename, strerror(-rc));
rename(new_names.skey_filename,
file_names.skey_filename);
rename(new_names.info_filename,
file_names.info_filename);
}
}
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;
}
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);
pr_verbose(keystore, "Successfully renamed key '%s' to '%s'", name,
newname);
out:
_keystore_free_key_filenames(&file_names);
_keystore_free_key_filenames(&new_names);
if (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);
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_CREATION_TIME, UTIL_REC_ALIGN_LEFT, 54,
REC_CREATION_TIME);
util_rec_def(rec, REC_CHANGE_TIME, UTIL_REC_ALIGN_LEFT, 54,
REC_CHANGE_TIME);
util_rec_def(rec, REC_REENC_TIME, UTIL_REC_ALIGN_LEFT, 54,
REC_REENC_TIME);
return rec;
}
static void _keystore_print_record(struct util_rec *rec,
const char *name,
struct properties *properties,
bool validation, const char *skey_filename,
size_t secure_key_size,
size_t clear_key_bitsize, bool valid,
bool is_old_mk, bool reenc_pending)
{
char temp_vp[VERIFICATION_PATTERN_LEN + 2];
char *volumes_argz = NULL;
size_t volumes_argz_len;
char *apqns_argz = NULL;
size_t sector_size = 0;
size_t apqns_argz_len;
char *description;
char *volume_type;
char *reencipher;
char *creation;
char *volumes;
char *change;
char *apqns;
char *temp;
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);
util_rec_set(rec, REC_KEY, name);
if (validation)
util_rec_set(rec, REC_STATUS, valid ? "Valid" : "Invalid");
util_rec_set(rec, REC_DESCRIPTION,
description != NULL ? description : "");
util_rec_set(rec, REC_SEC_KEY_SIZE, "%lu bytes", secure_key_size);
if (!validation || valid)
util_rec_set(rec, REC_CLR_KEY_SIZE, "%lu bits",
clear_key_bitsize);
else
util_rec_set(rec, REC_CLR_KEY_SIZE, "(unknown)");
util_rec_set(rec, REC_XTS,
IS_XTS(secure_key_size) ? "Yes" : "No");
if (validation) {
if (valid)
util_rec_set(rec, REC_MASTERKEY,
is_old_mk ? "OLD CCA master key" :
"CURRENT CCA master key");
else
util_rec_set(rec, REC_MASTERKEY, "(unknown)");
}
if (volumes_argz != NULL)
util_rec_set_argz(rec, REC_VOLUMES, volumes_argz,
volumes_argz_len);
else
util_rec_set(rec, REC_VOLUMES, "(none)");
if (apqns_argz != NULL)
util_rec_set_argz(rec, REC_APQNS,
apqns_argz, apqns_argz_len);
else
util_rec_set(rec, REC_APQNS, "(none)");
util_rec_set(rec, REC_KEY_FILE, skey_filename);
if (sector_size == 0)
util_rec_set(rec, REC_SECTOR_SIZE, "(system default)");
else
util_rec_set(rec, REC_SECTOR_SIZE, "%lu bytes",
sector_size);
util_rec_set(rec, REC_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_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);
}
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] properties the properties of the key
* @param[in] name the name of the key
*
* @returns 0 in case of success, 1 if at least one of the APQNs is not
* available
*/
static int _keystore_display_apqn_status(struct properties *properties,
const char *name)
{
int i, rc, card, domain, warning = 0;
char **apqn_list;
char *apqns;
apqns = properties_get(properties, PROP_NAME_APQNS);
if (apqns == NULL)
return 0;
apqn_list = str_list_split(apqns);
for (i = 0; apqn_list[i] != NULL; i++) {
if (sscanf(apqn_list[i], "%x.%x", &card, &domain) != 2)
continue;
rc = _keystore_is_apqn_online(card, domain);
if (rc != 1) {
printf("WARNING: The APQN %02x.%04x associated with "
"key '%s' is %s\n", card, domain, name,
rc == -1 ? "not a CCA card" : "not online");
warning = 1;
}
}
if (warning)
printf("\n");
free(apqns);
str_list_free_string_array(apqn_list);
return warning;
}
/**
* Displays the status of the associated volumes.
*
* @param[in] properties the properties of the key
* @param[in] name the name of the key
*
* @returns 0 in case of success, 1 if at least one of the volumes is not
* available
*/
static int _keystore_display_volume_status(struct properties *properties,
const char *name)
{
int i, warning = 0;
char **volume_list;
char *volumes;
char *ch;
volumes = properties_get(properties, PROP_NAME_VOLUMES);
if (volumes == NULL)
return 0;
volume_list = str_list_split(volumes);
for (i = 0; volume_list[i] != NULL; i++) {
ch = strchr(volume_list[i], ':');
if (ch != NULL)
*ch = '\0';
if (!_keystore_is_block_device(volume_list[i])) {
printf("WARNING: The volume '%s' associated with "
"key '%s' is not available\n", volume_list[i],
name);
warning = 1;
}
}
if (warning)
printf("\n");
free(volumes);
str_list_free_string_array(volume_list);
return warning;
}
/**
* Processing function for the key validate function. Prints validation
* information for the key to be validated.
*
* @param[in] keystore the keystore
* @param[in] name the name of the key
* @param[in] properties the properties object of the key
* @param[in] file_names the file names used by this key
* @param[in] private private data: struct validate_info
*
* @returns 0 if the validation is successful, a negative errno value otherwise
*/
static int _keystore_process_validate(struct keystore *keystore,
const char *name,
struct properties *properties,
struct key_filenames *file_names,
void *private)
{
struct validate_info *info = (struct validate_info *)private;
size_t clear_key_bitsize;
size_t secure_key_size;
u8 *secure_key;
int is_old_mk;
int rc, valid;
rc = _keystore_ensure_keyfiles_exist(file_names, name);
if (rc != 0)
goto out;
secure_key = read_secure_key(file_names->skey_filename,
&secure_key_size, keystore->verbose);
if (secure_key == NULL) {
rc = -ENOENT;
goto out;
}
rc = validate_secure_key(info->pkey_fd, secure_key, secure_key_size,
&clear_key_bitsize, &is_old_mk,
keystore->verbose);
if (rc != 0) {
valid = 0;
info->num_invalid++;
rc = 0;
} else {
info->num_valid++;
valid = 1;
}
free(secure_key);
_keystore_print_record(info->rec, name, properties, 1,
file_names->skey_filename, secure_key_size,
clear_key_bitsize, valid, is_old_mk,
_keystore_reencipher_key_exists(file_names));
if (valid && is_old_mk) {
util_print_indented("WARNING: The secure key is currently "
"enciphered with the OLD CCA master key. "
"To mitigate the danger of data loss "
"re-encipher it with the CURRENT CCA "
"master key\n", 0);
info->num_warnings++;
}
if (info->noapqncheck == 0)
if (_keystore_display_apqn_status(properties, name) != 0)
info->num_warnings++;
if (_keystore_display_volume_status(properties, name) != 0)
info->num_warnings++;
out:
if (rc != 0)
pr_verbose(keystore, "Failed to validate key '%s': %s",
name, strerror(-rc));
return rc;
}
/**
* Validates one or multiple keys in the keystore
*
* @param[in] keystore the key store
* @param[in] name_filter the name filter to select the key (can be NULL)
* @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,
_keystore_process_validate, &info);
util_rec_free(rec);
if (rc != 0) {
pr_verbose(keystore, "Failed to validate keys: %s",
strerror(-rc));
} else {
pr_verbose(keystore, "Successfully validated keys");
printf("%lu keys are valid, %lu keys are invalid, %lu "
"warnings\n", info.num_valid, info.num_invalid,
info.num_warnings);
}
return rc;
}
struct reencipher_params {
bool from_old;
bool to_new;
bool complete;
int inplace; /* -1 = autodetect, 0 = not in-place, 1 = in-place */
};
struct reencipher_info {
struct reencipher_params params;
int pkey_fd;
t_CSNBKTC dll_CSNBKTC;
unsigned long num_reenciphered;
unsigned long num_failed;
unsigned long num_skipped;
};
/**
* Perform the reencipherment of a key
*
* @param[in] keystore the keystore
* @param[in] name the name of the key
* @param[in] dll_CSNBKTC the CCA key token change function
* @param[in] params reenciphering parameters
* @param[in] secure_key a buffer containing the secure key
* @param[in] secure_key_size the size of the secure key
* @param[in] is_old_mk if true the key is currently re-enciphered with the
* OLD master key
* @returns 0 if the re-enciphering is successful, a negative errno value
* otherwise, 1 if it was skipped
*/
static int _keystore_perform_reencipher(struct keystore *keystore,
const char *name,
t_CSNBKTC dll_CSNBKTC,
struct reencipher_params *params,
u8 *secure_key, size_t secure_key_size,
bool is_old_mk)
{
int rc;
if (!params->from_old && !params->to_new) {
/* Autodetect reencipher mode */
if (is_old_mk) {
params->from_old = 1;
util_print_indented("The secure key is currently "
"enciphered with the OLD CCA "
"master key and is being "
"re-enciphered with the CURRENT "
"CCA master key\n", 0);
} else {
params->to_new = 1;
util_print_indented("The secure key is currently "
"enciphered with the CURRENT CCA "
"master key and is being "
"re-enciphered with the NEW CCA "
"master key\n", 0);
}
}
if (params->from_old) {
if (!is_old_mk) {
printf("The secure key '%s' is already enciphered "
"with the CURRENT CCA master key\n", name);
return 1;
}
if (params->inplace == -1)
params->inplace = 1;
pr_verbose(keystore,
"Secure key '%s' will be re-enciphered from OLD "
"to the CURRENT CCA master key", name);
rc = key_token_change(dll_CSNBKTC,
secure_key, secure_key_size,
METHOD_OLD_TO_CURRENT,
keystore->verbose);
if (rc != 0) {
warnx("Failed to re-encipher '%s' from OLD to "
"CURRENT CCA master key", name);
return rc;
}
}
if (params->to_new) {
pr_verbose(keystore,
"Secure key '%s' will be re-enciphered from "
"CURRENT to the NEW CCA master key", name);
if (params->inplace == -1)
params->inplace = 0;
rc = key_token_change(dll_CSNBKTC,
secure_key, secure_key_size,
METHOD_CURRENT_TO_NEW,
keystore->verbose);
if (rc != 0) {
warnx("Failed to re-encipher '%s' from CURRENT to "
"NEW CCA master key", name);
return rc;
}
}
return 0;
}
/**
* Processing function for the key re-enciphering function.
*
* @param[in] keystore the keystore
* @param[in] name the name of the key
* @param[in] properties the properties object of the key (not used here)
* @param[in] file_names the file names used by this key
* @param[in] private private data: struct reencipher_info
*
* @returns 0 if the re-enciphering is successful, a negative errno value
* otherwise
*/
static int _keystore_process_reencipher(struct keystore *keystore,
const char *name,
struct properties *properties,
struct key_filenames *file_names,
void *private)
{
struct reencipher_info *info = (struct reencipher_info *)private;
struct reencipher_params params = info->params;
size_t clear_key_bitsize;
size_t secure_key_size;
u8 *secure_key = NULL;
char *out_file;
int is_old_mk;
char *temp;
int rc;
rc = _keystore_ensure_keyfiles_exist(file_names, name);
if (rc != 0)
goto out;
pr_verbose(keystore, "Complete reencipher: %d", params.complete);
pr_verbose(keystore, "In-place reencipher: %d", params.inplace);
if (params.complete) {
if (!_keystore_reencipher_key_exists(file_names)) {
warnx("Staged re-enciphering 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;
}
rc = validate_secure_key(info->pkey_fd, secure_key, secure_key_size,
&clear_key_bitsize, &is_old_mk,
keystore->verbose);
if (rc != 0) {
if (params.complete) {
warnx("Key '%s' is not valid, re-enciphering is not "
"completed", name);
warnx("The new CCA 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->dll_CSNBKTC, &params,
secure_key, secure_key_size,
is_old_mk);
if (rc < 0)
goto out;
if (rc > 0) {
info->num_skipped++;
rc = 0;
goto out;
}
}
pr_verbose(keystore, "In-place reencipher: %d", params.inplace);
out_file = params.inplace == 1 ? file_names->skey_filename :
file_names->renc_filename;
rc = write_secure_key(out_file, secure_key,
secure_key_size, keystore->verbose);
if (rc != 0)
goto out;
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 CCA 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 (secure_key != NULL)
free(secure_key);
printf("\n");
if (rc != 0) {
info->num_failed++;
pr_verbose(keystore, "Failed to re-encipher key '%s': %s",
name, strerror(-rc));
rc = 0;
}
return rc;
}
/**
* Reenciphers a key in the keystore
*
* @param[in] keystore the key store
* @param[in] name_filter the name filter to select the key (can be NULL)
* @param[in] apqn_filter the APQN filter to seletc the key (can be NULL)
* @param[in] from_old If true the key is reenciphered from the OLD to the
* CURRENT CCA master key.
* @param[in] to_new If true the key is reenciphered from the CURRENT to
* the OLD CCA master key.
* @param[in] inplace if true, the key will be re-enciphere in-place
* @param[in] staged if true, the key will be re-enciphere not in-place
* @param[in] complete if true, a pending re-encipherment is completed
* Note: if both from Old and toNew are FALSE, then the reencipherement mode is
* detected automatically. If both are TRUE then the key is reenciphered
* from the OLD to the NEW CCA master key.
* Note: if both inplace and staged are FLASE, then the key is re-enciphered
* inplace when for OLD-to-CURRENT, and is reenciphered staged for
* CURRENT-to-NEW.
* @returns 0 for success or a negative errno in case of an error
*/
int keystore_reencipher_key(struct keystore *keystore, const char *name_filter,
const char *apqn_filter,
bool from_old, bool to_new, bool inplace,
bool staged, bool complete, int pkey_fd,
t_CSNBKTC dll_CSNBKTC)
{
struct reencipher_info info;
int rc;
util_assert(keystore != NULL, "Internal error: keystore is NULL");
info.params.from_old = from_old;
info.params.to_new = to_new;
info.params.inplace = -1;
if (inplace)
info.params.inplace = 1;
if (staged)
info.params.inplace = 0;
info.params.complete = complete;
info.pkey_fd = pkey_fd;
info.dll_CSNBKTC = dll_CSNBKTC;
info.num_failed = 0;
info.num_reenciphered = 0;
info.num_skipped = 0;
rc = _keystore_process_filtered(keystore, name_filter, NULL,
apqn_filter, NULL,
_keystore_process_reencipher, &info);
if (rc != 0) {
pr_verbose(keystore, "Failed to re-encipher keys: %s",
strerror(-rc));
} else {
pr_verbose(keystore, "Successfully re-enciphered keys");
printf("%lu keys re-enciphered, %lu keys skipped, %lu keys "
"failed to re-encipher\n",
info.num_reenciphered, info.num_skipped,
info.num_failed);
if (info.num_failed > 0)
rc = -EIO;
}
return rc;
}
/**
* Copies (duplicates) a key in the keystore. Any existing volume associations
* are removed from the copy, because a volume can only be associated to one
* key. However, you can set new volume associations using the volumes
* parameter.
*
* @param[in] keystore the key store
* @param[in] name the name of the key
* @param[in] newname the new name of the key
* @param[in] volumes a comma separated list of volumes associated with this
* key (optional, can be NULL)
*
* @returns 0 for success or a negative errno in case of an error
*/
int keystore_copy_key(struct keystore *keystore, const char *name,
const char *newname, const char *volumes)
{
struct volume_check vol_check = { .keystore = keystore,
.name = newname, .set = 0 };
struct key_filenames file_names = { NULL, NULL, NULL };
struct key_filenames new_names = { NULL, NULL, NULL };
struct properties *key_prop = NULL;
size_t secure_key_size;
u8 *secure_key;
int rc;
util_assert(keystore != NULL, "Internal error: keystore is NULL");
util_assert(name != NULL, "Internal error: name is NULL");
util_assert(newname != NULL, "Internal error: newname is NULL");
rc = _keystore_get_key_filenames(keystore, name, &file_names);
if (rc != 0)
goto out;
rc = _keystore_ensure_keyfiles_exist(&file_names, name);
if (rc != 0)
goto out;
rc = _keystore_get_key_filenames(keystore, newname, &new_names);
if (rc != 0)
goto out;
rc = _keystore_ensure_keyfiles_not_exist(&new_names, newname);
if (rc != 0)
goto out;
secure_key = read_secure_key(file_names.skey_filename,
&secure_key_size, keystore->verbose);
if (secure_key == NULL) {
rc = -ENOENT;
goto out;
}
rc = write_secure_key(new_names.skey_filename, secure_key,
secure_key_size, keystore->verbose);
free(secure_key);
if (rc != 0)
goto out;
rc = _keystore_set_file_permission(keystore, new_names.skey_filename);
if (rc != 0)
goto out;
key_prop = properties_new();
rc = properties_load(key_prop, file_names.info_filename, 1);
if (rc != 0) {
warnx("Key '%s' does not exist or is invalid", name);
remove(file_names.skey_filename);
goto out;
}
/*
* Remove any volume association, since a volume can only be associated
* with one key
*/
rc = properties_set(key_prop, PROP_NAME_VOLUMES, "");
if (rc != 0)
goto out;
if (volumes != NULL) {
rc = _keystore_change_association(key_prop, PROP_NAME_VOLUMES,
volumes,
"volume",
_keystore_volume_check,
&vol_check);
if (rc != 0)
goto out;
}
rc = properties_remove(key_prop, PROP_NAME_CHANGE_TIME);
if (rc != 0 && rc != -ENOENT)
goto out;
rc = properties_remove(key_prop, PROP_NAME_REENC_TIME);
if (rc != 0 && rc != -ENOENT)
goto out;
rc = _keystore_set_timestamp_property(key_prop,
PROP_NAME_CREATION_TIME);
if (rc != 0)
goto out;
rc = properties_save(key_prop, new_names.info_filename, 1);
if (rc != 0) {
pr_verbose(keystore,
"Key info file '%s' could not be written: %s",
new_names.info_filename, strerror(-rc));
remove(new_names.skey_filename);
goto out;
}
rc = _keystore_set_file_permission(keystore, new_names.info_filename);
if (rc != 0)
goto out;
pr_verbose(keystore, "Successfully copied key '%s' to '%s'", name,
newname);
out:
if (rc != 0) {
remove(new_names.skey_filename);
remove(new_names.info_filename);
}
_keystore_free_key_filenames(&file_names);
_keystore_free_key_filenames(&new_names);
if (key_prop != NULL)
properties_free(key_prop);
if (rc != 0)
pr_verbose(keystore, "Failed to copy key '%s'to '%s': %s",
name, newname, strerror(-rc));
return rc;
}
/**
* Exports a key from the keystore to a file
*
* @param[in] keystore the key store
* @param[in] name the name of the key
* @param[in] export_file the name of the file to export the key to
*
* @returns 0 for success or a negative errno in case of an error
*/
int keystore_export_key(struct keystore *keystore, const char *name,
const char *export_file)
{
struct key_filenames file_names = { NULL, NULL, NULL };
size_t secure_key_size;
u8 *secure_key;
int rc;
util_assert(keystore != NULL, "Internal error: keystore is NULL");
util_assert(name != NULL, "Internal error: name is NULL");
util_assert(export_file != NULL, "Internal error: export_file is NULL");
rc = _keystore_get_key_filenames(keystore, name, &file_names);
if (rc != 0)
goto out;
rc = _keystore_ensure_keyfiles_exist(&file_names, name);
if (rc != 0)
goto out;
secure_key = read_secure_key(file_names.skey_filename,
&secure_key_size, keystore->verbose);
if (secure_key == NULL) {
rc = -ENOENT;
goto out;
}
rc = write_secure_key(export_file, secure_key,
secure_key_size, keystore->verbose);
free(secure_key);
pr_verbose(keystore, "Successfully exported key '%s' to '%s'", name,
export_file);
out:
_keystore_free_key_filenames(&file_names);
if (rc != 0)
pr_verbose(keystore, "Failed to export key '%s': %s",
name, strerror(-rc));
return rc;
}
/**
* Prompts the user to confirm deletion of a key
*
* @param[in] keystore the key store
* @param[in] name the name of the key
* @param[in] file_names the file names of the key
*
* @returnd 0 if the user confirmed the deletion, a negative errno value
* otherwise
*/
static int _keystore_propmp_for_remove(struct keystore *keystore,
const char *name,
struct key_filenames *file_names)
{
struct properties *key_prop;
char str[20];
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'? ", program_invocation_short_name, name);
if (fgets(str, sizeof(str), stdin) == NULL) {
rc = -EIO;
goto out;
}
if (str[strlen(str) - 1] == '\n')
str[strlen(str) - 1] = '\0';
pr_verbose(keystore, "Prompt reply: '%s'", str);
if (strcasecmp(str, "y") != 0 && strcasecmp(str, "yes") != 0) {
rc = -ECANCELED;
goto out;
}
out:
properties_free(key_prop);
return rc;
}
/**
* Removes a key from the keystore
*
* @param[in] keystore the key store
* @param[in] name the name of the key
* @param[in] quiet if true no confirmation prompt is shown
*
* @returns 0 for success or a negative errno in case of an error
*/
int keystore_remove_key(struct keystore *keystore, const char *name,
bool quiet)
{
struct key_filenames file_names = { NULL, NULL, NULL };
int rc;
util_assert(keystore != NULL, "Internal error: keystore is NULL");
util_assert(name != NULL, "Internal error: name is NULL");
rc = _keystore_get_key_filenames(keystore, name, &file_names);
if (rc != 0)
goto out;
rc = _keystore_ensure_keyfiles_exist(&file_names, name);
if (rc != 0)
goto out;
if (!quiet) {
if (_keystore_propmp_for_remove(keystore, name,
&file_names) != 0)
goto out;
}
if (remove(file_names.skey_filename) != 0) {
rc = -errno;
pr_verbose(keystore, "Failed to remove '%s': %s",
file_names.skey_filename, strerror(-rc));
goto out;
}
if (remove(file_names.info_filename) != 0) {
rc = -errno;
pr_verbose(keystore, "Failed to remove '%s': %s",
file_names.info_filename, strerror(-rc));
}
if (_keystore_reencipher_key_exists(&file_names)) {
if (remove(file_names.renc_filename) != 0) {
rc = -errno;
pr_verbose(keystore, "Failed to remove '%s': %s",
file_names.renc_filename, strerror(-rc));
}
}
pr_verbose(keystore, "Successfully removed key '%s'", name);
out:
_keystore_free_key_filenames(&file_names);
if (rc != 0)
pr_verbose(keystore, "Failed to remove key '%s': %s",
name, strerror(-rc));
return rc;
}
/**
* Processing function for the key display function.
*
* @param[in] keystore the keystore
* @param[in] name the name of the key
* @param[in] properties the properties object of the key
* @param[in] file_names the file names used by this key
* @param[in] private private data: struct reencipher_info
*
* @returns 0 if the display is successful, a negative errno value otherwise
*/
static int _keystore_display_key(struct keystore *keystore,
const char *name,
struct properties *properties,
struct key_filenames *file_names,
void *private)
{
struct util_rec *rec = (struct util_rec *)private;
struct secaeskeytoken *secure_key;
size_t secure_key_size;
int rc = 0;
secure_key = (struct secaeskeytoken *)
read_secure_key(file_names->skey_filename,
&secure_key_size, keystore->verbose);
if (secure_key == NULL)
return -EIO;
if (secure_key_size < SECURE_KEY_SIZE) {
pr_verbose(keystore,
"Size of secure key is too small: %lu expected %lu",
secure_key_size, SECURE_KEY_SIZE);
rc = -EIO;
goto out;
}
_keystore_print_record(rec, name, properties, 0,
file_names->skey_filename, secure_key_size,
IS_XTS(secure_key_size) ? secure_key->bitsize * 2
: secure_key->bitsize,
0, 0,
_keystore_reencipher_key_exists(file_names));
out:
free(secure_key);
return rc;
}
/**
* Lists keys in the keystore that matches the filters
*
* @param[in] keystore the key store
* @param[in] name_filter the name filter. Can contain wild cards.
* NULL means no name filter.
* @param[in] volume_filter the volume filter. Can contain wild cards, and
* mutliple volume filters separated by commas.
* The ':dm-name' part of the volume is optional
* for the volume filter. If not specified, the filter
* checks the volume part only.
* NULL means no volume filter.
* @param[in] apqn_filter the APQN filter. Can contain wild cards, and
* mutliple APQN filters separated by commas.
* NULL means no APQN filter.
* @param[in] volume_type The volume type. NULL means no volume type filter.
*
* @returns 0 for success or a negative errno in case of an error
*/
int keystore_list_keys(struct keystore *keystore, const char *name_filter,
const char *volume_filter, const char *apqn_filter,
const char *volume_type)
{
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;
}
rec = _keystore_setup_record(0);
rc = _keystore_process_filtered(keystore, name_filter, volume_filter,
apqn_filter, volume_type,
_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;
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,
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] 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,
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);
}
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 PBKDF2 as key derivation function for LUKS2
* volumes. LUKS2 uses Argon2i as default, but this
* might cause out-of-memory errors when multiple LUKS2
* volumes are opened automatically via /etc/crypttab
*/
util_asprintf(&cmd,
"cryptsetup luksFormat %s%s--type luks2 "
"--master-key-file '%s' --key-size %lu "
"--cipher %s --pbkdf pbkdf2 %s%s%s",
info->batch_mode ? "-q " : "",
keystore->verbose ? "-v " : "",
key_file_name, key_file_size * 8,
cipher_spec, 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] 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,
struct crypt_info *info)
{
char temp[1000];
if (strcasecmp(volume_type, VOLUME_TYPE_PLAIN) == 0) {
if (sector_size > 0) {
sprintf(temp,
"WARNING: volume '%s' is using a sector size "
"of %lu. At the time this utility was "
"developed, systemd's support of crypttab did "
"not support to specify a sector size with "
"plain dm-crypt devices. The generated "
"crypttab entry might or might not work, and "
"might need manual adoptions.", volume,
sector_size);
util_print_indented(temp, 0);
}
sprintf(temp, ",sector-size=%lu", sector_size);
printf("%s\t%s\t%s\tplain,cipher=%s,size=%lu%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) {
printf("%s\t%s\t%s\tluks", dmname, volume,
info->keyfile != NULL ? info->keyfile : "none");
if (info->keyfile != NULL) {
if (info->keyfile_offset > 0)
printf(",keyfile-offset=%lu",
info->keyfile_offset);
if (info->keyfile_size > 0)
printf(",keyfile-size=%lu", info->keyfile_size);
}
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;
}
/**
* Returns the size of the secure key file
*
* @param[in] keystore the keystore
* @param[in] skey_filename the file name of the secure key
*
* @returns the size of the secure key, or -1 in case of an error
*/
static size_t _keystore_get_key_file_size(struct keystore *keystore,
const char *skey_filename)
{
size_t secure_key_size;
struct stat sb;
if (stat(skey_filename, &sb)) {
pr_verbose(keystore, "Key file '%s': %s",
skey_filename, strerror(errno));
return -1;
}
secure_key_size = sb.st_size;
if (secure_key_size < SECURE_KEY_SIZE) {
pr_verbose(keystore,
"Size of secure key is too small: %lu expected %lu",
secure_key_size, SECURE_KEY_SIZE);
return -1;
}
return secure_key_size;
}
/**
* Processing function for the cryptsetup and crypttab functions.
* Extracts the required information and calls the secondary processing function
* contained in struct crypt_info.
*
* @param[in] keystore the keystore
* @param[in] name the name of the key
* @param[in] properties the properties object of the key
* @param[in] file_names the file names used by this key
* @param[in] private private data: struct crypt_info
*
* @returns 0 if the validation is successful, a negative errno value otherwise
*/
static int _keystore_process_crypt(struct keystore *keystore,
const char *name,
struct properties *properties,
struct key_filenames *file_names,
void *private)
{
struct crypt_info *info = (struct crypt_info *)private;
char **volume_list = NULL;
char *cipher_spec = NULL;
char *volume_type = NULL;
size_t secure_key_size;
size_t sector_size = 0;
char *volumes = NULL;
char *dmname;
char *temp;
int rc = 0;
char *vol;
char *ch;
int i;
secure_key_size = _keystore_get_key_file_size(keystore,
file_names->skey_filename);
if (secure_key_size < SECURE_KEY_SIZE) {
pr_verbose(keystore,
"Size of secure key is too small: %lu expected %lu",
secure_key_size, SECURE_KEY_SIZE);
rc = -EIO;
goto out;
}
cipher_spec = _keystore_build_cipher_spec(properties,
IS_XTS(secure_key_size));
if (cipher_spec == NULL) {
rc = -EINVAL;
goto out;
}
volumes = properties_get(properties, PROP_NAME_VOLUMES);
if (volumes == NULL)
return -EINVAL;
volume_list = str_list_split(volumes);
temp = properties_get(properties, PROP_NAME_SECTOR_SIZE);
if (temp != NULL) {
util_assert(sscanf(temp, "%lu", &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, 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);
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;
}
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, _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, _keystore_process_crypt,
&info);
str_list_free_string_array(info.volume_filter);
if (rc != 0)
pr_verbose(keystore, "Cryptsetup failed with: %s",
strerror(-rc));
else
pr_verbose(keystore, "Successfully generated crypttab entries");
return rc;
}
/**
* Frees a keystore object
*
* @param[in] keystore the key store
*/
void keystore_free(struct keystore *keystore)
{
util_assert(keystore != NULL, "Internal error: keystore is NULL");
_keystore_unlock_repository(keystore);
free(keystore->directory);
free(keystore);
}