mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Fix typos reported by (Debian) lintian. Closes: https://github.com/ibm-s390-linux/s390-tools/pull/168 Signed-off-by: Frank Heimes <frank.heimes@canonical.com> Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
5037 lines
146 KiB
C
5037 lines
146 KiB
C
/*
|
|
* zkey-ekmfweb - EKMFWeb zkey 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.
|
|
*/
|
|
|
|
#include <argz.h>
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <dlfcn.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <err.h>
|
|
#include <sys/utsname.h>
|
|
|
|
#include <openssl/evp.h>
|
|
#include <openssl/x509.h>
|
|
#include <openssl/pem.h>
|
|
#include <openssl/opensslv.h>
|
|
|
|
#include "lib/zt_common.h"
|
|
#include "lib/util_libc.h"
|
|
#include "lib/util_panic.h"
|
|
#include "lib/util_path.h"
|
|
#include "lib/util_base.h"
|
|
#include "lib/util_rec.h"
|
|
|
|
#include "zkey-ekmfweb.h"
|
|
#include "../kms-plugin.h"
|
|
#include "../cca.h"
|
|
#include "../utils.h"
|
|
#include "../pkey.h"
|
|
#include "../properties.h"
|
|
|
|
#ifndef OPENSSL_VERSION_PREREQ
|
|
#if defined(OPENSSL_VERSION_MAJOR) && defined(OPENSSL_VERSION_MINOR)
|
|
#define OPENSSL_VERSION_PREREQ(maj, min) \
|
|
((OPENSSL_VERSION_MAJOR << 16) + \
|
|
OPENSSL_VERSION_MINOR >= ((maj) << 16) + (min))
|
|
#else
|
|
#define OPENSSL_VERSION_PREREQ(maj, min) \
|
|
(OPENSSL_VERSION_NUMBER >= (((maj) << 28) | \
|
|
((min) << 20)))
|
|
#endif
|
|
#endif
|
|
|
|
#if OPENSSL_VERSION_PREREQ(3, 0)
|
|
#include <openssl/core_names.h>
|
|
#endif
|
|
|
|
#define FREE_AND_SET_NULL(ptr) \
|
|
do { \
|
|
if ((ptr) != NULL) \
|
|
free((void *)ptr); \
|
|
(ptr) = NULL; \
|
|
} while (0)
|
|
|
|
#define _set_error(ph, fmt...) plugin_set_error(&(ph)->pd, fmt)
|
|
|
|
/**
|
|
* 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 *UNUSED(config_path))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
static 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.
|
|
*/
|
|
static 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;
|
|
}
|
|
|
|
/**
|
|
* Checks if the plugin configuration is complete. Sets the appropriate flags
|
|
* in the plugin handle
|
|
*
|
|
* @param ph the plugin handle
|
|
*/
|
|
static void _check_config_complete(struct plugin_handle *ph)
|
|
{
|
|
ph->apqns_configured =
|
|
plugin_check_property(&ph->pd, EKMFWEB_CONFIG_APQNS);
|
|
|
|
ph->connection_configured =
|
|
plugin_check_property(&ph->pd, EKMFWEB_CONFIG_URL) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_VERIFY_SERVER_CERT) &&
|
|
plugin_check_property(&ph->pd, EKMFWEB_CONFIG_VERIFY_HOSTNAME);
|
|
|
|
ph->settings_retrieved =
|
|
plugin_check_property(&ph->pd, EKMFWEB_CONFIG_EKMFWEB_PUBKEY);
|
|
|
|
ph->templates_retrieved =
|
|
plugin_check_property(&ph->pd, EKMFWEB_CONFIG_TEMPLATE_XTS1) &&
|
|
plugin_check_property(&ph->pd, EKMFWEB_CONFIG_TEMPLATE_XTS2) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_TEMPLATE_NONXTS) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_TEMPLATE_IDENTITY) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_TEMPLATE_XTS1_LABEL) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_TEMPLATE_XTS2_LABEL) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_TEMPLATE_NONXTS_LABEL) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_TEMPLATE_IDENTITY_LABEL) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_TEMPLATE_XTS1_ID) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_TEMPLATE_XTS2_ID) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_TEMPLATE_NONXTS_ID) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_TEMPLATE_IDENTITY_ID);
|
|
|
|
ph->identity_key_generated =
|
|
plugin_check_property(&ph->pd, EKMFWEB_CONFIG_IDENTITY_KEY) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_ALGORITHM) &&
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_PARAMS);
|
|
|
|
ph->registered =
|
|
plugin_check_property(&ph->pd,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_LABEL) &&
|
|
plugin_check_property(&ph->pd, EKMFWEB_CONFIG_IDENTITY_KEY_ID);
|
|
|
|
ph->config_complete = ph->apqns_configured &&
|
|
ph->connection_configured &&
|
|
ph->settings_retrieved &&
|
|
ph->templates_retrieved &&
|
|
ph->identity_key_generated &&
|
|
ph->registered;
|
|
}
|
|
|
|
/**
|
|
* 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.identity_secure_key = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY);
|
|
|
|
ph->ekmf_config.base_url = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_URL);
|
|
ph->ekmf_config.tls_ca = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_CA_BUNDLE);
|
|
ph->ekmf_config.tls_client_cert = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_CLIENT_CERT);
|
|
ph->ekmf_config.tls_client_key = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_CLIENT_KEY);
|
|
|
|
tmp = properties_get(ph->pd.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->pd.properties,
|
|
EKMFWEB_CONFIG_SERVER_PUBKEY);
|
|
ph->ekmf_config.tls_server_cert = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_SERVER_CERT);
|
|
tmp = properties_get(ph->pd.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->pd.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;
|
|
|
|
ph->ekmf_config.login_token = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_LOGIN_TOKEN);
|
|
|
|
ph->ekmf_config.ekmf_server_pubkey = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_EKMFWEB_PUBKEY);
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Removes the login token file, if the error indicates an authorization or
|
|
* authentication error (-EACCES or -EPERM)
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param error the negative errno value of the last error
|
|
*/
|
|
static void _remove_login_token_if_error(struct plugin_handle *ph, int error)
|
|
{
|
|
switch (error) {
|
|
case -EACCES:
|
|
case -EPERM:
|
|
remove(ph->ekmf_config.login_token);
|
|
FREE_AND_SET_NULL(ph->ekmf_config.login_token);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* UnlLoads the CCA library
|
|
*
|
|
* @param ph the plugin handle
|
|
*/
|
|
static void _unload_cca_library(struct plugin_handle *ph)
|
|
{
|
|
if (ph->cca.cca_lib != NULL)
|
|
dlclose(ph->cca.cca_lib);
|
|
ph->cca.cca_lib = NULL;
|
|
|
|
ph->ext_lib.type = 0;
|
|
ph->ext_lib.cca = NULL;
|
|
}
|
|
|
|
/**
|
|
* Selects one the CCA APQNs associated with this plugin, and loads the CCA
|
|
* library and sets up the external library field in the plugin handle.
|
|
*
|
|
* @param ph the plugin handle
|
|
*
|
|
* @returns 0 on success, a negative errno in case of an error.
|
|
*/
|
|
static int _select_cca_adapter(struct plugin_handle *ph)
|
|
{
|
|
struct cca_lib cca = { 0 };
|
|
char *apqns;
|
|
int rc = 0;
|
|
|
|
apqns = properties_get(ph->pd.properties, EKMFWEB_CONFIG_APQNS);
|
|
if (apqns == NULL) {
|
|
_set_error(ph, "No APQN are associated with the plugin.");
|
|
rc = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
pr_verbose(&ph->pd, "Associated APQNs: %s", apqns);
|
|
|
|
_unload_cca_library(ph);
|
|
|
|
rc = select_cca_adapter_by_apqns(&ph->pd, apqns, &cca);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to select one of the associated APQNs: "
|
|
"%s", apqns);
|
|
goto out;
|
|
}
|
|
|
|
ph->cca.cca_lib = cca.lib_csulcca;
|
|
ph->ext_lib.type = EKMF_EXT_LIB_CCA;
|
|
ph->ext_lib.cca = &ph->cca;
|
|
|
|
out:
|
|
if (apqns != NULL)
|
|
free(apqns);
|
|
|
|
if (rc != 0)
|
|
_unload_cca_library(ph);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
struct plugin_handle *ph;
|
|
int rc;
|
|
|
|
util_assert(config_path != NULL, "Internal error: config_path is NULL");
|
|
|
|
ph = util_malloc(sizeof(struct plugin_handle));
|
|
memset(ph, 0, sizeof(struct plugin_handle));
|
|
|
|
rc = plugin_init(&ph->pd, "zkey-ekmfweb", config_path,
|
|
EKMFWEB_CONFIG_FILE, verbose);
|
|
if (rc != 0)
|
|
goto error;
|
|
|
|
rc = _get_ekmf_config(ph);
|
|
if (rc != 0)
|
|
goto error;
|
|
|
|
_check_config_complete(ph);
|
|
pr_verbose(&ph->pd, "Plugin configuration is %scomplete",
|
|
ph->config_complete ? "" : "in");
|
|
|
|
return (kms_handle_t)ph;
|
|
|
|
error:
|
|
kms_terminate(ph);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
struct plugin_handle *ph = handle;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
|
|
pr_verbose(&ph->pd, "Plugin terminating");
|
|
|
|
_free_ekmf_config(ph);
|
|
_unload_cca_library(ph);
|
|
|
|
if (ph->curl_handle != NULL)
|
|
ekmf_curl_destroy(ph->curl_handle);
|
|
|
|
plugin_term(&ph->pd);
|
|
free(ph);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
struct plugin_handle *ph = handle;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
|
|
pr_verbose(&ph->pd, "Last error: '%s'", ph->pd.error_msg);
|
|
|
|
if (strlen(ph->pd.error_msg) == 0)
|
|
return NULL;
|
|
|
|
return ph->pd.error_msg;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
struct plugin_handle *ph = handle;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
util_assert(key_type != NULL, "Internal error: key_type is NULL");
|
|
|
|
plugin_clear_error(&ph->pd);
|
|
|
|
if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns information about the public key in the PEM file
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param pem_file the name of a PEM file containing the public key
|
|
* @param pkey_type on return: If not NULL, the PKEY type (EVP_PKEY_EC
|
|
* or EVP_PKEY_RSA)
|
|
* @param ecc_curve_nid on return: If not NULL and it is an ECC key, the
|
|
* OpenSSL NID of the curve of the ECC key.
|
|
* @param rsa_mod_bits on return: If not NULL and it is an RSA key, the
|
|
* modulus bit size of the RSA key.
|
|
*
|
|
* @returns 0 on success, or a negative errno in case of an error.
|
|
*/
|
|
static int _get_pub_key_info(struct plugin_handle *ph, const char *pem_file,
|
|
int *pkey_type, int *ecc_curve_nid,
|
|
int *rsa_mod_bits)
|
|
{
|
|
int rc = 0, curve_nid, mod_len;
|
|
#if OPENSSL_VERSION_PREREQ(3, 0)
|
|
size_t curve_len;
|
|
char curve[80];
|
|
#endif
|
|
EVP_PKEY *pkey;
|
|
FILE *fp;
|
|
|
|
fp = fopen(pem_file, "r");
|
|
if (fp == NULL) {
|
|
rc = -errno;
|
|
_set_error(ph, "Failed to open pubkey PEM file '%s': %s",
|
|
pem_file, strerror(-rc));
|
|
return rc;
|
|
}
|
|
|
|
pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
|
|
|
|
fclose(fp);
|
|
|
|
if (pkey == NULL) {
|
|
rc = -EIO;
|
|
_set_error(ph, "Failed to read pubkey from PEM file '%s': %s",
|
|
pem_file, strerror(-rc));
|
|
return rc;
|
|
}
|
|
|
|
if (pkey_type != NULL)
|
|
*pkey_type = EVP_PKEY_id(pkey);
|
|
|
|
switch (EVP_PKEY_id(pkey)) {
|
|
case EVP_PKEY_EC:
|
|
#if !OPENSSL_VERSION_PREREQ(3, 0)
|
|
curve_nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(
|
|
EVP_PKEY_get0_EC_KEY(pkey)));
|
|
#else
|
|
if (!EVP_PKEY_get_utf8_string_param(pkey,
|
|
OSSL_PKEY_PARAM_GROUP_NAME,
|
|
curve, sizeof(curve),
|
|
&curve_len)) {
|
|
EVP_PKEY_free(pkey);
|
|
_set_error(ph, "Failed to get the curve name");
|
|
return -EIO;
|
|
}
|
|
curve_nid = OBJ_sn2nid(curve);
|
|
#endif
|
|
if (ecc_curve_nid != NULL)
|
|
*ecc_curve_nid = curve_nid;
|
|
break;
|
|
|
|
case EVP_PKEY_RSA:
|
|
#if !OPENSSL_VERSION_PREREQ(3, 0)
|
|
mod_len = BN_num_bits(RSA_get0_n(EVP_PKEY_get0_RSA(pkey)));
|
|
#else
|
|
if (!EVP_PKEY_get_int_param(pkey, OSSL_PKEY_PARAM_BITS,
|
|
&mod_len)) {
|
|
EVP_PKEY_free(pkey);
|
|
_set_error(ph, "Failed to get the RSA key size");
|
|
return -EIO;
|
|
}
|
|
#endif
|
|
if (rsa_mod_bits != NULL)
|
|
*rsa_mod_bits = mod_len;
|
|
break;
|
|
|
|
default:
|
|
rc = -EIO;
|
|
_set_error(ph, "Unknown pubkey type: %d", EVP_PKEY_id(pkey));
|
|
break;
|
|
}
|
|
|
|
EVP_PKEY_free(pkey);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
int rc, type = 0, curve = 0, mod_bits = 0;
|
|
struct plugin_handle *ph = handle;
|
|
char *tmp = NULL;
|
|
bool rsa = false;
|
|
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
|
|
char *info;
|
|
#endif
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
|
|
pr_verbose(&ph->pd, "Display Info");
|
|
|
|
plugin_clear_error(&ph->pd);
|
|
|
|
tmp = properties_get(ph->pd.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->pd.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->pd.properties, EKMFWEB_CONFIG_CLIENT_CERT);
|
|
printf(" Client certificate: %s\n", tmp != NULL ? tmp : "(none)");
|
|
if (tmp != NULL)
|
|
free(tmp);
|
|
tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_CLIENT_KEY);
|
|
printf(" Client private key: %s\n", tmp != NULL ? tmp : "(none)");
|
|
if (tmp != NULL) {
|
|
free(tmp);
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_CLIENT_KEY_PASSPHRASE);
|
|
if (tmp != NULL) {
|
|
printf(" "
|
|
"(passphrase protected)\n");
|
|
free(tmp);
|
|
}
|
|
}
|
|
tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_SERVER_CERT);
|
|
if (tmp != NULL) {
|
|
printf(" Trusting the server certificate\n");
|
|
free(tmp);
|
|
}
|
|
tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_SERVER_PUBKEY);
|
|
if (tmp != NULL) {
|
|
printf(" Using server public key pinning\n");
|
|
free(tmp);
|
|
}
|
|
tmp = properties_get(ph->pd.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->pd.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);
|
|
}
|
|
|
|
tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_EKMFWEB_PUBKEY);
|
|
if (tmp != NULL) {
|
|
rc = _get_pub_key_info(ph, tmp, &type, &curve, &mod_bits);
|
|
if (rc == 0) {
|
|
switch (type) {
|
|
case EVP_PKEY_EC:
|
|
printf(" EKMF Web public key: ECC (%s)\n",
|
|
OBJ_nid2sn(curve));
|
|
break;
|
|
case EVP_PKEY_RSA:
|
|
printf(" EKMF Web public key: RSA "
|
|
"(%d bits)\n", mod_bits);
|
|
break;
|
|
default:
|
|
printf(" EKMF Web public key: "
|
|
"(unknown key type)\n");
|
|
break;
|
|
}
|
|
} else {
|
|
printf(" EKMF Web public key: (not available)\n");
|
|
}
|
|
free(tmp);
|
|
} else {
|
|
printf(" EKMF Web public key: (configuration required)\n");
|
|
}
|
|
|
|
printf(" Key templates:\n");
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_TEMPLATE_IDENTITY);
|
|
printf(" Identity: %s\n", tmp != NULL ? tmp :
|
|
"(configuration required)");
|
|
if (tmp != NULL)
|
|
free(tmp);
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_TEMPLATE_IDENTITY_LABEL);
|
|
if (tmp != NULL) {
|
|
printf(" Label template: %s\n", tmp);
|
|
free(tmp);
|
|
}
|
|
|
|
tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_TEMPLATE_XTS1);
|
|
printf(" XTS-Key1: %s\n", tmp != NULL ? tmp :
|
|
"(configuration required)");
|
|
if (tmp != NULL)
|
|
free(tmp);
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_TEMPLATE_XTS1_LABEL);
|
|
if (tmp != NULL) {
|
|
printf(" Label template: %s\n", tmp);
|
|
free(tmp);
|
|
}
|
|
|
|
tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_TEMPLATE_XTS2);
|
|
printf(" XTS-Key2: %s\n", tmp != NULL ? tmp :
|
|
"(configuration required)");
|
|
if (tmp != NULL)
|
|
free(tmp);
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_TEMPLATE_XTS2_LABEL);
|
|
if (tmp != NULL) {
|
|
printf(" Label template: %s\n", tmp);
|
|
free(tmp);
|
|
}
|
|
|
|
tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_TEMPLATE_NONXTS);
|
|
printf(" Non-XTS: %s\n", tmp != NULL ? tmp :
|
|
"(configuration required)");
|
|
if (tmp != NULL)
|
|
free(tmp);
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_TEMPLATE_NONXTS_LABEL);
|
|
if (tmp != NULL) {
|
|
printf(" Label template: %s\n", tmp);
|
|
free(tmp);
|
|
}
|
|
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_ALGORITHM);
|
|
if (tmp != NULL) {
|
|
printf(" Identity key: %s", tmp);
|
|
rsa = strcmp(tmp, EKMFWEB_KEY_ALGORITHM_RSA) == 0;
|
|
free(tmp);
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_PARAMS);
|
|
if (tmp != NULL) {
|
|
printf(" (%s%s)", tmp, rsa ? " bits" : "");
|
|
free(tmp);
|
|
}
|
|
printf("\n");
|
|
} else {
|
|
printf(" Identity key: (configuration required)\n");
|
|
}
|
|
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC);
|
|
if (tmp != NULL) {
|
|
printf(" (re-enciphering pending)\n");
|
|
free(tmp);
|
|
}
|
|
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_LABEL);
|
|
printf(" Registered key label: %s\n", tmp != NULL ?
|
|
tmp : "(registration required)");
|
|
if (tmp != NULL)
|
|
free(tmp);
|
|
|
|
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
|
|
printf(" Key transport settings:\n");
|
|
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_SESSION_KEY_CURVE);
|
|
printf(" Session Key: ECC (%s)\n",
|
|
tmp != NULL ? tmp : "secp521r1");
|
|
if (tmp != NULL)
|
|
free(tmp);
|
|
|
|
info = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_INFO);
|
|
if (info != NULL && strncmp(info, "RSA", 3) == 0) {
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_SESSION_RSA_SIGN_DIGEST);
|
|
printf(" RSA sign digest: %s\n",
|
|
tmp != NULL ? tmp : "SHA512");
|
|
if (tmp != NULL)
|
|
free(tmp);
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_SESSION_RSA_SIGN_PSS);
|
|
printf(" RSA sign alorithm: %s\n",
|
|
tmp != NULL && strcasecmp(tmp, "yes") == 0 ?
|
|
"RSA-PSS" : "RSA");
|
|
if (tmp != NULL)
|
|
free(tmp);
|
|
}
|
|
if (info != NULL)
|
|
free(info);
|
|
#endif
|
|
|
|
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
|
|
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
|
|
#define OPT_KT_RSA_SIGNATURE_DIGEST 263
|
|
#define OPT_KT_RSA_PSS_SIGNATURE 264
|
|
#endif
|
|
|
|
static 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 explicitly. 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,
|
|
},
|
|
{
|
|
.option = { "refresh-settings", 0, NULL, 'R' },
|
|
.desc = "Refresh the EKMF Web server settings. This is "
|
|
"automatically performed when the connection to the "
|
|
"EKMF Web server is (re-)configured. Use this option "
|
|
"when the settings of the already configured EKMF Web "
|
|
"server have changed",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.flags = UTIL_OPT_FLAG_SECTION,
|
|
.desc = "EKMFWEB SPECIFIC OPTIONS FOR IDENTITY KEY GENERATION",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "gen-identity-key", 0, NULL, 'i'},
|
|
.desc = "Generate an identity key for the EKMF Web plugin. "
|
|
"An identity key is automatically generated when the "
|
|
"EKMF Web server connection has been configured. Use "
|
|
"this option to generate a new identity key. You need "
|
|
"to re-generate a registration certificate with the "
|
|
"newly generated identity key, and re-register this "
|
|
"zkey client with the EKMF Web server.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.flags = UTIL_OPT_FLAG_SECTION,
|
|
.desc = "EKMFWEB SPECIFIC OPTIONS FOR CERTIFICATE GENERATION",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "gen-csr", required_argument, NULL, 'c'},
|
|
.argument = "CSR-PEM-FILE",
|
|
.desc = "Generate a certificate signing request (CSR) with the "
|
|
"identity key and store it into the specified PEM "
|
|
"file. You pass this CSR to a certificate authority "
|
|
"(CA) to have it issue a CA signed certificate for the "
|
|
"EKMF Web plugin. You need to register the certificate "
|
|
"with EKMF Web before you can access EKMF Web.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "gen-self-signed-cert", required_argument, NULL,
|
|
'C'},
|
|
.argument = "CERT-PEM-FILE",
|
|
.desc = "Generate a self signed certificate with the "
|
|
"identity key and store it into the specified PEM "
|
|
"file. You need to register the certificate with EKMF "
|
|
"Web before you can access EKMF Web.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "cert-subject", required_argument, NULL, 's'},
|
|
.argument = "SUBJECT-RDNS",
|
|
.desc = "The subject name for generating a certificate signing "
|
|
"request (CSR) or self signed certificate, in the form "
|
|
"'<type>=<value>(;<type>=<value>)*[;]' with types "
|
|
"recognized by OpenSSL.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "cert-extensions", required_argument, NULL, 'e'},
|
|
.argument = "EXTENSIONS",
|
|
.desc = "The certificate extensions for generating a "
|
|
"certificate signing request (CSR) or self signed "
|
|
"certificate, in the form '<name>=[critical,]<value(s)>"
|
|
" (;<name>=[critical,]<value(s)>)*[;]' with extension "
|
|
"names and values recognized by OpenSSL.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "renew-cert", required_argument, NULL, 'N'},
|
|
.argument = "CERT-PEM-FILE",
|
|
.desc = "An existing PEM file containing the certificate to be "
|
|
"renewed. The certificate's subject name and extensions"
|
|
" are used to generate the certificate signing request "
|
|
"(CSR) or renewed self signed certificate.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "csr-new-header", 0, NULL, 'n'},
|
|
.desc = "Adds the word NEW to the PEM file header and footer "
|
|
"lines on the certificate signing request. Some "
|
|
"software and some CAs need this.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "cert-validity-days", required_argument, NULL, 'd'},
|
|
.argument = "DAYS",
|
|
.desc = "The number of days to certify the self signed "
|
|
"certificate. The default is 30 days.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "cert-digest", required_argument, NULL, 'D'},
|
|
.argument = "DIGEST",
|
|
.desc = "The digest algorithm to use when generating a "
|
|
"certificate signing request or self signed "
|
|
"certificate. The default is determined by OpenSSL.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
#ifdef EKMF_SUPPORTS_RSA_PSS_CERTIFICATES
|
|
{
|
|
.option = { "cert-rsa-pss", 0, NULL, 'P'},
|
|
.desc = "Use the RSA-PSS algorithm to sign the certificate "
|
|
"signing request or the self signed certificate. This "
|
|
"option is only honored when the identity key type is "
|
|
"RSA, it is ignored otherwise.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
#endif
|
|
{
|
|
.flags = UTIL_OPT_FLAG_SECTION,
|
|
.desc = "EKMFWEB SPECIFIC OPTIONS FOR REGISTRATION",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "register", required_argument, NULL, 'r'},
|
|
.argument = "CERT-FILE",
|
|
.desc = "Register the zkey client with EKMF Web by generating "
|
|
"an identity key in EKMF Web using the certificate "
|
|
"from the specified file. Supported certificate files "
|
|
"formats are .pem, .crt, .cert, .cer, and .der (i.e. "
|
|
"either base64 or DER encoded). If you want to "
|
|
"register a self signed certificate that you are about "
|
|
"to generate using option '--gen-self-signed-cert', "
|
|
"then specify the same certificate file name here, "
|
|
"and the generated certificate is registered "
|
|
"right away.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "label-tags", required_argument, NULL, 'T'},
|
|
.argument = "LABEL-TAGS",
|
|
.desc = "The label tags for generating the identity key in "
|
|
"EKMF Web when registering the zkey client, in the "
|
|
"form '<tag>=<value>(,<tag>=<value>)*[,]' with tags as "
|
|
"defined by the key template. Use 'zkey kms info' to "
|
|
"display the key templates used by zkey. For "
|
|
"registration, the template for identity keys is used.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
|
|
{
|
|
.flags = UTIL_OPT_FLAG_SECTION,
|
|
.desc = "EKMFWEB SPECIFIC OPTIONS FOR KEY TRANSPORT",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "session-rsa-sign-digest", required_argument, NULL,
|
|
OPT_KT_RSA_SIGNATURE_DIGEST },
|
|
.argument = "DIGEST",
|
|
.flags = UTIL_OPT_FLAG_NOSHORT,
|
|
.desc = "The digest algorithm to use for signing the key "
|
|
"transport request, when the identity key type is "
|
|
"RSA. Ignored otherwise. The default is SHA512.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
{
|
|
.option = { "session-rsa-sign-algo", required_argument, NULL,
|
|
OPT_KT_RSA_PSS_SIGNATURE },
|
|
.argument = "ALGORITHM",
|
|
.flags = UTIL_OPT_FLAG_NOSHORT,
|
|
.desc = "The algorithm to use for signing the key transport "
|
|
"request, when the identity key type is RSA, ignored "
|
|
"otherwise. Supported algorithms are 'RSA'and "
|
|
"'RSA-PSS'.",
|
|
.command = KMS_COMMAND_CONFIGURE,
|
|
},
|
|
#endif
|
|
UTIL_OPT_END,
|
|
};
|
|
|
|
static const struct util_opt generate_options[] = {
|
|
{
|
|
.flags = UTIL_OPT_FLAG_SECTION,
|
|
.desc = "EKMFWEB SPECIFIC OPTIONS",
|
|
.command = KMS_COMMAND_GENERATE,
|
|
},
|
|
{
|
|
.option = { "label-tags", required_argument, NULL, 'T'},
|
|
.argument = "LABEL-TAGS",
|
|
.desc = "The label tags for generating a secure key in EKMF "
|
|
"Web, in the form '<tag>=<value>(,<tag>=<value>)*[,]' "
|
|
"with tags as defined by the key template. Use 'zkey "
|
|
"kms info' to display the key templates used by zkey. "
|
|
"For XTS type keys the two templates for XTS-Key1 and "
|
|
"XTS-Key2 are used. For non-XTS type keys, the "
|
|
"template for Non-XTS keys is used.",
|
|
.command = KMS_COMMAND_GENERATE,
|
|
},
|
|
UTIL_OPT_END,
|
|
};
|
|
|
|
static const struct util_opt remove_options[] = {
|
|
{
|
|
.flags = UTIL_OPT_FLAG_SECTION,
|
|
.desc = "EKMFWEB SPECIFIC OPTIONS",
|
|
.command = KMS_COMMAND_REMOVE,
|
|
},
|
|
{
|
|
.option = { "state", required_argument, NULL, 's'},
|
|
.argument = "STATE",
|
|
.desc = "The state to which to change the key in EKMF Web, "
|
|
"after removing the secure key from the local secure "
|
|
"key repository. Possible states are 'DEACTIVATED', "
|
|
"'COMPROMISED', 'DESTROYED', and "
|
|
"'DESTROYED-COMPROMISED'. If this option is not "
|
|
"specified, the state of the key in EKMF Web is not "
|
|
"changed, but the key is removed from the local "
|
|
"secure key repository only.",
|
|
.command = KMS_COMMAND_REMOVE,
|
|
},
|
|
UTIL_OPT_END,
|
|
};
|
|
|
|
static const struct util_opt list_options[] = {
|
|
{
|
|
.flags = UTIL_OPT_FLAG_SECTION,
|
|
.desc = "EKMFWEB SPECIFIC OPTIONS",
|
|
.command = KMS_COMMAND_LIST,
|
|
},
|
|
{
|
|
.option = { "states", required_argument, NULL, 's'},
|
|
.argument = "STATES",
|
|
.desc = "The states of the keys that are to be listed. "
|
|
"Multiple states can be separated by comma. Possible "
|
|
"states are 'PRE-ACTIVATION', 'ACTIVE', 'DEACTIVATED', "
|
|
"'COMPROMISED', 'DESTROYED', and "
|
|
"'DESTROYED-COMPROMISED'. If this "
|
|
"option is not specified, only keys in state 'ACTIVE' "
|
|
"are listed.",
|
|
.command = KMS_COMMAND_LIST,
|
|
},
|
|
{
|
|
.option = { "all", 0, NULL, 'a'},
|
|
.desc = "List all keys that can be used for volume encryption. "
|
|
"If this option is not specified, then only volume "
|
|
"encryption keys that are allowed to be exported by "
|
|
"EKMF Web using the identity key of this zkey client "
|
|
"are listed.",
|
|
.command = KMS_COMMAND_LIST,
|
|
},
|
|
UTIL_OPT_END,
|
|
};
|
|
|
|
/**
|
|
* 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 UNUSED(max_opts))
|
|
{
|
|
util_assert(command != NULL, "Internal error: command is NULL");
|
|
|
|
if (strcasecmp(command, KMS_COMMAND_CONFIGURE) == 0)
|
|
return configure_options;
|
|
if (strcasecmp(command, KMS_COMMAND_GENERATE) == 0)
|
|
return generate_options;
|
|
if (strcasecmp(command, KMS_COMMAND_REMOVE) == 0)
|
|
return remove_options;
|
|
if (strcasecmp(command, KMS_COMMAND_LIST) == 0)
|
|
return list_options;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Gets the OpenSSL curve NID from the infos from the identity template.
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param curve the name of the curve from the template
|
|
* @param key_size the size of the key in bits
|
|
*
|
|
* @returns the OpenSSL NID for the curve, or NID_undef in case of an error
|
|
*/
|
|
static int _get_curve_nid(struct plugin_handle *ph, const char *curve,
|
|
size_t key_size)
|
|
{
|
|
int nid = NID_undef;
|
|
|
|
if (strcmp(curve, EKMFWEB_CURVE_PRIME) == 0) {
|
|
switch (key_size) {
|
|
case 192:
|
|
nid = NID_X9_62_prime192v1;
|
|
break;
|
|
case 224:
|
|
nid = NID_secp224r1;
|
|
break;
|
|
case 256:
|
|
nid = NID_X9_62_prime256v1;
|
|
break;
|
|
case 384:
|
|
nid = NID_secp384r1;
|
|
break;
|
|
case 521:
|
|
nid = NID_secp521r1;
|
|
break;
|
|
default:
|
|
_set_error(ph, "Unsupported bit size %u of curve '%s'",
|
|
key_size, curve);
|
|
goto out;
|
|
}
|
|
} else if (strcmp(curve, EKMFWEB_CURVE_BAINPOOL) == 0) {
|
|
switch (key_size) {
|
|
case 160:
|
|
nid = NID_brainpoolP160r1;
|
|
break;
|
|
case 192:
|
|
nid = NID_brainpoolP192r1;
|
|
break;
|
|
case 224:
|
|
nid = NID_brainpoolP224r1;
|
|
break;
|
|
case 256:
|
|
nid = NID_brainpoolP256r1;
|
|
break;
|
|
case 320:
|
|
nid = NID_brainpoolP320r1;
|
|
break;
|
|
case 384:
|
|
nid = NID_brainpoolP384r1;
|
|
break;
|
|
case 512:
|
|
nid = NID_brainpoolP512r1;
|
|
break;
|
|
default:
|
|
_set_error(ph, "Unsupported bit size %u of curve '%s'",
|
|
key_size, curve);
|
|
goto out;
|
|
}
|
|
} else {
|
|
_set_error(ph, "Unsupported curve '%s'", curve);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
return nid;
|
|
}
|
|
|
|
struct template_cb_data {
|
|
const char *template;
|
|
struct ekmf_template_info **info;
|
|
};
|
|
|
|
/**
|
|
* Callback for ekmf_list_templates function to get template info by name
|
|
*
|
|
* @param curl_handle a CURL handle that can be used to perform further
|
|
* EKMFWeb functions within the callback.
|
|
* @param template_info a struct containing information about the template.
|
|
* If any of the information needs to be kept, then the
|
|
* callback function must make a copy of the
|
|
* information. The memory holding the information
|
|
* passed to the callback is no longer valid after the
|
|
* callback has returned.
|
|
* @param private the private pointer that was specified with the
|
|
* ekmf_list_templates invocation.
|
|
*
|
|
* @returns 0 on success, a negative errno in case of an error.
|
|
*/
|
|
static int _template_cb(CURL *UNUSED(curl_handle),
|
|
struct ekmf_template_info *template_info,
|
|
void *private)
|
|
{
|
|
struct template_cb_data *data = private;
|
|
int rc;
|
|
|
|
if (*data->info != NULL)
|
|
return 0;
|
|
|
|
if (strcmp(template_info->name, data->template) != 0)
|
|
return 0;
|
|
|
|
rc = ekmf_clone_template_info(template_info, data->info);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Get information about a template by name
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param template the name of the template
|
|
* @param info On return: the template info. Must be freed by the
|
|
* caller via ekmf_free_template_info.
|
|
*
|
|
* @returns 0 on success, a negative errno in case of an error.
|
|
*/
|
|
static int _get_template_by_name(struct plugin_handle *ph, const char *template,
|
|
struct ekmf_template_info **info)
|
|
{
|
|
struct template_cb_data data;
|
|
char *error_msg = NULL;
|
|
int rc;
|
|
|
|
*info = NULL;
|
|
|
|
data.template = template;
|
|
data.info = info;
|
|
|
|
rc = ekmf_list_templates(&ph->ekmf_config, &ph->curl_handle,
|
|
_template_cb, &data, template,
|
|
EKMFWEB_TEMPLATE_STATE_ACTIVE,
|
|
&error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to get the template '%s': %s", template,
|
|
error_msg != NULL ? error_msg : strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
if (*info == NULL) {
|
|
rc = -ENOENT;
|
|
_set_error(ph, "Template '%s' does not exist", template);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (error_msg != NULL)
|
|
free(error_msg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Check a template if it is using the desired settings
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param info the template info
|
|
* @param keystore_type the expected keystore type
|
|
* @param no_warnig if true, do not issue warning messages
|
|
*
|
|
* @returns 0 on success, a negative errno in case of an error.
|
|
*/
|
|
static int _check_template(struct plugin_handle *ph,
|
|
struct ekmf_template_info *info,
|
|
const char *keystore_type, bool no_warning)
|
|
{
|
|
char *identity_key_param = NULL;
|
|
char *identity_key_alg = NULL;
|
|
size_t modulus_bits;
|
|
char *msg = NULL;
|
|
int curve_nid;
|
|
int rc = 0;
|
|
|
|
if (strcmp(info->state, EKMFWEB_TEMPLATE_STATE_ACTIVE) != 0) {
|
|
if (strcmp(info->state, EKMFWEB_TEMPLATE_STATE_HISTORY) == 0)
|
|
_set_error(ph, "Template '%s' is in state '%s'. "
|
|
"If the template has been recently changed, "
|
|
"run 'zkey kms configure --refresh-settings'"
|
|
" to refresh the templates.", info->name,
|
|
EKMFWEB_TEMPLATE_STATE_HISTORY);
|
|
else
|
|
_set_error(ph, "Template '%s' is in state '%s', "
|
|
"but only templates in state '%s' can "
|
|
"be used.", info->name, info->state,
|
|
EKMFWEB_TEMPLATE_STATE_ACTIVE);
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (strcmp(info->key_state, EKMFWEB_KEY_STATE_ACTIVE) != 0) {
|
|
_set_error(ph, "Template '%s' generates key in state '%s', but "
|
|
"only templates that generate keys in state '%s' "
|
|
"are supported.", info->name, info->key_state,
|
|
EKMFWEB_KEY_STATE_ACTIVE);
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (strcmp(info->keystore_type, keystore_type) != 0) {
|
|
_set_error(ph, "Template '%s' uses key store type '%s', but "
|
|
"only key store type '%s' is supported for %s.",
|
|
info->name, info->keystore_type, keystore_type,
|
|
strcmp(keystore_type,
|
|
EKMFWEB_KEYSTORE_TYPE_PERV_ENCR) == 0 ?
|
|
"volume encryption keys" : "identity keys");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (strcmp(info->keystore_type, EKMFWEB_KEYSTORE_TYPE_PERV_ENCR) == 0) {
|
|
if (strcmp(info->key_type, EKMFWEB_KEY_TYPE_CIPHER) != 0) {
|
|
_set_error(ph, "Template '%s' generates keys of type "
|
|
"'%s', but only key type '%s' is supported "
|
|
"for volume encryption keys.",
|
|
info->name, info->key_type,
|
|
EKMFWEB_KEY_TYPE_CIPHER);
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (strcmp(info->algorithm, EKMFWEB_KEY_ALGORITHM_AES) != 0) {
|
|
_set_error(ph, "Template '%s' generates keys with "
|
|
"algorithm '%s', but only algorithm '%s' is "
|
|
"supported for volume encryption keys.",
|
|
info->name, info->algorithm,
|
|
EKMFWEB_KEY_ALGORITHM_AES);
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (info->export_allowed == false) {
|
|
_set_error(ph, "Template '%s' generates key that are "
|
|
"not allowed to be exported, but only "
|
|
"templates that generate keys that are "
|
|
"allowed to be exported are supported for "
|
|
"volume encryption keys.",
|
|
info->name);
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (strcmp(info->keystore_type, EKMFWEB_KEYSTORE_TYPE_IDENTITY) == 0) {
|
|
if (strcmp(info->algorithm, EKMFWEB_KEY_ALGORITHM_ECC) != 0 &&
|
|
strcmp(info->algorithm, EKMFWEB_KEY_ALGORITHM_RSA) != 0) {
|
|
_set_error(ph, "Template '%s' generates keys with "
|
|
"algorithm '%s', but only algorithms '%s' "
|
|
"and '%s' are supported for identity keys.",
|
|
info->name, info->algorithm,
|
|
EKMFWEB_KEY_ALGORITHM_ECC,
|
|
EKMFWEB_KEY_ALGORITHM_RSA);
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (no_warning)
|
|
goto out;
|
|
|
|
identity_key_alg = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_ALGORITHM);
|
|
if (identity_key_alg == NULL)
|
|
goto out;
|
|
|
|
if (strcmp(info->algorithm, identity_key_alg) != 0) {
|
|
util_asprintf(&msg, "WARNING: Template '%s' uses "
|
|
"algorithm '%s', but the existing "
|
|
"identity key uses algorithm '%s'. You "
|
|
"may need to generate a new identity "
|
|
"key and re-register this zkey client.",
|
|
info->name, info->algorithm,
|
|
identity_key_alg);
|
|
util_print_indented(msg, 0);
|
|
free(msg);
|
|
goto out;
|
|
}
|
|
|
|
identity_key_param = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_PARAMS);
|
|
if (identity_key_param == NULL)
|
|
goto out;
|
|
|
|
if (strcmp(identity_key_alg, EKMFWEB_KEY_ALGORITHM_ECC) == 0) {
|
|
curve_nid = _get_curve_nid(ph, info->curve,
|
|
info->key_size);
|
|
if (curve_nid == NID_undef) {
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (OBJ_txt2nid(identity_key_param) != curve_nid) {
|
|
util_asprintf(&msg, "WARNING: Template '%s' "
|
|
"uses algorithm ECC with curve "
|
|
"'%s', but the existing identity "
|
|
"key uses curve '%s'. You may "
|
|
"need to generate a new identity "
|
|
"key and re-register this zkey "
|
|
"client.", info->name,
|
|
OBJ_nid2sn(curve_nid),
|
|
identity_key_param);
|
|
util_print_indented(msg, 0);
|
|
free(msg);
|
|
}
|
|
} else if (strcmp(identity_key_alg, EKMFWEB_KEY_ALGORITHM_RSA)
|
|
== 0) {
|
|
modulus_bits = strtoul(identity_key_param, NULL, 10);
|
|
if (modulus_bits != info->key_size) {
|
|
util_asprintf(&msg, "WARNING: Template '%s' "
|
|
"uses algorithm RSA with a "
|
|
"modulus bit size of %lu, but "
|
|
"the existing identity key uses "
|
|
"%lu bits. You may need to "
|
|
"generate a new identity key and "
|
|
"re-register this zkey client.",
|
|
info->name, info->key_size,
|
|
modulus_bits);
|
|
util_print_indented(msg, 0);
|
|
free(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (identity_key_alg != NULL)
|
|
free(identity_key_alg);
|
|
if (identity_key_param != NULL)
|
|
free(identity_key_param);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Check the 2 XTS templates
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param xts1_info the template info if XTS key 1
|
|
* @param xts2_info the template info if XTS key 2
|
|
*
|
|
* @returns 0 on success, a negative errno in case of an error.
|
|
*/
|
|
static int _check_xts_templates(struct plugin_handle *ph,
|
|
struct ekmf_template_info *xts1_info,
|
|
struct ekmf_template_info *xts2_info)
|
|
{
|
|
size_t i;
|
|
|
|
if (strcasecmp(xts1_info->label_template,
|
|
xts2_info->label_template) == 0) {
|
|
_set_error(ph, "The 2 XTS templates can not have the same "
|
|
"label template.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (xts1_info->key_size != xts2_info->key_size) {
|
|
_set_error(ph, "The 2 XTS templates must have the same key "
|
|
"size.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (xts1_info->label_tags.num_tag_defs !=
|
|
xts2_info->label_tags.num_tag_defs) {
|
|
_set_error(ph, "The 2 XTS templates must have the same label "
|
|
"tags. The templates differ in the number of label "
|
|
"tags: '%s' '%s'", xts1_info->label_template,
|
|
xts2_info->label_template);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < xts1_info->label_tags.num_tag_defs; i++) {
|
|
if (strcasecmp(xts1_info->label_tags.tag_defs[i].name,
|
|
xts2_info->label_tags.tag_defs[i].name) != 0) {
|
|
_set_error(ph, "The 2 XTS templates must have the same "
|
|
"label tags. Mismatch in tag '%s': "
|
|
"'%s' '%s'",
|
|
xts1_info->label_tags.tag_defs[i].name,
|
|
xts1_info->label_template,
|
|
xts2_info->label_template);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct template_infos {
|
|
char *template;
|
|
struct ekmf_template_info *info;
|
|
const char *name_prop;
|
|
const char *label_prop;
|
|
const char *id_prop;
|
|
const char *keystore_type;
|
|
};
|
|
|
|
#define NUM_TEMPLATES 4
|
|
#define IDENTITY 0
|
|
#define XTS1 1
|
|
#define XTS2 2
|
|
#define NONXTS 3
|
|
|
|
/**
|
|
* Retrieves the key templates to be used by the plugin
|
|
*
|
|
* @param ph the plugin handle
|
|
*
|
|
* @returns 0 on success, a negative errno in case of an error.
|
|
*/
|
|
static int _get_templates(struct plugin_handle *ph)
|
|
{
|
|
struct template_infos tmpl[NUM_TEMPLATES] = {
|
|
{ .template = NULL, .info = NULL,
|
|
.name_prop = EKMFWEB_CONFIG_TEMPLATE_IDENTITY,
|
|
.label_prop = EKMFWEB_CONFIG_TEMPLATE_IDENTITY_LABEL,
|
|
.id_prop = EKMFWEB_CONFIG_TEMPLATE_IDENTITY_ID,
|
|
.keystore_type = EKMFWEB_KEYSTORE_TYPE_IDENTITY, },
|
|
{ .template = NULL, .info = NULL,
|
|
.name_prop = EKMFWEB_CONFIG_TEMPLATE_XTS1,
|
|
.label_prop = EKMFWEB_CONFIG_TEMPLATE_XTS1_LABEL,
|
|
.id_prop = EKMFWEB_CONFIG_TEMPLATE_XTS1_ID,
|
|
.keystore_type = EKMFWEB_KEYSTORE_TYPE_PERV_ENCR, },
|
|
{ .template = NULL, .info = NULL,
|
|
.name_prop = EKMFWEB_CONFIG_TEMPLATE_XTS2,
|
|
.label_prop = EKMFWEB_CONFIG_TEMPLATE_XTS2_LABEL,
|
|
.id_prop = EKMFWEB_CONFIG_TEMPLATE_XTS2_ID,
|
|
.keystore_type = EKMFWEB_KEYSTORE_TYPE_PERV_ENCR, },
|
|
{ .template = NULL, .info = NULL,
|
|
.name_prop = EKMFWEB_CONFIG_TEMPLATE_NONXTS,
|
|
.label_prop = EKMFWEB_CONFIG_TEMPLATE_NONXTS_LABEL,
|
|
.id_prop = EKMFWEB_CONFIG_TEMPLATE_NONXTS_ID,
|
|
.keystore_type = EKMFWEB_KEYSTORE_TYPE_PERV_ENCR, },
|
|
};
|
|
char *error_msg = NULL;
|
|
int t, rc;
|
|
|
|
rc = ekmf_get_settings(&ph->ekmf_config, &ph->curl_handle,
|
|
&tmpl[IDENTITY].template, &tmpl[XTS1].template,
|
|
&tmpl[XTS2].template, &tmpl[NONXTS].template,
|
|
&error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to get settings from EKMF Web: %s",
|
|
error_msg != NULL ? error_msg : strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
for (t = 0; t < NUM_TEMPLATES; t++) {
|
|
rc = _get_template_by_name(ph, tmpl[t].template, &tmpl[t].info);
|
|
if (rc != 0)
|
|
goto out;
|
|
}
|
|
|
|
rc = _check_xts_templates(ph, tmpl[XTS1].info, tmpl[XTS2].info);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
for (t = 0; t < NUM_TEMPLATES; t++) {
|
|
rc = _check_template(ph, tmpl[t].info, tmpl[t].keystore_type,
|
|
false);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = plugin_set_or_remove_property(&ph->pd, tmpl[t].name_prop,
|
|
tmpl[t].template);
|
|
if (rc != 0)
|
|
goto out;
|
|
rc = plugin_set_or_remove_property(&ph->pd, tmpl[t].label_prop,
|
|
tmpl[t].info->label_template);
|
|
if (rc != 0)
|
|
goto out;
|
|
rc = plugin_set_or_remove_property(&ph->pd, tmpl[t].id_prop,
|
|
tmpl[t].info->uuid);
|
|
if (rc != 0)
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
for (t = 0; t < NUM_TEMPLATES; t++) {
|
|
if (tmpl[t].info != NULL)
|
|
ekmf_free_template_info(tmpl[t].info);
|
|
if (tmpl[t].template != NULL)
|
|
free(tmpl[t].template);
|
|
}
|
|
if (error_msg != NULL)
|
|
free(error_msg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the EKMF Web system settings. This requires a login. If no
|
|
* valid login token is available, a login is performed.
|
|
*
|
|
* @param ph the plugin handle
|
|
*
|
|
* @returns 0 on success, a negative errno in case of an error.
|
|
*/
|
|
static int _get_ekmfweb_settings(struct plugin_handle *ph)
|
|
{
|
|
int rc, type = 0, curve = NID_undef;
|
|
char *error_msg = NULL;
|
|
|
|
_check_config_complete(ph);
|
|
|
|
if (ph->ekmf_config.login_token != NULL) {
|
|
remove(ph->ekmf_config.login_token);
|
|
FREE_AND_SET_NULL(ph->ekmf_config.login_token);
|
|
}
|
|
rc = plugin_set_or_remove_property(&ph->pd, EKMFWEB_CONFIG_LOGIN_TOKEN,
|
|
NULL);
|
|
if (rc != 0)
|
|
goto out;
|
|
rc = plugin_set_or_remove_property(&ph->pd, EKMFWEB_CONFIG_PASSCODE_URL,
|
|
NULL);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = ekmf_check_feature(&ph->ekmf_config, &ph->curl_handle,
|
|
&error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
if (rc == -ENOTSUP)
|
|
_set_error(ph, "%s", error_msg);
|
|
else
|
|
_set_error(ph, "Failed to check the features of the "
|
|
"EKMF Web server at '%s': %s",
|
|
ph->ekmf_config.base_url,
|
|
error_msg != NULL ? error_msg :
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
rc = kms_login((kms_handle_t)ph);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
if (ph->ekmf_config.ekmf_server_pubkey != NULL)
|
|
remove(ph->ekmf_config.ekmf_server_pubkey);
|
|
FREE_AND_SET_NULL(ph->ekmf_config.ekmf_server_pubkey);
|
|
|
|
util_asprintf((char **)&ph->ekmf_config.ekmf_server_pubkey,
|
|
"%s/%s", ph->pd.config_path,
|
|
EKMFWEB_CONFIG_EKMFWEB_PUBKEY_FILE);
|
|
|
|
rc = ekmf_get_public_key(&ph->ekmf_config, &ph->curl_handle,
|
|
&error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to get the public key of the EKMF Web "
|
|
"server at '%s': %s", ph->ekmf_config.base_url,
|
|
error_msg != NULL ? error_msg : strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = plugin_set_file_permission(&ph->pd,
|
|
ph->ekmf_config.ekmf_server_pubkey);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_EKMFWEB_PUBKEY,
|
|
ph->ekmf_config.ekmf_server_pubkey);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = _get_pub_key_info(ph, ph->ekmf_config.ekmf_server_pubkey, &type,
|
|
&curve, NULL);
|
|
if (rc != 0)
|
|
goto out;
|
|
if (type != EVP_PKEY_EC || curve == NID_undef)
|
|
curve = NID_secp521r1;
|
|
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_SESSION_KEY_CURVE,
|
|
OBJ_nid2sn(curve));
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = _get_templates(ph);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
out:
|
|
if (error_msg != NULL)
|
|
free(error_msg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* 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->pd.verbose)) {
|
|
_set_error(ph, "Operation 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->pd, "url: '%s'", url);
|
|
|
|
FREE_AND_SET_NULL(ph->ekmf_config.base_url);
|
|
ph->ekmf_config.base_url = url;
|
|
|
|
rc = properties_set(ph->pd.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 = plugin_set_or_remove_property(&ph->pd, 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 = plugin_set_or_remove_property(&ph->pd, 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 = plugin_set_or_remove_property(&ph->pd, 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 = plugin_set_or_remove_property(&ph->pd,
|
|
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->pd.config_path,
|
|
EKMFWEB_CONFIG_SERVER_CERT_FILE);
|
|
util_asprintf(&server_pubkey_temp, "%s/%s-tmp", ph->pd.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->pd.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 = plugin_check_certificate(&ph->pd, server_cert_temp, &self_signed,
|
|
&valid);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to check certificate PEM file '%s': %s",
|
|
server_cert_temp, strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
pr_verbose(&ph->pd, "verified: %d", verified);
|
|
pr_verbose(&ph->pd, "self signed: %d", self_signed);
|
|
pr_verbose(&ph->pd, "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->pd.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 intend to work with "
|
|
"[y/N]? ", program_invocation_short_name);
|
|
if (!prompt_for_yes(ph->pd.verbose)) {
|
|
_set_error(ph, "Operation 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 = plugin_set_or_remove_property(&ph->pd,
|
|
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 = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_VERIFY_HOSTNAME,
|
|
ph->ekmf_config.tls_verify_host ?
|
|
"yes" : "no");
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = _get_ekmfweb_settings(ph);
|
|
if (rc != 0) {
|
|
util_print_indented("The server you are connected with is not "
|
|
"a valid EKMF Web server, or is not "
|
|
"configured properly", 0);
|
|
goto out;
|
|
}
|
|
|
|
FREE_AND_SET_NULL(ph->ekmf_config.tls_server_cert);
|
|
util_asprintf(&server_cert_file, "%s/%s", ph->pd.config_path,
|
|
EKMFWEB_CONFIG_SERVER_CERT_FILE);
|
|
if (tls_trust_server_cert) {
|
|
ph->ekmf_config.tls_server_cert = util_strdup(server_cert_file);
|
|
rc = plugin_activate_temp_file(&ph->pd, server_cert_temp,
|
|
server_cert_file);
|
|
if (rc != 0)
|
|
goto out;
|
|
} else {
|
|
remove(server_cert_file);
|
|
}
|
|
rc = plugin_set_or_remove_property(&ph->pd, 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->pd.config_path,
|
|
EKMFWEB_CONFIG_SERVER_PUBKEY_FILE);
|
|
if (tls_pin_server_pubkey) {
|
|
ph->ekmf_config.tls_pinned_pubkey =
|
|
util_strdup(server_pubkey_file);
|
|
rc = plugin_activate_temp_file(&ph->pd, server_pubkey_temp,
|
|
server_pubkey_file);
|
|
if (rc != 0)
|
|
goto out;
|
|
} else {
|
|
remove(server_pubkey_file);
|
|
}
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
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;
|
|
bool refresh_settings;
|
|
bool generate_identity_key;
|
|
const char *sscert_pem_file;
|
|
const char *csr_pem_file;
|
|
const char *cert_subject;
|
|
const char *cert_extensions;
|
|
const char *renew_cert_pem_file;
|
|
bool csr_new_header;
|
|
const char *cert_validity_days;
|
|
const char *cert_digest;
|
|
#ifdef EKMF_SUPPORTS_RSA_PSS_CERTIFICATES
|
|
bool cert_rsa_pss;
|
|
#endif
|
|
const char *register_cert_file;
|
|
const char *register_label_tags;
|
|
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
|
|
const char *session_rsa_sign_digest;
|
|
const char *session_rsa_sign_algo;
|
|
#endif
|
|
};
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Generates (or re-generates) a identity key for the plugin using the
|
|
* settings from the identity template
|
|
*
|
|
* @param ph the plugin handle
|
|
*
|
|
* @returns 0 on success, a negative errno in case of an error.
|
|
*/
|
|
static int _generate_identity_key(struct plugin_handle *ph)
|
|
{
|
|
struct ekmf_template_info *template_info = NULL;
|
|
struct ekmf_key_gen_info gen_info;
|
|
char *template_uuid = NULL;
|
|
char *reenc_file = NULL;
|
|
char *error_msg = NULL;
|
|
char key_params[200];
|
|
int rc = 0;
|
|
|
|
_check_config_complete(ph);
|
|
|
|
if (!ph->apqns_configured) {
|
|
_set_error(ph, "The configuration is incomplete, you must "
|
|
"first configure the APQNs used with this plugin.");
|
|
return -EINVAL;
|
|
}
|
|
if (!ph->templates_retrieved) {
|
|
_set_error(ph, "The configuration is incomplete, you must "
|
|
"first configure the EKMF Web server connection.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = kms_login((kms_handle_t)ph);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
template_uuid = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_TEMPLATE_IDENTITY_ID);
|
|
if (template_uuid == NULL) {
|
|
rc = -EIO;
|
|
_set_error(ph, "No identity key template configured");
|
|
goto out;
|
|
}
|
|
|
|
rc = ekmf_get_template(&ph->ekmf_config, &ph->curl_handle,
|
|
template_uuid, &template_info,
|
|
&error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to get identity key template '%s': %s",
|
|
template_uuid, error_msg != NULL ? error_msg :
|
|
strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = _check_template(ph, template_info, EKMFWEB_KEYSTORE_TYPE_IDENTITY,
|
|
true);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
pr_verbose(&ph->pd, "Identity template algorithm: '%s'",
|
|
template_info->algorithm);
|
|
pr_verbose(&ph->pd, "Identity template key size: %lu",
|
|
template_info->key_size);
|
|
|
|
if (strcmp(template_info->algorithm, EKMFWEB_KEY_ALGORITHM_ECC) == 0) {
|
|
pr_verbose(&ph->pd, "Identity template curve: '%s'",
|
|
template_info->curve);
|
|
|
|
gen_info.type = EKMF_KEY_TYPE_ECC;
|
|
gen_info.params.ecc.curve_nid =
|
|
_get_curve_nid(ph, template_info->curve,
|
|
template_info->key_size);
|
|
if (gen_info.params.ecc.curve_nid == NID_undef)
|
|
return -EINVAL;
|
|
|
|
strcpy(key_params, OBJ_nid2sn(gen_info.params.ecc.curve_nid));
|
|
} else if (strcmp(template_info->algorithm,
|
|
EKMFWEB_KEY_ALGORITHM_RSA) == 0) {
|
|
gen_info.type = EKMF_KEY_TYPE_RSA;
|
|
switch (template_info->key_size) {
|
|
case 512:
|
|
case 1024:
|
|
case 2048:
|
|
case 4096:
|
|
gen_info.params.rsa.modulus_bits =
|
|
template_info->key_size;
|
|
break;
|
|
default:
|
|
_set_error(ph, "Invalid modulus bits: '%s'",
|
|
template_info->key_size);
|
|
return -EINVAL;
|
|
}
|
|
gen_info.params.rsa.pub_exp =
|
|
DEFAULT_IDENTITY_KEY_PUBLIC_EXPONENT;
|
|
|
|
sprintf(key_params, "%lu", gen_info.params.rsa.modulus_bits);
|
|
} else {
|
|
_set_error(ph, "Invalid identity template algorithm type '%s'",
|
|
template_info->algorithm);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ph->ekmf_config.identity_secure_key != NULL) {
|
|
printf("ATTENTION: An identity key already exists!\n");
|
|
util_print_indented("When you generate a new identity key, "
|
|
"you will need to re-register this zkey "
|
|
"client with the EKMF Web server.", 0);
|
|
printf("%s: Re-generate the identity key [y/N]? ",
|
|
program_invocation_short_name);
|
|
if (!prompt_for_yes(ph->pd.verbose)) {
|
|
_set_error(ph, "Operation aborted by user");
|
|
return -ECANCELED;
|
|
}
|
|
} else {
|
|
util_asprintf((char **)&ph->ekmf_config.identity_secure_key,
|
|
"%s/%s", ph->pd.config_path,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_FILE);
|
|
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY,
|
|
ph->ekmf_config.identity_secure_key);
|
|
if (rc != 0)
|
|
goto out;
|
|
}
|
|
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_ALGORITHM,
|
|
template_info->algorithm);
|
|
if (rc != 0)
|
|
goto out;
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_PARAMS,
|
|
key_params);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = _select_cca_adapter(ph);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = ekmf_generate_identity_key(&ph->ekmf_config, &gen_info,
|
|
&ph->ext_lib, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to generate the identity key: %s",
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
rc = plugin_set_file_permission(&ph->pd,
|
|
ph->ekmf_config.identity_secure_key);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
reenc_file = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC);
|
|
if (reenc_file != NULL) {
|
|
remove(reenc_file);
|
|
free(reenc_file);
|
|
properties_remove(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC);
|
|
}
|
|
|
|
properties_remove(ph->pd.properties, EKMFWEB_CONFIG_IDENTITY_KEY_LABEL);
|
|
properties_remove(ph->pd.properties, EKMFWEB_CONFIG_IDENTITY_KEY_ID);
|
|
|
|
pr_verbose(&ph->pd, "Generated identity key into '%s'",
|
|
ph->ekmf_config.identity_secure_key);
|
|
|
|
out:
|
|
if (template_uuid != NULL)
|
|
free(template_uuid);
|
|
if (error_msg != NULL)
|
|
free(error_msg);
|
|
if (template_info != NULL)
|
|
ekmf_free_template_info(template_info);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Parses an unsigned number from a string.
|
|
*
|
|
* @param str the string to parse
|
|
*
|
|
* @returns the parsed number, or -1 in case of an error.
|
|
*/
|
|
static long _parse_unsigned(const char *str)
|
|
{
|
|
long val;
|
|
char *endp;
|
|
|
|
val = strtol(str, &endp, 0);
|
|
if (*str == '\0' || *endp != '\0' ||
|
|
(val == LONG_MAX && errno == ERANGE))
|
|
return -1;
|
|
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* Generates certificate signing request or self signed certificate using the
|
|
* identity key
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param csr_pem_file name of the PEM file to store a CSR to. NULL if no
|
|
* CSR is to be generated.
|
|
* @param sscert_pem_file name of the PEM file to store a self signed
|
|
* certificate to. NULL if no certificate is to be
|
|
* generated.
|
|
* @param subject the subject RNDs separated by semicolon (;). Can be
|
|
* NULL if a renew certificate is specified.
|
|
* @param extensions the extensions separated by semicolon (;). Can be
|
|
* NULL.
|
|
* @param renew_cert_pem_file name of a PEM file containing a certificate to
|
|
* renew. Can be NULL.
|
|
* @param csr_new_header if true output NEW header and footer lines in CSR
|
|
* @param validity_days the number of days the certificate is valid. Only
|
|
* valid when generating a self signed certificate.
|
|
* Can be NULL.
|
|
* @param digest the digest to use with CSR and certificates. Can be
|
|
* NULL
|
|
* @param rsa_pss if true, RSA-PSS is used with RSA-based identity
|
|
* keys
|
|
*
|
|
* @returns 0 on success, a negative errno in case of an error.
|
|
*/
|
|
static int _generate_csr_sscert(struct plugin_handle *ph,
|
|
const char *csr_pem_file,
|
|
const char *sscert_pem_file,
|
|
const char *subject, const char *extensions,
|
|
const char *renew_cert_pem_file,
|
|
bool csr_new_header, const char *validity_days,
|
|
const char *digest, bool rsa_pss)
|
|
{
|
|
struct ekmf_rsa_pss_params rsa_pss_parms = {
|
|
.salt_len = RSA_PSS_SALTLEN_MAX, .mgf_digest_nid = 0 };
|
|
char **subject_rdn_list = NULL;
|
|
char **extension_list = NULL;
|
|
size_t num_subject_rdns = 0;
|
|
int digest_nid = NID_undef;
|
|
size_t num_extensions = 0;
|
|
int days = 30;
|
|
int rc = 0;
|
|
size_t i;
|
|
|
|
_check_config_complete(ph);
|
|
|
|
if (!ph->apqns_configured) {
|
|
_set_error(ph, "The configuration is incomplete, you must "
|
|
"first configure the APQNs used with this plugin.");
|
|
return -EINVAL;
|
|
}
|
|
if (!ph->identity_key_generated) {
|
|
_set_error(ph, "The configuration is incomplete, you must "
|
|
"first configure the EKMF Web server connection.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (csr_pem_file != NULL && sscert_pem_file != NULL) {
|
|
_set_error(ph, "Either '--gen-csr' or option "
|
|
"'--gen-self-signed-cert' can be specified.");
|
|
return -EINVAL;
|
|
}
|
|
if (csr_new_header && csr_pem_file == NULL) {
|
|
_set_error(ph, "Option '--csr-new-header' is only valid with "
|
|
"option '--gen-csr'.");
|
|
return -EINVAL;
|
|
}
|
|
if (validity_days != NULL && sscert_pem_file == NULL) {
|
|
_set_error(ph, "Option '--cert-validity-days' is only valid "
|
|
"with option '--gen-self-signed-cert'.");
|
|
return -EINVAL;
|
|
}
|
|
if (subject == NULL && renew_cert_pem_file == NULL) {
|
|
_set_error(ph, "Option '--cert-subject' is required, unless "
|
|
" option '--renew-cert' is specified.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (validity_days != NULL) {
|
|
days = _parse_unsigned(validity_days);
|
|
if (days <= 0) {
|
|
_set_error(ph, "Invalid validity days: '%s'",
|
|
validity_days);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (digest != NULL) {
|
|
digest_nid = OBJ_txt2nid(digest);
|
|
if (digest_nid == NID_undef) {
|
|
_set_error(ph, "Invalid digest: '%s'", digest);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (subject != NULL) {
|
|
rc = parse_list(subject, &subject_rdn_list, &num_subject_rdns);
|
|
if (rc != 0)
|
|
goto out;
|
|
}
|
|
|
|
if (extensions != NULL) {
|
|
rc = parse_list(extensions, &extension_list, &num_extensions);
|
|
if (rc != 0)
|
|
goto out;
|
|
}
|
|
|
|
rc = _select_cca_adapter(ph);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
if (csr_pem_file != NULL) {
|
|
rc = ekmf_generate_csr(&ph->ekmf_config,
|
|
(const char **)subject_rdn_list,
|
|
num_subject_rdns, true,
|
|
renew_cert_pem_file,
|
|
(const char **)extension_list,
|
|
num_extensions, digest_nid,
|
|
rsa_pss ? &rsa_pss_parms : NULL,
|
|
csr_pem_file, csr_new_header,
|
|
&ph->ext_lib, ph->pd.verbose);
|
|
} else {
|
|
rc = ekmf_generate_ss_cert(&ph->ekmf_config,
|
|
(const char **)subject_rdn_list,
|
|
num_subject_rdns, true,
|
|
renew_cert_pem_file,
|
|
(const char **)extension_list,
|
|
num_extensions, days, digest_nid,
|
|
rsa_pss ? &rsa_pss_parms : NULL,
|
|
sscert_pem_file, &ph->ext_lib,
|
|
ph->pd.verbose);
|
|
}
|
|
switch (rc) {
|
|
case 0:
|
|
break;
|
|
case -EBADMSG:
|
|
_set_error(ph, "The subject or extensions could not be parsed "
|
|
"or are not recognized by OpenSSL.");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
case -EEXIST:
|
|
_set_error(ph, "One of the subject name entries or extensions "
|
|
"is a duplicate.");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
case -ENOTSUP:
|
|
_set_error(ph, "The specified digest is not supported.");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
default:
|
|
_set_error(ph, "Failed to generate the %s: %s",
|
|
csr_pem_file != NULL ? "certificate signing request"
|
|
: "self signed certificate",
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
if (csr_pem_file != NULL)
|
|
pr_verbose(&ph->pd, "Generated certificate signing request "
|
|
"into '%s'", csr_pem_file);
|
|
else
|
|
pr_verbose(&ph->pd, "Generated self signed certificate into "
|
|
"'%s'", sscert_pem_file);
|
|
|
|
out:
|
|
if (subject_rdn_list != NULL) {
|
|
for (i = 0; i < num_subject_rdns; i++)
|
|
free(subject_rdn_list[i]);
|
|
free(subject_rdn_list);
|
|
}
|
|
if (extension_list != NULL) {
|
|
for (i = 0; i < num_extensions; i++)
|
|
free(extension_list[i]);
|
|
free(extension_list);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Checks that none of the options for generating a CSR or self signed
|
|
* certificate 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_gen_csr_sscert_opts(struct plugin_handle *ph,
|
|
struct config_options *opts)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (opts->cert_subject != NULL) {
|
|
_set_error(ph, "Option '--cert-subject' is only valid "
|
|
"together with options '--gen-csr' or "
|
|
"'--gen-self-signed-cert'.");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (opts->cert_extensions != NULL) {
|
|
_set_error(ph, "Option '--cert-extensions' is only "
|
|
"valid together with options '--gen-csr' or "
|
|
"'--gen-self-signed-cert'.");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (opts->renew_cert_pem_file != NULL) {
|
|
_set_error(ph, "Option '--renew-cert' is only "
|
|
"valid together with options '--gen-csr' or "
|
|
"'--gen-self-signed-cert'.");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (opts->csr_new_header == true) {
|
|
_set_error(ph, "Option '--csr-new-header' is only "
|
|
"valid together with option '--gen-csr'.");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (opts->cert_validity_days != NULL) {
|
|
_set_error(ph, "Option '--cert-validity-days' is only "
|
|
"valid together with option "
|
|
"'--gen-self-signed-cert'.");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (opts->cert_digest != NULL) {
|
|
_set_error(ph, "Option '--cert-digest' is only "
|
|
"valid together with options '--gen-csr' or "
|
|
"'--gen-self-signed-cert'.");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
#ifdef EKMF_SUPPORTS_RSA_PSS_CERTIFICATES
|
|
if (opts->cert_rsa_pss == true) {
|
|
_set_error(ph, "Option '--cert-rsa-pss' is only "
|
|
"valid together with option '--gen-csr' or "
|
|
"'--gen-self-signed-cert'");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
#endif
|
|
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Frees an EKMF tag list
|
|
*
|
|
* @param ekmf_tag_list the EKMF tag list
|
|
*/
|
|
static void _free_ekmf_tags(struct ekmf_tag_list *ekmf_tag_list)
|
|
{
|
|
size_t i;
|
|
|
|
if (ekmf_tag_list->tags == NULL)
|
|
return;
|
|
|
|
for (i = 0; i < ekmf_tag_list->num_tags; i++) {
|
|
free((char *)ekmf_tag_list->tags[i].name);
|
|
free((char *)ekmf_tag_list->tags[i].value);
|
|
}
|
|
|
|
free(ekmf_tag_list->tags);
|
|
ekmf_tag_list->tags = NULL;
|
|
ekmf_tag_list->num_tags = 0;
|
|
}
|
|
|
|
/**
|
|
* Parses the label tags passed in via option (<tag>=<value>;<tag>=<value;....)
|
|
* and allocates an array of KMS properties. The tags must be freed by the
|
|
* caller.
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param template_info the template info
|
|
* @param label_tags the label tags option value (can be NULL)
|
|
* @param ekmf_tag_list On return: a list of label tags
|
|
*
|
|
* @returns 0 on success, or a negative errno in case of an error.
|
|
*/
|
|
static int _parse_label_tags(struct plugin_handle *ph,
|
|
const struct ekmf_template_info *template_info,
|
|
const char *label_tags,
|
|
struct ekmf_tag_list *ekmf_tag_list)
|
|
{
|
|
const struct ekmf_tag_def_list *tag_defs;
|
|
char *tag, *value = NULL;
|
|
char **tag_list = NULL;
|
|
size_t i, k;
|
|
int rc = 0;
|
|
|
|
tag_defs = &template_info->label_tags;
|
|
|
|
pr_verbose(&ph->pd, "Label tags: '%s'", label_tags);
|
|
|
|
if (label_tags != NULL && strlen(label_tags) == 0)
|
|
label_tags = NULL;
|
|
tag_list = str_list_split(label_tags != NULL ? label_tags : "");
|
|
|
|
ekmf_tag_list->tags = util_malloc(sizeof(struct ekmf_tag) *
|
|
tag_defs->num_tag_defs);
|
|
ekmf_tag_list->num_tags = tag_defs->num_tag_defs;
|
|
|
|
memset(ekmf_tag_list->tags, 0,
|
|
sizeof(struct ekmf_tag) * tag_defs->num_tag_defs);
|
|
|
|
for (i = 0, k = 0; i < tag_defs->num_tag_defs; i++) {
|
|
pr_verbose(&ph->pd, "Expected tag: '%s'",
|
|
tag_defs->tag_defs[i].name);
|
|
pr_verbose(&ph->pd, "Specified tag: '%s'", tag_list[k]);
|
|
|
|
tag = tag_list[k] != NULL ? util_strdup(tag_list[k]) : NULL;
|
|
if (tag != NULL) {
|
|
value = strchr(tag, '=');
|
|
if (value != NULL) {
|
|
*value = '\0';
|
|
value++;
|
|
}
|
|
}
|
|
|
|
ekmf_tag_list->tags[i].name =
|
|
util_strdup(tag_defs->tag_defs[i].name);
|
|
|
|
if (strcasecmp(tag_defs->tag_defs[i].name,
|
|
EKMFWEB_SEQNO_TAG) == 0) {
|
|
/* <seqno> tag may or may not be specified */
|
|
if (tag != NULL && strcasecmp(tag,
|
|
EKMFWEB_SEQNO_TAG) == 0) {
|
|
/* <seqno> tag may or may not have a value */
|
|
if (value != NULL) {
|
|
ekmf_tag_list->tags[i].value =
|
|
util_strdup(value);
|
|
util_str_toupper((char *)
|
|
ekmf_tag_list->tags[i].value);
|
|
} else {
|
|
ekmf_tag_list->tags[i].value =
|
|
util_strdup(EKMFWEB_SEQNO_NEXT);
|
|
}
|
|
k++;
|
|
} else {
|
|
ekmf_tag_list->tags[i].value =
|
|
util_strdup(EKMFWEB_SEQNO_NEXT);
|
|
}
|
|
} else {
|
|
if (tag == NULL) {
|
|
if (label_tags != NULL)
|
|
_set_error(ph, "Failed to parse label "
|
|
"tags. Expected tag '%s', "
|
|
"but no more tags are "
|
|
"specified.",
|
|
tag_defs->tag_defs[i].name);
|
|
else
|
|
_set_error(ph, "Option '--label-tags' "
|
|
"is required. Use 'zkey kms "
|
|
"info' to see which label "
|
|
"tags are required by the "
|
|
"key template(s)");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (strcasecmp(tag, tag_defs->tag_defs[i].name) != 0) {
|
|
_set_error(ph, "Failed to parse the specified "
|
|
"label tags: Expected tag '%s', but "
|
|
"found '%s'.",
|
|
tag_defs->tag_defs[i].name, tag);
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (value == NULL) {
|
|
_set_error(ph, "Failed to parse the specified "
|
|
"label tags: Tag '%s' has no value",
|
|
tag_defs->tag_defs[i].name);
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
ekmf_tag_list->tags[i].value = util_strdup(value);
|
|
util_str_toupper((char *)ekmf_tag_list->tags[i].value);
|
|
k++;
|
|
}
|
|
|
|
pr_verbose(&ph->pd, "Tag: '%s', Value: '%s'",
|
|
ekmf_tag_list->tags[i].name,
|
|
ekmf_tag_list->tags[i].value);
|
|
|
|
if (tag != NULL)
|
|
free(tag);
|
|
tag = NULL;
|
|
}
|
|
|
|
if (tag_list[k] != NULL) {
|
|
_set_error(ph, "Failed to parse the specified label tags: More "
|
|
"tags specified than expected: '%s'", tag_list[k]);
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (tag != NULL)
|
|
free(tag);
|
|
if (tag_list != NULL)
|
|
str_list_free_string_array(tag_list);
|
|
if (rc != 0)
|
|
_free_ekmf_tags(ekmf_tag_list);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Loads the certificate from a file into memory.
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param cert_file the file name of the certificate file
|
|
* @param cert On return: an allocated buffer containing the data
|
|
* @param cert_size On return: the size of the certificate data
|
|
*
|
|
* @returns 0 on success, or a negative errno in case of an error.
|
|
*/
|
|
static int _load_certificate(struct plugin_handle *ph, const char *cert_file,
|
|
unsigned char **cert, size_t *cert_size)
|
|
{
|
|
size_t count, size;
|
|
unsigned char *buf;
|
|
struct stat sb;
|
|
int rc = 0;
|
|
FILE *fp;
|
|
|
|
if (stat(cert_file, &sb)) {
|
|
rc = -errno;
|
|
_set_error(ph, "Failed to read certificate from file '%s': %s",
|
|
cert_file, strerror(-rc));
|
|
return rc;
|
|
}
|
|
size = sb.st_size;
|
|
|
|
fp = fopen(cert_file, "r");
|
|
if (fp == NULL) {
|
|
rc = -errno;
|
|
_set_error(ph, "Failed to read certificate from file '%s': %s",
|
|
cert_file, strerror(-rc));
|
|
return rc;
|
|
}
|
|
|
|
buf = util_malloc(size);
|
|
count = fread(buf, 1, size, fp);
|
|
if (count != size) {
|
|
rc = ferror(fp) ? -errno : -EIO;
|
|
_set_error(ph, "Failed to read certificate from file '%s': %s",
|
|
cert_file, strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
*cert_size = size;
|
|
*cert = buf;
|
|
|
|
pr_verbose(&ph->pd, "%lu bytes read from file '%s'", size, cert_file);
|
|
out:
|
|
if (rc != 0)
|
|
free(buf);
|
|
fclose(fp);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Registers the client with EKMF Web
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param cert_file the certificate file to register
|
|
* @param label_tags the label tags for generating an identity key
|
|
*
|
|
* @returns 0 on success, a negative errno in case of an error.
|
|
*/
|
|
static int _register_client(struct plugin_handle *ph, const char *cert_file,
|
|
const char *label_tags)
|
|
{
|
|
struct ekmf_template_info *template_info = NULL;
|
|
struct ekmf_tag_list label_tag_list = { 0 };
|
|
struct ekmf_key_info *key_info = NULL;
|
|
const char *template_uuid = NULL;
|
|
unsigned char *cert = NULL;
|
|
char *description = NULL;
|
|
struct utsname utsname;
|
|
char *error_msg = NULL;
|
|
size_t cert_size = 0;
|
|
char *key_id = NULL;
|
|
int rc;
|
|
|
|
_check_config_complete(ph);
|
|
|
|
if (!ph->apqns_configured) {
|
|
_set_error(ph, "The configuration is incomplete, you must "
|
|
"first configure the APQNs used with this plugin.");
|
|
return -EINVAL;
|
|
}
|
|
if (!ph->identity_key_generated) {
|
|
_set_error(ph, "The configuration is incomplete, you must "
|
|
"first configure the EKMF Web server connection.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = kms_login((kms_handle_t)ph);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
key_id = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_LABEL);
|
|
if (key_id != NULL) {
|
|
free(key_id);
|
|
util_print_indented("ATTENTION: The zkey client has already "
|
|
"been registered with EKMF Web!\n"
|
|
"When you re-register with EKMF Web you "
|
|
"will no longer have access to keys that "
|
|
"have been generated in EKMF Web with your "
|
|
"previous registration, until an EKMF Web "
|
|
"operator approves the export of these "
|
|
"keys for the identity key that is being "
|
|
"generated with this registration.", 0);
|
|
printf("%s: Re-register the zkey client [y/N]? ",
|
|
program_invocation_short_name);
|
|
if (!prompt_for_yes(ph->pd.verbose)) {
|
|
_set_error(ph, "Operation aborted by user");
|
|
return -ECANCELED;
|
|
}
|
|
}
|
|
|
|
rc = _load_certificate(ph, cert_file, &cert, &cert_size);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
template_uuid = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_TEMPLATE_IDENTITY_ID);
|
|
if (template_uuid == NULL) {
|
|
rc = -EIO;
|
|
_set_error(ph, "No identity key template configured");
|
|
goto out;
|
|
}
|
|
|
|
rc = ekmf_get_template(&ph->ekmf_config, &ph->curl_handle,
|
|
template_uuid, &template_info,
|
|
&error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to get identity key template '%s': %s",
|
|
template_uuid, error_msg != NULL ? error_msg :
|
|
strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = _check_template(ph, template_info, EKMFWEB_KEYSTORE_TYPE_IDENTITY,
|
|
false);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = _parse_label_tags(ph, template_info, label_tags, &label_tag_list);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
if (uname(&utsname) != 0) {
|
|
rc = -errno;
|
|
_set_error(ph, "Failed to obtain the system's hostname: %s",
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
util_asprintf(&description, "Identity key for zkey client on system %s",
|
|
utsname.nodename);
|
|
|
|
rc = ekmf_generate_key(&ph->ekmf_config, &ph->curl_handle,
|
|
template_info->name, description,
|
|
&label_tag_list, NULL, NULL, cert, cert_size,
|
|
&key_info, &error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to generate identity key in EKMF Web:"
|
|
" %s", error_msg != NULL ? error_msg :
|
|
strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_ID,
|
|
key_info->uuid);
|
|
if (rc != 0)
|
|
goto out;
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_LABEL,
|
|
key_info->label);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
pr_verbose(&ph->pd, "Generated identity key id: '%s'", key_info->uuid);
|
|
pr_verbose(&ph->pd, "Generated identity key label: '%s'",
|
|
key_info->label);
|
|
|
|
out:
|
|
if (template_uuid != NULL)
|
|
free((char *)template_uuid);
|
|
if (error_msg != NULL)
|
|
free(error_msg);
|
|
if (template_info != NULL)
|
|
ekmf_free_template_info(template_info);
|
|
_free_ekmf_tags(&label_tag_list);
|
|
if (key_info != NULL)
|
|
ekmf_free_key_info(key_info);
|
|
if (cert != NULL)
|
|
free(cert);
|
|
if (description != NULL)
|
|
free(description);
|
|
|
|
return rc;
|
|
}
|
|
|
|
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
|
|
/*
|
|
* Configures the key transport specific settings
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param session_rsa_sign_digest the name of the digest for RSA signatures
|
|
* @param session_rsa_sign_algo the signature algorithm for RSA signatures
|
|
*
|
|
* @returns 0 on success, a negative errno in case of an error.
|
|
*/
|
|
static int _configure_key_transport(struct plugin_handle *ph,
|
|
const char *session_rsa_sign_digest,
|
|
const char *session_rsa_sign_algo)
|
|
{
|
|
int nid, rc = 0;
|
|
|
|
if (session_rsa_sign_digest != NULL) {
|
|
nid = OBJ_txt2nid(session_rsa_sign_digest);
|
|
if (nid == NID_undef) {
|
|
_set_error(ph, "Invalid RSA signature digest '%s'",
|
|
session_rsa_sign_digest);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_SESSION_RSA_SIGN_DIGEST,
|
|
session_rsa_sign_digest);
|
|
if (rc != 0)
|
|
goto out;
|
|
}
|
|
|
|
if (session_rsa_sign_algo != NULL) {
|
|
if (strcasecmp(session_rsa_sign_algo, "RSA") == 0) {
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_SESSION_RSA_SIGN_PSS,
|
|
NULL);
|
|
if (rc != 0)
|
|
goto out;
|
|
} else if (strcasecmp(session_rsa_sign_algo, "RSA-PSS") == 0) {
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_SESSION_RSA_SIGN_PSS,
|
|
"yes");
|
|
if (rc != 0)
|
|
goto out;
|
|
} else {
|
|
_set_error(ph, "Invalid RSA signature algorithm '%s'",
|
|
session_rsa_sign_algo);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
out:
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* 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)
|
|
{
|
|
struct config_options opts = { 0 };
|
|
struct plugin_handle *ph = handle;
|
|
bool config_changed = false;
|
|
char *apqn_str = NULL;
|
|
int rc = 0;
|
|
size_t i;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
util_assert(num_apqns == 0 || apqns != NULL,
|
|
"Internal error: apqns is NULL but num_apqns > 0");
|
|
util_assert(num_options == 0 || options != NULL,
|
|
"Internal error: options is NULL but num_options > 0 ");
|
|
|
|
pr_verbose(&ph->pd, "Configure");
|
|
for (i = 0; i < num_apqns; i++) {
|
|
pr_verbose(&ph->pd, " APQN: %02x.%04x", apqns[i].card,
|
|
apqns[i].domain);
|
|
}
|
|
for (i = 0; i < num_options; i++) {
|
|
if (isalnum(options[i].option))
|
|
pr_verbose(&ph->pd, " Option '%c': '%s'",
|
|
options[i].option,
|
|
options[i].argument != NULL ?
|
|
options[i].argument : "(null)");
|
|
else
|
|
pr_verbose(&ph->pd, " Option %d: '%s'",
|
|
options[i].option,
|
|
options[i].argument != NULL ?
|
|
options[i].argument : "(null)");
|
|
}
|
|
|
|
plugin_clear_error(&ph->pd);
|
|
|
|
if (apqns != NULL) {
|
|
if (num_apqns > 0) {
|
|
rc = cross_check_cca_apka_apqns(&ph->pd, apqns,
|
|
num_apqns);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Your APKA master key setup is "
|
|
"improper");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
apqn_str = build_kms_apqn_string(apqns, num_apqns);
|
|
rc = properties_set(ph->pd.properties, EKMFWEB_CONFIG_APQNS,
|
|
apqn_str);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to set APQNs property: %s",
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
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;
|
|
case 'R':
|
|
opts.refresh_settings = true;
|
|
break;
|
|
case 'i':
|
|
opts.generate_identity_key = true;
|
|
break;
|
|
case 'c':
|
|
opts.csr_pem_file = options[i].argument;
|
|
break;
|
|
case 'C':
|
|
opts.sscert_pem_file = options[i].argument;
|
|
break;
|
|
case 's':
|
|
opts.cert_subject = options[i].argument;
|
|
break;
|
|
case 'e':
|
|
opts.cert_extensions = options[i].argument;
|
|
break;
|
|
case 'N':
|
|
opts.renew_cert_pem_file = options[i].argument;
|
|
break;
|
|
case 'n':
|
|
opts.csr_new_header = true;
|
|
break;
|
|
case 'd':
|
|
opts.cert_validity_days = options[i].argument;
|
|
break;
|
|
case 'D':
|
|
opts.cert_digest = options[i].argument;
|
|
break;
|
|
#ifdef EKMF_SUPPORTS_RSA_PSS_CERTIFICATES
|
|
case 'P':
|
|
opts.cert_rsa_pss = true;
|
|
break;
|
|
#endif
|
|
case 'r':
|
|
opts.register_cert_file = options[i].argument;
|
|
break;
|
|
case 'T':
|
|
opts.register_label_tags = options[i].argument;
|
|
break;
|
|
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
|
|
case OPT_KT_RSA_SIGNATURE_DIGEST:
|
|
opts.session_rsa_sign_digest = options[i].argument;
|
|
break;
|
|
case OPT_KT_RSA_PSS_SIGNATURE:
|
|
opts.session_rsa_sign_algo = options[i].argument;
|
|
break;
|
|
#endif
|
|
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;
|
|
opts.refresh_settings = false; /* Already done */
|
|
}
|
|
} else {
|
|
rc = _error_connection_opts(ph, &opts);
|
|
}
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
if (opts.refresh_settings) {
|
|
rc = _get_ekmfweb_settings(ph);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
config_changed = true;
|
|
}
|
|
|
|
if ((ph->connection_configured && !ph->identity_key_generated) ||
|
|
opts.generate_identity_key) {
|
|
rc = _generate_identity_key(ph);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
config_changed = true;
|
|
}
|
|
|
|
if (opts.csr_pem_file != NULL || opts.sscert_pem_file != NULL)
|
|
rc = _generate_csr_sscert(ph, opts.csr_pem_file,
|
|
opts.sscert_pem_file,
|
|
opts.cert_subject,
|
|
opts.cert_extensions,
|
|
opts.renew_cert_pem_file,
|
|
opts.csr_new_header,
|
|
opts.cert_validity_days,
|
|
opts.cert_digest,
|
|
#ifdef EKMF_SUPPORTS_RSA_PSS_CERTIFICATES
|
|
opts.cert_rsa_pss,
|
|
#endif
|
|
false);
|
|
else
|
|
rc = _error_gen_csr_sscert_opts(ph, &opts);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
if (opts.register_cert_file != NULL) {
|
|
rc = _register_client(ph, opts.register_cert_file,
|
|
opts.register_label_tags);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
config_changed = true;
|
|
} else {
|
|
if (opts.register_label_tags != NULL) {
|
|
_set_error(ph, "Option ' --label-tags' is only valid "
|
|
"together with option '--register'.");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
|
|
if (opts.session_rsa_sign_digest != NULL ||
|
|
opts.session_rsa_sign_algo != NULL) {
|
|
rc = _configure_key_transport(ph, opts.session_rsa_sign_digest,
|
|
opts.session_rsa_sign_algo);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
config_changed = true;
|
|
}
|
|
#endif
|
|
|
|
out:
|
|
if (apqn_str != NULL)
|
|
free(apqn_str);
|
|
|
|
if (rc == 0) {
|
|
if (config_changed) {
|
|
rc = plugin_save_config(&ph->pd);
|
|
if (rc != 0)
|
|
goto ret;
|
|
|
|
_check_config_complete(ph);
|
|
pr_verbose(&ph->pd,
|
|
"Plugin configuration is %scomplete",
|
|
ph->config_complete ? "" : "in");
|
|
}
|
|
|
|
if (!ph->config_complete)
|
|
rc = -EAGAIN;
|
|
}
|
|
|
|
ret:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
struct plugin_handle *ph = handle;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
|
|
pr_verbose(&ph->pd, "Deconfigure");
|
|
|
|
plugin_clear_error(&ph->pd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Prompts the user for input on stdin, and returns the entered value.
|
|
* The returned string must be freed by the caller.
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param msg the message to prompt for the input (can be NULL)
|
|
*
|
|
* @returns the entered value, or NULL in case of an error.
|
|
*/
|
|
static char *_prompt_for_input(struct plugin_handle *ph, const char *msg)
|
|
{
|
|
size_t input_len = 0;
|
|
char *input = NULL;
|
|
int rc;
|
|
|
|
while (input_len == 0 || input == NULL || strlen(input) < 1) {
|
|
if (msg != NULL)
|
|
printf("%s: %s: ", program_invocation_short_name, msg);
|
|
|
|
rc = getline(&input, &input_len, stdin);
|
|
if (rc < 0) {
|
|
_set_error(ph, "Failed to read from stdin: %s",
|
|
strerror(errno));
|
|
if (input != NULL)
|
|
free(input);
|
|
return NULL;
|
|
}
|
|
|
|
if (input != NULL && input[strlen(input) - 1] == '\n')
|
|
input[strlen(input) - 1] = '\0';
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
struct plugin_handle *ph = handle;
|
|
char *passcode_url = NULL;
|
|
char *error_msg = NULL;
|
|
char *passcode = NULL;
|
|
char *user_id = NULL;
|
|
bool valid = false;
|
|
int rc;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
|
|
pr_verbose(&ph->pd, "Login");
|
|
|
|
plugin_clear_error(&ph->pd);
|
|
|
|
if (!ph->connection_configured) {
|
|
_set_error(ph, "The configuration is incomplete, run 'zkey "
|
|
"kms configure [OPTIONS]' to complete the "
|
|
"configuration.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ph->ekmf_config.login_token != NULL) {
|
|
rc = ekmf_check_login_token(&ph->ekmf_config, &valid, NULL,
|
|
ph->pd.verbose);
|
|
pr_verbose(&ph->pd, "Login token valid: %d", valid);
|
|
|
|
if (rc == 0 && valid)
|
|
return 0;
|
|
|
|
remove(ph->ekmf_config.login_token);
|
|
FREE_AND_SET_NULL(ph->ekmf_config.login_token);
|
|
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_LOGIN_TOKEN,
|
|
NULL);
|
|
if (rc != 0)
|
|
goto out;
|
|
}
|
|
|
|
passcode_url = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_PASSCODE_URL);
|
|
if (passcode_url == NULL) {
|
|
util_asprintf(&passcode_url, "%s%s", ph->ekmf_config.base_url,
|
|
EKMFWEB_PASSCODE_URL);
|
|
|
|
rc = plugin_set_or_remove_property(&ph->pd,
|
|
EKMFWEB_CONFIG_PASSCODE_URL,
|
|
passcode_url);
|
|
if (rc != 0)
|
|
goto out;
|
|
}
|
|
|
|
pr_verbose(&ph->pd, "passcode url: '%s'", passcode_url);
|
|
|
|
user_id = _prompt_for_input(ph, "EKMF Web user ID");
|
|
if (user_id == NULL) {
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
pr_verbose(&ph->pd, "User-id: '%s'", user_id);
|
|
|
|
util_print_indented("Go to the following web page in your web browser, "
|
|
"login with the same user ID as entered above and "
|
|
"your password, and obtain a one time passcode and "
|
|
"enter it here.", 0);
|
|
printf("%s\n", passcode_url);
|
|
|
|
passcode = _prompt_for_input(ph, "Passcode");
|
|
if (passcode == NULL) {
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
pr_verbose(&ph->pd, "Passcode: '%s'", passcode);
|
|
|
|
util_asprintf((char **)&ph->ekmf_config.login_token, "%s/%s",
|
|
ph->pd.config_path, EKMFWEB_CONFIG_LOGIN_TOKEN_FILE);
|
|
|
|
rc = ekmf_login(&ph->ekmf_config, &ph->curl_handle, user_id, passcode,
|
|
&error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to login to EKMF Web server at '%s': "
|
|
"%s", ph->ekmf_config.base_url,
|
|
error_msg != NULL ? error_msg : strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
rc = plugin_set_file_permission(&ph->pd, ph->ekmf_config.login_token);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = plugin_set_or_remove_property(&ph->pd, EKMFWEB_CONFIG_LOGIN_TOKEN,
|
|
ph->ekmf_config.login_token);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = plugin_save_config(&ph->pd);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
out:
|
|
if (passcode_url != NULL)
|
|
free(passcode_url);
|
|
if (user_id != NULL)
|
|
free(user_id);
|
|
if (passcode != NULL)
|
|
free(passcode);
|
|
if (error_msg != NULL)
|
|
free(error_msg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* 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 plugin_handle *ph = handle;
|
|
char *reenc_file = NULL;
|
|
const char *tmp = NULL;
|
|
size_t i;
|
|
int rc;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
util_assert(num_options == 0 || options != NULL,
|
|
"Internal error: options is NULL but num_options > 0 ");
|
|
|
|
pr_verbose(&ph->pd, "Re-encipher mode: %d, kmreg=%d", mode, mkreg);
|
|
for (i = 0; i < num_options; i++) {
|
|
if (isalnum(options[i].option))
|
|
pr_verbose(&ph->pd, " Option '%c': '%s'",
|
|
options[i].option,
|
|
options[i].argument != NULL ?
|
|
options[i].argument : "(null)");
|
|
else
|
|
pr_verbose(&ph->pd, " Option %d: '%s'",
|
|
options[i].option,
|
|
options[i].argument != NULL ?
|
|
options[i].argument : "(null)");
|
|
}
|
|
|
|
plugin_clear_error(&ph->pd);
|
|
|
|
if (ph->ekmf_config.identity_secure_key == NULL)
|
|
return 0;
|
|
|
|
reenc_file = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC);
|
|
if (reenc_file != NULL && mode == KMS_REENC_MODE_AUTO)
|
|
mode = KMS_REENC_MODE_STAGED_COMPLETE;
|
|
|
|
if (mode == KMS_REENC_MODE_STAGED_COMPLETE) {
|
|
if (reenc_file == NULL) {
|
|
_set_error(ph, "Staged re-enciphering is not pending");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
printf("Completing re-enciphering of identity key.\n");
|
|
|
|
rc = remove(ph->ekmf_config.identity_secure_key);
|
|
if (rc != 0) {
|
|
rc = -errno;
|
|
_set_error(ph, "Failed to remove file '%s': %s",
|
|
ph->ekmf_config.identity_secure_key,
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
rc = rename(reenc_file, ph->ekmf_config.identity_secure_key);
|
|
if (rc != 0) {
|
|
rc = -errno;
|
|
_set_error(ph, "Failed to rename file '%s' to '%s': %s",
|
|
reenc_file,
|
|
ph->ekmf_config.identity_secure_key,
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
rc = properties_remove(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to remove property %s: %s",
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC,
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
rc = plugin_save_config(&ph->pd);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
printf("Successfully completed re-enciphering of identity "
|
|
"key.\n");
|
|
|
|
rc = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (reenc_file != NULL)
|
|
free(reenc_file);
|
|
reenc_file = NULL;
|
|
|
|
rc = _select_cca_adapter(ph);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
switch (mkreg) {
|
|
case KMS_REENC_MKREG_AUTO:
|
|
case KMS_REENC_MKREG_TO_NEW:
|
|
if (mode == KMS_REENC_MODE_AUTO)
|
|
mode = KMS_REENC_MODE_STAGED;
|
|
|
|
if (mode == KMS_REENC_MODE_STAGED)
|
|
util_asprintf(&reenc_file, "%s/%s", ph->pd.config_path,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC_FILE);
|
|
|
|
printf("Re-enciphering the identity key with the APKA master "
|
|
"key in the NEW register.\n");
|
|
|
|
rc = ekmf_reencipher_identity_key(&ph->ekmf_config, true,
|
|
reenc_file, &ph->ext_lib,
|
|
ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to re-encipher identity key "
|
|
"'%s': %s",
|
|
ph->ekmf_config.identity_secure_key,
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
break;
|
|
|
|
case KMS_REENC_MKREG_FROM_OLD:
|
|
if (mode == KMS_REENC_MODE_AUTO)
|
|
mode = KMS_REENC_MODE_IN_PLACE;
|
|
|
|
if (mode == KMS_REENC_MODE_STAGED)
|
|
util_asprintf(&reenc_file, "%s/%s", ph->pd.config_path,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC_FILE);
|
|
|
|
printf("Re-enciphering the identity key with the APKA master "
|
|
"key in the CURRENT register.\n");
|
|
|
|
rc = ekmf_reencipher_identity_key(&ph->ekmf_config, false,
|
|
reenc_file, &ph->ext_lib,
|
|
ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to re-encipher identity key "
|
|
"'%s': %s",
|
|
ph->ekmf_config.identity_secure_key,
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
break;
|
|
|
|
case KMS_REENC_MKREG_FROM_OLD_TO_NEW:
|
|
if (mode == KMS_REENC_MODE_AUTO)
|
|
mode = KMS_REENC_MODE_STAGED;
|
|
|
|
if (mode == KMS_REENC_MODE_STAGED)
|
|
util_asprintf(&reenc_file, "%s/%s", ph->pd.config_path,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC_FILE);
|
|
|
|
printf("Re-enciphering the identity key with the APKA master "
|
|
"key in the CURRENT and then the NEW register.\n");
|
|
|
|
rc = ekmf_reencipher_identity_key(&ph->ekmf_config, false,
|
|
reenc_file, &ph->ext_lib,
|
|
ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to re-encipher identity key "
|
|
"'%s': %s",
|
|
ph->ekmf_config.identity_secure_key,
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
if (reenc_file != NULL) {
|
|
tmp = ph->ekmf_config.identity_secure_key;
|
|
ph->ekmf_config.identity_secure_key = reenc_file;
|
|
}
|
|
|
|
rc = ekmf_reencipher_identity_key(&ph->ekmf_config, true,
|
|
reenc_file, &ph->ext_lib,
|
|
ph->pd.verbose);
|
|
|
|
if (tmp != NULL)
|
|
ph->ekmf_config.identity_secure_key = tmp;
|
|
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to re-encipher identity key "
|
|
"'%s': %s",
|
|
ph->ekmf_config.identity_secure_key,
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
_set_error(ph, "Invalid re-encipher MK register selection");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (mode == KMS_REENC_MODE_STAGED) {
|
|
rc = plugin_set_file_permission(&ph->pd, reenc_file);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = properties_set(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC,
|
|
reenc_file);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to set property %s: %s",
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC,
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
} else {
|
|
rc = properties_remove(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC);
|
|
if (rc != 0 && rc != -ENOENT) {
|
|
_set_error(ph, "Failed to remove property %s: %s",
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_REENC,
|
|
strerror(-rc));
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
rc = plugin_save_config(&ph->pd);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = 0;
|
|
|
|
if (mode == KMS_REENC_MODE_STAGED)
|
|
util_print_indented("Staged re-enciphering is initiated for "
|
|
"the identity key. After the NEW master "
|
|
"key has been set to become the CURRENT "
|
|
"master key run 'zkey kms reencipher' with "
|
|
"option '--complete' to complete the "
|
|
"re-enciphering process.", 0);
|
|
else
|
|
printf("Successfully re-enciphered the identity key\n");
|
|
|
|
out:
|
|
if (rc != 0 && reenc_file != NULL)
|
|
remove(reenc_file);
|
|
if (reenc_file != NULL)
|
|
free(reenc_file);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Converts a list of KMS properties into a EKMF tag list
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param properties a list of properties to associate the key with
|
|
* @param num_properties the number of properties in above array
|
|
* @param ekmf_tag_list On return: a list of tags
|
|
* @param null_values_only if true, only properties with a NULL-value are
|
|
* converted. If false, only properties with a non-NULL
|
|
* value are converted.
|
|
*
|
|
* @returns 0 on success, or a negative errno in case of an error.
|
|
*/
|
|
static int _properties_to_ekmf_tags(struct plugin_handle *UNUSED(ph),
|
|
const struct kms_property *properties,
|
|
size_t num_properties,
|
|
struct ekmf_tag_list *ekmf_tag_list,
|
|
bool null_values_only)
|
|
{
|
|
size_t i, k;
|
|
|
|
ekmf_tag_list->num_tags = 0;
|
|
ekmf_tag_list->tags = util_malloc(
|
|
sizeof(struct ekmf_tag) * num_properties);
|
|
|
|
for (i = 0, k = 0; i < num_properties; i++) {
|
|
if (!null_values_only && properties[i].value == NULL)
|
|
continue;
|
|
if (null_values_only && properties[i].value != NULL)
|
|
continue;
|
|
|
|
ekmf_tag_list->tags[k].name = util_strdup(properties[i].name);
|
|
ekmf_tag_list->tags[k].value = properties[i].value != NULL ?
|
|
util_strdup(properties[i].value) : NULL;
|
|
ekmf_tag_list->num_tags++;
|
|
k++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Converts an EKMF tag list into a list of KMS properties
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param ekmf_tag_list The list of tags
|
|
* @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.
|
|
*/
|
|
static int _ekmf_tags_to_properties(struct plugin_handle *UNUSED(ph),
|
|
const struct ekmf_tag_list *ekmf_tag_list,
|
|
struct kms_property **properties,
|
|
size_t *num_properties)
|
|
{
|
|
struct kms_property *props;
|
|
size_t i;
|
|
|
|
props = util_malloc(sizeof(struct kms_property) *
|
|
ekmf_tag_list->num_tags);
|
|
|
|
for (i = 0; i < ekmf_tag_list->num_tags; i++) {
|
|
props[i].name = util_strdup(ekmf_tag_list->tags[i].name);
|
|
props[i].value = ekmf_tag_list->tags[i].value != NULL ?
|
|
util_strdup(ekmf_tag_list->tags[i].value) : NULL;
|
|
}
|
|
|
|
*properties = props;
|
|
*num_properties = ekmf_tag_list->num_tags;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Restricts an retrieved secure key from further export and checks the
|
|
* required key attributes. If the secure key is not as expected, the user
|
|
* is prompted to confirm the use of the key.
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param key_blob the secure key to restrict
|
|
* @param key_blob_length the size of the the secure key
|
|
*
|
|
* @returns 0 on success, or a negative errno in case of an error.
|
|
*/
|
|
static int _restrict_key(struct plugin_handle *ph, unsigned char *key_blob,
|
|
size_t key_blob_length)
|
|
{
|
|
struct cca_lib cca = { 0 };
|
|
int rc;
|
|
|
|
cca.lib_csulcca = ph->cca.cca_lib;
|
|
cca.dll_CSNBRKA = (t_CSNBRKA)dlsym(cca.lib_csulcca, "CSNBRKA");
|
|
if (cca.dll_CSNBRKA == NULL) {
|
|
_set_error(ph, "Failed to get CCA verb CSNBRKA");
|
|
return -ELIBACC;
|
|
}
|
|
|
|
rc = restrict_key_export(&cca, key_blob, key_blob_length,
|
|
ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to export-restrict the retrieved secure "
|
|
"key: %s", strerror(-rc));
|
|
return rc;
|
|
}
|
|
|
|
rc = check_aes_cipher_key(key_blob, key_blob_length);
|
|
if (rc != 0) {
|
|
warnx("The secure key retrieved from EKMF Web might not be "
|
|
"secure");
|
|
printf("%s: Do you want to use it anyway [y/N]? ",
|
|
program_invocation_short_name);
|
|
if (!prompt_for_yes(ph->pd.verbose)) {
|
|
warnx("Operation aborted");
|
|
return -ECANCELED;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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).
|
|
* @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)
|
|
{
|
|
struct ekmf_template_info *template_info = NULL;
|
|
struct ekmf_tag_list custom_tag_list = { 0 };
|
|
struct ekmf_tag_list label_tag_list = { 0 };
|
|
struct ekmf_key_info *key_info = NULL;
|
|
int curve_nid = 0, digest_nid = 0;
|
|
struct plugin_handle *ph = handle;
|
|
const char *template_uuid = NULL;
|
|
char *identity_key_uuid = NULL;
|
|
const char *label_tags = NULL;
|
|
const char *tmpl_prop_name;
|
|
char *error_msg = NULL;
|
|
bool rsa_pss = false;
|
|
char *tmp;
|
|
size_t i;
|
|
int rc;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
util_assert(num_properties == 0 || properties != NULL,
|
|
"Internal error: properties is NULL but num_properties"
|
|
" > 0 ");
|
|
util_assert(num_options == 0 || options != NULL,
|
|
"Internal error: options is NULL but num_options > 0 ");
|
|
util_assert(key_blob != NULL, "Internal error: key_blob is NULL");
|
|
util_assert(key_blob_length != NULL, "Internal error: key_blob_length "
|
|
"is NULL");
|
|
util_assert(key_id != NULL, "Internal error: key_id is NULL");
|
|
util_assert(key_label != NULL, "Internal error: key_label is NULL");
|
|
|
|
pr_verbose(&ph->pd, "Generate key: key-type: '%s', keybits: %lu, "
|
|
"mode: %d", key_type, key_bits, key_mode);
|
|
for (i = 0; i < num_properties; i++) {
|
|
util_assert(properties[i].name != NULL,
|
|
"Internal error: property name is NULL");
|
|
util_assert(properties[i].value != NULL,
|
|
"Internal error: property value is NULL");
|
|
pr_verbose(&ph->pd, " Property '%s': '%s", properties[i].name,
|
|
properties[i].value);
|
|
}
|
|
for (i = 0; i < num_options; i++) {
|
|
if (isalnum(options[i].option))
|
|
pr_verbose(&ph->pd, " Option '%c': '%s'",
|
|
options[i].option,
|
|
options[i].argument != NULL ?
|
|
options[i].argument : "(null)");
|
|
else
|
|
pr_verbose(&ph->pd, " Option %d: '%s'",
|
|
options[i].option,
|
|
options[i].argument != NULL ?
|
|
options[i].argument : "(null)");
|
|
}
|
|
|
|
plugin_clear_error(&ph->pd);
|
|
|
|
if (!ph->config_complete) {
|
|
_set_error(ph, "The configuration is incomplete, run 'zkey "
|
|
"kms configure [OPTIONS]' to complete the "
|
|
"configuration.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) != 0) {
|
|
_set_error(ph, "Key type '%s' is not supported by EKMF Web",
|
|
key_type);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
switch (key_mode) {
|
|
case KMS_KEY_MODE_NON_XTS:
|
|
tmpl_prop_name = EKMFWEB_CONFIG_TEMPLATE_NONXTS_ID;
|
|
break;
|
|
case KMS_KEY_MODE_XTS_1:
|
|
tmpl_prop_name = EKMFWEB_CONFIG_TEMPLATE_XTS1_ID;
|
|
break;
|
|
case KMS_KEY_MODE_XTS_2:
|
|
tmpl_prop_name = EKMFWEB_CONFIG_TEMPLATE_XTS2_ID;
|
|
break;
|
|
default:
|
|
_set_error(ph, "Unsupported key mode: %d", key_mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
identity_key_uuid = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_ID);
|
|
if (identity_key_uuid == NULL) {
|
|
_set_error(ph, "The zkey client is not registered with EKMF "
|
|
"Web, run 'zkey kms configure --register CERT-FILE' "
|
|
"to register the zkey client.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pr_verbose(&ph->pd, "identity_key_uuid: '%s'", identity_key_uuid);
|
|
|
|
rc = _select_cca_adapter(ph);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
for (i = 0; i < num_options; i++) {
|
|
switch (options[i].option) {
|
|
case 'T':
|
|
label_tags = options[i].argument;
|
|
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;
|
|
}
|
|
}
|
|
|
|
template_uuid = properties_get(ph->pd.properties, tmpl_prop_name);
|
|
if (template_uuid == NULL) {
|
|
rc = -EIO;
|
|
_set_error(ph, "No key template configured");
|
|
goto out;
|
|
}
|
|
|
|
rc = ekmf_get_template(&ph->ekmf_config, &ph->curl_handle,
|
|
template_uuid, &template_info,
|
|
&error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to get key template '%s': %s",
|
|
template_uuid, error_msg != NULL ? error_msg :
|
|
strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = _check_template(ph, template_info,
|
|
EKMFWEB_KEYSTORE_TYPE_PERV_ENCR, true);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
if (key_bits == 0)
|
|
key_bits = template_info->key_size;
|
|
if (key_bits != template_info->key_size) {
|
|
_set_error(ph, "Key size %u bits is not allowed by the "
|
|
"template used to generate the key. The template "
|
|
"uses a key size of %u bits.", key_bits,
|
|
template_info->key_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = _parse_label_tags(ph, template_info, label_tags, &label_tag_list);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = _properties_to_ekmf_tags(ph, properties, num_properties,
|
|
&custom_tag_list, false);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = ekmf_generate_key(&ph->ekmf_config, &ph->curl_handle,
|
|
template_info->name, "Generated by zkey",
|
|
&label_tag_list, &custom_tag_list,
|
|
identity_key_uuid, NULL, 0, &key_info,
|
|
&error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to generate key in EKMF Web: %s",
|
|
error_msg != NULL ? error_msg : strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_SESSION_KEY_CURVE);
|
|
if (tmp != NULL) {
|
|
curve_nid = OBJ_txt2nid(tmp);
|
|
free(tmp);
|
|
}
|
|
|
|
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_SESSION_RSA_SIGN_DIGEST);
|
|
if (tmp != NULL) {
|
|
digest_nid = OBJ_txt2nid(tmp);
|
|
free(tmp);
|
|
}
|
|
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_SESSION_RSA_SIGN_PSS);
|
|
if (tmp != NULL) {
|
|
if (strcasecmp(tmp, "yes") == 0)
|
|
rsa_pss = true;
|
|
free(tmp);
|
|
}
|
|
#endif
|
|
|
|
rc = ekmf_retrieve_key(&ph->ekmf_config, &ph->curl_handle,
|
|
key_info->uuid, curve_nid, digest_nid, rsa_pss,
|
|
identity_key_uuid, key_blob, key_blob_length,
|
|
&error_msg, &ph->ext_lib, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to retrieve the generated key from EKMF "
|
|
"Web: %s", error_msg != NULL ? error_msg :
|
|
strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = _restrict_key(ph, key_blob, *key_blob_length);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
strncpy(key_id, key_info->uuid, key_id_size);
|
|
key_id[key_id_size - 1] = '\0';
|
|
|
|
strncpy(key_label, key_info->label, key_label_size);
|
|
key_label[key_label_size - 1] = '\0';
|
|
|
|
pr_verbose(&ph->pd, "Generated key id: '%s'", key_id);
|
|
pr_verbose(&ph->pd, "Generated key label: '%s'", key_label);
|
|
|
|
out:
|
|
if (identity_key_uuid != NULL)
|
|
free(identity_key_uuid);
|
|
if (template_uuid != NULL)
|
|
free((char *)template_uuid);
|
|
if (template_info != NULL)
|
|
ekmf_free_template_info(template_info);
|
|
_free_ekmf_tags(&label_tag_list);
|
|
_free_ekmf_tags(&custom_tag_list);
|
|
if (error_msg != NULL)
|
|
free(error_msg);
|
|
if (key_info != NULL)
|
|
ekmf_free_key_info(key_info);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
struct ekmf_tag_list delete_tag_list = { 0 };
|
|
struct ekmf_tag_list set_tag_list = { 0 };
|
|
struct ekmf_key_info *key_info = NULL;
|
|
struct plugin_handle *ph = handle;
|
|
char *updated_on = NULL;
|
|
char *error_msg = NULL;
|
|
size_t i;
|
|
int rc;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
util_assert(key_id != NULL, "Internal error: key_id is NULL");
|
|
util_assert(num_properties == 0 || properties != NULL,
|
|
"Internal error: properties is NULL but num_properties"
|
|
" > 0 ");
|
|
|
|
pr_verbose(&ph->pd, "Set key properties: key-ID: '%s'", key_id);
|
|
for (i = 0; i < num_properties; i++) {
|
|
util_assert(properties[i].name != NULL,
|
|
"Internal error: property name is NULL");
|
|
|
|
pr_verbose(&ph->pd, " Property '%s': '%s'", properties[i].name,
|
|
properties[i].value != NULL ? properties[i].value :
|
|
"(null)");
|
|
}
|
|
|
|
plugin_clear_error(&ph->pd);
|
|
|
|
if (!ph->config_complete) {
|
|
_set_error(ph, "The configuration is incomplete, run 'zkey "
|
|
"kms configure [OPTIONS]' to complete the "
|
|
"configuration.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = _properties_to_ekmf_tags(ph, properties, num_properties,
|
|
&set_tag_list, false);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = _properties_to_ekmf_tags(ph, properties, num_properties,
|
|
&delete_tag_list, true);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = ekmf_get_key_info(&ph->ekmf_config, &ph->curl_handle,
|
|
key_id, &key_info, &error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to get key '%s': %s", key_id,
|
|
error_msg != NULL ? error_msg : strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
if (set_tag_list.num_tags > 0) {
|
|
rc = ekmf_set_key_tags(&ph->ekmf_config, &ph->curl_handle,
|
|
key_id, &set_tag_list,
|
|
key_info->updated_on, &updated_on,
|
|
&error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to set custom tags for key "
|
|
"'%s': %s", key_id, error_msg != NULL ?
|
|
error_msg : strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (delete_tag_list.num_tags > 0) {
|
|
rc = ekmf_delete_key_tags(&ph->ekmf_config, &ph->curl_handle,
|
|
key_id, &delete_tag_list,
|
|
updated_on != NULL ? updated_on :
|
|
key_info->updated_on,
|
|
NULL, &error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to delete custom tags for key "
|
|
"'%s': %s", key_id, error_msg != NULL ?
|
|
error_msg : strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
_free_ekmf_tags(&set_tag_list);
|
|
_free_ekmf_tags(&delete_tag_list);
|
|
if (key_info != NULL)
|
|
ekmf_free_key_info(key_info);
|
|
if (updated_on != NULL)
|
|
free(updated_on);
|
|
if (error_msg != NULL)
|
|
free(error_msg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
struct ekmf_key_info *key_info = NULL;
|
|
struct plugin_handle *ph = handle;
|
|
char *error_msg = NULL;
|
|
int rc;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
util_assert(key_id != NULL, "Internal error: key_id is NULL");
|
|
util_assert(properties != NULL, "Internal error: properties is NULL");
|
|
util_assert(num_properties != NULL,
|
|
"Internal error: num_properties is NULL");
|
|
|
|
pr_verbose(&ph->pd, "Get key properties: key-ID: '%s'", key_id);
|
|
|
|
plugin_clear_error(&ph->pd);
|
|
|
|
if (!ph->config_complete) {
|
|
_set_error(ph, "The configuration is incomplete, run 'zkey "
|
|
"kms configure [OPTIONS]' to complete the "
|
|
"configuration.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = ekmf_get_key_info(&ph->ekmf_config, &ph->curl_handle,
|
|
key_id, &key_info, &error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to get key '%s': %s", key_id,
|
|
error_msg != NULL ? error_msg : strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = _ekmf_tags_to_properties(ph, &key_info->custom_tags, properties,
|
|
num_properties);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
out:
|
|
if (key_info != NULL)
|
|
ekmf_free_key_info(key_info);
|
|
if (error_msg != NULL)
|
|
free(error_msg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
struct key_state {
|
|
const char *state;
|
|
const char *new_states[7];
|
|
};
|
|
|
|
static const struct key_state states[] = {
|
|
{ .state = EKMFWEB_KEY_STATE_PRE_ACTIVATION,
|
|
.new_states = {
|
|
EKMFWEB_KEY_STATE_ACTIVE,
|
|
EKMFWEB_KEY_STATE_COMPROMISED,
|
|
EKMFWEB_KEY_STATE_DESTROYED,
|
|
NULL },
|
|
},
|
|
{ .state = EKMFWEB_KEY_STATE_ACTIVE,
|
|
.new_states = {
|
|
EKMFWEB_KEY_STATE_ACTIVE,
|
|
EKMFWEB_KEY_STATE_DEACTIVATED,
|
|
EKMFWEB_KEY_STATE_COMPROMISED,
|
|
EKMFWEB_KEY_STATE_DESTROYED,
|
|
NULL },
|
|
},
|
|
{ .state = EKMFWEB_KEY_STATE_DEACTIVATED,
|
|
.new_states = {
|
|
EKMFWEB_KEY_STATE_COMPROMISED,
|
|
EKMFWEB_KEY_STATE_DESTROYED,
|
|
NULL },
|
|
},
|
|
{ .state = EKMFWEB_KEY_STATE_COMPROMISED,
|
|
.new_states = {
|
|
EKMFWEB_KEY_STATE_DESTROYED_COMPROMISED,
|
|
NULL },
|
|
},
|
|
{ .state = EKMFWEB_KEY_STATE_DESTROYED,
|
|
.new_states = { NULL },
|
|
},
|
|
{ .state = EKMFWEB_KEY_STATE_DESTROYED_COMPROMISED,
|
|
.new_states = { NULL },
|
|
},
|
|
{ .state = NULL, .new_states = { NULL, }, },
|
|
};
|
|
|
|
/**
|
|
* Checks if the new state is a valid state. If the current state is also
|
|
* specified, then it checks also if the new state can be set from the current
|
|
* state
|
|
*
|
|
* @param ph the plugin handle
|
|
* @param name the key name
|
|
* @param new_state the new state to set
|
|
* @param cur_state the current state (can be NULL).
|
|
*
|
|
* @returns 0 on success, or a negative errno in case of an error.
|
|
*/
|
|
static int _check_state(struct plugin_handle *ph, const char *name,
|
|
const char *new_state, const char *cur_state)
|
|
{
|
|
bool ok = false;
|
|
int i, k;
|
|
|
|
for (i = 0; states[i].state != NULL; i++) {
|
|
if (strcasecmp(new_state, states[i].state) == 0) {
|
|
ok = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ok) {
|
|
_set_error(ph, "Invalid state specified: '%s'", new_state);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (cur_state == NULL)
|
|
return 0;
|
|
|
|
for (i = 0; states[i].state != NULL; i++) {
|
|
if (strcasecmp(cur_state, states[i].state) == 0) {
|
|
for (k = 0; states[i].new_states[k] != NULL; k++) {
|
|
if (strcasecmp(new_state,
|
|
states[i].new_states[k]) == 0)
|
|
return 0;
|
|
}
|
|
|
|
_set_error(ph, "Key '%s' is in state '%s' and can not "
|
|
"be changed to state '%s'", name,
|
|
cur_state, new_state);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
_set_error(ph, "Key '%s' is in an invalid state: '%s'", name,
|
|
cur_state);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
struct ekmf_key_info *key_info = NULL;
|
|
struct plugin_handle *ph = handle;
|
|
char *error_msg = NULL;
|
|
char *state = NULL;
|
|
int rc = 0;
|
|
size_t i;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
util_assert(key_id != NULL, "Internal error: key_id is NULL");
|
|
util_assert(num_options == 0 || options != NULL,
|
|
"Internal error: options is NULL but num_options > 0 ");
|
|
|
|
pr_verbose(&ph->pd, "Remove key: key-ID: '%s'", key_id);
|
|
for (i = 0; i < num_options; i++) {
|
|
if (isalnum(options[i].option))
|
|
pr_verbose(&ph->pd, " Option '%c': '%s'",
|
|
options[i].option,
|
|
options[i].argument != NULL ?
|
|
options[i].argument : "(null)");
|
|
else
|
|
pr_verbose(&ph->pd, " Option %d: '%s'",
|
|
options[i].option,
|
|
options[i].argument != NULL ?
|
|
options[i].argument : "(null)");
|
|
}
|
|
|
|
plugin_clear_error(&ph->pd);
|
|
|
|
for (i = 0; i < num_options; i++) {
|
|
switch (options[i].option) {
|
|
case 's':
|
|
state = util_strdup(options[i].argument);
|
|
util_str_toupper(state);
|
|
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 (state == NULL)
|
|
goto out;
|
|
|
|
pr_verbose(&ph->pd, "State to set: '%s'", state);
|
|
|
|
if (!ph->config_complete) {
|
|
_set_error(ph, "The configuration is incomplete, run 'zkey "
|
|
"kms configure [OPTIONS]' to complete the "
|
|
"configuration.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = ekmf_get_key_info(&ph->ekmf_config, &ph->curl_handle,
|
|
key_id, &key_info, &error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to get key '%s': %s", key_id,
|
|
error_msg != NULL ? error_msg : strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
pr_verbose(&ph->pd, "Key state: '%s'", key_info->state);
|
|
|
|
rc = _check_state(ph, key_info->label, state, key_info->state);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = ekmf_set_key_state(&ph->ekmf_config, &ph->curl_handle,
|
|
key_id, state, key_info->updated_on,
|
|
&error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to set key state '%s': %s",
|
|
key_id, error_msg != NULL ? error_msg :
|
|
strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (key_info != NULL)
|
|
ekmf_free_key_info(key_info);
|
|
if (error_msg != NULL)
|
|
free(error_msg);
|
|
if (state != NULL)
|
|
free(state);
|
|
|
|
return rc;
|
|
}
|
|
|
|
struct list_data {
|
|
struct plugin_handle *ph;
|
|
bool list_all;
|
|
const char *exporting_key;
|
|
kms_list_callback callback;
|
|
void *private;
|
|
};
|
|
|
|
/**
|
|
* Check if the key can be exported by the exporting key
|
|
*
|
|
* @param key_info the key to check
|
|
* @param exporting_key the exporting key
|
|
*
|
|
* @returns true if export is allowed, false otherwise
|
|
*/
|
|
static bool _check_exportability(struct ekmf_key_info *key_info,
|
|
const char *exporting_key)
|
|
{
|
|
bool found = false;
|
|
size_t i;
|
|
|
|
if (!key_info->export_control.export_allowed || exporting_key == NULL)
|
|
return false;
|
|
|
|
for (i = 0; i < key_info->export_control.num_exporting_keys; i++) {
|
|
if (strcmp(key_info->export_control.exporting_keys[i].uuid,
|
|
exporting_key) == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/*
|
|
* Like argz_add, but formats the string first
|
|
*/
|
|
static error_t argz_add_fmt(char **argz, size_t *argz_len, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
error_t rc;
|
|
char *str;
|
|
|
|
va_start(ap, fmt);
|
|
util_vasprintf(&str, fmt, ap);
|
|
va_end(ap);
|
|
|
|
rc = argz_add(argz, argz_len, str);
|
|
|
|
free(str);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Callback function used with the ekmf_list_keys function. This
|
|
* callback is called for each key found.
|
|
*
|
|
* @param curl_handle a CURL handle that can be used to perform further
|
|
* EKMFWeb functions within the callback.
|
|
* @param template_info a struct containing information about the key.
|
|
* If any of the information needs to be kept, then the
|
|
* callback function must make a copy of the
|
|
* information. The memory holding the information
|
|
* passed to the callback is no longer valid after the
|
|
* callback has returned.
|
|
* @param private the private pointer that was specified with the
|
|
* ekmf_list_keys invocation.
|
|
*
|
|
* @returns zero for success, a negative errno in case of an error.
|
|
* When a nonzero return code is returned, the key listing process stops,
|
|
* and ekmf_list_keys returns the return code from the callback.
|
|
*/
|
|
static int _list_callback(CURL *curl_handle, struct ekmf_key_info *key_info,
|
|
void *private)
|
|
{
|
|
struct kms_property *properties = NULL;
|
|
struct list_data *data = private;
|
|
size_t i, num_properties = 0;
|
|
size_t addl_info_len = 0;
|
|
char *addl_info = NULL;
|
|
int rc = 0;
|
|
|
|
data->ph->curl_handle = curl_handle;
|
|
|
|
if (strcmp(key_info->keystore_type,
|
|
EKMFWEB_KEYSTORE_TYPE_PERV_ENCR) != 0)
|
|
goto out;
|
|
if (strcmp(key_info->key_type, EKMFWEB_KEY_TYPE_CIPHER) != 0)
|
|
goto out;
|
|
if (strcmp(key_info->algorithm, EKMFWEB_KEY_ALGORITHM_AES) != 0)
|
|
goto out;
|
|
if (!data->list_all && !_check_exportability(key_info,
|
|
data->exporting_key))
|
|
goto out;
|
|
|
|
rc = _ekmf_tags_to_properties(data->ph, &key_info->custom_tags,
|
|
&properties, &num_properties);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = argz_add_fmt(&addl_info, &addl_info_len, "State: %s",
|
|
key_info->state);
|
|
if (rc != 0)
|
|
goto out;
|
|
for (i = 0; i < key_info->export_control.num_exporting_keys; i++) {
|
|
rc = argz_add_fmt(&addl_info, &addl_info_len, "%s %s",
|
|
i == 0 ? "Exporting keys:" : " ",
|
|
key_info->export_control.exporting_keys[i].name);
|
|
if (rc != 0)
|
|
goto out;
|
|
}
|
|
|
|
rc = data->callback(key_info->uuid, key_info->label,
|
|
KEY_TYPE_CCA_AESCIPHER, key_info->key_size,
|
|
properties, num_properties,
|
|
addl_info, addl_info_len, data->private);
|
|
|
|
out:
|
|
if (addl_info != NULL)
|
|
free(addl_info);
|
|
if (properties != NULL) {
|
|
for (i = 0; i < num_properties; i++) {
|
|
free((char *)properties[i].name);
|
|
free((char *)properties[i].value);
|
|
}
|
|
free(properties);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* List keys managed by the KMS. This list is independent of the zkey key
|
|
* repository. It lists keys as known by the KMS.
|
|
*
|
|
* @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.
|
|
* @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)
|
|
{
|
|
struct ekmf_tag_list tag_list = { 0 };
|
|
struct plugin_handle *ph = handle;
|
|
struct list_data data = { 0 };
|
|
char **state_list = NULL;
|
|
char *error_msg = NULL;
|
|
char *states = NULL;
|
|
int rc = 0;
|
|
size_t i;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
util_assert(num_properties == 0 || properties != NULL,
|
|
"Internal error: properties is NULL but num_properties "
|
|
"> 0 ");
|
|
util_assert(callback != NULL, "Internal error: callback is NULL");
|
|
|
|
pr_verbose(&ph->pd, "List Keys, label-pattern: '%s'",
|
|
label_pattern != NULL ? label_pattern : "(null)");
|
|
for (i = 0; i < num_properties; i++) {
|
|
util_assert(properties[i].name != NULL,
|
|
"Internal error: property name is NULL");
|
|
util_assert(properties[i].value != NULL,
|
|
"Internal error: property value is NULL");
|
|
pr_verbose(&ph->pd, " Property '%s': '%s", properties[i].name,
|
|
properties[i].value);
|
|
}
|
|
for (i = 0; i < num_options; i++) {
|
|
if (isalnum(options[i].option))
|
|
pr_verbose(&ph->pd, " Option '%c': '%s'",
|
|
options[i].option,
|
|
options[i].argument != NULL ?
|
|
options[i].argument : "(null)");
|
|
else
|
|
pr_verbose(&ph->pd, " Option %d: '%s'",
|
|
options[i].option,
|
|
options[i].argument != NULL ?
|
|
options[i].argument : "(null)");
|
|
}
|
|
|
|
plugin_clear_error(&ph->pd);
|
|
|
|
if (!ph->config_complete) {
|
|
_set_error(ph, "The configuration is incomplete, run 'zkey "
|
|
"kms configure [OPTIONS]' to complete the "
|
|
"configuration.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
data.ph = ph;
|
|
data.list_all = false;
|
|
data.callback = callback;
|
|
data.private = private_data;
|
|
|
|
for (i = 0; i < num_options; i++) {
|
|
switch (options[i].option) {
|
|
case 's':
|
|
states = util_strdup(options[i].argument);
|
|
util_str_toupper(states);
|
|
break;
|
|
case 'a':
|
|
data.list_all = 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;
|
|
}
|
|
}
|
|
|
|
pr_verbose(&ph->pd, "State filter: '%s'", states != NULL ? states :
|
|
"(none)");
|
|
pr_verbose(&ph->pd, "List all: %d", data.list_all);
|
|
|
|
if (states != NULL) {
|
|
state_list = str_list_split(states);
|
|
for (i = 0; state_list[i] != NULL; i++) {
|
|
rc = _check_state(ph, NULL, state_list[i], NULL);
|
|
if (rc != 0)
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
data.exporting_key = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_ID);
|
|
|
|
rc = _properties_to_ekmf_tags(ph, properties, num_properties,
|
|
&tag_list, false);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
rc = ekmf_list_keys(&ph->ekmf_config, &ph->curl_handle,
|
|
_list_callback, &data, label_pattern, states,
|
|
&tag_list, &error_msg, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to list keys: %s",
|
|
error_msg != NULL ? error_msg : strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (states != NULL)
|
|
free(states);
|
|
if (state_list != NULL)
|
|
str_list_free_string_array(state_list);
|
|
if (error_msg != NULL)
|
|
free(error_msg);
|
|
if (data.exporting_key != NULL)
|
|
free((char *)data.exporting_key);
|
|
_free_ekmf_tags(&tag_list);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
struct plugin_handle *ph = handle;
|
|
int curve_nid = 0, digest_nid = 0;
|
|
char *identity_key_uuid = NULL;
|
|
char *error_msg = NULL;
|
|
bool rsa_pss = false;
|
|
int rc = 0;
|
|
char *tmp;
|
|
|
|
util_assert(handle != NULL, "Internal error: handle is NULL");
|
|
util_assert(key_blob != NULL, "Internal error: key_blob is NULL");
|
|
util_assert(key_blob_length != NULL, "Internal error: key_blob_length "
|
|
"is NULL");
|
|
|
|
pr_verbose(&ph->pd, "Import Key, key-ID: '%s'", key_id);
|
|
|
|
plugin_clear_error(&ph->pd);
|
|
|
|
if (!ph->config_complete) {
|
|
_set_error(ph, "The configuration is incomplete, run 'zkey "
|
|
"kms configure [OPTIONS]' to complete the "
|
|
"configuration.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
identity_key_uuid = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_IDENTITY_KEY_ID);
|
|
if (identity_key_uuid == NULL) {
|
|
_set_error(ph, "The zkey client is not registered with EKMF "
|
|
"Web, run 'zkey kms configure --register CERT-FILE' "
|
|
"to register the zkey client.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pr_verbose(&ph->pd, "identity_key_uuid: '%s'", identity_key_uuid);
|
|
|
|
rc = _select_cca_adapter(ph);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_SESSION_KEY_CURVE);
|
|
if (tmp != NULL) {
|
|
curve_nid = OBJ_txt2nid(tmp);
|
|
free(tmp);
|
|
}
|
|
|
|
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_SESSION_RSA_SIGN_DIGEST);
|
|
if (tmp != NULL) {
|
|
digest_nid = OBJ_txt2nid(tmp);
|
|
free(tmp);
|
|
}
|
|
|
|
tmp = properties_get(ph->pd.properties,
|
|
EKMFWEB_CONFIG_SESSION_RSA_SIGN_PSS);
|
|
if (tmp != NULL) {
|
|
if (strcasecmp(tmp, "yes") == 0)
|
|
rsa_pss = true;
|
|
free(tmp);
|
|
}
|
|
#endif
|
|
|
|
rc = ekmf_retrieve_key(&ph->ekmf_config, &ph->curl_handle,
|
|
key_id, curve_nid, digest_nid, rsa_pss,
|
|
identity_key_uuid, key_blob, key_blob_length,
|
|
&error_msg, &ph->ext_lib, ph->pd.verbose);
|
|
if (rc != 0) {
|
|
_set_error(ph, "Failed to retrieve key '%s' from EKMF "
|
|
"Web: %s", key_id, error_msg != NULL ? error_msg :
|
|
strerror(-rc));
|
|
_remove_login_token_if_error(ph, rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = _restrict_key(ph, key_blob, *key_blob_length);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
out:
|
|
if (identity_key_uuid != NULL)
|
|
free(identity_key_uuid);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static const struct kms_functions kms_functions = {
|
|
.api_version = KMS_API_VERSION_1,
|
|
.kms_bind = kms_bind,
|
|
.kms_initialize = kms_initialize,
|
|
.kms_terminate = kms_terminate,
|
|
.kms_get_last_error = kms_get_last_error,
|
|
.kms_supports_key_type = kms_supports_key_type,
|
|
.kms_display_info = kms_display_info,
|
|
.kms_get_command_options = kms_get_command_options,
|
|
.kms_configure = kms_configure,
|
|
.kms_deconfigure = kms_deconfigure,
|
|
.kms_login = kms_login,
|
|
.kms_reenciper = kms_reenciper,
|
|
.kms_generate_key = kms_generate_key,
|
|
.kms_set_key_properties = kms_set_key_properties,
|
|
.kms_get_key_properties = kms_get_key_properties,
|
|
.kms_remove_key = kms_remove_key,
|
|
.kms_list_keys = kms_list_keys,
|
|
.kms_import_key = kms_import_key,
|
|
};
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
return &kms_functions;
|
|
}
|