diff --git a/zkey/Makefile b/zkey/Makefile index 100e4087..78602338 100644 --- a/zkey/Makefile +++ b/zkey/Makefile @@ -73,9 +73,10 @@ properties.o: check-dep-zkey properties.c properties.h keystore.o: keystore.c keystore.h properties.h pkey.h cca.h ep11.h utils.h zkey-cryptsetup.o: check-dep-zkey-cryptsetup zkey-cryptsetup.c pkey.h cca.h \ ep11.h misc.h utils.h +kms.o: kms.c kms.h kms-plugin.h zkey: LDLIBS = -ldl -lcrypto -zkey: zkey.o pkey.o cca.o ep11.o properties.o keystore.o utils.o $(libs) +zkey: zkey.o pkey.o cca.o ep11.o properties.o keystore.o utils.o kms.o $(libs) $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ zkey-cryptsetup: LDLIBS = -ldl -lcryptsetup -ljson-c -lcrypto @@ -86,13 +87,14 @@ install-common: $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man1 -install-zkey: +install-zkey: zkey $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 zkey $(DESTDIR)$(USRBINDIR) $(INSTALL) -m 644 -c zkey.1 $(DESTDIR)$(MANDIR)/man1 $(INSTALL) -d -m 770 $(DESTDIR)$(SYSCONFDIR)/zkey $(INSTALL) -d -m 770 $(DESTDIR)$(SYSCONFDIR)/zkey/repository + $(INSTALL) -m 644 -c kms-plugins.conf $(DESTDIR)$(SYSCONFDIR)/zkey -install-zkey-cryptsetup: +install-zkey-cryptsetup: zkey-cryptsetup $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 zkey-cryptsetup $(DESTDIR)$(USRBINDIR) $(INSTALL) -m 644 -c zkey-cryptsetup.1 $(DESTDIR)$(MANDIR)/man1 diff --git a/zkey/kms-plugin.h b/zkey/kms-plugin.h new file mode 100644 index 00000000..6a5bac57 --- /dev/null +++ b/zkey/kms-plugin.h @@ -0,0 +1,539 @@ +/* + * zkey - Generate, re-encipher, and validate secure keys + * + * This header file defines the interface to a Key Management System (KMS) + * Plugin. + * + * Copyright IBM Corp. 2020 + * + * 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 KMS_PLUGIN_H +#define KMS_PLUGIN_H + +#include +#include + +#include "lib/util_opt.h" + +/** + * Informs a KMS plugin that it is bound to a zkey repository. + * + * Note: This function is called before kms_initialize()! + * + * @param config_path name of a directory where the KMS plugin can store + * its configuration and other files it needs to store + * + * @returns 0 on success, or a negative errno in case of an error. + */ +int kms_bind(const char *config_path); + +typedef void *kms_handle_t; + +/** + * Initializes a KMS plugin for usage by zkey. When a repository is bound to a + * KMS plugin, zkey calls this function when opening the repository. + * + * @param config_path name of a directory where the KMS plugin can store + * its configuration and other files it needs to store + * @param verbose if true, the plugin should write verbose or debug + * messages to stderr during further processing. + * + * @returns a KMS plugin handle, or NULL in case of an error. + */ +kms_handle_t kms_initialize(const char *config_path, bool verbose); + +/** + * Terminates the use of a KMS plugin. When a repository is bound to a KMS + * plugin, zkey calls this function when closing the repository. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * + * @returns 0 on success, or a negative errno in case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + */ +int kms_terminate(const kms_handle_t handle); + +/** + * Returns a textual message about the last occurred error that occurred in the + * last called KMS plugin function. If no error occurred (i.e. the last plugin + * function returned rc = 0), then NULL is returned. + * The returned string is static or contained within the handle. It is valid + * only until the next KMS plugin function is called. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * + * @returns an error message of NULL + */ +const char *kms_get_last_error(const kms_handle_t handle); + +/** + * Returns true if the KMS plugin supports the specified key type. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * @param key_type the zkey key type, euch as 'CCA-AESDATA', + * 'CCA-AESCIPHER', 'EP11-AES'. + * + * @returns true if the KMS plugin supports the key type, false otherwise. + */ +bool kms_supports_key_type(const kms_handle_t handle, const char *key_type); + +/** + * Displays information about the KMS Plugin and its current configuration on + * stdout. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * + * @returns 0 on success, or a negative errno in case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + */ +int kms_display_info(const kms_handle_t handle); + +#define KMS_COMMAND_CONFIGURE "configure" +#define KMS_COMMAND_REENCIPHER "reencipher" +#define KMS_COMMAND_GENERATE "generate" +#define KMS_COMMAND_REMOVE "remove" +#define KMS_COMMAND_LIST "list" +#define KMS_COMMAND_LIST_IMPORT "list-import" + +/** + * Returns a list of KMS specific command line options that zkey should accept + * and pass to the appropriate KMS plugin function. The option list must be + * terminated by an UTIL_OPT_END entry (see util_opt.h). The options returned + * must not interfere with the already defined options of the zkey command. + * Field 'command' of the returned options should either be NULL or specify + * the command that it is for. + * + * If max_opts is not -1, then only up to max_opts options are allowed. If more + * options are returned, only up to max_opts options are used by zkey. + * + * @param command the command for which the KMS-specific options are + * to be returned, see KMS_COMMAND_xxx defines + * @param max_opts maximum number of options allowed. If -1 then there + * is no limit. + * + * @returns a list of options terminated by an UTIL_OPT_END entry, or NULL in + * case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + */ +const struct util_opt *kms_get_command_options(const char *command, + int max_opts); + +struct kms_apqn { + unsigned short card; + unsigned short domain; +}; + +struct kms_option { + int option; /** option character as in struct option */ + const char *argument; /** argument of the option (if any) */ +}; + + +/** + * Configures (or re-configures) a KMS plugin. This function can be called + * several times to configure a KMS plugin is several steps (if supported by the + * KMS plugin). In case a configuration is not fully complete, this function + * may return -EAGAIN to indicate that it has accepted the configuration so far, + * but the configuration needs to be completed. + * + * No kms_login is performed before calling this function. If the KMS plugin + * requires a login for the configuration, it must perform it itself within this + * function. + * + * A KMS plugin must be associated with at least one APQN. Thus, in a multi-step + * configuration, a list f APQNs must be specified at least once. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * @param apqns a list of APQNs to associate with the KMS plugin, or + * NULL if no APQNs are specified. + * @param num_apqns number of APQNs in above array. 0 if no APQNs are + * specified. + * @param options a list of options as specified by the user. These + * options are a subset of the possible options as + * returned by kms_get_command_options() with command + * KMS_COMMAND_CONFIGURE. + * @param num_options number of options in above array. + * + * @returns 0 on success, or a negative errno in case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + * -EAGAIN to indicate that the specified configuration was accepted so far, but + * the configuration is still incomplete, and needs to be completed. + */ +int kms_configure(const kms_handle_t handle, + const struct kms_apqn *apqns, size_t num_apqns, + const struct kms_option *options, size_t num_options); + +/** + * De-configures a KMS plugin. This is called by zkey when a repository is + * unbound from a KMS plugin. It gives the KMS plugin the chance to gracefully + * remove any files that the plugin has stored in its config directory. zkey + * will unconditionally remove all left over files when this function returns. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * + * @returns 0 on success, or a negative errno in case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + */ +int kms_deconfigure(const kms_handle_t handle); + +/** + * Allows the KMS plugin to perform a login to the KMS (if required). This + * function is called at least once before any key operation function, typically + * shortly after opening the repository. + * The KMS plugin may prompt the user (by reading from stdin) for its + * credentials, if needed. + * + * It is suggested that a KMS plugin performs a login with the KMS once, and + * stores a login token (or similar) in its config directory. The next time + * the kms_login function is called, the login token can be reused (if still + * valid). This avoids to prompt the user for every key operation. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * + * @returns 0 on success, or a negative errno in case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + */ +int kms_login(const kms_handle_t handle); + +enum kms_reencipher_mode { + KMS_REENC_MODE_AUTO = 0, + KMS_REENC_MODE_IN_PLACE = 1, + KMS_REENC_MODE_STAGED = 2, + KMS_REENC_MODE_STAGED_COMPLETE = 3, +}; + +enum kms_reenc_mkreg { + KMS_REENC_MKREG_AUTO = 0, + KMS_REENC_MKREG_FROM_OLD = 1, + KMS_REENC_MKREG_TO_NEW = 2, + KMS_REENC_MKREG_FROM_OLD_TO_NEW = 3, +}; + +/** + * Called when the master keys of an APQN associated with the KMS plugin has + * been changed. The KMS plugin can then re-encipher all its secure keys (if + * any) that it has stored in its config directory. + * + * Keys that have been generated by the KMS plugin and stored in the zkey + * repository do not need to be re-enciphered by the KMS plugin. Those are + * re-enciphered by zkey without the help of the KMS plugin. + * + * HSM have different master key registers. Typically a CURRENT and a NEW master + * key register exists. The NEW register may be loaded with the new to be set + * master key, and secure keys can be re-enciphered with it proactively. + * + * CCA also supports an OLD master key register, that contains the previously + * used master key. You thus can re-encipher a secure key that is currently + * enciphered with the master key from the OLD register with the master key + * from the CURRENT register. + * + * HSMs may also support different master keys for different key types or + * algorithms. It is up to the KMS plugin to know which master key registers + * are used for its secure keys + * + * A staged re-encipherment is performed by re-enciphering a secure key with + * the new HSM master key, without making it available for use in the first + * stage. Only when the staged re-encipherment is completed, then the previously + * re-enciphered secure key is make available for use and the old on is removed. + * + * An in-place re-encipherment replaces the secure key right away with its + * re-enciphered version. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * @param mode Re-encipherment mode + * @param mkreg Re-encipherment register selection + * @param options a list of options as specified by the user. These + * options are a subset of the possible options as + * returned by kms_get_command_options() with command + * KMS_COMMAND_REENCIPHER. + * @param num_options number of options in above array. + * + * @returns 0 on success, or a negative errno in case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + */ +int kms_reenciper(const kms_handle_t handle, + enum kms_reencipher_mode mode, enum kms_reenc_mkreg mkreg, + const struct kms_option *options, size_t num_options); + +struct kms_property { + const char *name; + const char *value; +}; + +#define KMS_KEY_ID_SIZE 256 +#define KMS_KEY_LABEL_SIZE 256 + +enum kms_key_mode { + KMS_KEY_MODE_NON_XTS = 0, + KMS_KEY_MODE_XTS_1 = 1, + KMS_KEY_MODE_XTS_2 = 2, +}; + +/** + * Generates a key in or with the KMS and returns a secure key that is + * enciphered under the current HSM master key. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * @param key_type the zkey key type, euch as 'CCA-AESDATA', + * 'CCA-AESCIPHER', 'EP11-AES'. + * @param key_bits the key bit size (e.g. 256 for an AES 256 bit key). + * 0 to use the plugin's default key size. + * @param key_mode mode of the key, e.g. non-XTS key, or first/second + * XTS key to be generated. A KMS plugin may need to + * use different key templates for the different key + * modes (i.e. key parts). + * @param properties a list of properties to associate the key with + * @param num_properties the number of properties in above array + * @param options a list of options as specified by the user. These + * options are a subset of the possible options as + * returned by kms_get_command_options() with command + * KMS_COMMAND_GENERATE. + * @param num_options number of options in above array. + * @param key_blob a buffer to return the key blob. The size of the + * buffer is specified in key_blob_length + * @param key_blob_length on entry: the size of the key_blob buffer. + * on exit: the size of the key blob returned. + * @param key_id a buffer to return the key-ID of the generated key. + * The key-id is a textual identifier uniquely + * identifying a key in the KMS and the KMS plugin. + * The returned key-id contains the terminating zero. + * @paran key_id_size size of the key_id buffer. It should be at least + * KMS_KEY_ID_SIZE + 1 bytes large. + * @param key_label a buffer to return the key-label of the generated + * key. The key-label is a textual identifier used to + * identify a key in the user interface of the KMS. + * A key label may be equal to the key-ID, or it may + * different. The returned key-label contains the + * terminating zero. + * @paran key_label_size size of the key_lanble buffer. It should be at least + * KMS_KEY_LABEL_SIZE + 1 bytes large. + * + * @returns 0 on success, or a negative errno in case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + */ +int kms_generate_key(const kms_handle_t handle, const char *key_type, + size_t key_bits, enum kms_key_mode key_mode, + const struct kms_property *properties, + size_t num_properties, const struct kms_option *options, + size_t num_options, unsigned char *key_blob, + size_t *key_blob_length, char *key_id, + size_t key_id_size, char *key_label, + size_t key_label_size); + +/** + * Sets (adds/replaces/removes) properties of a key. Already existing properties + * with the same property name are replaced, non-existing properties are added. + * To remove a property, set the property value to NULL. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * @param key_id the key-ID to set the properties for + * @param properties a list of properties to set + * @param num_properties the number of properties in above array + * + * @returns 0 on success, or a negative errno in case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + */ +int kms_set_key_properties(const kms_handle_t handle, const char *key_id, + const struct kms_property *properties, + size_t num_properties); + +/** + * Gets properties of a key. + * + * The returned list of properties must be freed by the caller. Each property + * name and value must be freed individually (using free()), as well as the + * complete array. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * @param key_id the key-ID to set the properties for + * @param properties On return: a list of properties + * @param num_properties On return: the number of properties in above array + * + * @returns 0 on success, or a negative errno in case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + */ +int kms_get_key_properties(const kms_handle_t handle, const char *key_id, + struct kms_property **properties, + size_t *num_properties); + +/** + * Called when zkey removes a KMS-bound key from the zkey repository. The KMS + * plugin can then set the state of the key in the KMS, or remove it also from + * the KMS (this is usually not done). + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * @param key_id the key-ID to set the properties for + * @param options a list of options as specified by the user. These + * options are a subset of the possible options as + * returned by kms_get_command_options() with command + * KMS_COMMAND_REMOVE. + * @param num_options number of options in above array. + * + * @returns 0 on success, or a negative errno in case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + */ +int kms_remove_key(const kms_handle_t handle, const char *key_id, + const struct kms_option *options, size_t num_options); + +/** + * Callback used with the kms_list_keys() function. Called for each key. + * + * @param key_id the key-ID of the key + * @param label the label of the key. + * @param key_type the type of the key (CCA-AESDATA, etc) + * @param key_bits the key size in bits + * @param properties a list of properties of the key + * @param num_properties the number of properties in above array + * @param addl_info_argz an argz string containing additional KMS plugin + * specific infos to be displayed, or NULL if none. + * @param addl_info_len length of the argz string in addl_info_argz + * @param private_data the private data pointer + * + * @returns 0 on success, or a negative errno in case of an error. + */ +typedef int (*kms_list_callback)(const char *key_id, const char *label, + const char *key_type, size_t key_bits, + const struct kms_property *properties, + size_t num_properties, + const char *addl_info_argz, + size_t addl_info_len, void *private_data); + +/** + * List keys managed by the KMS. This list is independent of the zkey key + * repository. It lists keys as known by the KMS. + * + * Note: The list function is used to display a list of keys managed by the KMS, + * but also for producing a list of keys to import. Because the two use cases + * might require different plugin specific options, kms_get_command_options() + * allows two commands to get options for: + * - KMS_COMMAND_LIST: Options for displaying a list. + * - KMS_COMMAND_LIST_IMPORT: Options for building a list to import keys. + * Functionkms_list_keys() is called for both cases with the appropriate + * options as returned by kms_get_command_options() for the case. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * @param label_pattern a pattern of the label used to filter the keys, or + * NULL if no label pattern is specified. + * @param properties a list of properties used to to filter the keys, or + * NULL if no properties filter is specified. + * @param num_properties the number of properties in above array. + * @param options a list of options as specified by the user. These + * options are a subset of the possible options as + * returned by kms_get_command_options() with command + * KMS_COMMAND_LIST or KMS_COMMAND_LIST_IMPORT. + * @param num_options number of options in above array.* + * @param callback a callback function that is called for each key that + * matches the filter (if any). + * @private_data a private pointer passed as is to the callback + * function. Can be used to pass user specific + * information to the callback. + * + * @returns 0 on success, or a negative errno in case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + */ +int kms_list_keys(const kms_handle_t handle, const char *label_pattern, + const struct kms_property *properties, size_t num_properties, + const struct kms_option *options, size_t num_options, + kms_list_callback callback, void *private_data); + +/** + * Imports a key from the KMS and returns a secure key that is + * enciphered under the current HSM master key. + * + * @param handle the KMS plugin handle obtained from kms_initialize() + * @param key_id the key-ID of the key to import + * @param key_blob a buffer to return the key blob. The size of the + * buffer is specified in key_blob_length + * @param key_blob_length on entry: the size of the key_blob buffer. + * on exit: the size of the key blob returned. + * + * @returns 0 on success, or a negative errno in case of an error. + * Function kms_get_last_error() can be used to obtain more details about the + * error. + */ +int kms_import_key(const kms_handle_t handle, const char *key_id, + unsigned char *key_blob, size_t *key_blob_length); + +#define KMS_API_VERSION_1 1 + +struct kms_functions { + unsigned int api_version; + int (*kms_bind)(const char *config_path); + kms_handle_t (*kms_initialize)(const char *config_path, bool verbose); + int (*kms_terminate)(const kms_handle_t handle); + const char *(*kms_get_last_error)(const kms_handle_t handle); + bool (*kms_supports_key_type)(const kms_handle_t handle, + const char *key_type); + int (*kms_display_info)(const kms_handle_t handle); + const struct util_opt *(*kms_get_command_options)( + const char *command, int max_opts); + int (*kms_configure)(const kms_handle_t handle, + const struct kms_apqn *apqns, size_t num_apqns, + const struct kms_option *options, + size_t num_options); + int (*kms_deconfigure)(const kms_handle_t handle); + int (*kms_login)(const kms_handle_t handle); + int (*kms_reenciper)(const kms_handle_t handle, + enum kms_reencipher_mode mode, + enum kms_reenc_mkreg mkreg, + const struct kms_option *options, + size_t num_options); + int (*kms_generate_key)(const kms_handle_t handle, + const char *key_type, size_t key_bits, + enum kms_key_mode key_mode, + const struct kms_property *properties, + size_t num_properties, + const struct kms_option *options, + size_t num_options, + unsigned char *key_blob, + size_t *key_blob_length, char *key_id, + size_t key_id_size, char *key_label, + size_t key_label_size); + int (*kms_set_key_properties)(const kms_handle_t handle, + const char *key_id, + const struct kms_property *properties, + size_t num_properties); + int (*kms_get_key_properties)(const kms_handle_t handle, + const char *key_id, + struct kms_property **properties, + size_t *num_properties); + int (*kms_remove_key)(const kms_handle_t handle, const char *key_id, + const struct kms_option *options, + size_t num_options); + int (*kms_list_keys)(const kms_handle_t handle, + const char *label_pattern, + const struct kms_property *properties, + size_t num_properties, + const struct kms_option *options, + size_t num_options, + kms_list_callback callback, void *private_data); + int (*kms_import_key)(const kms_handle_t handle, const char *key_id, + unsigned char *key_blob, + size_t *key_blob_length); +}; + +/** + * Returns an address of a structure containing the KMS plugin functions. + * This function is exported by the KMS plugin, and its address is obtain + * via dlsym() after loading the plugin via dlopen(). + * * + * @returns the address of a structure or NULL in case of an error. + */ +const struct kms_functions *kms_get_functions(void); + +#endif diff --git a/zkey/kms-plugins.conf b/zkey/kms-plugins.conf new file mode 100644 index 00000000..03439f5a --- /dev/null +++ b/zkey/kms-plugins.conf @@ -0,0 +1,3 @@ +# List of zkey KMS plugins +# Format: +# = \ No newline at end of file diff --git a/zkey/kms.c b/zkey/kms.c new file mode 100644 index 00000000..e2b5e21a --- /dev/null +++ b/zkey/kms.c @@ -0,0 +1,741 @@ +/* + * zkey - Generate, re-encipher, and validate secure keys + * + * Copyright IBM Corp. 2020 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/util_base.h" +#include "lib/util_libc.h" +#include "lib/util_rec.h" +#include "lib/util_panic.h" + +#include "kms.h" + +#define ENVVAR_ZKEY_REPOSITORY "ZKEY_REPOSITORY" +#define DEFAULT_KEYSTORE "/etc/zkey/repository" + +#define ENVVAR_ZKEY_KMS_PLUGINS "ZKEY_KMS_PLUGINS" +#define DEFAULT_KMS_PLUGINS "/etc/zkey/kms-plugins.conf" + +#define KMS_CONFIG_FILE "kms.conf" +#define KMS_CONFIG_PROP_KMS "kms" +#define KMS_CONFIG_PROP_KMS_CONFIG "config" +#define KMS_CONFIG_PROP_APQNS "apqns" +#define KMS_CONFIG_LOCAL "local" + +typedef const struct kms_functions *(*kms_get_functions_t)(void); + +#define pr_verbose(verbose, fmt...) do { \ + if (verbose) \ + warnx(fmt); \ + } while (0) + +/** + * Opens kms-plugins.conf. Looks for the file in /etc/zkey/, or if environment + * variable ZKEY_KMS_PLUGINS is set then it uses the file name and path + * specified there. + */ +static FILE *open_kms_plugins_file(bool verbose) +{ + char *conf; + FILE *fp; + + conf = getenv(ENVVAR_ZKEY_KMS_PLUGINS); + if (conf == NULL) + conf = DEFAULT_KMS_PLUGINS; + + pr_verbose(verbose, "Opening KMS plugins config file '%s'", conf); + fp = fopen(conf, "r"); + if (fp == NULL) { + warnx("File '%s': %s", conf, strerror(errno)); + return NULL; + } + + return fp; +} + +/** + * Lists the KMS plugins defined in kms-plugins.conf + * + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 for success or a negative errno in case of an error. + */ +int list_kms_plugins(bool verbose) +{ + struct util_rec *rec; + char line[4096]; + int rc = 0; + size_t len; + FILE *fp; + char *ch; + + fp = open_kms_plugins_file(verbose); + if (fp == NULL) + return -EIO; + + rec = util_rec_new_wide("-"); + util_rec_def(rec, "PLUGIN", UTIL_REC_ALIGN_LEFT, 25, "KMS-Plugin"); + util_rec_def(rec, "LIB", UTIL_REC_ALIGN_LEFT, 25, "Shared library"); + util_rec_print_hdr(rec); + + while (fgets(line, sizeof(line), fp) != NULL) { + len = strlen(line); + if (len < 1) + continue; + if (line[0] == '#') + continue; + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + ch = strchr(line, '='); + if (ch == NULL) { + rc = -EPERM; + warnx("Syntax error in kms-plugins.conf. Line: '%s'", + line); + goto out; + } + + *ch = '\0'; + ch++; + + util_rec_set(rec, "PLUGIN", line); + util_rec_set(rec, "LIB", ch); + util_rec_print(rec); + } + +out: + util_rec_free(rec); + fclose(fp); + + return rc; +} + +/** + * Loads a KMS plugin by its plugin name. Looks up the shared library in + * kms-plugins.conf for the specified plugin name, and loads the shared library + * via dlopen + * + * @param[in] plugin the plugin name (as in kms-plugins.conf) + * @param[out] kms_functions the plugin functions obtain from the plugin + * @param[out] plugin_lib the library handle (free with dlclose) + * @param[out] plugin_name the full name of the plugin loaded + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 for success or a negative errno in case of an error. + */ +static int load_kms_plugin(const char *plugin, + const struct kms_functions **kms_functions, + void **plugin_lib, char **plugin_name, bool verbose) +{ + kms_get_functions_t _kms_get_functions; + char *so_name = NULL; + FILE *fp = NULL; + char line[4096]; + int rc = 0; + size_t len; + char *ch; + + util_assert(plugin != NULL, "Internal error: plugin is NULL"); + util_assert(kms_functions != NULL, + "Internal error: kms_functions is NULL"); + util_assert(plugin_lib != NULL, "Internal error: plugin_lib is NULL"); + util_assert(plugin_name != NULL, "Internal error: plugin_name is NULL"); + + fp = open_kms_plugins_file(verbose); + if (fp == NULL) + return -EIO; + + while (fgets(line, sizeof(line), fp) != NULL) { + len = strlen(line); + if (len < 1) + continue; + if (line[0] == '#') + continue; + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + ch = strchr(line, '='); + if (ch == NULL) { + rc = -EPERM; + warnx("Syntax error in kms-plugins.conf. Line: '%s'", + line); + goto out; + } + + *ch = '\0'; + ch++; + + if (strcasecmp(line, plugin) == 0) { + so_name = ch; + *plugin_name = util_strdup(line); + break; + } + } + + if (so_name == NULL) { + rc = -ENOENT; + warnx("KMS plugin '%s' not found.", plugin); + goto out; + } + + pr_verbose(verbose, "Loading KMS plugin '%s': '%s'", *plugin_name, + so_name); + *plugin_lib = dlopen(so_name, RTLD_GLOBAL | RTLD_NOW); + if (*plugin_lib == NULL) { + pr_verbose(verbose, "%s", dlerror()); + warnx("Failed to load KMS plugin '%s': '%s'", *plugin_name, + so_name); + rc = -ELIBACC; + goto out; + } + + _kms_get_functions = (kms_get_functions_t)dlsym(*plugin_lib, + "kms_get_functions"); + if (_kms_get_functions == NULL) { + pr_verbose(verbose, "%s", dlerror()); + warnx("Failed to load KMS plugin '%s': '%s'", *plugin_name, + so_name); + rc = -ELIBACC; + goto out; + } + + *kms_functions = _kms_get_functions(); + if (*kms_functions == NULL) { + pr_verbose(verbose, "kms_get_functions() reutned NULL"); + warnx("Failed to load KMS plugin '%s': '%s'", *plugin_name, + so_name); + rc = -ELIBACC; + goto out; + } + + pr_verbose(verbose, "Successfully loaded KMS plugin '%s': '%s' (API " + "version: %u)", *plugin_name, so_name, + (*kms_functions)->api_version); + +out: + if (fp != NULL) + fclose(fp); + if (rc != 0 && *plugin_lib != NULL) { + dlclose(*plugin_lib); + *plugin_lib = NULL; + } + if (rc != 0 && *plugin_name != NULL) { + free(*plugin_name); + *plugin_name = NULL; + } + + return rc; +} + +/** + * Load the kms.conf properties file that is located in the repository + * directory. The returned properties object must be freed by the caller using + * properties_free() when no longer needed. + * + * @param[in] repository the repository directory + * @param[out] kms_props On return: a new properties object with the + * properties read from kms.conf. + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 for success or a negative errno in case of an error. + */ +static int load_kms_properties(const char *repository, + struct properties **kms_props, bool verbose) +{ + struct properties *props = NULL; + char *filename = NULL; + int rc; + + util_assert(repository != NULL, "Internal error: repository is NULL"); + util_assert(kms_props != NULL, "Internal error: kms_props is NULL"); + + util_asprintf(&filename, "%s/%s", repository, KMS_CONFIG_FILE); + + pr_verbose(verbose, "Trying to load '%s'", filename); + + props = properties_new(); + rc = properties_load(props, filename, true); + if (rc != 0) { + pr_verbose(verbose, "Failed to load '%s': %s", filename, + strerror(-rc)); + goto out; + } + + *kms_props = props; + +out: + if (filename != NULL) + free(filename); + if (rc != 0) + properties_free(props); + + return rc; +} + +/** + * Save the kms.conf properties file that is located in the repository + * directory. + * + * @param[in] keystore the keystore + * @param[in] kms_props The properties object to save to kms.conf. + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 for success or a negative errno in case of an error. + */ +static int _save_kms_properties(const struct keystore *keystore, + struct properties *kms_props, bool verbose) +{ + char *filename = NULL; + int rc; + + util_assert(keystore != NULL, "Internal error: keystore is NULL"); + util_assert(kms_props != NULL, "Internal error: kms_props is NULL"); + + util_asprintf(&filename, "%s/%s", keystore->directory, KMS_CONFIG_FILE); + + pr_verbose(verbose, "Saving '%s'", filename); + + rc = properties_save(kms_props, filename, true); + if (rc != 0) { + pr_verbose(verbose, "Failed to save '%s': %s", filename, + strerror(-rc)); + goto out; + } + + if (chmod(filename, keystore->mode) != 0) { + rc = -errno; + warnx("chmod failed on file '%s': %s", filename, strerror(-rc)); + return rc; + } + + if (chown(filename, geteuid(), keystore->owner) != 0) { + rc = -errno; + warnx("chown failed on file '%s': %s", filename, strerror(-rc)); + return rc; + } + +out: + if (filename != NULL) + free(filename); + + return rc; +} + +/** + * Check if a KMS plugin is configured for the current repository, and if so, + * load the KMS plugin. + * + * @param[out] kms_info Filled with information about the KMS plugin and + * configuration. If no KMS plugin is configured, + * all fields are set to NULL. + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 for success or a negative errno in case of an error. + * Note: It is not an error case of no plugin is configured. + */ +int check_for_kms_plugin(struct kms_info *kms_info, bool verbose) +{ + char *directory, *plugin = NULL; + int rc; + + util_assert(kms_info != NULL, "Internal error: kms_info is NULL"); + + memset(kms_info, 0, sizeof(*kms_info)); + + directory = getenv(ENVVAR_ZKEY_REPOSITORY); + if (directory == NULL) + directory = DEFAULT_KEYSTORE; + + rc = load_kms_properties(directory, &kms_info->props, verbose); + if (rc != 0) { + pr_verbose(verbose, "No KMS plugin is configured"); + rc = 0; + goto out; + } + + plugin = properties_get(kms_info->props, KMS_CONFIG_PROP_KMS); + if (plugin == NULL || strcasecmp(plugin, KMS_CONFIG_LOCAL) == 0) { + pr_verbose(verbose, "No KMS plugin is configured"); + rc = 0; + goto out; + } + + rc = load_kms_plugin(plugin, &kms_info->funcs, &kms_info->plugin_lib, + &kms_info->plugin_name, verbose); + if (rc != 0) + goto out; + +out: + if (plugin != NULL) + free(plugin); + if (rc != 0) + free_kms_plugin(kms_info); + + return rc; +} + +/** + * Initializes the KMS plugin. + * + * @param[in] kms_info The KMS Plugin info + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 for success or a negative errno in case of an error. + */ +int init_kms_plugin(struct kms_info *kms_info, bool verbose) +{ + char *config_path = NULL; + char **apqn_list = NULL; + char *apqns = NULL; + int i, rc = 0; + + util_assert(kms_info != NULL, "Internal error: kms_info is NULL"); + + config_path = properties_get(kms_info->props, + KMS_CONFIG_PROP_KMS_CONFIG); + if (config_path == NULL) { + warnx("Incomplete KMS configuration"); + rc = -EIO; + goto out; + } + + if (kms_info->funcs->kms_initialize != NULL) { + kms_info->handle = kms_info->funcs->kms_initialize(config_path, + verbose); + if (kms_info->handle == NULL) { + warnx("KMS plugin '%s' failed to initialize", + kms_info->plugin_name); + rc = -EIO; + goto out; + } + } + + apqns = properties_get(kms_info->props, KMS_CONFIG_PROP_APQNS); + if (apqns != NULL && strlen(apqns) > 0) { + apqn_list = str_list_split(apqns); + for (i = 0; apqn_list[i] != NULL; i++) + ; + kms_info->num_apqns = i; + kms_info->apqns = util_malloc(i * sizeof(struct kms_apqn)); + for (i = 0; apqn_list[i] != NULL; i++) { + if (sscanf(apqn_list[i], "%hx.%hx", + &kms_info->apqns[i].card, + &kms_info->apqns[i].domain) != 2) { + warnx("The APQN '%s' is not valid", + apqn_list[i]); + rc = -EINVAL; + goto out; + } + } + } + +out: + if (config_path != NULL) + free(config_path); + if (apqns != NULL) + free(apqns); + if (apqn_list != NULL) + str_list_free_string_array(apqn_list); + + return rc; +} + +/** + * Terminates, free and close a KMS plugin + * + * @param[in] kms_info The KMS Plugin info to free + */ +void free_kms_plugin(struct kms_info *kms_info) +{ + if (kms_info == NULL) + return; + + if (kms_info->handle != NULL && kms_info->funcs != NULL && + kms_info->funcs->kms_terminate != NULL) + kms_info->funcs->kms_terminate(kms_info->handle); + kms_info->handle = NULL; + + if (kms_info->props != NULL) + properties_free(kms_info->props); + kms_info->props = NULL; + + if (kms_info->plugin_lib != NULL) + dlclose(kms_info->plugin_lib); + kms_info->plugin_lib = NULL; + kms_info->funcs = NULL; + + if (kms_info->plugin_name != NULL) + free(kms_info->plugin_name); + kms_info->plugin_name = NULL; + + if (kms_info->apqns != NULL) + free(kms_info->apqns); + kms_info->apqns = NULL; + kms_info->num_apqns = 0; +} + +/** + * Prints the error message from the KMS plugin's last error + */ +void print_last_kms_error(const struct kms_info *kms_info) +{ + const char *msg; + + if (kms_info == NULL || kms_info->funcs == NULL || + kms_info->funcs->kms_get_last_error == NULL || + kms_info->handle == NULL) + return; + + msg = kms_info->funcs->kms_get_last_error(kms_info->handle); + if (msg == NULL) + return; + + util_print_indented(msg, 0); +} + +/** + * Binds the specified KMS plugin to the current repository. + * If the repository is already bound to a KMS plugin, then -EALREADY is + * returned. + * + * @param[in] keystore the keystore to bind to the plugin + * @param[in] plugin the name of the KMS plugin to bind + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 for success or a negative errno in case of an error. + */ +int bind_kms_plugin(struct keystore *keystore, const char *plugin, + bool verbose) +{ + char *directory, *config_dir = NULL; + const struct kms_functions *funcs; + struct properties *props = NULL; + char *plugin_name = NULL; + void *plugin_lib = NULL; + bool created = false; + int rc; + + util_assert(keystore != NULL, "Internal error: keystore is NULL"); + util_assert(plugin != NULL, "Internal error: plugin is NULL"); + + rc = load_kms_properties(keystore->directory, &props, verbose); + if (rc == 0) { + plugin_name = properties_get(props, KMS_CONFIG_PROP_KMS); + if (plugin_name != NULL && + strcasecmp(plugin_name, KMS_CONFIG_LOCAL) != 0) { + warnx("The repository is already bound to KMS plugin " + "'%s'", plugin_name); + rc = -EALREADY; + goto out; + } + + properties_free(props); + props = NULL; + } + + rc = load_kms_plugin(plugin, &funcs, &plugin_lib, &plugin_name, + verbose); + if (rc != 0) + goto out; + + directory = getenv(ENVVAR_ZKEY_REPOSITORY); + if (directory == NULL) + directory = DEFAULT_KEYSTORE; + util_asprintf(&config_dir, "%s/%s", directory, plugin_name); + pr_verbose(verbose, "Plugin config dir: '%s'", config_dir); + + rc = mkdir(config_dir, keystore->mode); + if (rc != 0) { + rc = -errno; + warnx("Failed to create directory '%s': %s", config_dir, + strerror(-rc)); + goto out; + } + created = true; + + if (chmod(config_dir, keystore->mode) != 0) { + rc = -errno; + warnx("chmod failed on directory '%s': %s", config_dir, + strerror(-rc)); + return rc; + } + + if (chown(config_dir, geteuid(), keystore->owner) != 0) { + rc = -errno; + warnx("chown failed on directory '%s': %s", config_dir, + strerror(-rc)); + return rc; + } + + if (funcs->kms_bind != NULL) { + rc = funcs->kms_bind(config_dir); + if (rc != 0) { + warnx("KMS plugin '%s' failed to bind to the " + "repository: %s", plugin_name, strerror(-rc)); + goto out; + } + } + + props = properties_new(); + rc = properties_set(props, KMS_CONFIG_PROP_KMS, plugin_name); + if (rc != 0) + goto out; + + rc = properties_set(props, KMS_CONFIG_PROP_KMS_CONFIG, config_dir); + if (rc != 0) + goto out; + + rc = _save_kms_properties(keystore, props, verbose); + if (rc != 0) { + warnx("Failed to save kms.conf into repository directory"); + goto out; + } + +out: + if (plugin_name != NULL) + free(plugin_name); + if (props != NULL) + properties_free(props); + if (plugin_lib != NULL) + dlclose(plugin_lib); + if (config_dir != NULL) { + if (rc != 0 && created) + rmdir(config_dir); + free(config_dir); + } + + return rc; +} + +/** + * Removes a directory and all its contents. + */ +static int remove_directory_recursively(const char *directory) +{ + char *filename = NULL; + struct dirent *de; + DIR *dirp; + int rc = 0; + + dirp = opendir(directory); + if (dirp == NULL) { + rc = -errno; + warnx("Failed to open directory '%s'", directory); + return rc; + } + + while ((de = readdir(dirp))) { + util_asprintf(&filename, "%s/%s", directory, de->d_name); + if (de->d_type == DT_DIR) { + if (strcmp(de->d_name, ".") != 0 && + strcmp(de->d_name, "..") != 0) + rc = remove_directory_recursively(filename); + } else { + rc = remove(filename); + if (rc != 0) { + rc = -errno; + warnx("Failed to remove '%s': %s", filename, + strerror(-rc)); + } + } + free(filename); + + if (rc != 0) + break; + } + closedir(dirp); + if (rc != 0) + goto out; + + if (rmdir(directory) != 0) { + rc = -errno; + warnx("Failed to remove '%s': %s", filename, strerror(-rc)); + goto out; + } + +out: + return rc; +} + +/** + * Unbinds the currently bound KMS plugin from the current repository. + * If the repository is not bound to a KMS plugin, then -ENOENT is + * returned. + * + * @param[in] kms_info information of the currently bound plugin. + * This function does NOT free the plugin, this must + * be done by the caller using free_kms_plugin(). + * @param[in] keystore the keystore to bind to the plugin + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 for success or a negative errno in case of an error. + */ +int unbind_kms_plugin(struct kms_info *kms_info, struct keystore *keystore, + bool UNUSED(verbose)) +{ + char *config_dir = NULL; + char *filename = NULL; + int rc; + + util_assert(kms_info != NULL, "Internal error: kms_info is NULL"); + util_assert(keystore != NULL, "Internal error: keystore is NULL"); + + if (kms_info->plugin_lib == NULL) { + rc = -ENOENT; + warnx("The repository is not bound to a KMS plugin"); + goto out; + } + + if (kms_info->funcs->kms_deconfigure != NULL && + kms_info->handle != NULL) { + rc = kms_info->funcs->kms_deconfigure(kms_info->handle); + if (rc != 0) { + warnx("KMS plugin '%s' failed to unbind from the " + "repository: %s", kms_info->plugin_name, + strerror(-rc)); + print_last_kms_error(kms_info); + goto out; + } + } + + config_dir = properties_get(kms_info->props, + KMS_CONFIG_PROP_KMS_CONFIG); + if (config_dir != NULL) { + rc = remove_directory_recursively(config_dir); + if (rc != 0) { + warnx("Failed to remove the KMS plugin's config " + "directory: %s", strerror(-rc)); + goto out; + } + } + + util_asprintf(&filename, "%s/%s", keystore->directory, KMS_CONFIG_FILE); + rc = remove(filename); + if (rc != 0) { + rc = -errno; + warnx("Failed to remove '%s': %s", filename, strerror(-rc)); + goto out; + } + +out: + if (config_dir != NULL) + free(config_dir); + if (filename != NULL) + free(filename); + return rc; +} diff --git a/zkey/kms.h b/zkey/kms.h new file mode 100644 index 00000000..86c1fd05 --- /dev/null +++ b/zkey/kms.h @@ -0,0 +1,46 @@ +/* + * zkey - Generate, re-encipher, and validate secure keys + * + * This header file defines functions for Key Management System (KMS) plugin + * handling + * + * Copyright IBM Corp. 2020 + * + * 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 KMS_H +#define KMS_H + +#include "kms-plugin.h" +#include "properties.h" +#include "keystore.h" + +struct kms_info { + void *plugin_lib; + const struct kms_functions *funcs; + char *plugin_name; + struct properties *props; + struct kms_apqn *apqns; + size_t num_apqns; + kms_handle_t handle; +}; + +int list_kms_plugins(bool verbose); + +int check_for_kms_plugin(struct kms_info *kms_info, bool verbose); + +int init_kms_plugin(struct kms_info *kms_info, bool verbose); + +void free_kms_plugin(struct kms_info *kms_info); + +void print_last_kms_error(const struct kms_info *kms_info); + +int bind_kms_plugin(struct keystore *keystore, const char *plugin, + bool verbose); + +int unbind_kms_plugin(struct kms_info *kms_info, struct keystore *keystore, + bool verbose); + +#endif