forked from pool/s390-tools
a7f8ed0265
Lots of features implemented for SLES15 SP1. OBS-URL: https://build.opensuse.org/request/show/648783 OBS-URL: https://build.opensuse.org/package/show/Base:System/s390-tools?expand=0&rev=57
3428 lines
97 KiB
Diff
3428 lines
97 KiB
Diff
Subject: zkey: Add keystore implementation
|
|
From: Philipp Rudo <prudo@linux.ibm.com>
|
|
|
|
Summary: zkey: Add support of protected key crypto for dm-crypt.
|
|
Description: Support the usage of protected key crypto for dm-crypt disks in
|
|
plain format by providing a tool to manage a key repository
|
|
allowing to associate secure keys with disk partitions or logical
|
|
volumes.
|
|
Upstream-ID: c944f23d7e1f983499b4c5fcf04430dc49902f04
|
|
Problem-ID: SEC1800
|
|
|
|
Upstream-Description:
|
|
|
|
zkey: Add keystore implementation
|
|
|
|
Add a keystore implementation that stores secure AES keys in a
|
|
key repository, located in a directory, e.g. '/etc/zkey/repository'.
|
|
The keystore allows you to generate, validate, re-encipher, modify,
|
|
list, delete, etc secure keys.
|
|
|
|
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
|
|
Reviewed-by: Hendrik Brueckner <brueckner@linux.ibm.com>
|
|
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
|
|
|
|
|
|
Signed-off-by: Philipp Rudo <prudo@linux.ibm.com>
|
|
---
|
|
zkey/Makefile | 3
|
|
zkey/keystore.c | 3299 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
zkey/keystore.h | 77 +
|
|
3 files changed, 3378 insertions(+), 1 deletion(-)
|
|
|
|
--- a/zkey/Makefile
|
|
+++ b/zkey/Makefile
|
|
@@ -27,8 +27,9 @@ libs = $(rootdir)/libutil/libutil.a
|
|
zkey.o: zkey.c pkey.h misc.h
|
|
pkey.o: pkey.c pkey.h
|
|
properties.o: properties.c properties.h
|
|
+keystore.o: keystore.c keystore.h properties.h
|
|
|
|
-zkey: zkey.o pkey.o properties.o $(libs)
|
|
+zkey: zkey.o pkey.o properties.o keystore.o $(libs)
|
|
|
|
install: all
|
|
$(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR)
|
|
--- /dev/null
|
|
+++ b/zkey/keystore.c
|
|
@@ -0,0 +1,3299 @@
|
|
+/*
|
|
+ * 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 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 "Encrypted with"
|
|
+#define REC_CREATION_TIME "Created"
|
|
+#define REC_CHANGE_TIME "Changed"
|
|
+#define REC_REENC_TIME "Re-enciphered"
|
|
+
|
|
+#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 faild on file '%s': %s", filename, strerror(-rc));
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ if (chown(filename, geteuid(), keystore->owner) != 0) {
|
|
+ rc = -errno;
|
|
+ warnx("chown faild 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;
|
|
+}
|
|
+
|
|
+typedef int (*check_association_t)(const char *value, bool remove,
|
|
+ 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, &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, &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, &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 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] 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,
|
|
+ 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 = 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;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * 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[out] normalized normalized value on return or NULL if no change
|
|
+ * @param[in] private private data (not used here)
|
|
+ *
|
|
+ * @returns 0 if successful, a negative errno value otherwise
|
|
+ */
|
|
+static int _keystore_apqn_check(const char *apqn, bool remove,
|
|
+ char **normalized, void *UNUSED(private))
|
|
+{
|
|
+ int rc, card, domain;
|
|
+ regmatch_t pmatch[1];
|
|
+ regex_t reg_buf;
|
|
+
|
|
+ *normalized = NULL;
|
|
+
|
|
+ rc = regcomp(®_buf, "[[:xdigit:]]+\\.[[:xdigit:]]", REG_EXTENDED);
|
|
+ if (rc != 0)
|
|
+ return -EIO;
|
|
+
|
|
+ rc = regexec(®_buf, apqn, (size_t) 1, pmatch, 0);
|
|
+ if (rc != 0) {
|
|
+ warnx("the APQN '%s' is not valid", apqn);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (sscanf(apqn, "%x.%x", &card, &domain) != 2)
|
|
+ return -EINVAL;
|
|
+
|
|
+ util_asprintf(normalized, "%02x.%04x", card, domain);
|
|
+
|
|
+ if (remove)
|
|
+ return 0;
|
|
+
|
|
+ rc = _keystore_is_apqn_online(card, domain);
|
|
+ if (rc != 1) {
|
|
+ warnx("The APQN %02x.%04x is %s", card, domain,
|
|
+ rc == -1 ? "not a CCA card" : "not online");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+struct volume_check {
|
|
+ struct keystore *keystore;
|
|
+ const char *name;
|
|
+ const char *volume;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * 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;
|
|
+
|
|
+ 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[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,
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ rc = _keystore_process_filtered(info->keystore, NULL, info->volume,
|
|
+ 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("'%s' does not exist", directory);
|
|
+ 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;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * 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] 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.
|
|
+ */
|
|
+static int _keystore_create_info_file(struct keystore *keystore,
|
|
+ const char *name,
|
|
+ const char *info_filename,
|
|
+ const char *description,
|
|
+ const char *volumes, const char *apqns,
|
|
+ size_t sector_size)
|
|
+{
|
|
+ struct volume_check vol_check = { .keystore = keystore, .name = name };
|
|
+ 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, NULL);
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ rc = properties_save(key_props, info_filename, 1);
|
|
+ if (rc != 0) {
|
|
+ pr_verbose(keystore,
|
|
+ "Key info file '%s' could not be written: %s",
|
|
+ info_filename, strerror(-rc));
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ rc = _keystore_set_file_permission(keystore, info_filename);
|
|
+ if (rc != 0) {
|
|
+ remove(info_filename);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ properties_free(key_props);
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Extracts a card/domain pair from the specified APQns, or uses AUTOSELECT
|
|
+ * if no APQNs are specified.
|
|
+ */
|
|
+static int _keystore_get_card_domain(const char *apqns, unsigned int *card,
|
|
+ unsigned int *domain)
|
|
+{
|
|
+ char **apqn_list;
|
|
+ char *normalized = NULL;
|
|
+ int rc = 0;
|
|
+
|
|
+ *card = AUTOSELECT;
|
|
+ *domain = AUTOSELECT;
|
|
+
|
|
+ if (apqns == NULL)
|
|
+ return 0;
|
|
+
|
|
+ apqn_list = str_list_split(apqns);
|
|
+ if (apqn_list[0] == NULL)
|
|
+ goto out;
|
|
+
|
|
+ rc = _keystore_apqn_check(apqn_list[0], 0, &normalized, NULL);
|
|
+ if (normalized != NULL)
|
|
+ free(normalized);
|
|
+ if (rc != 0)
|
|
+ goto out;
|
|
+
|
|
+ if (sscanf(apqn_list[0], "%x.%x", card, domain) != 2) {
|
|
+ rc = -EINVAL;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+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] 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] 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, size_t sector_size,
|
|
+ size_t keybits, bool xts, const char *clear_key_file,
|
|
+ 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.info_filename,
|
|
+ description, volumes, apqns,
|
|
+ sector_size);
|
|
+ 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 && rc != -EEXIST)
|
|
+ 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] 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 kley to import
|
|
+ *
|
|
+ * @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, size_t sector_size,
|
|
+ const char *import_file)
|
|
+{
|
|
+ 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.info_filename,
|
|
+ description, volumes, apqns,
|
|
+ sector_size);
|
|
+ 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 && rc != -EEXIST)
|
|
+ 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. IfNULL then the APQNs
|
|
+ * are not changed.
|
|
+ * @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.
|
|
+ *
|
|
+ * @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, long int sector_size)
|
|
+{
|
|
+ struct volume_check vol_check = { .keystore = keystore, .name = name };
|
|
+ struct key_filenames file_names = { NULL, NULL, NULL };
|
|
+ struct properties *key_props = NULL;
|
|
+ char temp[10];
|
|
+ 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, NULL);
|
|
+ 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;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ rc = _keystore_set_file_permission(keystore, file_names.info_filename);
|
|
+ if (rc != 0)
|
|
+ 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 };
|
|
+ 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);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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 (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, 23, 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_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 *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 *reencipher;
|
|
+ char *creation;
|
|
+ char *volumes;
|
|
+ char *change;
|
|
+ char *apqns;
|
|
+ char *temp;
|
|
+
|
|
+ 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", §or_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);
|
|
+
|
|
+ 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_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);
|
|
+}
|
|
+
|
|
+struct validate_info {
|
|
+ struct util_rec *rec;
|
|
+ int pkey_fd;
|
|
+ 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 "
|
|
+ "and should be re-enciphered with the "
|
|
+ "CURRENT CCA master key as soon as "
|
|
+ "possible to avoid data loss\n", 0);
|
|
+ info->num_warnings++;
|
|
+ }
|
|
+ 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)
|
|
+ *
|
|
+ * @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, 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.rec = rec;
|
|
+ info.num_valid = 0;
|
|
+ info.num_invalid = 0;
|
|
+ info.num_warnings = 0;
|
|
+
|
|
+ rc = _keystore_process_filtered(keystore, name_filter, NULL,
|
|
+ apqn_filter,
|
|
+ _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 in 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("Possibly the CCA master key not yet been set?");
|
|
+ } 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, ¶ms,
|
|
+ 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;
|
|
+
|
|
+ rc = _keystore_set_file_permission(keystore, out_file);
|
|
+ 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 = properties_save(properties, file_names->info_filename, 1);
|
|
+ if (rc != 0) {
|
|
+ pr_verbose(keystore,
|
|
+ "Failed to write key info file '%s': %s",
|
|
+ file_names->info_filename, strerror(-rc));
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ rc = _keystore_set_file_permission(keystore,
|
|
+ file_names->info_filename);
|
|
+ if (rc != 0)
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ 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 has completed for "
|
|
+ "key '%s'. Run 'zkey reencipher' with option "
|
|
+ "'--complete' when the NEW CCA master key has "
|
|
+ "been set (moved to the CURRENT master key "
|
|
+ "register) 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,
|
|
+ _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 };
|
|
+ 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 *volumes = NULL;
|
|
+ char **volume_list = NULL;
|
|
+ char str[20];
|
|
+ int rc, i;
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ volumes = properties_get(key_prop, PROP_NAME_VOLUMES);
|
|
+ if (volumes != NULL && strlen(volumes) > 0) {
|
|
+ volume_list = str_list_split(volumes);
|
|
+
|
|
+ warnx("When you remove key '%s' the following volumes will "
|
|
+ "no longer be usable:", name);
|
|
+ for (i = 0; volume_list[i] != NULL; i++)
|
|
+ fprintf(stderr, "%s\n", volume_list[i]);
|
|
+ }
|
|
+
|
|
+ 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);
|
|
+ if (volume_list != NULL)
|
|
+ str_list_free_string_array(volume_list);
|
|
+
|
|
+ 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.
|
|
+ *
|
|
+ * @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)
|
|
+{
|
|
+ struct util_rec *rec;
|
|
+ int rc;
|
|
+
|
|
+ util_assert(keystore != NULL, "Internal error: keystore is NULL");
|
|
+
|
|
+ rec = _keystore_setup_record(0);
|
|
+
|
|
+ rc = _keystore_process_filtered(keystore, name_filter, volume_filter,
|
|
+ apqn_filter, _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:/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;
|
|
+ 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,
|
|
+ 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 sector_size the sector size in bytes or 0 if not specified
|
|
+ * @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,
|
|
+ struct crypt_info *info)
|
|
+{
|
|
+ char temp[100];
|
|
+ int rc = 0;
|
|
+ char *cmd;
|
|
+
|
|
+ sprintf(temp, "--sector-size %lu ", sector_size);
|
|
+ util_asprintf(&cmd,
|
|
+ "cryptsetup plainOpen %s--key-file '%s' --key-size %lu "
|
|
+ "--cipher %s %s%s %s",
|
|
+ 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);
|
|
+ }
|
|
+
|
|
+ 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 sector_size the sector size in bytes or 0 if not specified
|
|
+ * @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,
|
|
+ struct crypt_info *UNUSED(info))
|
|
+{
|
|
+ char temp[1000];
|
|
+
|
|
+ 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 may or may not work, and may 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,hash=plain%s\n",
|
|
+ dmname, volume, key_file_name, cipher_spec, key_file_size * 8,
|
|
+ sector_size > 0 ? temp : "");
|
|
+
|
|
+ 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;
|
|
+ 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", §or_size) == 1,
|
|
+ "Internal error: sscanf failed");
|
|
+ free(temp);
|
|
+ }
|
|
+
|
|
+ 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, 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);
|
|
+ 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
|
|
+ *
|
|
+ * @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)
|
|
+{
|
|
+ struct crypt_info info = { 0 };
|
|
+ int rc;
|
|
+
|
|
+ util_assert(keystore != NULL, "Internal error: keystore is NULL");
|
|
+
|
|
+ if (volume_filter == NULL)
|
|
+ volume_filter = "*";
|
|
+ info.execute = execute;
|
|
+ info.volume_filter = str_list_split(volume_filter);
|
|
+ info.process_func = _keystore_process_cryptsetup;
|
|
+
|
|
+ rc = _keystore_process_filtered(keystore, NULL, volume_filter, NULL,
|
|
+ _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.
|
|
+ *
|
|
+ * @returns 0 for success or a negative errno in case of an error
|
|
+ */
|
|
+int keystore_crypttab(struct keystore *keystore, const char *volume_filter)
|
|
+{
|
|
+ struct crypt_info info = { 0 };
|
|
+ int rc;
|
|
+
|
|
+ util_assert(keystore != NULL, "Internal error: keystore is NULL");
|
|
+
|
|
+ if (volume_filter == NULL)
|
|
+ volume_filter = "*";
|
|
+ info.volume_filter = str_list_split(volume_filter);
|
|
+ info.process_func = _keystore_process_crypttab;
|
|
+
|
|
+ rc = _keystore_process_filtered(keystore, NULL, volume_filter, NULL,
|
|
+ _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);
|
|
+}
|
|
--- /dev/null
|
|
+++ b/zkey/keystore.h
|
|
@@ -0,0 +1,77 @@
|
|
+/*
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef KEYSTORE_H
|
|
+#define KEYSTORE_H
|
|
+
|
|
+#include <stdbool.h>
|
|
+
|
|
+#include "pkey.h"
|
|
+
|
|
+struct keystore {
|
|
+ bool verbose;
|
|
+ char *directory;
|
|
+ int lock_fd;
|
|
+ mode_t mode;
|
|
+ gid_t owner;
|
|
+};
|
|
+
|
|
+struct keystore *keystore_new(const char *directory, bool verbose);
|
|
+
|
|
+int keystore_generate_key(struct keystore *keystore, const char *name,
|
|
+ const char *description, const char *volumes,
|
|
+ const char *apqns, size_t sector_size,
|
|
+ size_t keybits, bool xts, const char *clear_key_file,
|
|
+ int pkey_fd);
|
|
+
|
|
+int keystore_import_key(struct keystore *keystore, const char *name,
|
|
+ const char *description, const char *volumes,
|
|
+ const char *apqns, size_t sector_size,
|
|
+ const char *import_file);
|
|
+
|
|
+int keystore_change_key(struct keystore *keystore, const char *name,
|
|
+ const char *description, const char *volumes,
|
|
+ const char *apqns, long int sector_size);
|
|
+
|
|
+int keystore_rename_key(struct keystore *keystore, const char *name,
|
|
+ const char *newname);
|
|
+
|
|
+int keystore_validate_key(struct keystore *keystore, const char *name_filter,
|
|
+ const char *apqn_filter, int pkey_fd);
|
|
+
|
|
+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);
|
|
+
|
|
+int keystore_copy_key(struct keystore *keystore, const char *name,
|
|
+ const char *newname, const char *volumes);
|
|
+
|
|
+int keystore_export_key(struct keystore *keystore, const char *name,
|
|
+ const char *export_file);
|
|
+
|
|
+int keystore_remove_key(struct keystore *keystore, const char *name,
|
|
+ bool quiet);
|
|
+
|
|
+int keystore_list_keys(struct keystore *keystore, const char *name_filter,
|
|
+ const char *volume_filter, const char *apqn_filter);
|
|
+
|
|
+int keystore_cryptsetup(struct keystore *keystore, const char *volume_filter,
|
|
+ bool execute);
|
|
+
|
|
+int keystore_crypttab(struct keystore *keystore, const char *volume_filter);
|
|
+
|
|
+void keystore_free(struct keystore *keystore);
|
|
+
|
|
+
|
|
+
|
|
+#endif
|