Files
s390-tools/libekmfweb/ekmfweb.c
Ingo Franzki 35cc35894b libekmfweb: Fix integer overflow errors in base64 encode/decode functions
On large data sizes the calculation may cause an overflow, if done with
int type. Do the calculation with size_t instead to avoid an overflow.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00

5411 lines
160 KiB
C

/*
* libekmfweb - EKMFWeb client library
*
* 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 <stdio.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <err.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <time.h>
#include <curl/curl.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/ssl.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <json-c/json.h>
#ifndef JSON_C_TO_STRING_NOSLASHESCAPE
#define JSON_C_TO_STRING_NOSLASHESCAPE (1 << 4)
#endif
#include "lib/zt_common.h"
#include "libseckey/sk_openssl.h"
#include "ekmfweb/ekmfweb.h"
#include "utilities.h"
#include "cca.h"
#define DEFAULT_SESSION_EC_KEY_CURVE NID_secp521r1
#define MAX_KEY_BLOB_SIZE CCA_MAX_PKA_KEY_TOKEN_SIZE
#define MAX_SYM_KEY_BLOB_SIZE CCA_MAX_SYM_KEY_TOKEN_SIZE
#define EKMF_URI_SYSTEM_PUBKEY "/api/v1/system/publicKey"
#define EKMF_URI_SYSTEM_SETTINGS "/api/v1/system/settings"
#define EKMF_URI_SYSTEM_FEATURES "/api/v1/system/features"
#define EKMF_URI_SYSTEM_LOGIN "/api/v1/system/login"
#define EKMF_URI_KEYS_GENERATE "/api/v1/keys"
#define EKMF_URI_KEYS_EXPORT "/api/v1/keys/%s/export"
#define EKMF_URI_KEYS_TAGS "/api/v1/keys/%s/tags"
#define EKMF_URI_KEYS_EXPORT_CONTROL "/api/v1/keys/%s/exportControl"
#define EKMF_URI_KEYS_SET_TAG "/api/v1/keys/%s/tags/%s"
#define EKMF_URI_KEYS_GET "/api/v1/keys/%s"
#define EKMF_URI_KEYS_SET_STATE "/api/v1/keys/%s"
#define EKMF_URI_KEYS_LIST "/api/v1/keys" \
"?state=%s" \
"&orderBy=%s" \
"&namePattern=%s" \
"&tags=%s"
#define EKMF_URI_KEYS_LIST_STATE "&state="
#define EKMF_URI_TEMPLATE_GET "/api/v1/templates/%s"
#define EKMF_URI_TEMPLATE_LIST "/api/v1/templates" \
"?templateStates=%s" \
"&orderBy=%s" \
"&namePattern=%s"
#define EKMF_URI_TEMPLATE_SEQNO "/api/v1/templates/%s/sequenceNumber"
#define LIST_ELEMENTS_PER_PAGE 20
#define TEMPLATE_STATE_ACTIVE "ACTIVE"
#define KEY_STATE_ACTIVE "ACTIVE"
#define KEY_ALGORITHM_AES "AES"
#define KEYSTORE_TYPE_PERV_ENCR "PERVASIVE_ENCRYPTION"
#define ORDER_BY_NAME_ASC "name%3Aasc"
#define ORDER_BY_LABEL_ASC "label%3Aasc"
#define SETTING_ID_IDENTITY_TEMPLATE "ekmf.web.public.identity.template"
#define SETTING_ID_XTS_KEY1_TEMPLATE "ekmf.web.public.xts.template.1"
#define SETTING_ID_XTS_KEY2_TEMPLATE "ekmf.web.public.xts.template.2"
#define SETTING_ID_NON_XTS_TEMPLATE "ekmf.web.public.non-xts.template"
#define FEATURE_ID_PERVASIVE_ENCRYPTION \
"com.ibm.ccc.ekmf.web.features.PervasiveEncryptionFeature"
void __attribute__ ((constructor)) ekmf_init(void);
void __attribute__ ((destructor)) ekmf_exit(void);
#define pr_verbose(verbose, fmt...) do { \
if (verbose) \
warnx(fmt); \
} while (0)
#define CURL_ERROR_CHECK(rc, text, verbose, label) \
do { \
if (rc != CURLE_OK) { \
pr_verbose(verbose, "%s: %s", text, \
curl_easy_strerror(rc)); \
goto label; \
} \
} while (0)
#define JSON_CHECK_OBJ(obj, type, rc_var, rc, text, verbose, label) \
do { \
if (obj == NULL || \
!json_object_is_type(obj, type)) { \
rc_var = rc; \
pr_verbose(verbose, "%s: %s", text, \
strerror(-rc_var)); \
goto label; \
} \
} while (0)
#define JSON_CHECK_ERROR(cond, rc_var, rc, text, verbose, label) \
do { \
if (cond) { \
rc_var = rc; \
pr_verbose(verbose, "%s: %s", text, \
strerror(-rc_var)); \
goto label; \
} \
} while (0)
struct curl_header_cb_data {
struct curl_slist **headers;
bool error;
bool verbose;
};
struct curl_write_cb_data {
json_tokener *tok;
json_object *obj;
bool error;
bool verbose;
};
struct curl_sslctx_cb_data {
const char *tls_server_cert;
bool error;
bool verbose;
};
#define CURL_CERTINFO_CERT "Cert:"
#define HTTP_HDR_CONTENT_TYPE "Content-Type:"
static const char *accepted_content_types[] = { "application/json",
"text/x-json",
NULL};
struct ext_lib_info {
struct sk_ext_lib ext_lib;
union {
struct sk_ext_cca_lib cca; /* Used if type = EXT_LIB_CCA */
struct sk_ext_ep11_lib ep11; /* Used if type = EXT_LIB_EP11 */
};
};
static void _ekmf_copy_ext_lib(const struct ekmf_ext_lib *ekmf_ext_lib,
struct ext_lib_info *ext_lib_info)
{
switch (ekmf_ext_lib->type) {
case EKMF_EXT_LIB_CCA:
ext_lib_info->ext_lib.type = SK_EXT_LIB_CCA;
ext_lib_info->ext_lib.cca = &ext_lib_info->cca;
ext_lib_info->cca.cca_lib = ekmf_ext_lib->cca->cca_lib;
break;
default:
ext_lib_info->ext_lib.type = 0;
}
}
static void _ekmf_copy_key_gen_info(const struct ekmf_key_gen_info *ekmf_info,
struct sk_key_gen_info *sk_info)
{
switch (ekmf_info->type) {
case EKMF_KEY_TYPE_ECC:
sk_info->type = SK_KEY_TYPE_EC;
sk_info->ec.curve_nid = ekmf_info->params.ecc.curve_nid;
break;
case EKMF_KEY_TYPE_RSA:
sk_info->type = SK_KEY_TYPE_RSA;
sk_info->rsa.modulus_bits = ekmf_info->params.rsa.modulus_bits;
sk_info->rsa.pub_exp = ekmf_info->params.rsa.pub_exp;
sk_info->rsa.x9_31 = 0;
break;
default:
sk_info->type = 0;
break;
}
}
static void _ekmf_copy_pss_params(
const struct ekmf_rsa_pss_params *ekmf_pss_params,
struct sk_rsa_pss_params *rsa_pss_params)
{
if (ekmf_pss_params != NULL) {
rsa_pss_params->mgf_digest_nid =
ekmf_pss_params->mgf_digest_nid;
rsa_pss_params->salt_len = ekmf_pss_params->salt_len;
} else {
rsa_pss_params->mgf_digest_nid = NID_undef;
rsa_pss_params->salt_len = 0;
}
}
/**
* Extract the public key from a certificate in PEM format and store it into a
* PEM file
*/
static int _ekmf_extract_pubkey(const char *cert, const char *pub_key_pem,
bool verbose)
{
EVP_PKEY *pkey = NULL;
X509 *x509 = NULL;
FILE *fp = NULL;
BIO *b = NULL;
int rc;
b = BIO_new_mem_buf(cert, -1);
if (b == NULL) {
pr_verbose(verbose, "BIO_new_mem_buf failed");
return -ENOMEM;
}
x509 = PEM_read_bio_X509(b, NULL, NULL, NULL);
if (x509 == NULL) {
pr_verbose(verbose, "PEM_read_bio_X509 failed");
rc = -EIO;
goto out;
}
pkey = X509_get0_pubkey(x509);
if (pkey == NULL) {
pr_verbose(verbose, "PEM_read_bio_X509 failed");
rc = -EIO;
goto out;
}
fp = fopen_nofollow(pub_key_pem, "w");
if (fp == NULL) {
rc = -errno;
pr_verbose(verbose, "File '%s': %s", pub_key_pem,
strerror(-rc));
goto out;
}
if (!PEM_write_PUBKEY(fp, pkey)) {
pr_verbose(verbose, "PEM_write_PUBKEY failed");
rc = -EIO;
goto out;
}
out:
if (fp != NULL)
fclose(fp);
if (x509 != NULL)
X509_free(x509);
if (b != NULL)
BIO_free(b);
return 0;
}
/**
* Process the attributes of a certificate supplied by curl and writes the
* PEM-format certificate attribute into the specified file pointer.
*/
static int _ekmf_process_certificate(FILE *fp, struct curl_slist *slist,
const char *pub_key_pem, bool verbose)
{
char *cert;
int rc;
for (; slist != NULL; slist = slist->next) {
pr_verbose(verbose, "%s", slist->data);
if (strncmp(slist->data, CURL_CERTINFO_CERT,
strlen(CURL_CERTINFO_CERT)) == 0) {
cert = slist->data + strlen(CURL_CERTINFO_CERT);
if (fp != NULL) {
if (fwrite(cert, strlen(cert), 1, fp) != 1) {
rc = -errno;
pr_verbose(verbose, "fwrite failed: %s",
strerror(-rc));
return rc;
}
}
if (pub_key_pem != NULL) {
rc = _ekmf_extract_pubkey(cert, pub_key_pem,
verbose);
if (rc != 0)
return rc;
}
}
}
return 0;
}
/**
* Callback called during curl_easy_perform() to handle received data.
* Parse the (potentially partial) JSON data.
*/
static size_t _ekmf_dummy_write_cb(void *UNUSED(contents), size_t size,
size_t nmemb, void *UNUSED(userp))
{
return size * nmemb;
}
/**
* Connects to the specified server url and obtains the servers certificate
* and its chain of signing certificates and stores them in the specified
* PEM files.
*
* @param config the configuration structure. Only the base_url must
* be specified, all others are optional.
* @param server_cert_pem Optional: name of a PEM file to store the servers
* certificate
* @param server_pubkey_pem Optional: name of a PEM file to store the servers
* public key (can be used for public key pinning)
* @param ca_bundle_pem Optional: name of a PEM file to store the CA
* certificate chain as a bundle
* @param verified On return: If the server 's certificate has been
* verified using the CA specification from the config
* (if ca = NULL: default system CAs, otherwise path
* or file to CAs).
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
*/
int ekmf_get_server_cert_chain(const struct ekmf_config *config,
const char *server_cert_pem,
const char *server_pubkey_pem,
const char *ca_bundle_pem,
bool *verified, char **error_msg, bool verbose)
{
char error_str[CURL_ERROR_SIZE] = { 0 };
struct curl_certinfo *ci;
long do_verify = 1;
FILE *fp = NULL;
struct stat sb;
int i, rc = 0;
CURL *curl;
if (config == NULL)
return -EINVAL;
if (error_msg != NULL)
*error_msg = NULL;
pr_verbose(verbose, "Getting certificate chain for '%s'",
config->base_url);
curl = curl_easy_init();
if (curl == NULL) {
pr_verbose(verbose, "curl_easy_init failed");
return CURLE_FAILED_INIT;
}
rc = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_str);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_ERRORBUFFER", verbose,
out);
rc = curl_easy_setopt(curl, CURLOPT_VERBOSE, verbose ? 1 : 0);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_VERBOSE", verbose, out);
rc = curl_easy_setopt(curl, CURLOPT_URL, config->base_url);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_URL", verbose, out);
if (config->tls_ca != NULL) {
if (stat(config->tls_ca, &sb) != 0) {
rc = -errno;
pr_verbose(verbose, "stat failed on '%s': %s",
config->tls_ca, strerror(-rc));
goto out;
}
if (S_ISDIR(sb.st_mode)) {
rc = curl_easy_setopt(curl, CURLOPT_CAPATH,
config->tls_ca);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CAPATH",
verbose, out);
} else {
rc = curl_easy_setopt(curl, CURLOPT_CAINFO,
config->tls_ca);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CAINFO",
verbose, out);
}
}
rc = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_FOLLOWLOCATION",
verbose, out);
if (config->tls_client_cert != NULL) {
rc = curl_easy_setopt(curl, CURLOPT_SSLCERT,
config->tls_client_cert);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLCERT",
verbose, out);
rc = curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM");
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLCERTTYPE",
verbose, out);
}
if (config->tls_client_key != NULL) {
rc = curl_easy_setopt(curl, CURLOPT_SSLKEY,
config->tls_client_key);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLKEY", verbose,
out);
rc = curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLKEYTYPE",
verbose, out);
if (config->tls_client_key_passphrase != NULL) {
rc = curl_easy_setopt(curl, CURLOPT_KEYPASSWD,
config->tls_client_key_passphrase);
CURL_ERROR_CHECK(rc,
"curl_easy_setopt CURLOPT_KEYPASSWD",
verbose, out);
}
}
retry:
rc = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, do_verify);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_VERIFYPEER", verbose,
out);
rc = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_VERIFYHOST", verbose,
out);
rc = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
_ekmf_dummy_write_cb);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_WRITEFUNCTION", verbose,
out);
rc = curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CERTINFO", verbose, out);
rc = curl_easy_perform(curl);
if (rc == CURLE_SSL_CACERT && do_verify != 0) {
do_verify = 0;
goto retry;
}
CURL_ERROR_CHECK(rc, "curl_easy_perform", verbose, out);
/*
* If the first try worked, the server certificate could be verified
* with the specified or default CA.
*/
if (verified != NULL)
*verified = do_verify != 0;
rc = curl_easy_getinfo(curl, CURLINFO_CERTINFO, &ci);
CURL_ERROR_CHECK(rc, "curl_easy_getinfo CURLINFO_CERTINFO", verbose,
out);
if (server_cert_pem != NULL) {
fp = fopen_nofollow(server_cert_pem, "w");
if (fp == NULL) {
rc = -errno;
pr_verbose(verbose, "File '%s': %s", server_cert_pem,
strerror(-rc));
goto out;
}
}
pr_verbose(verbose, "%d certificates", ci->num_of_certs);
/*
* Process all certificates in the list.
* First one is the server certificate, all following are
* CA certificates
*/
for (i = 0; i < ci->num_of_certs; i++) {
pr_verbose(verbose, "Certificate %d:", i);
rc = _ekmf_process_certificate(fp, ci->certinfo[i], i == 0 ?
server_pubkey_pem : NULL,
verbose);
if (rc != 0)
break;
if (i == 0) {
if (fp != NULL)
fclose(fp);
fp = NULL;
/*
* Save CA-chain if requested, but only if the
* server certificate wasn't verified by the specified
* or default CA.
*/
if (ci->num_of_certs > 1 && ca_bundle_pem != NULL &&
do_verify == 0) {
fp = fopen_nofollow(ca_bundle_pem, "w");
if (fp == NULL) {
rc = -errno;
pr_verbose(verbose, "File '%s': %s",
ca_bundle_pem,
strerror(-rc));
break;
}
}
}
}
out:
if (fp != NULL)
fclose(fp);
if (rc > 0 && error_msg != NULL && *error_msg == NULL) {
pr_verbose(verbose, "Error: %s", error_str);
if (asprintf(error_msg, "CURL: %s", strlen(error_str) > 0 ?
error_str : curl_easy_strerror(rc)) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
}
}
curl_easy_cleanup(curl);
if (rc > 0)
rc = -EIO;
return rc;
}
/**
* Callback called during curl_easy_perform() to handle received headers.
* Check for the expected response content type.
*/
static size_t _ekmf_header_cb(void *contents, size_t size, size_t nmemb,
void *userp)
{
struct curl_header_cb_data *cb = (struct curl_header_cb_data *)userp;
size_t num = size * nmemb;
size_t ofs;
char *hdr = contents;
char *val, *str;
int i;
if (num < 2)
return num;
if (cb->headers != NULL) {
str = strndup((char *)contents, num - 2);
if (str == NULL) {
pr_verbose(cb->verbose, "strndup failed");
return 0;
}
*cb->headers = curl_slist_append(*cb->headers, str);
free(str);
if (*cb->headers == NULL) {
pr_verbose(cb->verbose, "curl_slist_append failed");
return 0;
}
}
if (num < strlen(HTTP_HDR_CONTENT_TYPE))
goto out;
if (strncasecmp(hdr, HTTP_HDR_CONTENT_TYPE,
strlen(HTTP_HDR_CONTENT_TYPE)) != 0)
goto out;
ofs = strlen(HTTP_HDR_CONTENT_TYPE);
val = hdr + ofs;
while (*val == ' ' && ofs < num) {
ofs++;
val++;
}
if (ofs >= num)
goto out;
for (i = 0; accepted_content_types[i] != NULL; i++) {
if (num - ofs >= strlen(accepted_content_types[i]) &&
strncasecmp(val, accepted_content_types[i],
strlen(accepted_content_types[i])) == 0)
goto out;
}
cb->error = true;
pr_verbose(cb->verbose, "Unexpected response Content-Type: %.*s",
(int)(num - ofs), val);
return 0;
out:
return num;
}
/**
* Callback called during curl_easy_perform() to handle received data.
* Parse the (potentially partial) JSON data.
*/
static size_t _ekmf_write_cb(void *contents, size_t size, size_t nmemb,
void *userp)
{
struct curl_write_cb_data *cb = (struct curl_write_cb_data *)userp;
enum json_tokener_error jerr;
size_t num = size * nmemb;
pr_verbose(cb->verbose, "Response Data: ->%.*s<-", (int)num,
(char *)contents);
if (cb->obj != NULL) {
pr_verbose(cb->verbose, "JSON data already complete, but "
"additional data received");
cb->error = true;
return 0;
}
cb->obj = json_tokener_parse_ex(cb->tok, (const char *)contents, num);
if (cb->obj == NULL) {
jerr = json_tokener_get_error(cb->tok);
if (jerr == json_tokener_continue)
goto out;
cb->error = true;
pr_verbose(cb->verbose, "json_tokener_parse_ex failed: %s",
json_tokener_error_desc(jerr));
return 0;
}
out:
return num;
}
/**
* Extracts the EKMFWeb API error information form the response object.
*/
static int _ekmf_get_api_error(json_object *response_obj, char **error_msg)
{
json_object *field;
int code;
if (response_obj == NULL || error_msg == NULL)
return -EINVAL;
if (!json_object_is_type(response_obj, json_type_object))
return -EBADMSG;
if (!json_object_object_get_ex(response_obj, "code", &field))
return -EBADMSG;
if (!json_object_is_type(field, json_type_int))
return -EBADMSG;
code = json_object_get_int(field);
if (!json_object_object_get_ex(response_obj, "message", &field))
return -EBADMSG;
if (!json_object_is_type(field, json_type_string))
return -EBADMSG;
if (asprintf(error_msg, "EKMFWeb: %d: %s", code,
json_object_get_string(field)) < 0)
return -ENOMEM;
return 0;
}
/**
* This callback called before the SSL handshake is performed.
* It adds a pinned server certificate to the SSL certificate store, so
* that it is treated as trusted, although it might be self-signed.
*/
static CURLcode _ekmf_sslctx_cb(CURL *UNUSED(curl), void *sslctx, void *parm)
{
struct curl_sslctx_cb_data *sslctx_cb = parm;
SSL_CTX *ssl_ctx = (SSL_CTX *)sslctx;
X509_STORE *store;
X509 *cert = NULL;
int rc;
if (ssl_ctx == NULL || sslctx_cb == NULL)
return CURLE_ABORTED_BY_CALLBACK;
if (sslctx_cb->tls_server_cert == NULL)
return CURLE_OK;
store = SSL_CTX_get_cert_store(ssl_ctx);
if (store == NULL) {
pr_verbose(sslctx_cb->verbose, "Failed to get SSL Store");
return CURLE_ABORTED_BY_CALLBACK;
}
rc = read_x509_certificate(sslctx_cb->tls_server_cert, &cert);
if (rc != 0) {
pr_verbose(sslctx_cb->verbose, "Failed to read the server "
"certificate from file '%s'",
sslctx_cb->tls_server_cert);
return CURLE_ABORTED_BY_CALLBACK;
}
rc = X509_STORE_add_cert(store, cert);
if (rc != 1) {
pr_verbose(sslctx_cb->verbose, "Failed to add server "
"certificate to SSL Store");
X509_free(cert);
return CURLE_ABORTED_BY_CALLBACK;
}
X509_free(cert);
return CURLE_OK;
}
/**
* Perform an HTTP request to the url constructed from the base_url in config
* and th uri specified using the specified HTTP request.
* The config structure contains information about TLS certificates.
* If specified, it serializes the request data (JSON) and sends it to the
* server. The response data (JSON) is parsed and returned in the response data.
* If the response content type is not JSON, then an error is returned.
* The HTTP status code is returned in status_code.
*
* @param config the configuration structure
* @param uri the uri (and query parameters) to concatenate to the
* base_url from the config structure.
* @param request the HTTP request to perform (e.g. GET, PUT, POST)
* @param request_data the JSON data to be sent with the request.
* @param request_headers a NULL terminated list of pointers to HTTP headers
* to send along with the request. Can be NULL.
* @param login_token if not NULL, a Bearer token to authorize with
* @param response_data on return the JSON response data is returned. When
* no longer needed, it must be released using
* json_object_put()
* @param response headers address of a curl_slist to add response headers to,
* or NULL to not return any headers.
* @param status_code on return the HTTP status code is returned
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param curl a CURL handle to perform the request with.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno or a positive CURL error code in
* case of an error
*/
static int _ekmf_perform_request(const struct ekmf_config *config,
const char *uri, const char *request,
json_object *request_data,
char **request_headers,
const char *login_token,
json_object **response_data,
struct curl_slist **response_headers,
long *status_code, char **error_msg,
CURL *curl, bool verbose)
{
const struct curl_tlssessioninfo *info = NULL;
struct curl_header_cb_data header_cb = { 0 };
struct curl_sslctx_cb_data sslctx_cb = { 0 };
struct curl_write_cb_data write_cb = { 0 };
char error_str[CURL_ERROR_SIZE] = { 0 };
struct curl_slist *list = NULL;
char *url = NULL;
const char *str;
struct stat sb;
char *auth_hdr;
int i, rc;
if (config == NULL || uri == NULL || request == NULL ||
status_code == NULL || curl == NULL)
return -EINVAL;
if (error_msg != NULL)
*error_msg = NULL;
if (asprintf(&url, "%s%s", config->base_url, uri) < 0) {
pr_verbose(verbose, "asprintf failed");
return -ENOMEM;
}
pr_verbose(verbose, "Performing request for '%s'", url);
curl_easy_reset(curl);
/*
* The CURLOPT_SSL_CTX_FUNCTION callback only works with the OpenSSL
* curl backend. Check that OpenSSL is the current curl backend.
*/
rc = curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &info);
CURL_ERROR_CHECK(rc, "curl_easy_getinfo CURLINFO_TLS_SSL_PTR", verbose,
out);
if (info->backend != CURLSSLBACKEND_OPENSSL) {
pr_verbose(verbose, "libcurl is not using the OpenSSL backend");
rc = -EIO;
goto out;
}
rc = curl_easy_setopt(curl, CURLOPT_VERBOSE, verbose ? 1 : 0);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_VERBOSE", verbose, out);
rc = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_str);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_ERRORBUFFER", verbose,
out);
rc = curl_easy_setopt(curl, CURLOPT_URL, url);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_URL", verbose, out);
rc = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER,
config->tls_verify_peer ? 1L : 0L);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_VERIFYPEER", verbose,
out);
rc = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST,
config->tls_verify_host ? 2L : 0L);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_VERIFYHOST", verbose,
out);
if (config->tls_ca != NULL) {
if (stat(config->tls_ca, &sb) != 0) {
rc = -errno;
pr_verbose(verbose, "stat failed on '%s': %s",
config->tls_ca, strerror(-rc));
goto out;
}
if (S_ISDIR(sb.st_mode)) {
rc = curl_easy_setopt(curl, CURLOPT_CAPATH,
config->tls_ca);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CAPATH",
verbose, out);
} else {
rc = curl_easy_setopt(curl, CURLOPT_CAINFO,
config->tls_ca);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CAINFO",
verbose, out);
}
}
if (config->tls_client_cert != NULL) {
rc = curl_easy_setopt(curl, CURLOPT_SSLCERT,
config->tls_client_cert);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLCERT",
verbose, out);
rc = curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM");
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLCERTTYPE",
verbose, out);
}
if (config->tls_client_key != NULL) {
rc = curl_easy_setopt(curl, CURLOPT_SSLKEY,
config->tls_client_key);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLKEY", verbose,
out);
rc = curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLKEYTYPE",
verbose, out);
if (config->tls_client_key_passphrase != NULL) {
rc = curl_easy_setopt(curl, CURLOPT_KEYPASSWD,
config->tls_client_key_passphrase);
CURL_ERROR_CHECK(rc,
"curl_easy_setopt CURLOPT_KEYPASSWD",
verbose, out);
}
}
if (config->tls_issuer_cert != NULL) {
rc = curl_easy_setopt(curl, CURLOPT_ISSUERCERT,
config->tls_issuer_cert);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_ISSUERCERT",
verbose, out);
}
if (config->tls_pinned_pubkey != NULL) {
rc = curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY,
config->tls_pinned_pubkey);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_PINNEDPUBLICKEY",
verbose, out);
}
if (config->max_redirs > 0) {
rc = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_FOLLOWLOCATION",
verbose, out);
rc = curl_easy_setopt(curl, CURLOPT_MAXREDIRS,
config->max_redirs);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_MAXREDIRS",
verbose, out);
rc = curl_easy_setopt(curl, CURLOPT_POSTREDIR,
CURL_REDIR_POST_ALL);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_POSTREDIR",
verbose, out);
} else {
rc = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_FOLLOWLOCATION",
verbose, out);
}
if (strcmp(request, "GET") == 0) {
rc = curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CUSTOMREQUEST",
verbose, out);
rc = curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_HTTPGET",
verbose, out);
} else {
rc = curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST,
request);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CUSTOMREQUEST",
verbose, out);
if (request_data != NULL) {
rc = curl_easy_setopt(curl, CURLOPT_POST, 1L);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_POST",
verbose, out);
list = curl_slist_append(list,
"Content-Type: application/json;charset=UTF-8");
if (list == NULL) {
pr_verbose(verbose, "curl_slist_append failed");
rc = -ENOMEM;
goto out;
}
/*
* The memory returned by json_object_to_json_string_ext
* is freed when the JSON object is freed.
*/
str = json_object_to_json_string_ext(request_data,
JSON_C_TO_STRING_PLAIN |
JSON_C_TO_STRING_NOSLASHESCAPE);
pr_verbose(verbose, "Request Data: ->%s<-", str);
rc = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, str);
CURL_ERROR_CHECK(rc,
"curl_easy_setopt CURLOPT_POSTFIELDS",
verbose, out);
}
}
list = curl_slist_append(list, "Accept: application/json");
if (list == NULL) {
pr_verbose(verbose, "curl_slist_append failed");
rc = -ENOMEM;
goto out;
}
list = curl_slist_append(list, "Accept-Charset: UTF-8");
if (list == NULL) {
pr_verbose(verbose, "curl_slist_append failed");
rc = -ENOMEM;
goto out;
}
/* Disable "Expect: 100-continue" */
list = curl_slist_append(list, "Expect:");
if (list == NULL) {
pr_verbose(verbose, "curl_slist_append failed");
rc = -ENOMEM;
goto out;
}
if (login_token != NULL) {
/*
* Note: We could alternatively use CURLOPT_XOAUTH2_BEARER, with
* CURLOPT_HTTPAUTH using CURLAUTH_BEARER, but this seems to
* cause a memory leak in some curl versions.
*/
if (asprintf(&auth_hdr, "Authorization: Bearer %s",
login_token) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
list = curl_slist_append(list, auth_hdr);
free(auth_hdr);
if (list == NULL) {
pr_verbose(verbose, "curl_slist_append failed");
rc = -ENOMEM;
goto out;
}
}
for (i = 0; request_headers != NULL &&
request_headers[i] != NULL; i++) {
list = curl_slist_append(list, request_headers[i]);
if (list == NULL) {
pr_verbose(verbose, "curl_slist_append failed");
rc = -ENOMEM;
goto out;
}
}
rc = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_HTTPHEADER", verbose,
out);
header_cb.headers = response_headers;
header_cb.verbose = verbose;
write_cb.verbose = verbose;
write_cb.tok = json_tokener_new();
if (write_cb.tok == NULL) {
pr_verbose(verbose, "json_tokener_new failed");
rc = -ENOMEM;
goto out;
}
sslctx_cb.tls_server_cert = config->tls_server_cert;
sslctx_cb.verbose = verbose;
rc = curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, _ekmf_header_cb);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_HEADERFUNCTION", verbose,
out);
rc = curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)&header_cb);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_HEADERDATA", verbose,
out);
rc = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _ekmf_write_cb);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_WRITEFUNCTION", verbose,
out);
rc = curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&write_cb);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_WRITEDATA", verbose,
out);
if (config->tls_server_cert != NULL) {
rc = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION,
_ekmf_sslctx_cb);
CURL_ERROR_CHECK(rc, "curl_easy_setopt "
"CURLOPT_SSL_CTX_FUNCTION", verbose, out);
rc = curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA,
&sslctx_cb);
CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_CTX_DATA",
verbose, out);
}
rc = curl_easy_perform(curl);
if (rc != CURLE_OK) {
pr_verbose(verbose, "curl_easy_perform for '%s' failed: %s",
url, curl_easy_strerror(rc));
pr_verbose(verbose, "Error: %s", error_str);
if (header_cb.error) {
pr_verbose(verbose, "Unexpected Content-Type");
rc = -EBADMSG;
if (error_msg != NULL && *error_msg == NULL) {
if (asprintf(error_msg, "Unexpected response "
"Content-Type") < 0)
error_msg = NULL;
}
}
if (write_cb.error) {
pr_verbose(verbose, "JSON parsing failed");
rc = -EBADMSG;
if (error_msg != NULL && *error_msg == NULL) {
if (asprintf(error_msg, "Failed to JSON parse "
"the response content") < 0)
error_msg = NULL;
}
}
goto out;
}
rc = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status_code);
CURL_ERROR_CHECK(rc, "curl_easy_getinfo CURLINFO_RESPONSE_CODE",
verbose, out);
if (*status_code >= 400 && write_cb.obj != NULL &&
error_msg != NULL && *error_msg == NULL) {
rc = _ekmf_get_api_error(write_cb.obj, error_msg);
json_object_put(write_cb.obj);
write_cb.obj = NULL;
if (rc != 0)
goto out;
}
if (response_data != NULL) {
*response_data = write_cb.obj;
} else {
if (write_cb.obj != NULL)
json_object_put(write_cb.obj);
}
out:
if (write_cb.tok != NULL)
json_tokener_free(write_cb.tok);
if (url != NULL)
free(url);
if (list != NULL)
curl_slist_free_all(list);
if (rc > 0 && error_msg != NULL && *error_msg == NULL) {
if (asprintf(error_msg, "CURL: %s", strlen(error_str) > 0 ?
error_str : curl_easy_strerror(rc)) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
}
}
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, NULL);
return rc;
}
/**
* Allocates or reuses a CURL handle. If curl_handle is not NULL, and
* points to a non-NULL CURL handle, it is used, otherwise a new CURL handle
* is allocated.
*/
static int _ekmf_get_curl_handle(CURL **curl_handle, CURL **curl)
{
if (curl == NULL)
return -EINVAL;
if (curl_handle != NULL)
*curl = *curl_handle;
if (*curl == NULL)
*curl = curl_easy_init();
if (*curl == NULL)
return -EIO;
return 0;
}
/**
* Releases a CURL handle. If curl_handle is not NULL, then the used CURL
* handle is passed back via *curl_handle. If curl_handle is NULL, then the
* used CURL handle is destroyed.
*/
static void _ekmf_release_curl_handle(CURL **curl_handle, CURL *curl)
{
if (curl == NULL)
return;
if (curl_handle != NULL)
*curl_handle = curl;
else
curl_easy_cleanup(curl);
}
/**
* Print the certificate(s) contained in the specified PEM file.
*
* @param cert_pem the file name of the PEM file to print
* @param verbose if true, verbose messages are printed
*
* @returns -EIO if the file could not be opened. -ENOENT if the PEM file
* does not contain any certificates. 0 if success.
*/
int ekmf_print_certificates(const char *cert_pem, bool verbose)
{
int rc = -ENOENT;
X509 *cert;
FILE *fp;
if (cert_pem == NULL)
return -EINVAL;
fp = fopen(cert_pem, "r");
if (fp == NULL) {
pr_verbose(verbose, "File '%s': %s", cert_pem, strerror(errno));
return -EIO;
}
while (1) {
cert = PEM_read_X509(fp, NULL, NULL, NULL);
if (cert == NULL)
break;
X509_print_ex_fp(stdout, cert, 0, X509_FLAG_NO_EXTENSIONS);
X509_free(cert);
rc = 0;
}
fclose(fp);
return rc;
}
/**
* Checks if the login token stored in the file denoted by field login_token
* of the config structure is valid or not. The file (if existent) contains a
* JSON Web Token (JWT, see RFC7519). It is valid if the current date and time
* is before its expiration time ("exp" claim), and after or equal its
* not-before time ("nbf" claim).
* Note: The signature (if any) of the JWT is not checked, nor any other JWT
* fields.
*
* @param config the configuration structure
* @param valid On return: true if the token is valid, false if not
* @param login_token On return: If not NULL: the login token, if the
* token is still valid. The returned string must
* be freed by the caller when no longer needed.
* @param verbose if true, verbose messages are printed
*
* @returns a negative errno in case of an error, 0 if success.
*/
int ekmf_check_login_token(const struct ekmf_config *config, bool *valid,
char **login_token, bool verbose)
{
json_object *jwt_payload = NULL;
json_object *exp_claim = NULL;
json_object *nbf_claim = NULL;
char *token = NULL;
size_t count, size;
int64_t exp, nbf;
FILE *fp = NULL;
struct stat sb;
int rc = 0;
time_t now;
if (config == NULL || valid == NULL)
return -EINVAL;
if (config->login_token == NULL) {
*valid = false;
return 0;
}
if (login_token != NULL)
*login_token = NULL;
pr_verbose(verbose, "Reading login token from file : '%s'",
config->login_token);
if (stat(config->login_token, &sb)) {
rc = -errno;
pr_verbose(verbose, "stat on file %s failed: '%s'",
config->login_token, strerror(-rc));
return rc;
}
size = sb.st_size;
if (size == 0) {
pr_verbose(verbose, "File %s is empty", config->login_token);
rc = -EIO;
goto out;
}
token = (char *)malloc(size + 1);
if (token == NULL) {
pr_verbose(verbose, "Failed to allocate a buffer");
return -ENOMEM;
}
fp = fopen(config->login_token, "r");
if (fp == NULL) {
rc = -errno;
pr_verbose(verbose, "Failed to open file %s: '%s'",
config->login_token, strerror(-rc));
goto out;
}
count = fread(token, 1, size, fp);
if (count != size) {
pr_verbose(verbose, "Failed to read the token");
rc = -EIO;
goto out;
}
token[size] = '\0';
if (token[size - 1] == '\n')
token[size - 1] = '\0';
fclose(fp);
fp = NULL;
time(&now);
*valid = true;
rc = parse_json_web_token(token, NULL, &jwt_payload, NULL, NULL);
if (rc != 0) {
pr_verbose(verbose, "parse_json_web_token failed");
goto out;
}
if (json_object_object_get_ex(jwt_payload, "exp", &exp_claim) &&
json_object_is_type(exp_claim, json_type_int)) {
exp = json_object_get_int64(exp_claim);
if (exp == 0) {
pr_verbose(verbose,
"failed to get value from exp claim");
rc = -EIO;
goto out;
}
if (now > exp) {
pr_verbose(verbose, "JWT is expired");
*valid = false;
}
}
if (json_object_object_get_ex(jwt_payload, "nbf", &nbf_claim) &&
json_object_is_type(nbf_claim, json_type_int)) {
nbf = json_object_get_int64(nbf_claim);
if (nbf == 0) {
pr_verbose(verbose,
"failed to get value from nbf claim");
rc = -EIO;
goto out;
}
if (now <= nbf) {
pr_verbose(verbose, "JWT is not yet valid");
*valid = false;
}
}
if (login_token != NULL && *valid) {
*login_token = token;
token = NULL;
}
out:
if (jwt_payload != NULL)
json_object_put(jwt_payload);
if (token != NULL)
free(token);
if (fp != NULL)
fclose(fp);
return rc;
}
/**
* Performs a login of the specified user with a passcode. On success the
* returned login token is stored in the file denoted by field login_token
* of the config structure, so that it can be used by subsequent requests.
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param user_id the user-ID to log-in.
* @param passcode the passcode to log-in the user.
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if the passcode is no longer valid.
*/
int ekmf_login(const struct ekmf_config *config, CURL **curl_handle,
const char *user_id, const char *passcode, char **error_msg,
bool verbose)
{
json_object *response_obj = NULL;
json_object *request_obj = NULL;
const char *login_token, *tok;
CURL *curl = NULL;
long status_code;
FILE *fp = NULL;
size_t count;
int rc;
if (config == NULL || user_id == NULL || passcode == NULL)
return -EINVAL;
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
request_obj = json_object_new_object();
JSON_CHECK_ERROR(request_obj == NULL, rc, -ENOMEM,
"Failed to generate JSON object", verbose, out);
rc = json_object_object_add_ex(request_obj, "userId",
json_object_new_string(user_id), 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
rc = json_object_object_add_ex(request_obj, "passcode",
json_object_new_string(passcode), 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
rc = _ekmf_perform_request(config, EKMF_URI_SYSTEM_LOGIN, "POST",
request_obj, NULL, NULL, &response_obj, NULL,
&status_code, error_msg, curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 200:
break;
case 410:
pr_verbose(verbose, "The passcode is no longer valid");
rc = -EACCES;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EIO,
"No or invalid response content", verbose, out);
login_token = json_get_string(response_obj, "authorizationToken");
JSON_CHECK_ERROR(login_token == NULL, rc, -EBADMSG,
"Invalid response content", verbose, out);
if (strncmp(login_token, "Bearer ", 7) != 0) {
rc = -EBADMSG;
pr_verbose(verbose, "Received token is not a Bearer token");
goto out;
}
tok = &login_token[7];
while (*tok == ' ')
tok++;
fp = fopen_nofollow(config->login_token, "w");
if (fp == NULL) {
rc = -errno;
pr_verbose(verbose, "Failed to open file %s: '%s'",
config->login_token, strerror(-rc));
goto out;
}
count = fwrite(tok, 1, strlen(tok), fp);
if (count != strlen(tok)) {
pr_verbose(verbose, "Failed to write the token");
rc = -EIO;
goto out;
}
pr_verbose(verbose, "Login token successfully updated in file '%s'",
config->login_token);
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (request_obj != NULL)
json_object_put(request_obj);
if (response_obj != NULL)
json_object_put(response_obj);
if (fp != NULL)
fclose(fp);
return rc;
}
/**
* Request the EKMFWeb server's public signing key and store it into PEM file
* specified in field server_pubkey of the config structure.
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
*/
int ekmf_get_public_key(const struct ekmf_config *config, CURL **curl_handle,
char **error_msg, bool verbose)
{
json_object *response_obj = NULL;
char *login_token = NULL;
bool token_valid = false;
EVP_PKEY *pkey = NULL;
CURL *curl = NULL;
long status_code;
int rc;
if (config == NULL)
return -EINVAL;
rc = SK_OPENSSL_init(verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to initialize secure key support: "
"%s", strerror(-rc));
return rc;
}
rc = ekmf_check_login_token(config, &token_valid, &login_token,
verbose);
if (rc != 0 || !token_valid) {
pr_verbose(verbose, "No valid login token available");
rc = -EACCES;
goto out;
}
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
rc = _ekmf_perform_request(config, EKMF_URI_SYSTEM_PUBKEY, "GET",
NULL, NULL, login_token, &response_obj, NULL,
&status_code, error_msg, curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 200:
break;
case 401:
pr_verbose(verbose, "Not authorized");
rc = -EACCES;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EIO,
"No or invalid response content", verbose, out);
rc = json_web_key_as_pkey(response_obj, EVP_PKEY_RSA, &pkey);
if (rc != 0) {
pr_verbose(verbose, "Failed convert the JWK to PKEY");
goto out;
}
rc = write_public_key(config->ekmf_server_pubkey, pkey);
if (rc != 0) {
pr_verbose(verbose, "Failed to write public key '%s': %s",
config->ekmf_server_pubkey, strerror(-rc));
goto out;
}
pr_verbose(verbose, "EKMFWeb public key written to file '%s'",
config->ekmf_server_pubkey);
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (response_obj != NULL)
json_object_put(response_obj);
if (login_token != NULL)
free(login_token);
if (pkey != NULL)
EVP_PKEY_free(pkey);
SK_OPENSSL_term();
return rc;
}
/**
* Finds the setting with the specified ID in the settings array, and returns
* its value if found, or NULL otherwise. The returned string is allocated
* and must be freed by the caller when no lnger used.
*
* @param settings_array the JSON array containing the settings
* @param config the setting ID to get
* @param verbose if true, verbose messages are printed
*
* @returns an allocated string conmtaining the setting value, or NULL
*/
static char *_ekmf_find_setting(json_object *settings_array,
const char *setting_id, bool verbose)
{
const char *id, *value;
json_object *obj;
int rc = 0, i;
JSON_CHECK_OBJ(settings_array, json_type_array, rc, -EIO,
"No settings array", verbose, out);
for (i = 0; i < (int)json_object_array_length(settings_array); i++) {
obj = json_object_array_get_idx(settings_array, i);
JSON_CHECK_OBJ(obj, json_type_object, rc, -EIO,
"No settings object", verbose, out);
id = json_get_string(obj, "id");
JSON_CHECK_ERROR(id == NULL, rc, -EIO,
"No id field in settings object", verbose,
out);
if (strcmp(id, setting_id) != 0)
continue;
value = json_get_string(obj, "value");
JSON_CHECK_ERROR(value == NULL, rc, -EIO,
"No value field in settings object", verbose,
out);
return strdup(value);
}
pr_verbose(verbose, "Setting '%s' not found", setting_id);
out:
return NULL;
}
/**
* Retrieves settings from the EKMFWeb server, such as the template names for
* generating keys in EKMFWeb.
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param identity_template on return: If not NULL, the name of the template
* used to generate identity keys with. The caller
* must free the error string when it is not NULL.
* @param xts_key1_template on return: If not NULL, the name of the template
* used to generate the first XTS key with. The caller
* must free the error string when it is not NULL.
* @param xts_key1_template on return: If not NULL, the name of the template
* used to generate the second XTS key with. The caller
* must free the error string when it is not NULL.
* @param xts_key1_template on return: If not NULL, the name of the template
* used to generate a non-XTS key with. The caller
* must free the error string when it is not NULL.
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
*/
int ekmf_get_settings(const struct ekmf_config *config, CURL **curl_handle,
char **identity_template, char **xts_key1_template,
char **xts_key2_template, char **non_xts_template,
char **error_msg, bool verbose)
{
json_object *response_obj = NULL;
char *login_token = NULL;
bool token_valid = false;
CURL *curl = NULL;
long status_code;
int rc;
if (config == NULL)
return -EINVAL;
if (identity_template == NULL && xts_key1_template == NULL &&
xts_key2_template == NULL && non_xts_template == NULL)
return 0;
rc = ekmf_check_login_token(config, &token_valid, &login_token,
verbose);
if (rc != 0 || !token_valid) {
pr_verbose(verbose, "No valid login token available");
rc = -EACCES;
goto out;
}
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
rc = _ekmf_perform_request(config, EKMF_URI_SYSTEM_SETTINGS, "GET",
NULL, NULL, login_token, &response_obj, NULL,
&status_code, error_msg, curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 200:
break;
case 401:
pr_verbose(verbose, "Not authorized");
rc = -EACCES;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
JSON_CHECK_OBJ(response_obj, json_type_array, rc, -EIO,
"No or invalid response content", verbose, out);
if (identity_template != NULL) {
*identity_template = _ekmf_find_setting(response_obj,
SETTING_ID_IDENTITY_TEMPLATE, verbose);
if (*identity_template == NULL) {
if (error_msg != NULL) {
if (asprintf(error_msg, "The EKMF Web setting "
"'Identity Key Template Name' "
"must be configured") < 0)
error_msg = NULL;
}
rc = -EINVAL;
goto out;
}
}
if (xts_key1_template != NULL) {
*xts_key1_template = _ekmf_find_setting(response_obj,
SETTING_ID_XTS_KEY1_TEMPLATE, verbose);
if (*xts_key1_template == NULL) {
if (error_msg != NULL) {
if (asprintf(error_msg, "The EKMF Web setting "
"'XTS Key Template Name (Key 1)' "
"must be configured") < 0)
error_msg = NULL;
}
rc = -EINVAL;
goto out;
}
}
if (xts_key2_template != NULL) {
*xts_key2_template = _ekmf_find_setting(response_obj,
SETTING_ID_XTS_KEY2_TEMPLATE, verbose);
if (*xts_key2_template == NULL) {
if (error_msg != NULL) {
if (asprintf(error_msg, "The EKMF Web setting "
"'XTS Key Template Name (Key 2)' "
"must be configured") < 0)
error_msg = NULL;
}
rc = -EINVAL;
goto out;
}
}
if (non_xts_template != NULL) {
*non_xts_template = _ekmf_find_setting(response_obj,
SETTING_ID_NON_XTS_TEMPLATE, verbose);
if (*non_xts_template == NULL) {
if (error_msg != NULL) {
if (asprintf(error_msg, "The EKMF Web setting "
"'Non-XTS Key Template Name' "
"must be configured") < 0)
error_msg = NULL;
}
rc = -EINVAL;
goto out;
}
}
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (response_obj != NULL)
json_object_put(response_obj);
if (login_token != NULL)
free(login_token);
if (rc != 0) {
if (identity_template != NULL && *identity_template != NULL) {
free(*identity_template);
*identity_template = NULL;
}
if (xts_key1_template != NULL && *xts_key1_template != NULL) {
free(*xts_key1_template);
*xts_key1_template = NULL;
}
if (xts_key2_template != NULL && *xts_key2_template != NULL) {
free(*xts_key2_template);
*xts_key2_template = NULL;
}
if (non_xts_template != NULL && *non_xts_template != NULL) {
free(*non_xts_template);
*non_xts_template = NULL;
}
}
return rc;
}
/**
* Checks if the EKMFWeb server has the required Pervasive Encryption feature
* installed
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -ENOTSUP is returned, if the feature is not installed.
*/
int ekmf_check_feature(const struct ekmf_config *config, CURL **curl_handle,
char **error_msg, bool verbose)
{
json_object *response_obj = NULL, *obj;
bool found = false;
CURL *curl = NULL;
long status_code;
const char *id;
int rc, i;
if (config == NULL)
return -EINVAL;
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
rc = _ekmf_perform_request(config, EKMF_URI_SYSTEM_FEATURES, "GET",
NULL, NULL, NULL, &response_obj, NULL,
&status_code, error_msg, curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 200:
break;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
JSON_CHECK_OBJ(response_obj, json_type_array, rc, -EIO,
"No or invalid response content", verbose, out);
for (i = 0; i < (int)json_object_array_length(response_obj); i++) {
obj = json_object_array_get_idx(response_obj, i);
JSON_CHECK_OBJ(obj, json_type_object, rc, -EIO,
"No features object", verbose, out);
id = json_get_string(obj, "id");
JSON_CHECK_ERROR(id == NULL, rc, -EIO,
"No id field in settings object", verbose,
out);
if (strcmp(id, FEATURE_ID_PERVASIVE_ENCRYPTION) != 0)
continue;
found = true;
break;
}
if (!found) {
pr_verbose(verbose, "Feature '%s' is not installed",
FEATURE_ID_PERVASIVE_ENCRYPTION);
rc = -ENOTSUP;
if (error_msg != NULL &&
asprintf(error_msg, "EKMF Web feature "
"'Pervasive Encryption' is not installed.") < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
}
goto out;
}
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (response_obj != NULL)
json_object_put(response_obj);
return rc;
}
/**
* Build the party info JSON object as base64(sha256(key_uuid|timestamp)).
* Digest_nid specifies the digest to use, ot 0 to use the default (SHA256).
* The function returns the party info JSON object, as well as the raw party
* info.
*/
static int _ekmf_build_party_info(const char *key_uuid, const char *timestamp,
int digest_nid, unsigned char *party_info,
size_t *party_info_length,
json_object **party_info_obj, bool verbose)
{
unsigned int digest_len;
EVP_MD_CTX *ctx = NULL;
const EVP_MD *md;
int rc;
md = EVP_get_digestbynid(digest_nid != 0 ? digest_nid : NID_sha256);
if (md == NULL) {
pr_verbose(verbose, "Failed to get specified digest");
rc = -EINVAL;
goto out;
}
if (*party_info_length < (size_t)EVP_MD_size(md)) {
pr_verbose(verbose, "Party info buffer is too small");
rc = -ERANGE;
goto out;
}
ctx = EVP_MD_CTX_create();
if (ctx == NULL) {
pr_verbose(verbose, "Failed to allocate MD context");
rc = -ENOMEM;
goto out;
}
rc = EVP_DigestInit_ex(ctx, md, NULL);
if (rc != 1) {
pr_verbose(verbose, "Failed to initialize MD context");
rc = -EIO;
goto out;
}
rc = EVP_DigestUpdate(ctx, key_uuid, strlen(key_uuid));
if (rc != 1) {
pr_verbose(verbose, "Failed to add data to the MD context");
rc = -EIO;
goto out;
}
rc = EVP_DigestUpdate(ctx, timestamp, strlen(timestamp));
if (rc != 1) {
pr_verbose(verbose, "Failed to add data to the MD context");
rc = -EIO;
goto out;
}
rc = EVP_DigestFinal_ex(ctx, party_info, &digest_len);
if (rc != 1) {
pr_verbose(verbose, "Failed to finalize the MD context");
rc = -EIO;
goto out;
}
*party_info_length = digest_len;
*party_info_obj = json_object_new_base64url(party_info, digest_len);
rc = 0;
out:
if (ctx != NULL)
EVP_MD_CTX_destroy(ctx);
return rc;
}
/**
* Builds a (detached) JSON Web Signature using the secure identity key from
* the payload and returns a signature JSON object
*/
static int _ekmf_build_signature(unsigned char *key_blob,
size_t key_blob_length,
json_object *payload_obj,
json_object **signature_obj,
int digest_nid, bool use_rsa_pss,
const char *jws_kid,
const struct ekmf_ext_lib *ext_lib,
bool verbose)
{
struct sk_rsa_pss_params rsa_pss_params;
struct ext_lib_info ext_lib_info;
EVP_PKEY_CTX *pkey_ctx = NULL;
EVP_MD_CTX *md_ctx = NULL;
EVP_PKEY *pkey = NULL;
const char *payload;
const char *jws_alg;
int rc, curve_nid;
char *jws = NULL;
BIO *b;
_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);
rc = SK_OPENSSL_get_secure_key_as_pkey(key_blob, key_blob_length,
use_rsa_pss, &pkey,
&ext_lib_info.ext_lib,
verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to get the identity PKEY");
goto out;
}
/*
* Only the following combinations are allowed per RFC7518 for JSON
* Web Signatures (JWS) using ECC or RSA identity keys:
* alg=ES256: ECDSA using P-256 and SHA-256
* alg=ES384: ECDSA using P-384 and SHA-384
* alg=ES512: ECDSA using P-521 and SHA-512
* alg=RS256: RSA-PKCS1 using SHA-256
* alg=RS384: RSA-PKCS1 using SHA-384
* alg=RS512: RSA-PKCS1 using SHA-512
* alg=PS256: RSA-PSS using SHA-256, MGF1 with SHA-256, salt=digest
* alg=PS384: RSA-PSS using SHA-384, MGF1 with SHA-384, salt=digest
* alg=PS512: RSA-PSS using SHA-512, MGF1 with SHA-512, salt=digest
*/
switch (EVP_PKEY_id(pkey)) {
case EVP_PKEY_EC:
curve_nid = SK_OPENSSL_get_curve_from_ec_pkey(pkey);
switch (curve_nid) {
case NID_secp521r1:
digest_nid = NID_sha512;
jws_alg = "ES512";
break;
case NID_secp384r1:
digest_nid = NID_sha384;
jws_alg = "ES384";
break;
case NID_X9_62_prime256v1:
digest_nid = NID_sha256;
jws_alg = "ES256";
break;
default:
pr_verbose(verbose, "Unsupported curve");
rc = -EINVAL;
goto out;
}
break;
case EVP_PKEY_RSA:
switch (digest_nid) {
case NID_sha256:
jws_alg = "RS256";
break;
case NID_sha384:
jws_alg = "RS384";
break;
case NID_sha512:
case 0:
jws_alg = "RS512";
digest_nid = NID_sha512;
break;
default:
pr_verbose(verbose, "Unsupported digest");
rc = -EINVAL;
goto out;
}
break;
case EVP_PKEY_RSA_PSS:
switch (digest_nid) {
case NID_sha256:
jws_alg = "PS256";
break;
case NID_sha384:
jws_alg = "PS384";
break;
case NID_sha512:
case 0:
jws_alg = "PS512";
digest_nid = NID_sha512;
break;
default:
pr_verbose(verbose, "Unsupported digest");
rc = -EINVAL;
goto out;
}
rsa_pss_params.mgf_digest_nid = digest_nid;
rsa_pss_params.salt_len = RSA_PSS_SALTLEN_DIGEST;
break;
default:
pr_verbose(verbose, "Unsupported key type");
rc = -EINVAL;
goto out;
}
rc = SK_OPENSSL_setup_sign_context(pkey, false, digest_nid,
&rsa_pss_params, &md_ctx, &pkey_ctx,
verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to setup secure key sign context");
goto out;
}
payload = json_object_to_json_string_ext(payload_obj,
JSON_C_TO_STRING_PLAIN |
JSON_C_TO_STRING_NOSLASHESCAPE);
if (payload == NULL) {
pr_verbose(verbose, "Failed to get the payload string");
rc = -EIO;
goto out;
}
if (verbose) {
pr_verbose(verbose, "JWS Payload: ->%s<-", payload);
pr_verbose(verbose, "JWS alg: %s", jws_alg);
pr_verbose(verbose, "Public signing key:");
b = BIO_new_fp(stderr, BIO_NOCLOSE);
PEM_write_bio_PUBKEY(b, pkey);
BIO_free(b);
}
rc = create_json_web_signature(jws_alg, false, jws_kid,
(unsigned char *)payload,
strlen(payload), true, md_ctx, &jws);
if (rc != 0) {
pr_verbose(verbose, "Failed to build the JWS");
goto out;
}
*signature_obj = json_object_new_string(jws);
rc = 0;
out:
if (md_ctx != NULL)
EVP_MD_CTX_free(md_ctx);
if (pkey != NULL)
EVP_PKEY_free(pkey);
if (jws != NULL)
free(jws);
return rc;
}
/**
* Verifies the (detached) JSON Web Signature using the server's public signing
* key and the response payload.
* Note: This function removes the signature field from the response JSON
* object!
*/
static int _ekmf_verify_signature(json_object *response_obj,
EVP_PKEY *server_pubkey, bool verbose)
{
json_object *signature_obj = NULL;
const char *sign_payload;
BIO *b;
int rc;
if (response_obj == NULL)
return -EINVAL;
json_object_object_get_ex(response_obj, "signature",
&signature_obj);
JSON_CHECK_OBJ(signature_obj, json_type_string, rc, -EIO,
"Failed to get the response signature", verbose, out);
json_object_get(signature_obj); /* Take ownership */
json_object_object_del(response_obj, "signature");
sign_payload = json_object_to_json_string_ext(response_obj,
JSON_C_TO_STRING_PLAIN |
JSON_C_TO_STRING_NOSLASHESCAPE);
if (sign_payload == NULL) {
rc = -ENOMEM;
goto out;
}
if (verbose) {
pr_verbose(verbose, "JWS Payload: ->%s<-", sign_payload);
pr_verbose(verbose, "Public signing key:");
b = BIO_new_fp(stderr, BIO_NOCLOSE);
PEM_write_bio_PUBKEY(b, server_pubkey);
BIO_free(b);
}
rc = verify_json_web_signature(json_object_get_string(signature_obj),
(const unsigned char *)sign_payload,
strlen(sign_payload), server_pubkey);
if (rc != 0) {
pr_verbose(verbose, "Signature verify of response failed");
goto out;
}
pr_verbose(verbose, "Signature of response successfully verified");
out:
if (signature_obj != NULL)
json_object_put(signature_obj);
return rc;
}
/**
* Import the key retrieved from EKMFWeb.
*/
static int _ekmf_import_key(unsigned char *req_sess_key,
size_t req_sess_key_length,
unsigned char *req_party_info,
size_t req_party_info_length,
unsigned char *resp_party_info,
size_t resp_party_info_length,
json_object *resp_sess_jwk_obj,
json_object *resp_exp_jwk_obj,
unsigned char *key_blob, size_t *key_blob_length,
const struct ekmf_ext_lib *ext_lib, bool verbose)
{
size_t resp_sess_ec_key_length, resp_exported_key_length;
unsigned char resp_exported_key[MAX_SYM_KEY_BLOB_SIZE];
unsigned char transport_key[MAX_SYM_KEY_BLOB_SIZE];
unsigned char resp_sess_key[MAX_KEY_BLOB_SIZE];
size_t party_info_length, transport_key_length;
unsigned char *party_info = NULL;
int rc;
party_info_length = req_party_info_length + resp_party_info_length;
party_info = malloc(party_info_length);
if (party_info == NULL) {
pr_verbose(verbose, "Failed to allocate memory");
rc = -ENOMEM;
goto out;
}
memcpy(party_info, req_party_info, req_party_info_length);
memcpy(party_info + req_party_info_length, resp_party_info,
resp_party_info_length);
switch (ext_lib->type) {
case EKMF_EXT_LIB_CCA:
resp_sess_ec_key_length = sizeof(resp_sess_key);
rc = cca_import_key_from_json_web_key(ext_lib->cca,
resp_sess_jwk_obj,
resp_sess_key,
&resp_sess_ec_key_length,
verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to import the session EC "
"key");
goto out;
}
transport_key_length = sizeof(transport_key);
rc = cca_ec_dh_derive_importer(ext_lib->cca,
req_sess_key,
req_sess_key_length,
resp_sess_key,
resp_sess_ec_key_length,
party_info, party_info_length,
CCA_KDF_ANS_X9_63_CCA,
transport_key,
&transport_key_length,
verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to derive transport key");
goto out;
}
resp_exported_key_length = sizeof(resp_exported_key);
rc = cca_import_key_from_json_web_key(ext_lib->cca,
resp_exp_jwk_obj,
resp_exported_key,
&resp_exported_key_length,
verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to import the exported "
"key");
goto out;
}
rc = cca_import_external_key(ext_lib->cca, resp_exported_key,
resp_exported_key_length,
transport_key,
transport_key_length,
key_blob, key_blob_length,
verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to unwrap the exported "
"key with the transport key");
goto out;
}
break;
default:
pr_verbose(verbose, "Invalid ext lib type: %d", ext_lib->type);
return -EINVAL;
}
out:
if (party_info != NULL)
free(party_info);
return rc;
}
/*
* Callback function for SK_OPENSSL_get_public_from_secure_key. Construct
* a JSON Web key from a public EC key.
*/
static int _ekmf_pub_key_as_json_web_key(const struct sk_pub_key_info *pub_key,
void *private)
{
json_object **jwk_ret = private;
json_object *jwk_obj = NULL;
int rc = 0;
if (pub_key->type != SK_KEY_TYPE_EC)
return -EINVAL;
/* construct the JWK */
jwk_obj = json_object_new_object();
if (jwk_obj == NULL)
return -ENOMEM;
/*
* Note: The order of the fields is important, EKMFWeb expects it in
* exactly this order!
*/
rc = json_object_object_add_ex(jwk_obj, "kty",
json_object_new_string("EC"), 0);
rc |= json_object_object_add_ex(jwk_obj, "crv", json_object_new_string(
EC_curve_nid2nist(pub_key->ec.curve_nid)), 0);
rc |= json_object_object_add_ex(jwk_obj, "x",
json_object_new_base64url(pub_key->ec.x,
pub_key->ec.prime_len),
0);
rc |= json_object_object_add_ex(jwk_obj, "y",
json_object_new_base64url(pub_key->ec.y,
pub_key->ec.prime_len),
0);
if (rc != 0) {
json_object_put(jwk_obj);
return -EIO;
}
*jwk_ret = jwk_obj;
return 0;
}
/**
* Requests a key to be retrieved from EKMFweb and imported under the current
* HSM's master key.
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param key_uuid the UUID of the key to retrieve
* @param sess_ec_curve_nid The OpenSSL nid of the EC curve used for the session
* ECC key. If 0, then the default curve is used.
* @param sign_rsa_digest_nid The OpenSSL nid of a digest used to sign the
* request with if the identity key is an RSA-type key.
* If 0, then the default digest is used.
* Ignored for ECC-type identity keys.
* @param use_rsa_pss If true, and the identity key is an RSA-type key,
* use RSA-PSS to sign the request.
* @param signature_kid the Key ID for the signature of the request
* @param key_blob a buffer to store the retrieved key blob to
* @param key_blob_length On entry: the size ofthe buffer
* On return: the size of the key blob retrieved
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param ext_lib External secure key crypto library to use
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
* -EPERM is returned if the login token does not have permission to
* retrieve the key
*/
int ekmf_retrieve_key(const struct ekmf_config *config, CURL **curl_handle,
const char *key_uuid, int sess_ec_curve_nid,
int sign_rsa_digest_nid, bool use_rsa_pss,
const char *signature_kid, unsigned char *key_blob,
size_t *key_blob_length, char **error_msg,
const struct ekmf_ext_lib *ext_lib, bool verbose)
{
size_t req_party_info_length, resp_party_info_length;
unsigned char req_party_info[SHA512_DIGEST_LENGTH];
size_t req_sess_ec_key_length, identity_key_length;
unsigned char req_sess_ec_key[MAX_KEY_BLOB_SIZE];
unsigned char identity_key[MAX_KEY_BLOB_SIZE];
json_object *resp_originator_obj = NULL;
json_object *resp_addl_info_obj = NULL;
json_object *req_party_info_obj = NULL;
json_object *req_originator_obj = NULL;
json_object *req_timestamp_obj = NULL;
json_object *req_addl_info_obj = NULL;
json_object *req_signature_obj = NULL;
unsigned char *resp_party_info = NULL;
json_object *resp_sess_jwk_obj = NULL;
json_object *req_sess_jwk_obj = NULL;
json_object *resp_exp_jwk_obj = NULL;
struct ext_lib_info ext_lib_info;
json_object *response_obj = NULL;
json_object *request_obj = NULL;
struct sk_key_gen_info gen_info;
EVP_PKEY *server_pubkey = NULL;
char *escaped_uuid = NULL;
char *login_token = NULL;
bool token_valid = false;
CURL *curl = NULL;
long status_code;
char *uri = NULL;
int rc;
if (config == NULL || key_uuid == NULL || key_blob == NULL ||
key_blob_length == NULL || ext_lib == NULL)
return -EINVAL;
_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);
rc = SK_OPENSSL_init(verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to initialize secure key support: "
"%s", strerror(-rc));
return rc;
}
rc = ekmf_check_login_token(config, &token_valid, &login_token,
verbose);
if (rc != 0 || !token_valid) {
pr_verbose(verbose, "No valid login token available");
rc = -EACCES;
goto out;
}
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
rc = read_public_key(config->ekmf_server_pubkey, &server_pubkey);
if (rc != 0) {
pr_verbose(verbose, "Failed to read EKMFWeb server's public key"
" '%s': %s", config->ekmf_server_pubkey,
strerror(-rc));
goto out;
}
identity_key_length = sizeof(identity_key);
rc = read_key_blob(config->identity_secure_key, identity_key,
&identity_key_length);
if (rc != 0) {
pr_verbose(verbose, "Failed to read identity key from file "
"'%s': %s", config->identity_secure_key,
strerror(-rc));
goto out;
}
gen_info.type = SK_KEY_TYPE_EC;
gen_info.ec.curve_nid = sess_ec_curve_nid != 0 ? sess_ec_curve_nid :
DEFAULT_SESSION_EC_KEY_CURVE;
req_sess_ec_key_length = sizeof(req_sess_ec_key);
rc = SK_OPENSSL_generate_secure_key(req_sess_ec_key,
&req_sess_ec_key_length,
&gen_info, &ext_lib_info.ext_lib,
verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to generate a session EC "
"key");
goto out;
}
rc = SK_OPENSSL_get_public_from_secure_key(req_sess_ec_key,
req_sess_ec_key_length,
_ekmf_pub_key_as_json_web_key,
&req_sess_jwk_obj,
&ext_lib_info.ext_lib,
verbose);
if (rc != 0 || req_sess_jwk_obj == NULL) {
pr_verbose(verbose, "Failed to generate session JWK");
goto out;
}
req_timestamp_obj = get_json_timestamp();
JSON_CHECK_ERROR(req_timestamp_obj == NULL, rc, -EIO,
"Failed to generate timestamp", verbose, out);
req_party_info_length = sizeof(req_party_info);
rc = _ekmf_build_party_info(key_uuid,
json_object_get_string(req_timestamp_obj),
NID_sha256, req_party_info,
&req_party_info_length,
&req_party_info_obj, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to build the party info");
goto out;
}
/*
* Note: The order of the fields is important, EKMFWeb expects it in
* exactly this order!
*/
req_addl_info_obj = json_object_new_object();
JSON_CHECK_ERROR(req_addl_info_obj == NULL, rc, -ENOMEM,
"Failed to generate JSON object", verbose, out);
rc = json_object_object_add_ex(req_addl_info_obj, "kdf",
json_object_new_string("ANS-X9.63-CCA"),
0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
rc = json_object_object_add_ex(req_addl_info_obj, "requestedKey",
json_object_new_string(key_uuid), 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
rc = json_object_object_add_ex(req_addl_info_obj, "timestamp",
req_timestamp_obj, 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
req_timestamp_obj = NULL;
req_originator_obj = json_object_new_object();
JSON_CHECK_ERROR(req_originator_obj == NULL, rc, -ENOMEM,
"Failed to generate JSON object", verbose, out);
rc = json_object_object_add_ex(req_originator_obj, "session",
req_sess_jwk_obj, 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
req_sess_jwk_obj = NULL;
rc = json_object_object_add_ex(req_originator_obj, "partyInfo",
req_party_info_obj, 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
req_party_info_obj = NULL;
request_obj = json_object_new_object();
JSON_CHECK_ERROR(request_obj == NULL, rc, -ENOMEM,
"Failed to generate JSON object", verbose, out);
rc = json_object_object_add_ex(request_obj, "originator",
req_originator_obj, 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
req_originator_obj = NULL;
rc = json_object_object_add_ex(request_obj, "additionalInfo",
req_addl_info_obj, 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
req_addl_info_obj = NULL;
rc = _ekmf_build_signature(identity_key, identity_key_length,
request_obj, &req_signature_obj,
sign_rsa_digest_nid, use_rsa_pss,
signature_kid, ext_lib, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to build the signature");
goto out;
}
rc = json_object_object_add_ex(request_obj, "signature",
req_signature_obj, 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
req_signature_obj = NULL;
escaped_uuid = curl_easy_escape(curl, key_uuid, 0);
if (escaped_uuid == NULL) {
pr_verbose(verbose, "Failed to url-escape the key uuid");
rc = -EIO;
goto out;
}
if (asprintf(&uri, EKMF_URI_KEYS_EXPORT, escaped_uuid) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
rc = _ekmf_perform_request(config, uri, "POST", request_obj, NULL,
login_token, &response_obj, NULL,
&status_code, error_msg, curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 200:
break;
case 400:
pr_verbose(verbose, "Bad request");
rc = -EBADMSG;
goto out;
case 401:
pr_verbose(verbose, "Not authorized");
rc = -EACCES;
goto out;
case 403:
pr_verbose(verbose, "Insufficient permissions");
rc = -EPERM;
goto out;
case 404:
pr_verbose(verbose, "Not found");
rc = -ENOENT;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EBADMSG,
"No or invalid response", verbose, out);
rc = _ekmf_verify_signature(response_obj, server_pubkey, verbose);
if (rc != 0)
goto out;
json_object_object_get_ex(response_obj, "originator",
&resp_originator_obj);
JSON_CHECK_OBJ(resp_originator_obj, json_type_object, rc, -EBADMSG,
"Failed to get the response originator", verbose, out);
json_object_object_get_ex(resp_originator_obj, "session",
&resp_sess_jwk_obj);
JSON_CHECK_OBJ(resp_sess_jwk_obj, json_type_object, rc, -EBADMSG,
"Failed to get the response session key", verbose, out);
rc = json_object_get_base64url(resp_originator_obj, "partyInfo",
NULL, &resp_party_info_length);
if (rc != 0) {
pr_verbose(verbose, "Failed to get the response partyInfo");
goto out;
}
resp_party_info = malloc(resp_party_info_length);
if (resp_party_info == NULL) {
rc = -ENOMEM;
goto out;
}
rc = json_object_get_base64url(resp_originator_obj, "partyInfo",
resp_party_info,
&resp_party_info_length);
if (rc != 0) {
pr_verbose(verbose, "Failed to get the response partyInfo");
goto out;
}
json_object_object_get_ex(response_obj, "additionalInfo",
&resp_addl_info_obj);
JSON_CHECK_OBJ(resp_addl_info_obj, json_type_object, rc, -EBADMSG,
"Failed to get the response addl.info", verbose, out);
json_object_object_get_ex(resp_addl_info_obj, "exportedKey",
&resp_exp_jwk_obj);
JSON_CHECK_OBJ(resp_exp_jwk_obj, json_type_object, rc, -EBADMSG,
"Failed to get the response exported key", verbose, out);
rc = _ekmf_import_key(req_sess_ec_key, req_sess_ec_key_length,
req_party_info, req_party_info_length,
resp_party_info, resp_party_info_length,
resp_sess_jwk_obj, resp_exp_jwk_obj,
key_blob, key_blob_length, ext_lib, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to import the retrieved key");
goto out;
}
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (req_sess_jwk_obj != NULL)
json_object_put(req_sess_jwk_obj);
if (req_timestamp_obj != NULL)
json_object_put(req_timestamp_obj);
if (req_addl_info_obj != NULL)
json_object_put(req_addl_info_obj);
if (req_party_info_obj != NULL)
json_object_put(req_party_info_obj);
if (req_originator_obj != NULL)
json_object_put(req_originator_obj);
if (req_signature_obj != NULL)
json_object_put(req_signature_obj);
if (request_obj != NULL)
json_object_put(request_obj);
if (response_obj != NULL)
json_object_put(response_obj);
if (uri != NULL)
free(uri);
if (login_token != NULL)
free(login_token);
if (server_pubkey != NULL)
EVP_PKEY_free(server_pubkey);
if (resp_party_info != NULL)
free(resp_party_info);
if (escaped_uuid != NULL)
curl_free(escaped_uuid);
SK_OPENSSL_term();
return rc;
}
/**
* Callback function for the _ekmf_list_request function. This callback
* is called for each result element. The curl handle can be used to perform
* further requests within the callback. However, the curl handle must not be
* closed/destroyed!
*/
typedef int (*ekmf_element_cb_t)(CURL *curl, json_object *element,
void *private, bool verbose);
/**
* Performs a list request (GET) on a base list_uri and iterates over
* multiple pages. The response of a list request is expected to be a
* JSON array of elements. For each element, the element callback is called
* with the element.
* The list_uri must contain anything required to list the desired objects,
* except the page and perPage UTL parameters. Those are added by this function.
*/
static int _ekmf_list_request(const struct ekmf_config *config,
const char *list_uri, CURL *curl,
ekmf_element_cb_t element_cb, void *private,
const char *login_token, char **error_msg,
bool verbose)
{
json_object *response_obj = NULL;
json_object *element_obj;
int num, i, rc = 0;
unsigned int page;
char *uri = NULL;
long status_code;
bool has_query;
if (config == NULL || list_uri == NULL || element_cb == NULL ||
curl == NULL)
return -EINVAL;
has_query = strchr(list_uri, '?') != NULL;
for (page = 1; ; page++) {
if (asprintf(&uri, "%s%sperPage=%u&page=%u", list_uri,
has_query ? "&" : "?", LIST_ELEMENTS_PER_PAGE,
page) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL,
login_token, &response_obj, NULL,
&status_code, error_msg, curl,
verbose);
free(uri);
uri = NULL;
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 200:
break;
case 400:
pr_verbose(verbose, "Bad request");
rc = -EBADMSG;
goto out;
case 401:
pr_verbose(verbose, "Not authorized");
rc = -EACCES;
goto out;
case 403:
pr_verbose(verbose, "Insufficient permissions");
rc = -EPERM;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP "
"status code: %ld", status_code);
rc = -EIO;
goto out;
}
JSON_CHECK_OBJ(response_obj, json_type_array, rc, -EIO,
"No or invalid response content", verbose, out);
num = json_object_array_length(response_obj);
if (num == 0)
break;
for (i = 0; i < num; i++) {
element_obj = json_object_array_get_idx(response_obj,
i);
if (element_obj == NULL) {
pr_verbose(verbose, "Failed to get array "
"element for index %d", i);
rc = -EBADMSG;
goto out;
}
rc = element_cb(curl, element_obj, private, verbose);
if (rc != 0) {
pr_verbose(verbose, "Element-callback failed "
"for index %d", i);
goto out;
}
}
if (response_obj != NULL)
json_object_put(response_obj);
response_obj = NULL;
if (num < LIST_ELEMENTS_PER_PAGE)
break;
}
out:
if (uri != NULL)
free(uri);
if (response_obj != NULL)
json_object_put(response_obj);
return rc;
}
struct ekmf_template_cb_data_t {
ekmf_template_cb_t template_cb;
void *cb_private;
};
/**
* Callback for template list function. Builds the template info structure
* and calls the application callback.
*/
static int _ekmf_template_cb(CURL *curl, json_object *element,
void *private, bool verbose)
{
struct ekmf_template_cb_data_t *cb_data = private;
struct ekmf_template_info template = { 0 };
int rc;
if (cb_data->template_cb == NULL) {
pr_verbose(verbose, "No template callback function");
return -EINVAL;
}
rc = json_build_template_info(element, &template, false);
if (rc != 0) {
pr_verbose(verbose, "Failed to build template info");
goto out;
}
rc = cb_data->template_cb(curl, &template, cb_data->cb_private);
if (rc != 0) {
pr_verbose(verbose, "Template callback rc: %d", rc);
goto out;
}
out:
free_tag_def_list(&template.label_tags, false);
return rc;
}
/**
* List available key templates. Only templates in state ACTIVE, with key
* algorithm AES and keystore type PERVASIVE_ENCRYPTION are listed. The
* templates are ordered by name in ascending order.
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param template_cb a callback function that is called for each template
* found
* @param private a pointer that is passed as-is to the callback
* @param name_pattern a pattern to filter by name, or NULL to list all.
* @param state the state of the templates to list. If NULL then
* templates in state 'ACTIVE' are listed
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
* -EPERM is returned if the login token does not have permission to
* list the templates
*/
int ekmf_list_templates(const struct ekmf_config *config, CURL **curl_handle,
ekmf_template_cb_t template_cb, void *private,
const char *name_pattern, const char *state,
char **error_msg, bool verbose)
{
struct ekmf_template_cb_data_t cb_data;
char *escaped_name_pattern = NULL;
char *escaped_state = NULL;
char *login_token = NULL;
bool token_valid = false;
CURL *curl = NULL;
char *uri = NULL;
int rc;
if (config == NULL || template_cb == NULL)
return -EINVAL;
rc = ekmf_check_login_token(config, &token_valid, &login_token,
verbose);
if (rc != 0 || !token_valid) {
pr_verbose(verbose, "No valid login token available");
rc = -EACCES;
goto out;
}
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
cb_data.template_cb = template_cb;
cb_data.cb_private = private;
escaped_name_pattern = curl_easy_escape(curl, name_pattern != NULL ?
name_pattern : "*", 0);
if (escaped_name_pattern == NULL) {
pr_verbose(verbose, "Failed to url-escape the name pattern");
rc = -EIO;
goto out;
}
escaped_state = curl_easy_escape(curl, state != NULL ? state :
TEMPLATE_STATE_ACTIVE, 0);
if (escaped_state == NULL) {
pr_verbose(verbose, "Failed to url-escape the state");
rc = -EIO;
goto out;
}
if (asprintf(&uri, EKMF_URI_TEMPLATE_LIST, escaped_state,
ORDER_BY_NAME_ASC, escaped_name_pattern) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
rc = _ekmf_list_request(config, uri, curl, _ekmf_template_cb,
&cb_data, login_token, error_msg, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to perform the list request");
if (rc > 0)
rc = -EIO;
goto out;
}
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (login_token != NULL)
free(login_token);
if (uri != NULL)
free(uri);
if (escaped_name_pattern != NULL)
curl_free(escaped_name_pattern);
if (escaped_state != NULL)
curl_free(escaped_state);
return rc;
}
/**
* Get a template by its UUID.
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param template_uuid the UUID of the template to get
* @param template an address of a template info pointer. On return
* the pointer is updated to point to a newly allocated
* template info struct. It must be freed by the caller
* using ekmf_free_template_info when no longer needed.
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
* -EPERM is returned if the login token does not have permission to
* get the template
*/
int ekmf_get_template(const struct ekmf_config *config, CURL **curl_handle,
const char *template_uuid,
struct ekmf_template_info **template, char **error_msg,
bool verbose)
{
json_object *response_obj = NULL;
char *escaped_uuid = NULL;
char *login_token = NULL;
bool token_valid = false;
CURL *curl = NULL;
char *uri = NULL;
long status_code;
int rc;
if (config == NULL || template_uuid == NULL || template == NULL)
return -EINVAL;
*template = NULL;
rc = ekmf_check_login_token(config, &token_valid, &login_token,
verbose);
if (rc != 0 || !token_valid) {
pr_verbose(verbose, "No valid login token available");
rc = -EACCES;
goto out;
}
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
escaped_uuid = curl_easy_escape(curl, template_uuid, 0);
if (escaped_uuid == NULL) {
pr_verbose(verbose, "Failed to url-escape the template uuid");
rc = -EIO;
goto out;
}
if (asprintf(&uri, EKMF_URI_TEMPLATE_GET, escaped_uuid) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL,
login_token, &response_obj, NULL,
&status_code, error_msg, curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 200:
break;
case 400:
pr_verbose(verbose, "Bad request");
rc = -EBADMSG;
goto out;
case 401:
pr_verbose(verbose, "Not authorized");
rc = -EACCES;
goto out;
case 403:
pr_verbose(verbose, "Insufficient permissions");
rc = -EPERM;
goto out;
case 404:
pr_verbose(verbose, "Not found");
rc = -ENOENT;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EBADMSG,
"No or invalid response", verbose, out);
*template = calloc(1, sizeof(struct ekmf_template_info));
if (*template == NULL) {
pr_verbose(verbose, "calloc failed");
rc = -ENOMEM;
goto out;
}
rc = json_build_template_info(response_obj, *template, true);
if (rc != 0) {
pr_verbose(verbose, "Failed to build template info");
goto out;
}
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (response_obj != NULL)
json_object_put(response_obj);
if (login_token != NULL)
free(login_token);
if (uri != NULL)
free(uri);
if (escaped_uuid != NULL)
curl_free(escaped_uuid);
if (rc != 0 && *template != NULL) {
free_template_info(*template);
free(*template);
*template = NULL;
}
return rc;
}
/**
* Get the last used sequence number of a template by its UUID.
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param template_uuid the UUID of the template to get
* @param seqNumber On return: the last used sequence number of this
* template.
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
* -EPERM is returned if the login token does not have permission to
* get the template
*/
int ekmf_get_last_seq_no(const struct ekmf_config *config, CURL **curl_handle,
const char *template_uuid, unsigned int *seqNumber,
char **error_msg, bool verbose)
{
json_object *response_obj = NULL, *field = NULL;
char *escaped_uuid = NULL;
char *login_token = NULL;
bool token_valid = false;
CURL *curl = NULL;
char *uri = NULL;
long status_code;
int rc;
if (config == NULL || template_uuid == NULL || seqNumber == NULL)
return -EINVAL;
*seqNumber = 0;
rc = ekmf_check_login_token(config, &token_valid, &login_token,
verbose);
if (rc != 0 || !token_valid) {
pr_verbose(verbose, "No valid login token available");
rc = -EACCES;
goto out;
}
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
escaped_uuid = curl_easy_escape(curl, template_uuid, 0);
if (escaped_uuid == NULL) {
pr_verbose(verbose, "Failed to url-escape the template uuid");
rc = -EIO;
goto out;
}
if (asprintf(&uri, EKMF_URI_TEMPLATE_SEQNO, escaped_uuid) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL,
login_token, &response_obj, NULL,
&status_code, error_msg, curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 200:
break;
case 400:
pr_verbose(verbose, "Bad request");
rc = -EBADMSG;
goto out;
case 401:
pr_verbose(verbose, "Not authorized");
rc = -EACCES;
goto out;
case 403:
pr_verbose(verbose, "Insufficient permissions");
rc = -EPERM;
goto out;
case 404:
pr_verbose(verbose, "Not found");
rc = -ENOENT;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EBADMSG,
"No or invalid response", verbose, out);
json_object_object_get_ex(response_obj, "lastSequenceNumber", &field);
JSON_CHECK_OBJ(field, json_type_int, rc, -EBADMSG,
"Invalid response", verbose, out);
*seqNumber = json_object_get_int(field);
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (response_obj != NULL)
json_object_put(response_obj);
if (login_token != NULL)
free(login_token);
if (uri != NULL)
free(uri);
if (escaped_uuid != NULL)
curl_free(escaped_uuid);
return rc;
}
/**
* Clones a template info structure by making a deep copy of all strings and
* arrays.
* The copied template info must be freed using ekmf_free_template_info() by
* the caller.
*
* @param src the source template info structure
* @param dest the destination template info structure
*
* @returns zero for success, a negative errno in case of an error
*/
int ekmf_clone_template_info(const struct ekmf_template_info *src,
struct ekmf_template_info **dest)
{
if (src == NULL || dest == NULL)
return -EINVAL;
*dest = calloc(1, sizeof(struct ekmf_template_info));
if (*dest == NULL)
return -ENOMEM;
return clone_template_info(src, *dest);
}
/**
* Free a template info structure.
*
* @param template the template to free
*/
void ekmf_free_template_info(struct ekmf_template_info *template)
{
free_template_info(template);
free(template);
}
/**
* Gets the custom tags of a key by key-uuid. The custom tags are returned as
* JSON array. The returned JSON array must be freed by the caller using
* json_object_put().
*/
static int _ekmf_get_custom_tags(const struct ekmf_config *config,
const char *key_uuid, CURL *curl,
json_object **custom_tags,
const char *login_token, char **error_msg,
bool verbose)
{
json_object *response_obj = NULL;
char *escaped_uuid = NULL;
char *uri = NULL;
long status_code;
int rc;
if (config == NULL || key_uuid == NULL || custom_tags == NULL ||
curl == NULL)
return -EINVAL;
escaped_uuid = curl_easy_escape(curl, key_uuid, 0);
if (escaped_uuid == NULL) {
pr_verbose(verbose, "Failed to url-escape the key uuid");
rc = -EIO;
goto out;
}
if (asprintf(&uri, EKMF_URI_KEYS_TAGS, escaped_uuid) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL, login_token,
&response_obj, NULL, &status_code, error_msg,
curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 200:
break;
case 400:
pr_verbose(verbose, "Bad request");
rc = -EBADMSG;
goto out;
case 401:
pr_verbose(verbose, "Not authorized");
rc = -EACCES;
goto out;
case 403:
pr_verbose(verbose, "Insufficient permissions");
rc = -EPERM;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
JSON_CHECK_OBJ(response_obj, json_type_array, rc, -EIO,
"No or invalid response content", verbose, out);
*custom_tags = response_obj;
rc = 0;
out:
if (uri != NULL)
free(uri);
if (escaped_uuid != NULL)
curl_free(escaped_uuid);
if (rc != 0 && response_obj != NULL)
json_object_put(response_obj);
return rc;
}
/**
* Gets the export control infos of a key by key-uuid. The export control info
* is returned as JSON object. The returned JSON object must be freed by the
* caller using json_object_put().
*/
static int _ekmf_get_export_control(const struct ekmf_config *config,
const char *key_uuid, CURL *curl,
json_object **export_control,
const char *login_token, char **error_msg,
bool verbose)
{
json_object *response_obj = NULL;
char *escaped_uuid = NULL;
char *uri = NULL;
long status_code;
int rc;
if (config == NULL || key_uuid == NULL || export_control == NULL ||
curl == NULL)
return -EINVAL;
escaped_uuid = curl_easy_escape(curl, key_uuid, 0);
if (escaped_uuid == NULL) {
pr_verbose(verbose, "Failed to url-escape the key uuid");
rc = -EIO;
goto out;
}
if (asprintf(&uri, EKMF_URI_KEYS_EXPORT_CONTROL, escaped_uuid) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL, login_token,
&response_obj, NULL, &status_code, error_msg,
curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 200:
break;
case 400:
pr_verbose(verbose, "Bad request");
rc = -EBADMSG;
goto out;
case 401:
pr_verbose(verbose, "Not authorized");
rc = -EACCES;
goto out;
case 403:
pr_verbose(verbose, "Insufficient permissions");
rc = -EPERM;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EIO,
"No or invalid response content", verbose, out);
*export_control = response_obj;
rc = 0;
out:
if (uri != NULL)
free(uri);
if (escaped_uuid != NULL)
curl_free(escaped_uuid);
if (rc != 0 && response_obj != NULL)
json_object_put(response_obj);
return rc;
}
/**
* Get the custom tags for a key and build the key info structure
*/
static int _ekmf_build_key_info(const struct ekmf_config *config, CURL *curl,
const char *login_token, json_object *obj,
struct ekmf_key_info *key, bool copy,
char **error_msg, bool verbose)
{
json_object *export_control = NULL;
json_object *custom_tags = NULL;
int rc;
rc = _ekmf_get_custom_tags(config, json_get_string(obj, "keyId"),
curl, &custom_tags, login_token, error_msg,
verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to get the custom tags for key %s",
json_get_string(obj, "keyId"));
goto out;
}
rc = _ekmf_get_export_control(config, json_get_string(obj, "keyId"),
curl, &export_control, login_token,
error_msg, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to get the custom tags for key %s",
json_get_string(obj, "keyId"));
goto out;
}
rc = json_build_key_info(obj, custom_tags, export_control, key, copy);
if (rc != 0) {
pr_verbose(verbose, "Failed to build key info");
goto out;
}
out:
/*
* Add custom tags and export control JSON objects to the key object,
* so that these objects are also owned by the key object, and thus are
* freed together with it, when the caller puts/frees the key object.
*/
if (custom_tags != NULL)
json_object_object_add_ex(obj, "_custom_tags_", custom_tags, 0);
if (export_control != NULL)
json_object_object_add_ex(obj, "_export_control_",
export_control, 0);
return rc;
}
struct ekmf_key_cb_data_t {
const struct ekmf_config *config;
const char *login_token;
char **error_msg;
ekmf_key_cb_t key_cb;
void *cb_private;
};
/**
* Callback for key list function. Builds the key info structure
* and calls the application callback.
*/
static int _ekmf_key_cb(CURL *curl, json_object *element,
void *private, bool verbose)
{
struct ekmf_key_cb_data_t *cb_data = private;
struct ekmf_key_info key = { 0 };
int rc;
if (cb_data->key_cb == NULL) {
pr_verbose(verbose, "No key callback function");
return -EINVAL;
}
rc = _ekmf_build_key_info(cb_data->config, curl, cb_data->login_token,
element, &key, false, cb_data->error_msg,
verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to build key info");
goto out;
}
rc = cb_data->key_cb(curl, &key, cb_data->cb_private);
if (rc != 0) {
pr_verbose(verbose, "Key callback rc: %d", rc);
goto out;
}
out:
free_tag_list(&key.label_tags, false);
free_tag_list(&key.custom_tags, false);
free_export_control(&key.export_control, false);
return rc;
}
/**
* Builds the state URL parameter(s) from a comma separated list of states
*
* @param curl the curl handle
* @param states a comma separaed list of states
*
* @returns an allocated URL parameter value, or NULL in case of an error
*/
static char *_ekmf_build_state_filter(CURL *curl, const char *states)
{
char *list, *tok, *ret = NULL, *tmp;
char *escaped_state;
if (states == NULL)
goto error;
list = strdup(states);
if (list == NULL)
goto error;
tok = strtok(list, ",");
while (tok != NULL) {
escaped_state = curl_easy_escape(curl, tok, 0);
if (escaped_state == NULL)
goto error;
if (asprintf(&tmp, "%s%s%s", ret != NULL ? ret : "",
ret == NULL ? "" : EKMF_URI_KEYS_LIST_STATE,
escaped_state) < 0)
tmp = NULL;
curl_free(escaped_state);
if (tmp == NULL)
goto error;
if (ret != NULL)
free(ret);
ret = tmp;
tok = strtok(NULL, ",");
}
free(list);
return ret;
error:
if (ret != NULL)
free(ret);
return NULL;
}
/**
* List available keys. The keys are ordered by name in ascending order.
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param key_cb a callback function that is called for each key
* found
* @param private a pointer that is passed as-is to the callback
* @param name_pattern a pattern to filter by name, or NULL to list all.
* @param states the states of the keys to list, or NULL to list keys
* in ACTIVE state only. Multiple states can be
* specified separated by comma.
* @param tags a list of custom tags to use as filter, or NULL
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
* -EPERM is returned if the login token does not have permission to
* list the keys
*/
int ekmf_list_keys(const struct ekmf_config *config, CURL **curl_handle,
ekmf_key_cb_t key_cb, void *private,
const char *name_pattern, const char *states,
const struct ekmf_tag_list *tags,
char **error_msg, bool verbose)
{
struct ekmf_key_cb_data_t cb_data;
char *escaped_name_pattern = NULL;
json_object *tags_obj = NULL;
char *state_filter = NULL;
char *escaped_tags = NULL;
char *login_token = NULL;
bool token_valid = false;
CURL *curl = NULL;
char *uri = NULL;
size_t i;
int rc;
if (config == NULL || key_cb == NULL)
return -EINVAL;
rc = ekmf_check_login_token(config, &token_valid, &login_token,
verbose);
if (rc != 0 || !token_valid) {
pr_verbose(verbose, "No valid login token available");
rc = -EACCES;
goto out;
}
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
cb_data.config = config;
cb_data.login_token = login_token;
cb_data.error_msg = error_msg;
cb_data.key_cb = key_cb;
cb_data.cb_private = private;
escaped_name_pattern = curl_easy_escape(curl, name_pattern != NULL ?
name_pattern : "*", 0);
if (escaped_name_pattern == NULL) {
pr_verbose(verbose, "Failed to url-escape the name pattern");
rc = -EIO;
goto out;
}
state_filter = _ekmf_build_state_filter(curl, states != NULL ? states :
KEY_STATE_ACTIVE);
if (state_filter == NULL) {
pr_verbose(verbose, "Failed to build the state filter");
rc = -EIO;
goto out;
}
tags_obj = json_object_new_object();
JSON_CHECK_ERROR(tags_obj == NULL, rc, -ENOMEM,
"Failed to generate JSON object", verbose, out);
for (i = 0; tags != NULL && i < tags->num_tags; i++) {
if (tags->tags[i].name == NULL || tags->tags[i].value == NULL) {
rc = -EINVAL;
goto out;
}
rc = json_object_object_add_ex(tags_obj, tags->tags[i].name,
json_object_new_string(
tags->tags[i].value),
0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
"JSON object", verbose, out);
}
escaped_tags = curl_easy_escape(curl, json_object_to_json_string_ext(
tags_obj, JSON_C_TO_STRING_PLAIN |
JSON_C_TO_STRING_NOSLASHESCAPE), 0);
if (escaped_tags == NULL) {
pr_verbose(verbose, "Failed to url-escape the tags");
rc = -EIO;
goto out;
}
if (asprintf(&uri, EKMF_URI_KEYS_LIST, state_filter,
ORDER_BY_LABEL_ASC, escaped_name_pattern,
escaped_tags) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
rc = _ekmf_list_request(config, uri, curl, _ekmf_key_cb,
&cb_data, login_token, error_msg, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to perform the list request");
if (rc > 0)
rc = -EIO;
goto out;
}
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (login_token != NULL)
free(login_token);
if (uri != NULL)
free(uri);
if (state_filter != NULL)
free(state_filter);
if (escaped_name_pattern != NULL)
curl_free(escaped_name_pattern);
if (escaped_tags != NULL)
curl_free(escaped_tags);
if (tags_obj != NULL)
json_object_put(tags_obj);
return rc;
}
/**
* Get information about a key by its UUID.
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param key_uuid the UUID of the key to get info for
* @param key an address of a key info pointer. On return
* the pointer is updated to point to a newly allocated
* key info struct. It must be freed by the caller
* using ekmf_free_key_info when no longer needed.
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
* -EPERM is returned if the login token does not have permission to
* get the key info
*/
int ekmf_get_key_info(const struct ekmf_config *config, CURL **curl_handle,
const char *key_uuid, struct ekmf_key_info **key,
char **error_msg, bool verbose)
{
json_object *response_obj = NULL;
char *escaped_uuid = NULL;
char *login_token = NULL;
bool token_valid = false;
CURL *curl = NULL;
char *uri = NULL;
long status_code;
int rc;
if (config == NULL || key_uuid == NULL || key == NULL)
return -EINVAL;
*key = NULL;
rc = ekmf_check_login_token(config, &token_valid, &login_token,
verbose);
if (rc != 0 || !token_valid) {
pr_verbose(verbose, "No valid login token available");
rc = -EACCES;
goto out;
}
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
escaped_uuid = curl_easy_escape(curl, key_uuid, 0);
if (escaped_uuid == NULL) {
pr_verbose(verbose, "Failed to url-escape the key uuid");
rc = -EIO;
goto out;
}
if (asprintf(&uri, EKMF_URI_KEYS_GET, escaped_uuid) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL,
login_token, &response_obj, NULL,
&status_code, error_msg, curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 200:
break;
case 400:
pr_verbose(verbose, "Bad request");
rc = -EBADMSG;
goto out;
case 401:
pr_verbose(verbose, "Not authorized");
rc = -EACCES;
goto out;
case 403:
pr_verbose(verbose, "Insufficient permissions");
rc = -EPERM;
goto out;
case 404:
pr_verbose(verbose, "Not found");
rc = -ENOENT;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EBADMSG,
"No or invalid response", verbose, out);
*key = calloc(1, sizeof(struct ekmf_key_info));
if (*key == NULL) {
pr_verbose(verbose, "calloc failed");
rc = -ENOMEM;
goto out;
}
rc = _ekmf_build_key_info(config, curl, login_token, response_obj,
*key, true, error_msg, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to build template info");
goto out;
}
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (response_obj != NULL)
json_object_put(response_obj);
if (login_token != NULL)
free(login_token);
if (uri != NULL)
free(uri);
if (escaped_uuid != NULL)
curl_free(escaped_uuid);
if (rc != 0 && *key != NULL) {
free_key_info(*key);
free(*key);
*key = NULL;
}
return rc;
}
/**
* Changes the state of a key identified by its UUID. To update a key,
* the timestamp from the last update is required. This can be found in
* the key info struct in field update_on.
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param key_uuid the UUID of the key to get info for
* @param new_state the new state of the key
* @param updated_on the timestamp of the last update (must match)
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
* -EPERM is returned if the login token does not have permission to
* update the key.
* -EAGAIN is returned if the timestamp does not match, indicating that
* the key has been updated in the meantime.
*/
int ekmf_set_key_state(const struct ekmf_config *config, CURL **curl_handle,
const char *key_uuid, const char *new_state,
const char *updated_on, char **error_msg, bool verbose)
{
char *request_headers[2] = { NULL, NULL };
json_object *request_obj = NULL;
char *escaped_uuid = NULL;
char *login_token = NULL;
bool token_valid = false;
char *if_match_hdr = NULL;
CURL *curl = NULL;
char *uri = NULL;
long status_code;
int rc;
if (config == NULL || key_uuid == NULL || new_state == NULL ||
updated_on == NULL)
return -EINVAL;
rc = ekmf_check_login_token(config, &token_valid, &login_token,
verbose);
if (rc != 0 || !token_valid) {
pr_verbose(verbose, "No valid login token available");
rc = -EACCES;
goto out;
}
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
request_obj = json_object_new_object();
JSON_CHECK_ERROR(request_obj == NULL, rc, -ENOMEM,
"Failed to generate JSON object", verbose, out);
rc = json_object_object_add_ex(request_obj, "state",
json_object_new_string(new_state), 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
escaped_uuid = curl_easy_escape(curl, key_uuid, 0);
if (escaped_uuid == NULL) {
pr_verbose(verbose, "Failed to url-escape the key uuid");
rc = -EIO;
goto out;
}
if (asprintf(&uri, EKMF_URI_KEYS_SET_STATE, escaped_uuid) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
if (asprintf(&if_match_hdr, "If-Match : %s", updated_on) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
request_headers[0] = if_match_hdr;
rc = _ekmf_perform_request(config, uri, "PATCH", request_obj,
request_headers, login_token, NULL, NULL,
&status_code, error_msg, curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 204:
break;
case 400:
pr_verbose(verbose, "Bad request");
rc = -EBADMSG;
goto out;
case 401:
pr_verbose(verbose, "Not authorized");
rc = -EACCES;
goto out;
case 403:
pr_verbose(verbose, "Insufficient permissions");
rc = -EPERM;
goto out;
case 404:
pr_verbose(verbose, "Not found");
rc = -ENOENT;
goto out;
case 409:
pr_verbose(verbose, "Key was updated in the meantime");
rc = -EAGAIN;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (request_obj != NULL)
json_object_put(request_obj);
if (login_token != NULL)
free(login_token);
if (uri != NULL)
free(uri);
if (escaped_uuid != NULL)
curl_free(escaped_uuid);
if (if_match_hdr != NULL)
free(if_match_hdr);
return rc;
}
/**
* Sets (adds/changes) a custom tag of a key identified by its UUID. To update
* a key, the timestamp from the last update is required. This can be found in
* the key info struct in field update_on.
*
* @param config the configuration structure
* @param curl the CURL handle
* @param login_token the login token to authenticate
* @param key_uuid the UUID of the key to get info for
* @param tag the tag to set
* @param updated_on the timestamp of the last update (must match)
* @param delete if true, the tag is deleted, otherwise it is updated
* @param etag On return: the new update timestamp returned via
* the etag HTTP header. Must be freed by the caller.
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
* -EPERM is returned if the login token does not have permission to
* update the key.
* -EAGAIN is returned if the timestamp does not match, indicating that
* the key has been updated in the meantime.
*/
static int _ekmf_set_key_tag(const struct ekmf_config *config, CURL *curl,
const char *login_token, const char *key_uuid,
const struct ekmf_tag *tag, const char *updated_on,
bool delete, char **etag, char **error_msg,
bool verbose)
{
struct curl_slist *response_headers = NULL;
char *request_headers[2] = { NULL, NULL };
json_object *request_obj = NULL;
char *escaped_tag_name = NULL;
char *escaped_uuid = NULL;
char *if_match_hdr = NULL;
char *uri = NULL;
long status_code;
int rc;
if (config == NULL || curl == NULL || login_token == NULL ||
key_uuid == NULL || tag == NULL || updated_on == NULL ||
etag == NULL)
return -EINVAL;
*etag = NULL;
if (!delete) {
request_obj = json_object_new_object();
JSON_CHECK_ERROR(request_obj == NULL, rc, -ENOMEM,
"Failed to generate JSON object", verbose,
out);
rc = json_object_object_add_ex(request_obj, "value",
json_object_new_string(tag->value), 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
"JSON object", verbose, out);
}
escaped_uuid = curl_easy_escape(curl, key_uuid, 0);
if (escaped_uuid == NULL) {
pr_verbose(verbose, "Failed to url-escape the key uuid");
rc = -EIO;
goto out;
}
escaped_tag_name = curl_easy_escape(curl, tag->name, 0);
if (escaped_tag_name == NULL) {
pr_verbose(verbose, "Failed to url-escape the tag name");
rc = -EIO;
goto out;
}
if (asprintf(&uri, EKMF_URI_KEYS_SET_TAG, escaped_uuid,
escaped_tag_name) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
if (asprintf(&if_match_hdr, "If-Match : %s", updated_on) < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
goto out;
}
request_headers[0] = if_match_hdr;
rc = _ekmf_perform_request(config, uri, delete ? "DELETE" : "PUT",
request_obj, request_headers, login_token,
NULL, &response_headers, &status_code,
error_msg, curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 200:
case 204:
break;
case 400:
pr_verbose(verbose, "Bad request");
rc = -EBADMSG;
goto out;
case 401:
pr_verbose(verbose, "Not authorized");
rc = -EACCES;
goto out;
case 403:
pr_verbose(verbose, "Insufficient permissions");
rc = -EPERM;
goto out;
case 404:
pr_verbose(verbose, "Not found");
rc = -ENOENT;
goto out;
case 409:
pr_verbose(verbose, "Key was updated in the meantime");
rc = -EAGAIN;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
*etag = get_http_header_value(response_headers, "Etag");
if (*etag == NULL) {
pr_verbose(verbose, "No ETag in response headers");
rc = -EBADMSG;
goto out;
}
out:
if (request_obj != NULL)
json_object_put(request_obj);
if (uri != NULL)
free(uri);
if (escaped_uuid != NULL)
curl_free(escaped_uuid);
if (escaped_tag_name != NULL)
curl_free(escaped_tag_name);
if (if_match_hdr != NULL)
free(if_match_hdr);
if (response_headers != NULL)
curl_slist_free_all(response_headers);
return rc;
}
/**
* Sets (changed/adds) custom tags of a key identified by its UUID. To update a
* key, the timestamp from the last update is required. This can be found in
* the key info struct in field update_on.
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param key_uuid the UUID of the key to get info for
* @param tags a list of tags to set
* @param updated_on the timestamp of the last update (must match)
* @param new_updated_on on return: if not NULL, the new timestamp of the
* current update. Can be used for subsequent updates
* on the key.
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
* -EPERM is returned if the login token does not have permission to
* update the key.
* -EAGAIN is returned if the timestamp does not match, indicating that
* the key has been updated in the meantime.
*/
int ekmf_set_key_tags(const struct ekmf_config *config, CURL **curl_handle,
const char *key_uuid, const struct ekmf_tag_list *tags,
const char *updated_on, char **new_updated_on,
char **error_msg, bool verbose)
{
char *update_ts = (char *)updated_on;
char *login_token = NULL;
bool token_valid = false;
CURL *curl = NULL;
char *etag = NULL;
size_t i;
int rc;
if (config == NULL || key_uuid == NULL || tags == NULL ||
updated_on == NULL)
return -EINVAL;
rc = ekmf_check_login_token(config, &token_valid, &login_token,
verbose);
if (rc != 0 || !token_valid) {
pr_verbose(verbose, "No valid login token available");
rc = -EACCES;
goto out;
}
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
for (i = 0; i < tags->num_tags; i++) {
rc = _ekmf_set_key_tag(config, curl, login_token, key_uuid,
&tags->tags[i], update_ts, false, &etag,
error_msg, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to set tag '%s'",
tags->tags[i].name);
goto out;
}
if (update_ts != NULL && update_ts != updated_on)
free(update_ts);
update_ts = etag;
etag = NULL;
}
if (new_updated_on != NULL)
*new_updated_on = strdup(update_ts);
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (login_token != NULL)
free(login_token);
if (update_ts != NULL && update_ts != updated_on)
free(update_ts);
if (etag != NULL)
free(etag);
return rc;
}
/**
* Deletes custom tags of a key identified by its UUID. To update a
* key, the timestamp from the last update is required. This can be found in
* the key info struct in field update_on.
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param key_uuid the UUID of the key to get info for
* @param tags a list of tags to delete. Only the name of the tags
* must be present in the tag structs of the list, the
* values are ignored.
* @param updated_on the timestamp of the last update (must match)
* @param new_updated_on on return: if not NULL, the new timestamp of the
* current update. Can be used for subsequent updates
* on the key.
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
* -EPERM is returned if the login token does not have permission to
* update the key.
* -EAGAIN is returned if the timestamp does not match, indicating that
* the key has been updated in the meantime.
*/
int ekmf_delete_key_tags(const struct ekmf_config *config, CURL **curl_handle,
const char *key_uuid, const struct ekmf_tag_list *tags,
const char *updated_on, char **new_updated_on,
char **error_msg, bool verbose)
{
char *update_ts = (char *)updated_on;
char *login_token = NULL;
bool token_valid = false;
CURL *curl = NULL;
char *etag = NULL;
size_t i;
int rc;
if (config == NULL || key_uuid == NULL || tags == NULL ||
updated_on == NULL)
return -EINVAL;
rc = ekmf_check_login_token(config, &token_valid, &login_token,
verbose);
if (rc != 0 || !token_valid) {
pr_verbose(verbose, "No valid login token available");
rc = -EACCES;
goto out;
}
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
for (i = 0; i < tags->num_tags; i++) {
rc = _ekmf_set_key_tag(config, curl, login_token, key_uuid,
&tags->tags[i], update_ts, true, &etag,
error_msg, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to delete tag '%s'",
tags->tags[i].name);
goto out;
}
if (update_ts != NULL && update_ts != updated_on)
free(update_ts);
update_ts = etag;
etag = NULL;
}
if (new_updated_on != NULL)
*new_updated_on = strdup(update_ts);
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (login_token != NULL)
free(login_token);
if (update_ts != NULL && update_ts != updated_on)
free(update_ts);
if (etag != NULL)
free(etag);
return rc;
}
/**
* Clones a key info structure by making a deep copy of all strings and
* arrays.
* The copied key info must be freed using ekmf_free_key_info() by
* the caller.
*
* @param src the source key info structure
* @param dest the destination key info structure
*
* @returns zero for success, a negative errno in case of an error
*/
int ekmf_clone_key_info(const struct ekmf_key_info *src,
struct ekmf_key_info **dest)
{
if (src == NULL || dest == NULL)
return -EINVAL;
*dest = calloc(1, sizeof(struct ekmf_key_info));
if (*dest == NULL)
return -ENOMEM;
return clone_key_info(src, *dest);
}
/**
* Free a key info structure.
*
* @param key the key info to free
*/
void ekmf_free_key_info(struct ekmf_key_info *key)
{
free_key_info(key);
free(key);
}
/**
* Build the export control JSON object.
*
* @param exporting_key the key to add as exporting key
* @param expctl_obj on return: the export control JSON object
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
*/
static int _ekmf_build_export_control(const char *exporting_key,
json_object **expctl_obj, bool verbose)
{
json_object *exp_keys = NULL;
json_object *exp_ref = NULL;
char *href = NULL;
int rc = 0;
*expctl_obj = json_object_new_object();
JSON_CHECK_ERROR(*expctl_obj == NULL, rc, -ENOMEM,
"Failed to generate JSON object",
verbose, out);
rc = json_object_object_add_ex(*expctl_obj, "exportAllowed",
json_object_new_boolean(true), 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
"JSON object", verbose, out);
exp_keys = json_object_new_array();
JSON_CHECK_ERROR(exp_keys == NULL, rc, -ENOMEM,
"Failed to generate JSON object",
verbose, out);
exp_ref = json_object_new_object();
JSON_CHECK_ERROR(exp_ref == NULL, rc, -ENOMEM,
"Failed to generate JSON object",
verbose, out);
rc = json_object_object_add_ex(exp_ref, "rel",
json_object_new_string("exportAllowedWithKey"), 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
"JSON object", verbose, out);
JSON_CHECK_ERROR(asprintf(&href, "/keys/%s", exporting_key) < 0, rc,
-ENOMEM, "Failed to allocate string", verbose, out);
rc = json_object_object_add_ex(exp_ref, "href",
json_object_new_string(href), 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
"JSON object", verbose, out);
rc = json_object_array_add(exp_keys, exp_ref);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
"JSON object", verbose, out);
exp_ref = NULL;
rc = json_object_object_add_ex(*expctl_obj, "allowedKeys", exp_keys, 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
"JSON object", verbose, out);
exp_keys = NULL;
out:
if (exp_keys != NULL)
json_object_put(exp_keys);
if (exp_ref != NULL)
json_object_put(exp_ref);
if (href != NULL)
free(href);
if (rc != 0 && *expctl_obj != NULL) {
json_object_put(*expctl_obj);
*expctl_obj = NULL;
}
return rc;
}
/**
* Base64-encodes the data
*
* @param data the data to encode
* @param data_size the size of the data in bytes
*
* @returns the encoded data or NULL in case of an error.
* The caller must free the string when no longer needed.
*/
static char *_ekmf_base64_encode(const unsigned char *data, size_t data_size)
{
size_t outlen;
char *out;
int len;
outlen = (data_size / 3) * 4;
if (data_size % 3 > 0)
outlen += 4;
out = calloc(outlen + 1, 1);
if (out == NULL)
return NULL;
len = EVP_EncodeBlock((unsigned char *)out, data, data_size);
if (len < 0 || (size_t)len != outlen) {
free(out);
return NULL;
}
out[outlen] = '\0';
return out;
}
/**
* Build the key material JSON object
*
* @param certificate the certificate to generate an identity key from
* @param certificate_size the size of the certificate
* @param keymat_obj on return: the key material JSON object
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
*/
static int _ekmf_build_key_material(const unsigned char *certificate,
size_t certificate_size,
json_object **keymat_obj, bool verbose)
{
char *payload = NULL;
int rc = 0;
*keymat_obj = json_object_new_object();
JSON_CHECK_ERROR(*keymat_obj == NULL, rc, -ENOMEM,
"Failed to generate JSON object",
verbose, out);
rc = json_object_object_add_ex(*keymat_obj, "type",
json_object_new_string("ENCODED-CERTIFICATE"), 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
"JSON object", verbose, out);
payload = _ekmf_base64_encode(certificate, certificate_size);
JSON_CHECK_ERROR(payload == NULL, rc, -EIO,
"Failed to base64 encode the certificate",
verbose, out);
rc = json_object_object_add_ex(*keymat_obj, "payload",
json_object_new_string(payload), 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
"JSON object", verbose, out);
out:
if (rc != 0 && *keymat_obj != NULL) {
json_object_put(*keymat_obj);
*keymat_obj = NULL;
}
if (payload != NULL)
free(payload);
return rc;
}
/**
* Generates a new key in EKMFWeb
*
* To perform a single request, set curl_handle to NULL. This will cause the
* function to initialize a new CURL handle, use it, and destroy it.
* If you plan to perform multiple requests to the same host, supply the address
* of a CURL pointer that is initially NULL. This function will then initialize
* a new CURL handle on the first call. On subsequent calls, pass in the address
* of the same CURL pointer so that the CURL handle is reused. After the last
* request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
*
* @param config the configuration structure
* @param curl_handle address of a CURL handle used for reusing the same
* CURL handle with multiple requests.
* @param template the name of the template to generate the key with
* @param description Optional: a textual description of the key (can be
* NULL)
* @param label_tags list of label tags. The label tags are required as
* defined in the template
* @param custom_tags Optional: list of custom tags (can be NULL)
* @param exporting_key Optional: The uuid of the key that is allowed to
* export the newly generated key (can be NULL).
* @param certificate Optional: The certificate to generate an identity
* key from. Should be NULL for generating AES keys.
* @param certificate_size Optional: the size of the certificate. Required if
* certificate is not NULL.
* @param key_info Optional: On return: If not NULL, a key info struct
* is returned here containing key information. This
* must be freed by the caller with ekmf_free_key_info
* when no longer needed.
* @param error_msg on return: If not NULL, then a textual error message
* is returned in case of a failing request. The caller
* must free the error string when it is not NULL.
* @param verbose if true, verbose messages are printed
*
* @returns zero for success, a negative errno in case of an error.
* -EACCES is returned, if no or no valid login token is available.
* -EPERM is returned if the login token does not have permission to
* generate keys
*/
int ekmf_generate_key(const struct ekmf_config *config, CURL **curl_handle,
const char *template, const char *description,
const struct ekmf_tag_list *label_tags,
const struct ekmf_tag_list *custom_tags,
const char *exporting_key,
const unsigned char *certificate, size_t certificate_size,
struct ekmf_key_info **key_info,
char **error_msg, bool verbose)
{
json_object *response_obj = NULL;
json_object *request_obj = NULL;
json_object *expctl_obj = NULL;
json_object *keymat_obj = NULL;
json_object *tags_obj = NULL;
char *login_token = NULL;
bool token_valid = false;
CURL *curl = NULL;
long status_code;
int rc;
if (config == NULL || template == NULL || label_tags == NULL ||
label_tags->num_tags == 0 || label_tags->tags == NULL)
return -EINVAL;
if (custom_tags != NULL && custom_tags->num_tags > 0 &&
custom_tags->tags == NULL)
return -EINVAL;
if (certificate != NULL && certificate_size == 0)
return -EINVAL;
if (key_info != NULL)
*key_info = NULL;
rc = ekmf_check_login_token(config, &token_valid, &login_token,
verbose);
if (rc != 0 || !token_valid) {
pr_verbose(verbose, "No valid login token available");
rc = -EACCES;
goto out;
}
rc = _ekmf_get_curl_handle(curl_handle, &curl);
if (rc != 0) {
pr_verbose(verbose, "Failed to get CURL handle");
rc = -EIO;
goto out;
}
request_obj = json_object_new_object();
JSON_CHECK_ERROR(request_obj == NULL, rc, -ENOMEM,
"Failed to generate JSON object", verbose, out);
rc = json_object_object_add_ex(request_obj, "templateName",
json_object_new_string(template), 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
rc = build_json_tag_list(label_tags, &tags_obj);
if (rc != 0) {
pr_verbose(verbose, "Failed to build label tag JSON object");
goto out;
}
rc = json_object_object_add_ex(request_obj, "labelTags", tags_obj, 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
verbose, out);
tags_obj = NULL;
if (description != NULL) {
rc = json_object_object_add_ex(request_obj, "description",
json_object_new_string(description), 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
"JSON object", verbose, out);
}
if (certificate != NULL) {
rc = _ekmf_build_key_material(certificate, certificate_size,
&keymat_obj, verbose);
if (rc != 0)
goto out;
rc = json_object_object_add_ex(request_obj, "keyMaterial",
keymat_obj, 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO,
"Failed to add data to JSON object",
verbose, out);
keymat_obj = NULL;
}
if (custom_tags != NULL && custom_tags->num_tags > 0) {
rc = build_json_tag_list(custom_tags, &tags_obj);
if (rc != 0) {
pr_verbose(verbose, "Failed to build custom tag JSON "
"object");
goto out;
}
rc = json_object_object_add_ex(request_obj, "customTags",
tags_obj, 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
"JSON object", verbose, out);
tags_obj = NULL;
}
if (exporting_key != NULL) {
rc = _ekmf_build_export_control(exporting_key, &expctl_obj,
verbose);
if (rc != 0)
goto out;
rc = json_object_object_add_ex(request_obj, "exportControl",
expctl_obj, 0);
JSON_CHECK_ERROR(rc != 0, rc, -EIO,
"Failed to add data to JSON object",
verbose, out);
expctl_obj = NULL;
}
rc = _ekmf_perform_request(config, EKMF_URI_KEYS_GENERATE, "POST",
request_obj, NULL, login_token,
&response_obj, NULL, &status_code, error_msg,
curl, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed perform the REST call");
if (rc > 0)
rc = -EIO;
goto out;
}
switch (status_code) {
case 201:
break;
case 400:
pr_verbose(verbose, "Bad request");
rc = -EBADMSG;
goto out;
case 401:
pr_verbose(verbose, "Not authorized");
rc = -EACCES;
goto out;
case 403:
pr_verbose(verbose, "Insufficient permissions");
rc = -EPERM;
goto out;
case 409:
pr_verbose(verbose, "A key with this label exist already");
rc = -EEXIST;
goto out;
default:
pr_verbose(verbose, "REST Call failed with HTTP status code: "
"%ld", status_code);
rc = -EIO;
goto out;
}
JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EBADMSG,
"No or invalid response", verbose, out);
if (key_info != NULL) {
*key_info = calloc(1, sizeof(struct ekmf_key_info));
if (*key_info == NULL) {
pr_verbose(verbose, "calloc failed");
rc = -ENOMEM;
goto out;
}
rc = _ekmf_build_key_info(config, curl, login_token,
response_obj, *key_info, true,
error_msg, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to build the key info");
goto out;
}
}
out:
_ekmf_release_curl_handle(curl_handle, curl);
if (request_obj != NULL)
json_object_put(request_obj);
if (response_obj != NULL)
json_object_put(response_obj);
if (login_token != NULL)
free(login_token);
if (tags_obj != NULL)
json_object_put(tags_obj);
if (expctl_obj != NULL)
json_object_put(expctl_obj);
if (keymat_obj != NULL)
json_object_put(keymat_obj);
if (rc != 0 && key_info != NULL && *key_info != NULL) {
free(*key_info);
*key_info = NULL;
}
return rc;
}
/**
* Generate a secure identity key used to identify the client to EKMFWeb.
* The secure key blob is stored in a file specified in field
* identity_secure_key of the config structure. If an secure key already exists
* at that location, it is overwritten.
*
* @param config the configuration structure. Only field
* identity_secure_key must be specified, all others
* are optional.
* @param info key generation info, such as key type (ECC or RSA)
* and key parameters.
* @param ext_lib External secure key crypto library to use
* @param verbose if true, verbose messages are printed
*
* @returns a negative errno in case of an error, 0 if success.
*/
int ekmf_generate_identity_key(const struct ekmf_config *config,
const struct ekmf_key_gen_info *info,
const struct ekmf_ext_lib *ext_lib, bool verbose)
{
unsigned char key_blob[MAX_KEY_BLOB_SIZE];
size_t key_blob_size = sizeof(key_blob);
struct ext_lib_info ext_lib_info;
struct sk_key_gen_info gen_info;
int rc;
if (config == NULL || info == NULL || ext_lib == NULL)
return -EINVAL;
if (config->identity_secure_key == NULL)
return -EINVAL;
_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);
_ekmf_copy_key_gen_info(info, &gen_info);
rc = SK_OPENSSL_generate_secure_key(key_blob, &key_blob_size, &gen_info,
&ext_lib_info.ext_lib, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to generate a key: rc: %d - %s",
rc, strerror(-rc));
return rc;
}
rc = write_key_blob(config->identity_secure_key, key_blob,
key_blob_size);
if (rc != 0) {
pr_verbose(verbose, "Failed to write the key to file '%s' "
"rc: %d - %s", config->identity_secure_key, rc,
strerror(-rc));
return rc;
}
pr_verbose(verbose, "Secure identity key generated (%lu bytes) "
"and written to file '%s'", key_blob_size,
config->identity_secure_key);
return 0;
}
/**
* Re-encipher the secure identity key (form field identity_secure_key in
* config) used to identify the client to EKMFWeb.
* The secure key blob is encrypted using the HSM master key. Whenever the HSM
* master key is being changed, the secure identity key must be re-enciphered.
* You can either pro-actively re-encipher a secure key once the new master key
* has been prepared (but not yet made active): to_new = true; or you can
* re-encipher a secure key when the HSM master key has already been changed:
* to_new = false. This requires that the HSM still has the old master key.
* Not all HSMs support this.
*
* For pro-active re-encipherment it is suggested to store the re-enciphered
* secure key on a separate place, until the new HSM master key has been made
* active. Specify a file name in reenc_secure_key to do so. For an in-place
* re-encipherment, set reenc_secure_key = NULL.
*
* @param config the configuration structure. Only field
* identity_secure_key must be specified, all others
* are optional.
* @param to_new If true: the identity key is re-enciphered from the
* current to the new master key.
* If false: the identity key is re-enciphered from the
* old to the current master key.
* @param reenc_secure_key if not NULL, then the re-enciphered secure key is
* stored into the filename specified here. Otherwise
* the re-enciphered secure key replaces the original
* secure identity key.
* @param ext_lib External secure key crypto library to use
* @param verbose if true, verbose messages are printed
*
* @returns a negative errno in case of an error, 0 if success.
* A -ENODEV indicates that the master keys are not loaded.
*/
int ekmf_reencipher_identity_key(const struct ekmf_config *config,
bool to_new, const char *reenc_secure_key,
const struct ekmf_ext_lib *ext_lib,
bool verbose)
{
unsigned char key_blob[MAX_KEY_BLOB_SIZE];
size_t key_blob_size = sizeof(key_blob);
struct ext_lib_info ext_lib_info;
const char *out_file;
int rc;
if (config == NULL || ext_lib == NULL)
return -EINVAL;
if (config->identity_secure_key == NULL)
return -EINVAL;
_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);
rc = read_key_blob(config->identity_secure_key, key_blob,
&key_blob_size);
if (rc != 0) {
pr_verbose(verbose, "Failed to read identity key from file "
"'%s': %s", config->identity_secure_key,
strerror(-rc));
return rc;
}
rc = SK_OPENSSL_reencipher_secure_key(key_blob, key_blob_size, to_new,
&ext_lib_info.ext_lib, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to re-encipher the secure identity "
"key from file '%s': %s",
config->identity_secure_key, strerror(-rc));
return rc;
}
out_file = reenc_secure_key != NULL ? reenc_secure_key :
config->identity_secure_key;
rc = write_key_blob(out_file, key_blob, key_blob_size);
if (rc != 0) {
pr_verbose(verbose, "Failed to write identity key to file "
"'%s': %s", out_file, strerror(-rc));
return rc;
}
return 0;
}
/**
* Generate a certificate signing request using the secure identity key (field
* identity_secure_key in config structure) with the specified subject name,
* certificate extensions (if any), and writes the CSR to the specified file
* in PEM format.
*
* To renew an existing certificate, specify renew_cert = true. In this case
* the existing certificate (field sign_certificate in config struct) is read,
* and the subject name is extracted from it. Any specified subject name RDNs
* are added to the CSR. Also, the extensions are taken from the existing
* certificate, and any specified extensions are added to the CSR.
*
* The CSR is signed using the secure identity key (field identity_secure_key in
* config structure) with an signing algorithm matching the identity key (ECDSA,
* RSA-PKCS, or RSA-PSS if rsa_pss is true), and the specified digest. If the
* digest nid is zero, then a default digest is used.
*
* @param config the configuration structure. Only field
* identity_secure_key must be specified, all others
* are optional.
* @param subject_rdns an array of strings, each string representing an
* RDN in the form '[+]type=value'. If the type is
* prepended with a '+', then this RDN is added to the
* previous one.
* @param num_subject_rdns number of RDN elements in the array.
* @param subject_utf8 if true, RDNs of type MBSTRING_UTF8 are created,
* otherwise type is MBSTRING_ASC is used.
* @param renew_cert_filename if not NULL, specifies the file name of a PEM file
* containing an existing certificate that is renewed
* @param extensions an array of strings, each string representing an
* certificate extension in the form 'type=value'.
* @param num_extensions number of extension elements in the array.
* @param digest_nid the OpenSSL digest nid to use with the signature
* algorithm, or 0 to use the default
* @param rsa_pss_params if not NULL and the identity key is an RSA key, then
* the CSR is signed with RSA-PSS using the specified
* PSS parameters. Ignored if the identity key is an EC
* key
* @param csr_pem_filename the name of the PEM file to which the CSR is written
* @param new_hdr if true, output "NEW" in the PEM header lines
* @param ext_lib External secure key crypto library to use
* @param verbose if true, verbose messages are printed
*
* @returns a negative errno in case of an error, 0 if success:
* -EINVAL: invalid parameter
* -ENOMEM: Failed to allocate memory
* -EBADMSG: an RDN or extension is not formatted correctly
* -EIO: OpenSSL failed to create the CSR
* -EEXIST: if one of the RDN name entries or extensions to add is a
* duplicate
* -ENOTSUP: the specified digest is not supported
* any other errno from file I/O routines
*/
int ekmf_generate_csr(const struct ekmf_config *config,
const char *subject_rdns[], size_t num_subject_rdns,
bool subject_utf8, const char *renew_cert_filename,
const char *extensions[], size_t num_extensions,
int digest_nid,
struct ekmf_rsa_pss_params *rsa_pss_params,
const char *csr_pem_filename, bool new_hdr,
const struct ekmf_ext_lib *ext_lib, bool verbose)
{
unsigned char key_blob[MAX_KEY_BLOB_SIZE];
size_t key_blob_size = sizeof(key_blob);
struct sk_rsa_pss_params pss_params;
struct ext_lib_info ext_lib_info;
X509_REQ *req = NULL;
X509 *cert = NULL;
int rc;
if (config == NULL || ext_lib == NULL || csr_pem_filename == NULL)
return -EINVAL;
if (config->identity_secure_key == NULL)
return -EINVAL;
if (renew_cert_filename == NULL &&
(subject_rdns == NULL || num_subject_rdns == 0))
return -EINVAL;
if (num_extensions != 0 && extensions == NULL)
return -EINVAL;
_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);
rc = SK_OPENSSL_init(verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to initialize secure key support: "
"%s", strerror(-rc));
return rc;
}
rc = read_key_blob(config->identity_secure_key, key_blob,
&key_blob_size);
if (rc != 0) {
pr_verbose(verbose, "Failed to read identity key from file "
"'%s': %s", config->identity_secure_key,
strerror(-rc));
goto out;
}
if (renew_cert_filename != NULL) {
rc = read_x509_certificate(renew_cert_filename, &cert);
if (rc != 0) {
pr_verbose(verbose, "Failed to open renew cert file "
"'%s': %s", renew_cert_filename,
strerror(-rc));
goto out;
}
}
if (rsa_pss_params != NULL)
_ekmf_copy_pss_params(rsa_pss_params, &pss_params);
rc = SK_OPENSSL_generate_csr(key_blob, key_blob_size,
subject_rdns, num_subject_rdns,
subject_utf8, cert,
extensions, num_extensions,
digest_nid, rsa_pss_params != NULL ?
&pss_params : NULL, &req,
&ext_lib_info.ext_lib, verbose);
if (rc != 0) {
pr_verbose(verbose, "SK_OPENSSL_generate_csr failed "
"'%s': %s", csr_pem_filename, strerror(-rc));
goto out;
}
rc = write_x509_request(csr_pem_filename, req, new_hdr);
if (rc != 0) {
pr_verbose(verbose, "Failed to write CSR to file "
"'%s': %s", csr_pem_filename, strerror(-rc));
goto out;
}
pr_verbose(verbose, "Certificate Signing Request created successfully "
"into '%s'", csr_pem_filename);
out:
if (cert != NULL)
X509_free(cert);
if (req != NULL)
X509_REQ_free(req);
SK_OPENSSL_term();
return rc;
}
/**
* Generate a self signed certificate using the secure identity key (field
* identity_secure_key in config structure) with the specified subject name,
* certificate extensions (if any), and writes the certificate the specified
* file in PEM format.
*
* To renew an existing certificate, specify renew_cert = true. In this case
* the existing certificate (field sign_certificate in config struct) is read,
* and the subject name is extracted from it. Any specified subject name RDNs
* are added to the certificate. Also, the extensions are taken from the
* existing certificate, and any specified extensions are added to the new
* certificate.
*
* The certificate is signed using the secure identity key (field
* identity_secure_key in config structure) with an signing algorithm matching
* the identity key (ECDSA, RSA-PKCS, or RSA-PSS if rsa_pss is true), and the
* specified digest. If the digest nid is zero, then a default digest is used.
*
* @param config the configuration structure. Only field
* identity_secure_key must be specified, all others
* are optional.
* @param subject_rdns an array of strings, each string representing an
* RDN in the form '[+]type=value'. If the type is
* prepended with a '+', then this RDN is added to the
* previous one.
* @param num_subject_rdns number of RDN elements in the array.
* @param subject_utf8 if true, RDNs of type MBSTRING_UTF8 are created,
* otherwise type is MBSTRING_ASC is used.
* @param renew_cert_filename if not NULL, specifies the file name of a PEM file
* containing an existing certificate that is renewed
* @param extensions an array of strings, each string representing an
* certificate extension in the form 'type=value'.
* @param num_extensions number of extension elements in the array.
* @param validity_days number if day from the current date how long the
* certificate is valid.
* @param digest_nid the OpenSSL digest nid to use with the signature
* algorithm, or 0 to use the default
* @param rsa_pss_params if not NULL and the identity key is an RSA key, then
* the certificate is signed with RSA-PSS using the
* specified PSS parameters. Ignored if the identity
* key is an EC key
* @param cert_pem_filename the name of the PEM file to which the Certificate
* is written
* @param ext_lib External secure key crypto library to use
* @param verbose if true, verbose messages are printed
*
* @returns a negative errno in case of an error, 0 if success.
* -EINVAL: invalid parameter
* -ENOMEM: Failed to allocate memory
* -EBADMSG: an RDN or extension is not formatted correctly
* -EIO: OpenSSL failed to create the certificate
* -EEXIST: if one of the RDN name entries or extensions to add is a
* duplicate
* -ENOTSUP: the specified digest is not supported
* any other errno from file I/O routines
*/
int ekmf_generate_ss_cert(const struct ekmf_config *config,
const char *subject_rdns[], size_t num_subject_rdns,
bool subject_utf8, const char *renew_cert_filename,
const char *extensions[], size_t num_extensions,
int validity_days, int digest_nid,
struct ekmf_rsa_pss_params *rsa_pss_params,
const char *cert_pem_filename,
const struct ekmf_ext_lib *ext_lib, bool verbose)
{
unsigned char key_blob[MAX_KEY_BLOB_SIZE];
size_t key_blob_size = sizeof(key_blob);
struct sk_rsa_pss_params pss_params;
struct ext_lib_info ext_lib_info;
X509 *rcert = NULL;
X509 *cert = NULL;
int rc;
if (config == NULL || ext_lib == NULL || cert_pem_filename == NULL)
return -EINVAL;
if (config->identity_secure_key == NULL)
return -EINVAL;
if (renew_cert_filename == NULL &&
(subject_rdns == NULL || num_subject_rdns == 0))
return -EINVAL;
if (num_extensions != 0 && extensions == NULL)
return -EINVAL;
_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);
rc = SK_OPENSSL_init(verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to initialize secure key support: "
"%s", strerror(-rc));
return rc;
}
rc = read_key_blob(config->identity_secure_key, key_blob,
&key_blob_size);
if (rc != 0) {
pr_verbose(verbose, "Failed to read identity key from file "
"'%s': %s", config->identity_secure_key,
strerror(-rc));
goto out;
}
if (renew_cert_filename != NULL) {
rc = read_x509_certificate(renew_cert_filename, &rcert);
if (rc != 0) {
pr_verbose(verbose, "Failed to open renew cert file "
"'%s': %s", renew_cert_filename,
strerror(-rc));
goto out;
}
}
if (rsa_pss_params != NULL)
_ekmf_copy_pss_params(rsa_pss_params, &pss_params);
rc = SK_OPENSSL_generate_ss_cert(key_blob, key_blob_size,
subject_rdns, num_subject_rdns,
subject_utf8, rcert,
extensions, num_extensions,
validity_days, digest_nid,
rsa_pss_params != NULL ?
&pss_params : NULL, &cert,
&ext_lib_info.ext_lib, verbose);
if (rc != 0) {
pr_verbose(verbose, "SK_OPENSSL_generate_ss_cert failed "
"'%s': %s", cert_pem_filename, strerror(-rc));
goto out;
}
rc = write_x509_certificate(cert_pem_filename, cert);
if (rc != 0) {
pr_verbose(verbose, "Failed to write Certificate to file "
"'%s': %s", cert_pem_filename, strerror(-rc));
goto out;
}
pr_verbose(verbose, "Self-signed Certificate created successfully "
"into '%s'", cert_pem_filename);
out:
if (cert != NULL)
X509_free(cert);
if (rcert != NULL)
X509_free(rcert);
SK_OPENSSL_term();
return rc;
}
/**
* Validates that a certificate has the same public key as the secure identity
* key (field identity_secure_key in config structure) .
*
* @param config the configuration structure. Only field
* identity_secure_key must be specified, all others
* are optional.
* @param x509_cert the X509 certificate object to validate
* @param ext_lib External secure key crypto library to use
* @param verbose if true, verbose messages are printed
*
* @returns a negative errno in case of an error, 0 if success.
* -EINVAL: invalid parameter, or certificate is not valid
* -ENOMEM: Failed to allocate memory
* any other errno from file I/O routines
*/
int ekmf_validate_cert(const struct ekmf_config *config, const X509 *x509_cert,
const struct ekmf_ext_lib *ext_lib, bool verbose)
{
unsigned char key_blob[MAX_KEY_BLOB_SIZE];
size_t key_blob_size = sizeof(key_blob);
struct ext_lib_info ext_lib_info;
EVP_PKEY *pkey = NULL;
int rc;
if (config == NULL || ext_lib == NULL || x509_cert == NULL)
return -EINVAL;
if (config->identity_secure_key == NULL)
return -EINVAL;
_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);
rc = SK_OPENSSL_init(verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to initialize secure key support: "
"%s", strerror(-rc));
return rc;
}
rc = read_key_blob(config->identity_secure_key, key_blob,
&key_blob_size);
if (rc != 0) {
pr_verbose(verbose, "Failed to read identity key from file "
"'%s': %s", config->identity_secure_key,
strerror(-rc));
goto out;
}
rc = SK_OPENSSL_get_secure_key_as_pkey(key_blob, key_blob_size,
false, &pkey,
&ext_lib_info.ext_lib, verbose);
if (rc != 0) {
pr_verbose(verbose, "Failed to get the PKEY from the identity "
"key: %s", strerror(-rc));
goto out;
}
#if !OPENSSL_VERSION_PREREQ(3, 0)
if (EVP_PKEY_cmp(pkey, X509_get0_pubkey(x509_cert)) != 1) {
#else
if (EVP_PKEY_eq(pkey, X509_get0_pubkey(x509_cert)) != 1) {
#endif
pr_verbose(verbose, "The certificate does not match with the "
"identity key");
rc = -EINVAL;
goto out;
}
pr_verbose(verbose, "Certificate successfully validated");
out:
if (pkey != NULL)
EVP_PKEY_free(pkey);
SK_OPENSSL_term();
return rc;
}
/**
* Close the connection to the EKMFWeb server by destroying the CURL handle.
*
* @param curl_handle the CURL handle to destroy
*/
void ekmf_curl_destroy(CURL *curl_handle)
{
if (curl_handle == NULL)
return;
curl_easy_cleanup(curl_handle);
}
/**
* Library constructor
*/
void __attribute__ ((constructor)) ekmf_init(void)
{
CURLsslset rc;
/*
* Ensure that curl uses OpenSSL as SSL backend. If curl has already
* been itialized by the calling application, the backend can't be
* changed anymore, but we continue anyway. However, it will later be
* checked if curl uses the OpenSSL backend, and a HTTPS connection
* will fail if it is not using the OpenSSL backend.
*/
rc = curl_global_sslset(CURLSSLBACKEND_OPENSSL, NULL, NULL);
if (rc != CURLSSLSET_OK && rc != CURLSSLSET_TOO_LATE)
errx(EXIT_FAILURE, "libekmfweb: libcurl was not built with "
"the OpenSSL backend");
curl_global_init(CURL_GLOBAL_ALL);
}
/**
* Library destructor
*/
void __attribute__ ((destructor)) ekmf_exit(void)
{
curl_global_cleanup();
}