From cbf7f02d69918085a262c3370fe713ac8000e848 Mon Sep 17 00:00:00 2001 From: Ingo Franzki Date: Fri, 3 Apr 2020 11:11:29 +0200 Subject: [PATCH] libekmfweb: Add EKMFWeb client library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EKMFWeb client library provides functions to communicate with an EKMF Web server via REST calls over HTTPS. EKMF Web stands for IBM Enterprise Key Management Foundation - Web Edition and is used to manage keys in an enterprise. Signed-off-by: Ingo Franzki Signed-off-by: Jan Höppner --- Makefile | 2 +- README.md | 19 +- common.mak | 11 +- include/ekmfweb/ekmfweb.h | 91 ++++ libekmfweb/Makefile | 84 ++++ libekmfweb/ekmfweb.c | 1006 +++++++++++++++++++++++++++++++++++++ libekmfweb/libekmfweb.map | 6 + libekmfweb/utilities.c | 48 ++ libekmfweb/utilities.h | 20 + 9 files changed, 1282 insertions(+), 5 deletions(-) create mode 100644 include/ekmfweb/ekmfweb.h create mode 100644 libekmfweb/Makefile create mode 100644 libekmfweb/ekmfweb.c create mode 100644 libekmfweb/libekmfweb.map create mode 100644 libekmfweb/utilities.c create mode 100644 libekmfweb/utilities.h diff --git a/Makefile b/Makefile index f2a4680a..c474398d 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ ARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ -e s/arm.*/ar # Include common definitions include common.mak -LIB_DIRS = libvtoc libutil libzds libdasd libvmdump libccw libvmcp +LIB_DIRS = libvtoc libutil libzds libdasd libvmdump libccw libvmcp libekmfweb TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \ tape390 osasnmpd qetharp ip_watcher qethconf scripts zconf \ vmconvert vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \ diff --git a/README.md b/README.md index e20aa376..aa2188a5 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,12 @@ Package contents Provides simple tools to create a binary that can be used to implement simple network boot setups following the PXELINUX conventions. + * libekmfweb: + A shared library that provides functions to communicate with an EKMF Web + server via REST calls over HTTPS. EKMF Web stands for IBM Enterprise Key + Management Foundation - Web Edition, and is used to manage keys in an + enterprise. + For more information refer to the following publications: * "Device Drivers, Features, and Commands" chapter "Useful Linux commands" @@ -267,10 +273,11 @@ build options: | pfm | `HAVE_PFM` | cpacfstats | | net-snmp | `HAVE_SNMP` | osasnmpd | | glibc-static | `HAVE_LIBC_STATIC` | zfcpdump | -| openssl | `HAVE_OPENSSL` | genprotimg,zkey | +| openssl | `HAVE_OPENSSL` | genprotimg, zkey, libekmfweb | | cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup | -| json-c | `HAVE_JSONC` | zkey-cryptsetup | +| json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb | | glib2 | `HAVE_GLIB2` | genprotimg | +| libcurl | `HAVE_LIBCURL` | libekmfweb | This table lists additional build or install options: @@ -397,3 +404,11 @@ the different tools are provided: tool must be added to this group. The owner of the default key repository '/etc/zkey/repository' must be set to group 'zkeyadm' with write permission for this group. + +* libekmfweb: + For building the libekmfweb shared library you need openssl version 1.1.1 or + newer installed (openssl-devel.rpm). Also required are json-c version 0.13 or + newer (json-c-devel.rpm), and libcurl version 7.59 or newer + (libcurl-devel.rpm). + Tip: you may skip the libekmfweb build by adding `HAVE_OPENSSL=0`, + `HAVE_JSONC=0`, or `HAVE_LIBCURL=0` to the make invocation. diff --git a/common.mak b/common.mak index cb203b6c..0ef51761 100644 --- a/common.mak +++ b/common.mak @@ -163,6 +163,7 @@ USRSBINDIR = $(INSTALLDIR)/usr/sbin USRBINDIR = $(INSTALLDIR)/usr/bin BINDIR = $(INSTALLDIR)/sbin LIBDIR = $(INSTALLDIR)/lib +LIB64DIR = $(INSTALLDIR)/lib64 SYSCONFDIR = $(INSTALLDIR)/etc MANDIR = $(INSTALLDIR)/usr/share/man VARDIR = $(INSTALLDIR)/var @@ -172,14 +173,16 @@ ZFCPDUMP_DIR = $(TOOLS_LIBDIR)/zfcpdump # Systemd support files are installed only if a directory is specified # for SYSTEMDSYSTEMUNITDIR (e.g. /lib/systemd/system) SYSTEMDSYSTEMUNITDIR = +USRINCLUDEDIR = $(INSTALLDIR)/usr/include INSTDIRS = $(USRSBINDIR) $(USRBINDIR) $(BINDIR) $(LIBDIR) $(MANDIR) \ $(SYSCONFDIR) $(SYSCONFDIR)/sysconfig \ $(TOOLS_LIBDIR) $(TOOLS_DATADIR) \ - $(ZFCPDUMP_DIR) $(SYSTEMDSYSTEMUNITDIR) + $(ZFCPDUMP_DIR) $(SYSTEMDSYSTEMUNITDIR) \ + $(LIB64DIR) $(USRINCLUDEDIR) OWNER = $(shell id -un) GROUP = $(shell id -gn) -export INSTALLDIR BINDIR LIBDIR MANDIR OWNER GROUP +export INSTALLDIR BINDIR LIBDIR LIB64DIR MANDIR OWNER GROUP # Special defines for zfcpdump ZFCPDUMP_IMAGE = zfcpdump-image @@ -339,6 +342,10 @@ $(rootdir)/libvmcp/libvmcp.a: $(rootdir)/libvmcp $(MAKE) -C $(rootdir)/libvmcp/ libvmcp.a .PHONY: $(rootdir)/libvmcp +$(rootdir)/libekmfweb/libekmfweb.so: $(rootdir)/libekmfweb + $(MAKE) -C $(rootdir)/libekmfweb/ libekmfweb.so +.PHONY: $(rootdir)/libekmfweb + $(rootdir)/zipl/boot/data.o: $(MAKE) -C $(rootdir)/zipl/boot/ data.o diff --git a/include/ekmfweb/ekmfweb.h b/include/ekmfweb/ekmfweb.h new file mode 100644 index 00000000..a606005c --- /dev/null +++ b/include/ekmfweb/ekmfweb.h @@ -0,0 +1,91 @@ +/* + * 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. + */ + +#ifndef LIB_EKMFWEB_H +#define LIB_EKMFWEB_H + +#include +#include + +struct ekmf_config { + /** The base URL of the server. Should use https:// ! */ + const char *base_url; + /** Optional: File name of the CA bundle PEM file, or a name of a + * directory the multiple CA certificates. If this is NULL, then the + * default system path for CA certificates is used */ + const char *tls_ca; + /** Optional: File name of the client certificate PEM file */ + const char *tls_client_cert; + /** Optional: File name of the clients key PEM file */ + const char *tls_client_key; + /** Optional: Passphrase to read the clients key PEM file */ + const char *tls_client_key_passphrase; + /** Optional: File name of a PEM file holding a CA certificate of the + * issuer */ + const char *tls_issuer_cert; + /** Optional: File name of a PEM file containing the servers pinned + * public key. Public key pinning requires that verify_peer or + * verify_host (or both) is true. */ + const char *tls_pinned_pubkey; + /** Optional: File name of a PEM file containing the server's + * certificate. This can be used to allow peer verification with + * self-signed server certificates */ + const char *tls_server_cert; + /** If true, the peer certificate is verified */ + bool tls_verify_peer; + /** If true, that the server certificate is for the server it is known + * as (i.e. the hostname in the url) */ + bool tls_verify_host; + /** Maximum number of redirects to follow. Zero means that redirects are + * not followed. -1 means to infinitely follow redirects. */ + long max_redirs; +}; + +/** + * 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); + +/** + * 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); + +#endif diff --git a/libekmfweb/Makefile b/libekmfweb/Makefile new file mode 100644 index 00000000..e4aafd45 --- /dev/null +++ b/libekmfweb/Makefile @@ -0,0 +1,84 @@ +include ../common.mak + +ifneq (${HAVE_OPENSSL},0) + ifneq (${HAVE_JSONC},0) + ifneq (${HAVE_LIBCURL},0) + BUILD_TARGETS += libekmfweb.so + INSTALL_TARGETS += install-libekmfweb.so + else + BUILD_TARGETS += skip-libekmfweb-curl + INSTALL_TARGETS += skip-libekmfweb-curl + endif + else + BUILD_TARGETS += skip-libekmfweb-jsonc + INSTALL_TARGETS += skip-libekmfweb-jsonc + endif +else + BUILD_TARGETS += skip-libekmfweb-openssl + INSTALL_TARGETS += skip-libekmfweb-openssl +endif + +libs = $(rootdir)/libutil/libutil.a + +detect-openssl-version.dep: + echo "#include " > detect-openssl-version.dep + echo "#include " >> detect-openssl-version.dep + echo "#if OPENSSL_VERSION_NUMBER < 0x10101000L" >> detect-openssl-version.dep + echo " #error openssl version 1.1.1 is required" >> detect-openssl-version.dep + echo "#endif" >> detect-openssl-version.dep + echo "static void __attribute__((unused)) test(void) {" >> detect-openssl-version.dep + echo " EVP_PKEY_meth_remove(NULL);" >> detect-openssl-version.dep + echo "}" >> detect-openssl-version.dep + +check-dep-libekmfweb: detect-openssl-version.dep + $(call check_dep, \ + "libekmfweb", \ + "detect-openssl-version.dep", \ + "openssl-devel version >= 1.1.1", \ + "HAVE_OPENSSL=0", \ + -I. -lcrypto) + $(call check_dep, \ + "libekmfweb", \ + "json-c/json.h", \ + "json-c-devel", \ + "HAVE_JSONC=0") + $(call check_dep, \ + "libekmfweb", \ + "curl/curl.h", \ + "libcurl-devel", \ + "HAVE_LIBCURL=0") + touch check-dep-libekmfweb + +skip-libekmfweb-openssl: + echo " SKIP libekmfweb due to HAVE_OPENSSL=0" + +skip-libekmfweb-jsonc: + echo " SKIP libekmfweb due to HAVE_JSONC=0" + +skip-libekmfweb-curl: + echo " SKIP libekmfweb due to HAVE_LIBCURL=0" + +all: $(BUILD_TARGETS) + +ekmfweb.o: check-dep-libekmfweb ekmfweb.c utilities.h $(rootdir)include/ekmfweb/ekmfweb.h +utilities.o: check-dep-libekmfweb utilities.c utilities.h + +libekmfweb.so: ALL_CFLAGS += -fPIC +libekmfweb.so: LDLIBS = -ljson-c -lcrypto -lssl -lcurl -shared +libekmfweb.so: LDFLAGS = -shared -Wl,--version-script=libekmfweb.map \ + -Wl,-z,defs,-Bsymbolic +libekmfweb.so: ekmfweb.o utilities.o + $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ + +install-libekmfweb.so: libekmfweb.so + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 -T libekmfweb.so $(DESTDIR)$(LIB64DIR)/libekmfweb.so + $(INSTALL) -d -m 770 $(DESTDIR)$(USRINCLUDEDIR)/ekmfweb + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 $(rootdir)include/ekmfweb/ekmfweb.h $(DESTDIR)$(USRINCLUDEDIR)/ekmfweb + +install: all $(INSTALL_TARGETS) + +clean: + rm -f *.o libekmfweb.so check-dep-libekmfweb detect-openssl-version.dep + +.PHONY: all install clean skip-libekmfweb-openssl skip-libekmfweb-jsonc \ + skip-libekmfweb-curl install-libekmfweb.so diff --git a/libekmfweb/ekmfweb.c b/libekmfweb/ekmfweb.c new file mode 100644 index 00000000..0dfcfb20 --- /dev/null +++ b/libekmfweb/ekmfweb.c @@ -0,0 +1,1006 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#ifndef JSON_C_TO_STRING_NOSLASHESCAPE +#define JSON_C_TO_STRING_NOSLASHESCAPE (1 << 4) +#endif + +#include "lib/zt_common.h" + +#include "ekmfweb/ekmfweb.h" +#include "utilities.h" + +#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) + +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:" + +const char *accepted_content_types[] = { "application/json", + "text/x-json", + NULL}; + +/** + * 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(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(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(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) +{ + 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); + + 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; +} + +/** + * 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; +} + +/** + * Library constructor + */ +void __attribute__ ((constructor)) ekmf_init(void) +{ + curl_global_init(CURL_GLOBAL_ALL); +} + +/** + * Library destructor + */ +void __attribute__ ((destructor)) ekmf_exit(void) +{ + curl_global_cleanup(); +} + + diff --git a/libekmfweb/libekmfweb.map b/libekmfweb/libekmfweb.map new file mode 100644 index 00000000..1f8894e0 --- /dev/null +++ b/libekmfweb/libekmfweb.map @@ -0,0 +1,6 @@ +LIBEKMFWEB_1.0 { + global: + ekmf_get_server_cert_chain; + ekmf_print_certificates; + local: *; +}; diff --git a/libekmfweb/utilities.c b/libekmfweb/utilities.c new file mode 100644 index 00000000..e85e39f7 --- /dev/null +++ b/libekmfweb/utilities.c @@ -0,0 +1,48 @@ +/* + * 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 +#include +#include + +#include +#include + +#include "utilities.h" + +/** + * Reads a X.509 certificate from the specified PEM file. + * + * @param pem_filename the name of the PEM file to read + * @param cert on Return: the X.509 certificate object + * + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: invalid parameter + * -EIO: error during reading in the certificate + * any other errno as returned by fopen + */ +int read_x509_certificate(const char *pem_filename, X509 **cert) +{ + FILE *fp; + + if (pem_filename == NULL || cert == NULL) + return -EINVAL; + + fp = fopen(pem_filename, "r"); + if (fp == NULL) + return -errno; + + *cert = PEM_read_X509(fp, NULL, NULL, NULL); + + fclose(fp); + + if (*cert == NULL) + return -EIO; + + return 0; +} diff --git a/libekmfweb/utilities.h b/libekmfweb/utilities.h new file mode 100644 index 00000000..95c92bfb --- /dev/null +++ b/libekmfweb/utilities.h @@ -0,0 +1,20 @@ +/* + * 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. + */ + +#ifndef UTILITIES_H +#define UTILITIES_H + +#include +#include + +#include + +int read_x509_certificate(const char *pem_filename, X509 **cert); + +#endif