From 5a9c381225748a3f7a157248a91cf8d8591d5059 Mon Sep 17 00:00:00 2001 From: Ingo Franzki Date: Tue, 21 Jul 2020 10:30:15 +0200 Subject: [PATCH] zkey-ekmfweb: Configure EKMF Web server and TLS options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow the user to configure the connection to the EKMF Web server using the 'zkey kms configure' command. The communication is based on a RESTful API via HTTPS. Besides the URL of the EKMF Web server, other TLS related settings can be configured. Signed-off-by: Ingo Franzki Signed-off-by: Jan Höppner --- zkey/ekmfweb/zkey-ekmfweb.c | 883 +++++++++++++++++++++++++++++++++++- zkey/ekmfweb/zkey-ekmfweb.h | 14 + 2 files changed, 896 insertions(+), 1 deletion(-) diff --git a/zkey/ekmfweb/zkey-ekmfweb.c b/zkey/ekmfweb/zkey-ekmfweb.c index 9dd0723e..b5425932 100644 --- a/zkey/ekmfweb/zkey-ekmfweb.c +++ b/zkey/ekmfweb/zkey-ekmfweb.c @@ -16,6 +16,10 @@ #include #include +#include +#include +#include + #include "lib/zt_common.h" #include "lib/util_libc.h" #include "lib/util_panic.h" @@ -39,6 +43,13 @@ } \ } while (0) +#define FREE_AND_SET_NULL(ptr) \ + do { \ + if ((ptr) != NULL) \ + free((void *)ptr); \ + (ptr) = NULL; \ + } while (0) + /** * Clears the error message in the plugin handle * @@ -135,6 +146,44 @@ static int _set_file_permission(struct plugin_handle *ph, const char *filename) return 0; } +/** + * Makes a temporary file an active file, by first removing the current active + * file (if existent), and then renaming the temporary file to the active file. + * The active file permissions are also set to the permissions and the group of + * configuration directory. + * + * @param ph the plugin handle + * @param temp_file the name of the temporary file + * @param active_file the name of the active file + * + * @returns 0 on success, or a negative errno value on failure + */ +static int _activate_temp_file(struct plugin_handle *ph, const char *temp_file, + const char *active_file) +{ + int rc; + + if (util_path_exists(active_file)) { + rc = remove(active_file); + if (rc != 0) { + rc = -errno; + _set_error(ph, "remove failed on file '%s': %s", + active_file, strerror(-rc)); + return rc; + } + } + + rc = rename(temp_file, active_file); + if (rc != 0) { + rc = -errno; + _set_error(ph, "rename failed on file '%s': %s", + temp_file, strerror(-rc)); + return rc; + } + + return _set_file_permission(ph, active_file); +} + /** * Save the EKMFWeb plugin config file * @@ -168,6 +217,106 @@ out: return rc; } +/** + * Base64-encodes the passphrase to make it unreadable. + * + * @param passphrase the passphrase to encode + * + * @returns the encoded passphrase or NULL in case of an error. + * The caller must free the string when no longer needed. + */ +char *_encode_passphrase(const char *passphrase) +{ + int inlen, outlen, len; + char *out; + + inlen = strlen(passphrase); + outlen = (inlen / 3) * 4; + if (inlen % 3 > 0) + outlen += 4; + + out = util_malloc(outlen + 1); + memset(out, 0, outlen + 1); + + len = EVP_EncodeBlock((unsigned char *)out, (unsigned char *)passphrase, + inlen); + if (len != outlen) { + free(out); + return NULL; + } + + out[outlen] = '\0'; + return out; +} + +/** + * Base64-decodes the passphrase + * + * @param passphrase the passphrase to decode + * + * @returns the decoded passphrase or NULL in case of an error. + * The caller must free the string when no longer needed. + */ +char *_decode_passphrase(const char *passphrase) +{ + int inlen, outlen, len; + char *out; + + inlen = strlen(passphrase); + outlen = (inlen / 4) * 3; + if (inlen % 4 > 0) + outlen += 3; + + out = util_malloc(outlen + 1); + memset(out, 0, outlen + 1); + + len = EVP_DecodeBlock((unsigned char *)out, (unsigned char *)passphrase, + inlen); + if (len != outlen) { + free(out); + return NULL; + } + + out[outlen] = '\0'; + return out; +} + +/** + * Sets or removes a property. If value is NULL it is removed, otherwise it + * is set. + * + * @param ph the plugin handle + * @param name the name of the property + * @param value the value of the property or NULL + * + * @returns 0 on success, a negative errno in case of an error. + */ +static int _set_or_remove_property(struct plugin_handle *ph, const char *name, + const char *value) +{ + int rc = 0; + + if (value != NULL) { + rc = properties_set(ph->properties, name, value); + if (rc != 0) { + _set_error(ph, "Failed to set property '%s': %s", name, + strerror(-rc)); + goto out; + } + } else { + rc = properties_remove(ph->properties, name); + if (rc != 0 && rc != -ENOENT) { + _set_error(ph, "Failed to remove property '%s': %s", + name, strerror(-rc)); + goto out; + } + rc = 0; + } + +out: + return rc; +} + /** * Checks if a plugin config propertiy is set and not empty * @@ -203,7 +352,91 @@ static void _check_config_complete(struct plugin_handle *ph) { ph->apqns_configured = _check_property(ph, EKMFWEB_CONFIG_APQNS); - ph->config_complete = ph->apqns_configured; + ph->connection_configured = + _check_property(ph, EKMFWEB_CONFIG_URL) && + _check_property(ph, EKMFWEB_CONFIG_VERIFY_SERVER_CERT) && + _check_property(ph, EKMFWEB_CONFIG_VERIFY_HOSTNAME); + + ph->config_complete = ph->apqns_configured && + ph->connection_configured; +} + +/** + * Gets the EKMF config structure contents from the plugin properties + * + * @param ph the plugin handle + * + * @returns a KMS plugin handle, or NULL in case of an error. + */ +static int _get_ekmf_config(struct plugin_handle *ph) +{ + char *tmp; + + ph->ekmf_config.base_url = properties_get(ph->properties, + EKMFWEB_CONFIG_URL); + ph->ekmf_config.tls_ca = properties_get(ph->properties, + EKMFWEB_CONFIG_CA_BUNDLE); + ph->ekmf_config.tls_client_cert = properties_get(ph->properties, + EKMFWEB_CONFIG_CLIENT_CERT); + ph->ekmf_config.tls_client_key = properties_get(ph->properties, + EKMFWEB_CONFIG_CLIENT_KEY); + + tmp = properties_get(ph->properties, + EKMFWEB_CONFIG_CLIENT_KEY_PASSPHRASE); + if (tmp != NULL) { + ph->ekmf_config.tls_client_key_passphrase = + _decode_passphrase(tmp); + free(tmp); + } + ph->ekmf_config.tls_issuer_cert = NULL; + ph->ekmf_config.tls_pinned_pubkey = properties_get(ph->properties, + EKMFWEB_CONFIG_SERVER_PUBKEY); + ph->ekmf_config.tls_server_cert = properties_get(ph->properties, + EKMFWEB_CONFIG_SERVER_CERT); + tmp = properties_get(ph->properties, EKMFWEB_CONFIG_VERIFY_SERVER_CERT); + ph->ekmf_config.tls_verify_peer = + (tmp != NULL && strcasecmp(tmp, "yes") == 0); + if (tmp != NULL) + free(tmp); + tmp = properties_get(ph->properties, EKMFWEB_CONFIG_VERIFY_HOSTNAME); + ph->ekmf_config.tls_verify_host = + (tmp != NULL && strcasecmp(tmp, "yes") == 0); + if (tmp != NULL) + free(tmp); + ph->ekmf_config.max_redirs = 0; + + return 0; +} + +/** + * Frees the EKMF config structure contents + * + * @param ph the plugin handle + */ +static void _free_ekmf_config(struct plugin_handle *ph) +{ + if (ph->ekmf_config.base_url != NULL) + free((void *)ph->ekmf_config.base_url); + if (ph->ekmf_config.tls_ca != NULL) + free((void *)ph->ekmf_config.tls_ca); + if (ph->ekmf_config.tls_client_cert != NULL) + free((void *)ph->ekmf_config.tls_client_cert); + if (ph->ekmf_config.tls_client_key != NULL) + free((void *)ph->ekmf_config.tls_client_key); + if (ph->ekmf_config.tls_client_key_passphrase != NULL) + free((void *)ph->ekmf_config.tls_client_key_passphrase); + if (ph->ekmf_config.tls_issuer_cert != NULL) + free((void *)ph->ekmf_config.tls_issuer_cert); + if (ph->ekmf_config.tls_pinned_pubkey != NULL) + free((void *)ph->ekmf_config.tls_pinned_pubkey); + if (ph->ekmf_config.tls_server_cert != NULL) + free((void *)ph->ekmf_config.tls_server_cert); + if (ph->ekmf_config.login_token != NULL) + free((void *)ph->ekmf_config.login_token); + if (ph->ekmf_config.identity_secure_key != NULL) + free((void *)ph->ekmf_config.identity_secure_key); + if (ph->ekmf_config.ekmf_server_pubkey != NULL) + free((void *)ph->ekmf_config.ekmf_server_pubkey); } /** @@ -264,6 +497,10 @@ kms_handle_t kms_initialize(const char *config_path, bool verbose) goto error; } + rc = _get_ekmf_config(ph); + if (rc != 0) + goto error; + _check_config_complete(ph); pr_verbose(ph, "Plugin configuration is %scomplete", ph->config_complete ? "" : "in"); @@ -293,6 +530,11 @@ int kms_terminate(const kms_handle_t handle) pr_verbose(ph, "Plugin terminated"); + _free_ekmf_config(ph); + + if (ph->curl_handle != NULL) + ekmf_curl_destroy(ph->curl_handle); + if (ph->config_path != NULL) free((void *)ph->config_path); if (ph->properties != NULL) @@ -365,6 +607,7 @@ bool kms_supports_key_type(const kms_handle_t handle, int kms_display_info(const kms_handle_t handle) { struct plugin_handle *ph = handle; + char *tmp = NULL; util_assert(handle != NULL, "Internal error: handle is NULL"); @@ -372,8 +615,172 @@ int kms_display_info(const kms_handle_t handle) _clear_error(ph); + tmp = properties_get(ph->properties, EKMFWEB_CONFIG_URL); + printf(" EKMF Web server: %s\n", tmp != NULL ? tmp : + "(configuration required)"); + if (tmp != NULL) + free(tmp); + else + return 0; + tmp = properties_get(ph->properties, EKMFWEB_CONFIG_CA_BUNDLE); + printf(" CA-bundle: %s\n", tmp != NULL ? tmp : + "System's CA certificates"); + if (tmp != NULL) + free(tmp); + tmp = properties_get(ph->properties, EKMFWEB_CONFIG_CLIENT_CERT); + printf(" Client certificate: %s\n", tmp != NULL ? tmp : "(none)"); + if (tmp != NULL) + free(tmp); + tmp = properties_get(ph->properties, EKMFWEB_CONFIG_CLIENT_KEY); + printf(" Client private key: %s\n", tmp != NULL ? tmp : "(none)"); + if (tmp != NULL) { + free(tmp); + tmp = properties_get(ph->properties, + EKMFWEB_CONFIG_CLIENT_KEY_PASSPHRASE); + if (tmp != NULL) { + printf(" " + "(passphrase protected)\n"); + free(tmp); + } + } + tmp = properties_get(ph->properties, EKMFWEB_CONFIG_SERVER_CERT); + if (tmp != NULL) { + printf(" Trusting the server certificate\n"); + free(tmp); + } + tmp = properties_get(ph->properties, EKMFWEB_CONFIG_SERVER_PUBKEY); + if (tmp != NULL) { + printf(" Using server public key pinning\n"); + free(tmp); + } + tmp = properties_get(ph->properties, EKMFWEB_CONFIG_VERIFY_SERVER_CERT); + if (tmp != NULL) { + if (strcasecmp(tmp, "yes") == 0) + printf(" The server's certificate must be valid\n"); + free(tmp); + } else { + printf(" The server's certificate is not verified\n"); + } + tmp = properties_get(ph->properties, EKMFWEB_CONFIG_VERIFY_HOSTNAME); + if (tmp != NULL) { + if (strcasecmp(tmp, "yes") == 0) + printf(" The server's certificate must match the " + "hostname\n"); + free(tmp); + } + return 0; } +#define OPT_TLS_CLIENT_CERT 256 +#define OPT_TLS_CLIENT_KEY 257 +#define OPT_TLS_CLIENT_KEY_PASSPHRASE 258 +#define OPT_TLS_PIN_SERVER_PUBKEY 259 +#define OPT_TLS_TRUST_SERVER_CERT 260 +#define OPT_TLS_DONT_VERIFY_SERVER_CERT 261 +#define OPT_TLS_VERIFY_HOSTNAME 262 + +const struct util_opt configure_options[] = { + { + .flags = UTIL_OPT_FLAG_SECTION, + .desc = "EKMFWEB SPECIFIC OPTIONS FOR THE SERVER CONNECTION", + .command = KMS_COMMAND_CONFIGURE, + }, + { + .option = { "ekmfweb-url", required_argument, NULL, 'u'}, + .argument = "URL", + .desc = "The URL of the EKMF Web server. The URL should start " + "with 'https://', and may contain a port number " + "separated by a colon. If no port number is specified, " + "443 is used for HTTPS.", + .command = KMS_COMMAND_CONFIGURE, + }, + { + .option = { "tls-ca-bundle", required_argument, NULL, 'b'}, + .argument = "CA-BUNDLE", + .desc = "The CA bundle PEM file or directory containing the CA " + "certificates used to verify the EKMF Web server " + "certificate during TLS handshake. If this specifies a " + "directory path, then this directory must have been " + "prepared with OpenSSL's c_rehash utility. Default are " + "the system CA certificates.", + .command = KMS_COMMAND_CONFIGURE, + }, + { + .option = { "tls-client-cert", required_argument, NULL, + OPT_TLS_CLIENT_CERT }, + .flags = UTIL_OPT_FLAG_NOSHORT, + .argument = "PEM-FILE", + .desc = "The PEM file containing the client's TLS certificate " + "for use with TLS client authentication.", + .command = KMS_COMMAND_CONFIGURE, + }, + { + .option = { "tls-client-key", required_argument, NULL, + OPT_TLS_CLIENT_KEY }, + .flags = UTIL_OPT_FLAG_NOSHORT, + .argument = "PEM-FILE", + .desc = "The PEM file containing the client's private key " + "for use with TLS client authentication.", + .command = KMS_COMMAND_CONFIGURE, + }, + { + .option = { "tls-client-key-passphrase", required_argument, + NULL, OPT_TLS_CLIENT_KEY_PASSPHRASE }, + .flags = UTIL_OPT_FLAG_NOSHORT, + .argument = "PASSPHRASE", + .desc = "If the PEM file is passphrase protected, this option " + "specifies the passphrase to unlock the PEM file that " + "is specified with option '--tls-client-key'.", + .command = KMS_COMMAND_CONFIGURE, + }, + { + .option = { "tls-pin-server-pubkey", 0, NULL, + OPT_TLS_PIN_SERVER_PUBKEY }, + .flags = UTIL_OPT_FLAG_NOSHORT, + .desc = "Pin the EKMF Web server's public key to verify on " + "every connection that the public key of the EKMF Web " + "server's certificate is the same that was used when " + "the connection to the EKMF Web server was configured. " + "This option can only be used with CA signed EKMF Web " + "server certificates.", + .command = KMS_COMMAND_CONFIGURE, + }, + { + .option = { "tls-trust-server-cert", 0, NULL, + OPT_TLS_TRUST_SERVER_CERT }, + .flags = UTIL_OPT_FLAG_NOSHORT, + .desc = "Trust the EKMF Web server's certificate even if it is " + "a self signed certificate, or could not be verified " + "due to other reasons. This option can be used instead " + "of option '--tls-pin-server-pubkey' with self signed " + "EKMF Web server certificates.", + .command = KMS_COMMAND_CONFIGURE, + }, + { + .option = { "tls-dont-verify-server-cert", 0, NULL, + OPT_TLS_DONT_VERIFY_SERVER_CERT }, + .flags = UTIL_OPT_FLAG_NOSHORT, + .desc = "Do not verify the authenticity of the EKMF Web " + "server's certificate. For self signed EKMF Web server " + "certificates, this is the default. Use option " + "'--tls-pin-server-cert' to ensure the self signed " + "certificate's authenticity explicitely. CA signed " + "EKMF Web server certificates are verified by default. " + "This option disables the verification.", + .command = KMS_COMMAND_CONFIGURE, + }, + { + .option = { "tls-verify-hostname", 0, NULL, + OPT_TLS_VERIFY_HOSTNAME }, + .flags = UTIL_OPT_FLAG_NOSHORT, + .desc = "Verify that the EKMF Web server certificate's 'Common " + "Name' field or a 'Subject Alternate Name' field " + "matches the host name used to connect to the EKMF " + "Web server.", + .command = KMS_COMMAND_CONFIGURE, + }, + UTIL_OPT_END, +}; /** * Returns a list of KMS specific command line options that zkey should accept @@ -401,6 +808,9 @@ const struct util_opt *kms_get_command_options(const char *command, { util_assert(command != NULL, "Internal error: command is NULL"); + if (strcasecmp(command, KMS_COMMAND_CONFIGURE) == 0) + return configure_options; + return NULL; } @@ -796,6 +1206,418 @@ static char *_build_apqn_string(const struct kms_apqn *apqns, size_t num_apqns) return apqn_str; } +/** + * Check if the certificate is a self signed certificate, and if it is expired + * or not yet valid. + * + * @param ph the plugin handle + * @param cert_file the file name of the PEM file containing the cert + * @param self_signed on return: true if the cetr is a self signed cert + * @param valid on return: false if the cert is expired or not yet + * valid + * @returns 0 on success, a negative errno in case of an error. + */ +static int _check_certificate(struct plugin_handle *ph, + const char *cert_file, bool *self_signed, + bool *valid) +{ + X509 *cert; + FILE *fp; + int rc; + + fp = fopen(cert_file, "r"); + if (fp == NULL) { + rc = -errno; + _set_error(ph, "Failed to open certificate PEM file '%s': %s", + cert_file, strerror(-rc)); + return rc; + } + + cert = PEM_read_X509(fp, NULL, NULL, NULL); + fclose(fp); + + if (cert == NULL) { + _set_error(ph, "Failed to read certificate PEM file '%s'", + cert_file); + return -EIO; + } + + + *self_signed = (X509_NAME_cmp(X509_get_subject_name(cert), + X509_get_issuer_name(cert)) == 0); + + *valid = (X509_cmp_current_time(X509_get0_notBefore(cert)) < 0 && + X509_cmp_current_time(X509_get0_notAfter(cert)) > 0); + + X509_free(cert); + + return 0; +} + +/** + * Configures the connection to the EKMF Web server + * + * @param ph the plugin handle + * @param ekmfweb_url the URL of the EKMF Web server + * @param tls_ca_bundle the file or directory name of the CA bundle to use + * @param tls_client_cert the file name of the client certificate + * @param tls_client_key the file name of the client private key + * @param tls_client_key_passphrase the passphrase to unlock the key + * @param tls_pin_server_pubkey if true, pin the server public key + * @param tls_trust_server_cert if true, trust the server certificate + * @param tls_dont_verify_server_cert if true, don't verify the server cert + * @param tls_verify_hostname if true verify the server's hostname + * + * @returns 0 on success, a negative errno in case of an error. + */ +static int _configure_connection(struct plugin_handle *ph, + const char *ekmfweb_url, + const char *tls_ca_bundle, + const char *tls_client_cert, + const char *tls_client_key, + const char *tls_client_key_passphrase, + bool tls_pin_server_pubkey, + bool tls_trust_server_cert, + bool tls_dont_verify_server_cert, + bool tls_verify_hostname) +{ + char *server_pubkey_temp = NULL; + char *server_pubkey_file = NULL; + char *server_cert_file = NULL; + char *server_cert_temp = NULL; + bool self_signed = false; + bool add_https = false; + bool verified = false; + bool valid = false; + char *error = NULL; + char *url = NULL; + int rc = 0; + char *tmp; + + if (tls_client_cert != NULL && tls_client_key == NULL) { + _set_error(ph, "Option '--tls-client-key' is required when " + "option '--tls-client-cert' is specified."); + return -EINVAL; + } + if (tls_client_key != NULL && tls_client_cert == NULL) { + _set_error(ph, "Option '--tls-client-cert' is required when " + "option '--tls-client-key' is specified."); + return -EINVAL; + } + if (tls_client_key_passphrase != NULL && tls_client_key == NULL) { + _set_error(ph, "Option '--tls-client-key-passphrase' is only " + "valid together with option " + "'--tls-client-key'."); + return -EINVAL; + } + if (tls_pin_server_pubkey && tls_trust_server_cert) { + _set_error(ph, "Option ' --tls-pin-server-pubkey' is not valid " + "together with option '--tls-pin-server-cert"); + return -EINVAL; + } + + if (ph->ekmf_config.base_url != NULL) { + util_print_indented("ATTENTION: The EKMF Web server connection " + "has already been configured!\n" + "When you re-configure the EKMF Web server " + "connection, you may need to re-register " + "this zkey client with the changed EKMF " + "Web server.", 0); + printf("%s: Re-configure the EKMF Web server connection " + "[y/N]? ", + program_invocation_short_name); + if (!prompt_for_yes(ph->verbose)) { + _set_error(ph, "Opertion aborted by user"); + return -ECANCELED; + } + } + + if (strncmp(ekmfweb_url, "http://", 6) == 0) { + _set_error(ph, "The use of insecured HTTP is not allowed."); + return -EINVAL; + } + + if (strncmp(ekmfweb_url, "https://", 7) != 0) + add_https = true; + + util_asprintf(&url, "%s%s", add_https ? "https://" : "", ekmfweb_url); + if (url[strlen(url) - 1] == '/') + url[strlen(url) - 1] = '\0'; + + pr_verbose(ph, "url: '%s'", url); + + FREE_AND_SET_NULL(ph->ekmf_config.base_url); + ph->ekmf_config.base_url = url; + + rc = properties_set(ph->properties, EKMFWEB_CONFIG_URL, url); + if (rc != 0) { + _set_error(ph, "Failed to set URL property: " + "%s", strerror(-rc)); + goto out; + } + + FREE_AND_SET_NULL(ph->ekmf_config.tls_ca); + if (tls_ca_bundle != NULL) + ph->ekmf_config.tls_ca = util_strdup(tls_ca_bundle); + rc = _set_or_remove_property(ph, EKMFWEB_CONFIG_CA_BUNDLE, + tls_ca_bundle); + + FREE_AND_SET_NULL(ph->ekmf_config.tls_client_cert); + if (tls_client_cert != NULL) + ph->ekmf_config.tls_client_cert = util_strdup(tls_client_cert); + rc = _set_or_remove_property(ph, EKMFWEB_CONFIG_CLIENT_CERT, + tls_client_cert); + if (rc != 0) + goto out; + + FREE_AND_SET_NULL(ph->ekmf_config.tls_client_key); + if (tls_client_key != NULL) + ph->ekmf_config.tls_client_key = util_strdup(tls_client_key); + rc = _set_or_remove_property(ph, EKMFWEB_CONFIG_CLIENT_KEY, + tls_client_key); + if (rc != 0) + goto out; + + tmp = NULL; + FREE_AND_SET_NULL(ph->ekmf_config.tls_client_key_passphrase); + if (tls_client_key_passphrase != NULL) { + ph->ekmf_config.tls_client_key_passphrase = + util_strdup(tls_client_key_passphrase); + tmp = _encode_passphrase(tls_client_key_passphrase); + if (tmp == NULL) { + _set_error(ph, "Failed to encode the passphrase"); + rc = -EIO; + goto out; + } + } + rc = _set_or_remove_property(ph, EKMFWEB_CONFIG_CLIENT_KEY_PASSPHRASE, + tmp); + if (tmp != NULL) + free(tmp); + if (rc != 0) + goto out; + + util_asprintf(&server_cert_temp, "%s/%s-tmp", ph->config_path, + EKMFWEB_CONFIG_SERVER_CERT_FILE); + util_asprintf(&server_pubkey_temp, "%s/%s-tmp", ph->config_path, + EKMFWEB_CONFIG_SERVER_PUBKEY_FILE); + + rc = ekmf_get_server_cert_chain(&ph->ekmf_config, + server_cert_temp, + server_pubkey_temp, + NULL, &verified, + &error, ph->verbose); + if (rc != 0) { + _set_error(ph, "Failed to connect to EKMF Web server at '%s': " + "%s", ph->ekmf_config.base_url, + error != NULL ? error : strerror(-rc)); + goto out; + } + + rc = _check_certificate(ph, server_cert_temp, &self_signed, &valid); + if (rc != 0) + goto out; + + pr_verbose(ph, "verified: %d", verified); + pr_verbose(ph, "self signed: %d", self_signed); + pr_verbose(ph, "valid: %d", valid); + + util_print_indented("The EKMF Web server presented the following " + "certificate to identify itself:", 0); + + rc = ekmf_print_certificates(server_cert_temp, ph->verbose); + if (rc != 0) { + _set_error(ph, "Failed to print the server certificate: %s", + strerror(-rc)); + goto out; + } + + printf("\n"); + if (!valid) + printf("ATTENTION: The certificate is expired or not yet " + "valid.\n"); + if (self_signed) { + printf("ATTENTION: The certificate is self signed " + "and thus could not be verified.\n"); + } else if (!verified) { + if (!tls_dont_verify_server_cert) { + if (tls_ca_bundle != NULL) + _set_error(ph, "The certificate could not be " + "verified using the specified CA " + "bundle '%s'. Use option " + "'--tls-dont-verify-server-cert' to " + "connect to this server anyway.", + tls_ca_bundle); + else + _set_error(ph, "The certificate could not be " + "verified using the system's " + "CA certificates. Use option " + "'--tls-dont-verify-server-cert' to " + "connect to this server anyway."); + rc = -EINVAL; + goto out; + } + } + printf("%s: Is this the EKMF Web server you intent to work with " + "[y/N]? ", program_invocation_short_name); + if (!prompt_for_yes(ph->verbose)) { + _set_error(ph, "Opertion aborted by user"); + rc = -ECANCELED; + goto out; + } + + ph->ekmf_config.tls_verify_peer = !self_signed || tls_trust_server_cert; + if (tls_dont_verify_server_cert) + ph->ekmf_config.tls_verify_peer = false; + rc = _set_or_remove_property(ph, EKMFWEB_CONFIG_VERIFY_SERVER_CERT, + ph->ekmf_config.tls_verify_peer ? + "yes" : "no"); + if (rc != 0) + goto out; + + ph->ekmf_config.tls_verify_host = tls_verify_hostname; + rc = _set_or_remove_property(ph, EKMFWEB_CONFIG_VERIFY_HOSTNAME, + ph->ekmf_config.tls_verify_host ? + "yes" : "no"); + if (rc != 0) + goto out; + + FREE_AND_SET_NULL(ph->ekmf_config.tls_server_cert); + util_asprintf(&server_cert_file, "%s/%s", ph->config_path, + EKMFWEB_CONFIG_SERVER_CERT_FILE); + if (tls_trust_server_cert) { + ph->ekmf_config.tls_server_cert = util_strdup(server_cert_file); + rc = _activate_temp_file(ph, server_cert_temp, + server_cert_file); + if (rc != 0) + goto out; + } else { + remove(server_cert_file); + } + rc = _set_or_remove_property(ph, EKMFWEB_CONFIG_SERVER_CERT, + tls_trust_server_cert ? + server_cert_file : NULL); + if (rc != 0) + goto out; + + FREE_AND_SET_NULL(ph->ekmf_config.tls_pinned_pubkey); + util_asprintf(&server_pubkey_file, "%s/%s", ph->config_path, + EKMFWEB_CONFIG_SERVER_PUBKEY_FILE); + if (tls_pin_server_pubkey) { + ph->ekmf_config.tls_pinned_pubkey = + util_strdup(server_pubkey_file); + rc = _activate_temp_file(ph, server_pubkey_temp, + server_pubkey_file); + if (rc != 0) + goto out; + } else { + remove(server_pubkey_file); + } + rc = _set_or_remove_property(ph, EKMFWEB_CONFIG_SERVER_PUBKEY, + tls_pin_server_pubkey ? + server_pubkey_file : NULL); + if (rc != 0) + goto out; + +out: + if (server_cert_temp != NULL) { + remove(server_cert_temp); + free(server_cert_temp); + } + if (server_cert_file != NULL) + free(server_cert_file); + if (server_pubkey_temp != NULL) { + remove(server_pubkey_temp); + free(server_pubkey_temp); + } + if (server_pubkey_file != NULL) + free(server_pubkey_file); + if (error != NULL) + free(error); + + return rc; +} + +struct config_options { + const char *ekmfweb_url; + const char *tls_ca_bundle; + const char *tls_client_cert; + const char *tls_client_key; + const char *tls_client_key_passphrase; + bool tls_pin_server_pubkey; + bool tls_trust_server_cert; + bool tls_dont_verify_server_cert; + bool tls_verify_hostname; +}; + +/** + * Checks that none of the options for seting up a connection is specified, + * and sets up the error message and return code if + * so. + * + * @param ph the plugin handle + * @param opts the config options structure + * + * @returns 0 on success, a negative errno in case of an error. + */ +static int _error_connection_opts(struct plugin_handle *ph, + struct config_options *opts) +{ + int rc = 0; + + if (opts->tls_ca_bundle != NULL) { + _set_error(ph, "Option '--tls-ca-bundle' is only valid " + "together with option '--ekmfweb-url'."); + rc = -EINVAL; + goto out; + } + if (opts->tls_client_cert != NULL) { + _set_error(ph, "Option '--tls-client-cert' is only valid " + "together with option '--ekmfweb-url'."); + rc = -EINVAL; + goto out; + } + if (opts->tls_client_key != NULL) { + _set_error(ph, "Option '--tls-client-key' is only valid " + "together with option '--ekmfweb-url'."); + rc = -EINVAL; + goto out; + } + if (opts->tls_client_key_passphrase != NULL) { + _set_error(ph, "Option '--tls-client-key-passphrase' is only " + "valid together with option '--ekmfweb-url'."); + rc = -EINVAL; + goto out; + } + if (opts->tls_pin_server_pubkey) { + _set_error(ph, "Option '--tls-pin-server-pubkey' is only valid " + "together with option '--ekmfweb-url'."); + rc = -EINVAL; + goto out; + } + if (opts->tls_trust_server_cert) { + _set_error(ph, "Option '--tls-trust-server-cert' is only valid " + "together with option '--ekmfweb-url'."); + rc = -EINVAL; + goto out; + } + if (opts->tls_dont_verify_server_cert) { + _set_error(ph, "Option '--tls-dont-verify-server-cert' is only " + "valid together with option '--ekmfweb-url'."); + rc = -EINVAL; + goto out; + } + if (opts->tls_verify_hostname) { + _set_error(ph, "Option '--tls-verify-hostname' is only valid " + "together with option '--ekmfweb-url'."); + rc = -EINVAL; + goto out; + } + +out: + return rc; +} + /** * 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 @@ -827,6 +1649,7 @@ 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) { + struct config_options opts = { 0 }; struct plugin_handle *ph = handle; bool config_changed = false; char *apqn_str = NULL; @@ -876,6 +1699,64 @@ int kms_configure(const kms_handle_t handle, config_changed = true; } + for (i = 0; i < num_options; i++) { + switch (options[i].option) { + case 'u': + opts.ekmfweb_url = options[i].argument; + break; + case 'b': + opts.tls_ca_bundle = options[i].argument; + break; + case OPT_TLS_CLIENT_CERT: + opts.tls_client_cert = options[i].argument; + break; + case OPT_TLS_CLIENT_KEY: + opts.tls_client_key = options[i].argument; + break; + case OPT_TLS_CLIENT_KEY_PASSPHRASE: + opts.tls_client_key_passphrase = options[i].argument; + break; + case OPT_TLS_PIN_SERVER_PUBKEY: + opts.tls_pin_server_pubkey = true; + break; + case OPT_TLS_TRUST_SERVER_CERT: + opts.tls_trust_server_cert = true; + break; + case OPT_TLS_DONT_VERIFY_SERVER_CERT: + opts.tls_dont_verify_server_cert = true; + break; + case OPT_TLS_VERIFY_HOSTNAME: + opts.tls_verify_hostname = true; + break; + default: + rc = -EINVAL; + if (isalnum(options[i].option)) + _set_error(ph, "Unsupported option '%c'", + options[i].option); + else + _set_error(ph, "Unsupported option %d", + options[i].option); + goto out; + } + } + + if (opts.ekmfweb_url != NULL) { + rc = _configure_connection(ph, opts.ekmfweb_url, + opts.tls_ca_bundle, + opts.tls_client_cert, + opts.tls_client_key, + opts.tls_client_key_passphrase, + opts.tls_pin_server_pubkey, + opts.tls_trust_server_cert, + opts.tls_dont_verify_server_cert, + opts.tls_verify_hostname); + if (rc == 0) + config_changed = true; + } else { + rc = _error_connection_opts(ph, &opts); + } + if (rc != 0) + goto out; out: if (apqn_str != NULL) free(apqn_str); diff --git a/zkey/ekmfweb/zkey-ekmfweb.h b/zkey/ekmfweb/zkey-ekmfweb.h index b11f397d..09c20d37 100644 --- a/zkey/ekmfweb/zkey-ekmfweb.h +++ b/zkey/ekmfweb/zkey-ekmfweb.h @@ -22,13 +22,27 @@ struct plugin_handle { gid_t config_path_owner; struct properties *properties; bool apqns_configured; + bool connection_configured; bool config_complete; + struct ekmf_config ekmf_config; + CURL *curl_handle; char error_msg[1024]; bool verbose; }; #define EKMFWEB_CONFIG_FILE "ekmfweb.conf" +#define EKMFWEB_CONFIG_SERVER_CERT_FILE "server-cert.pem" +#define EKMFWEB_CONFIG_SERVER_PUBKEY_FILE "server-pubkey.pem" #define EKMFWEB_CONFIG_APQNS "apqns" +#define EKMFWEB_CONFIG_URL "url" +#define EKMFWEB_CONFIG_CA_BUNDLE "ca-bundle" +#define EKMFWEB_CONFIG_CLIENT_CERT "client-cert" +#define EKMFWEB_CONFIG_CLIENT_KEY "client-key" +#define EKMFWEB_CONFIG_CLIENT_KEY_PASSPHRASE "client-key-passphrase" +#define EKMFWEB_CONFIG_SERVER_CERT "server-cert" +#define EKMFWEB_CONFIG_SERVER_PUBKEY "server-pubkey" +#define EKMFWEB_CONFIG_VERIFY_SERVER_CERT "verify-server-cert" +#define EKMFWEB_CONFIG_VERIFY_HOSTNAME "verify-hostname" #endif