mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
KMIP STRUCTURE elements can be nested, which causes a recursion of functions kmip_decode_ttlv(), kmip_decode_xml(), and kmip_decode_json(). A malformed KMIP response may thus cause stack exhaustion. Limit the KMIP STRUCTURE nesting level to 32 levels. This is more than enough for currently defined KMIP responses. The practically used nesting level is 8 or 9, dependent on the type of KMIP response. 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>
535 lines
13 KiB
C
535 lines
13 KiB
C
/*
|
|
* libkmipclient - KMIP client library
|
|
*
|
|
* Copyright IBM Corp. 2021
|
|
*
|
|
* 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 <errno.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <openssl/err.h>
|
|
#include <openssl/x509.h>
|
|
#include <openssl/x509v3.h>
|
|
#include <openssl/opensslv.h>
|
|
|
|
#include "kmip.h"
|
|
#include "utils.h"
|
|
|
|
#ifndef OPENSSL_VERSION_PREREQ
|
|
#if defined(OPENSSL_VERSION_MAJOR) && defined(OPENSSL_VERSION_MINOR)
|
|
#define OPENSSL_VERSION_PREREQ(maj, min) \
|
|
((OPENSSL_VERSION_MAJOR << 16) + \
|
|
OPENSSL_VERSION_MINOR >= ((maj) << 16) + (min))
|
|
#else
|
|
#define OPENSSL_VERSION_PREREQ(maj, min) \
|
|
(OPENSSL_VERSION_NUMBER >= (((maj) << 28) | \
|
|
((min) << 20)))
|
|
#endif
|
|
#endif
|
|
|
|
/**
|
|
* Verify the pinned public key of the server of a plain TLS KMIP connection
|
|
*
|
|
* @param conn the KMIP connection to free
|
|
* @param cert_pubkey the server certificate's public key
|
|
* @param debug if true, debug messages are printed
|
|
*/
|
|
static int kmip_connection_tls_verify_pinned_pubkey(
|
|
struct kmip_connection *conn,
|
|
EVP_PKEY *cert_pubkey, bool debug)
|
|
{
|
|
EVP_PKEY *pinned_key = NULL;
|
|
int rc = 0;
|
|
FILE *fp;
|
|
|
|
fp = fopen(conn->config.tls_pinned_pubkey, "r");
|
|
if (fp == NULL) {
|
|
rc = -errno;
|
|
kmip_debug(debug, "Failed to read pinned public key '%s': %s",
|
|
conn->config.tls_pinned_pubkey, strerror(-rc));
|
|
return rc;
|
|
}
|
|
|
|
pinned_key = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
|
|
fclose(fp);
|
|
|
|
if (pinned_key == NULL) {
|
|
kmip_debug(debug, "PEM_read_PUBKEY failed: '%s'",
|
|
conn->config.tls_pinned_pubkey);
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
return -EIO;
|
|
}
|
|
|
|
#if !OPENSSL_VERSION_PREREQ(3, 0)
|
|
if (EVP_PKEY_cmp(pinned_key, cert_pubkey) != 1) {
|
|
#else
|
|
if (EVP_PKEY_eq(pinned_key, cert_pubkey) != 1) {
|
|
#endif
|
|
kmip_debug(debug, "Server public key does not match the pinned "
|
|
"public key '%s'", conn->config.tls_pinned_pubkey);
|
|
rc = -EPERM;
|
|
}
|
|
|
|
EVP_PKEY_free(pinned_key);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Verify the pinned server certificate key of the server of a plain TLS KMIP
|
|
* connection
|
|
*
|
|
* @param conn the KMIP connection to free
|
|
* @param server_cert the server certificate
|
|
* @param debug if true, debug messages are printed
|
|
*/
|
|
static int kmip_connection_tls_verify_pinned_cert(
|
|
struct kmip_connection *conn,
|
|
X509 *server_cert, bool debug)
|
|
{
|
|
X509 *pinned_cert = NULL;
|
|
int rc = 0;
|
|
FILE *fp;
|
|
|
|
fp = fopen(conn->config.tls_server_cert, "r");
|
|
if (fp == NULL) {
|
|
rc = -errno;
|
|
kmip_debug(debug, "Failed to read pinned server cert: %s",
|
|
conn->config.tls_server_cert, strerror(-rc));
|
|
return rc;
|
|
}
|
|
|
|
pinned_cert = PEM_read_X509(fp, NULL, NULL, NULL);
|
|
fclose(fp);
|
|
|
|
if (pinned_cert == NULL) {
|
|
kmip_debug(debug, "PEM_read_X509 failed: '%s'",
|
|
conn->config.tls_server_cert);
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
return -EIO;
|
|
}
|
|
|
|
if (X509_cmp(pinned_cert, server_cert) != 0) {
|
|
kmip_debug(debug, "Server certificate does not match the "
|
|
"pinned certificate '%s'",
|
|
conn->config.tls_server_cert);
|
|
rc = -EPERM;
|
|
}
|
|
|
|
X509_free(pinned_cert);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Verify the issuer certificate key of the server of a plain TLS KMIP
|
|
* connection
|
|
*
|
|
* @param conn the KMIP connection to free
|
|
* @param server_cert the server certificate
|
|
* @param debug if true, debug messages are printed
|
|
*/
|
|
static int kmip_connection_tls_verify_issuer_cert(
|
|
struct kmip_connection *conn,
|
|
X509 *server_cert, bool debug)
|
|
{
|
|
X509 *issuer_cert = NULL;
|
|
int rc = 0;
|
|
FILE *fp;
|
|
|
|
fp = fopen(conn->config.tls_issuer_cert, "r");
|
|
if (fp == NULL) {
|
|
rc = -errno;
|
|
kmip_debug(debug, "Failed to read issuer cert '%s': %s",
|
|
conn->config.tls_issuer_cert, strerror(-rc));
|
|
return rc;
|
|
}
|
|
|
|
issuer_cert = PEM_read_X509(fp, NULL, NULL, NULL);
|
|
fclose(fp);
|
|
|
|
if (issuer_cert == NULL) {
|
|
kmip_debug(debug, "PEM_read_X509 failed: '%s'",
|
|
conn->config.tls_issuer_cert);
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
return -EIO;
|
|
}
|
|
|
|
if (X509_check_issued(issuer_cert, server_cert) != X509_V_OK) {
|
|
kmip_debug(debug, "The server certificate was not issued by "
|
|
"certificate '%s'", conn->config.tls_issuer_cert);
|
|
rc = -EPERM;
|
|
}
|
|
|
|
X509_free(issuer_cert);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Verify the server of a plain TLS KMIP connection
|
|
*
|
|
* @param conn the KMIP connection to free
|
|
* @param debug if true, debug messages are printed
|
|
*/
|
|
static int kmip_connection_tls_verify_server(struct kmip_connection *conn,
|
|
bool debug)
|
|
{
|
|
X509 *server_cert;
|
|
int rc;
|
|
|
|
server_cert = SSL_get_peer_certificate(conn->plain_tls.ssl);
|
|
if (server_cert == NULL) {
|
|
kmip_debug(debug, "SSL_get_peer_certificate failed");
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
if (conn->config.tls_issuer_cert != NULL) {
|
|
rc = kmip_connection_tls_verify_issuer_cert(conn, server_cert,
|
|
debug);
|
|
if (rc != 0) {
|
|
kmip_debug(debug,
|
|
"kmip_connection_tls_verify_issuer_cert "
|
|
"failed");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (conn->config.tls_server_cert != NULL) {
|
|
rc = kmip_connection_tls_verify_pinned_cert(conn, server_cert,
|
|
debug);
|
|
if (rc != 0) {
|
|
kmip_debug(debug,
|
|
"kmip_connection_tls_verify_pinned_cert "
|
|
"failed");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (conn->config.tls_pinned_pubkey != NULL) {
|
|
rc = kmip_connection_tls_verify_pinned_pubkey(conn,
|
|
X509_get0_pubkey(server_cert),
|
|
debug);
|
|
if (rc != 0) {
|
|
kmip_debug(debug,
|
|
"kmip_connection_tls_pinned_pubkey failed");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
rc = 0;
|
|
|
|
out:
|
|
if (server_cert != NULL)
|
|
X509_free(server_cert);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Initializes a new plain TLS connection to a KMIP server.
|
|
*
|
|
* @param conn The KMIP connection
|
|
* @param debug if true, debug messages are printed
|
|
*
|
|
* @returns 0 in case of success, or a negative errno value
|
|
*/
|
|
int kmip_connection_tls_init(struct kmip_connection *conn, bool debug)
|
|
{
|
|
char *hostname = NULL, *port = NULL, *tok;
|
|
struct stat sb;
|
|
int rc;
|
|
|
|
if (conn == NULL)
|
|
return -EINVAL;
|
|
|
|
conn->plain_tls.ssl_ctx = SSL_CTX_new(TLS_client_method());
|
|
if (conn->plain_tls.ssl_ctx == NULL) {
|
|
kmip_debug(debug, "SSL_CTX_new failed");
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
return -EIO;
|
|
}
|
|
|
|
if (SSL_CTX_use_certificate_file(conn->plain_tls.ssl_ctx,
|
|
conn->config.tls_client_cert,
|
|
SSL_FILETYPE_PEM) != 1) {
|
|
kmip_debug(debug, "Loading the client certificate from '%s' "
|
|
"failed", conn->config.tls_client_cert);
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
if (SSL_CTX_use_PrivateKey(conn->plain_tls.ssl_ctx,
|
|
conn->config.tls_client_key) != 1) {
|
|
kmip_debug(debug, "Setting the client key from PKEY %p "
|
|
"failed", conn->config.tls_client_key);
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
rc = -EIO;
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (conn->config.tls_ca != NULL) {
|
|
if (stat(conn->config.tls_ca, &sb) != 0) {
|
|
rc = -errno;
|
|
kmip_debug(debug, "stat failed on '%s': %s",
|
|
conn->config.tls_ca, strerror(-rc));
|
|
goto out;
|
|
}
|
|
|
|
if (S_ISDIR(sb.st_mode)) {
|
|
if (SSL_CTX_load_verify_locations(
|
|
conn->plain_tls.ssl_ctx, NULL,
|
|
conn->config.tls_ca) != 1) {
|
|
kmip_debug(debug, "Setting the verify location "
|
|
"to '%s' failed",
|
|
conn->config.tls_ca);
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
} else {
|
|
if (SSL_CTX_load_verify_locations(
|
|
conn->plain_tls.ssl_ctx,
|
|
conn->config.tls_ca, NULL) != 1) {
|
|
kmip_debug(debug, "Setting the verify location "
|
|
"to '%s' failed",
|
|
conn->config.tls_ca);
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
conn->plain_tls.bio =
|
|
BIO_new_buffer_ssl_connect(conn->plain_tls.ssl_ctx);
|
|
if (conn->plain_tls.bio == NULL) {
|
|
kmip_debug(debug, "BIO_new_ssl_connect failed");
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
BIO_get_ssl(conn->plain_tls.bio, &conn->plain_tls.ssl);
|
|
if (conn->plain_tls.ssl == NULL) {
|
|
kmip_debug(debug, "BIO_get_ssl failed");
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
hostname = strdup(conn->config.server);
|
|
if (hostname == NULL) {
|
|
kmip_debug(debug, "strdup failed");
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* Split port number from hostname, if specified */
|
|
if (hostname[0] == '[') {
|
|
/* IPv6 address enclosed in square brackets */
|
|
tok = strchr(hostname, ']');
|
|
if (tok == NULL) {
|
|
kmip_debug(debug, "malformed IPv6 address");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
tok++;
|
|
if (*tok == ':') {
|
|
port = tok + 1;
|
|
*tok = 0;
|
|
}
|
|
} else {
|
|
/* hostname or IPv4 address */
|
|
tok = strchr(hostname, ':');
|
|
if (tok != NULL) {
|
|
port = tok + 1;
|
|
*tok = 0;
|
|
}
|
|
}
|
|
|
|
kmip_debug(debug, "hostname: '%s'", hostname);
|
|
if (port == NULL) {
|
|
port = KMIP_DEFAULT_PLAIN_TLS_PORT;
|
|
kmip_debug(debug, "port: default (%s)", port);
|
|
} else {
|
|
kmip_debug(debug, "port: %s", port);
|
|
}
|
|
|
|
if (conn->config.tls_verify_host) {
|
|
SSL_set_hostflags(conn->plain_tls.ssl,
|
|
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
|
|
#if OPENSSL_VERSION_PREREQ(4, 0)
|
|
if (SSL_set1_ipaddr(conn->plain_tls.ssl, hostname) != 1 &&
|
|
SSL_set1_dnsname(conn->plain_tls.ssl, hostname) != 1) {
|
|
kmip_debug(debug, "SSL_set1_ipaddr/dnsname failed");
|
|
#else
|
|
if (SSL_set1_host(conn->plain_tls.ssl, hostname) != 1) {
|
|
kmip_debug(debug, "SSL_set1_host failed");
|
|
#endif
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
SSL_set_verify(conn->plain_tls.ssl, (conn->config.tls_verify_peer ||
|
|
conn->config.tls_verify_host) ?
|
|
SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
|
|
|
|
if (conn->config.tls_cipher_list != NULL) {
|
|
if (SSL_set_cipher_list(conn->plain_tls.ssl,
|
|
conn->config.tls_cipher_list) != 1) {
|
|
kmip_debug(debug, "SSL_set_cipher_list failed");
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (conn->config.tls13_cipher_list != NULL) {
|
|
if (SSL_set_ciphersuites(conn->plain_tls.ssl,
|
|
conn->config.tls13_cipher_list) != 1) {
|
|
kmip_debug(debug, "SSL_set_ciphersuites failed");
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
SSL_set_mode(conn->plain_tls.ssl, SSL_MODE_AUTO_RETRY);
|
|
|
|
BIO_set_conn_hostname(conn->plain_tls.bio, hostname);
|
|
BIO_set_conn_port(conn->plain_tls.bio, port);
|
|
|
|
if (BIO_do_connect(conn->plain_tls.bio) != 1) {
|
|
kmip_debug(debug, "BIO_do_connect failed");
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
kmip_debug(debug, "TLS connection established using %s",
|
|
SSL_get_cipher_name(conn->plain_tls.ssl));
|
|
|
|
rc = kmip_connection_tls_verify_server(conn, debug);
|
|
if (rc != 0) {
|
|
kmip_debug(debug, "kmip_connection_tls_verify_server failed");
|
|
if (debug)
|
|
ERR_print_errors_fp(stderr);
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
rc = 0;
|
|
|
|
out:
|
|
if (rc != 0)
|
|
kmip_connection_tls_term(conn);
|
|
if (hostname != NULL)
|
|
free(hostname);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Perform a request over the KMIP connection
|
|
*
|
|
* @param conn n the KMIP connection
|
|
* @param request the request to send
|
|
* @param response On return: the received response. Must be freed by
|
|
* the caller.
|
|
*
|
|
* @param debug if true, debug messages are printed
|
|
*
|
|
* @returns 0 in case of success, or a negative errno value
|
|
*/
|
|
int kmip_connection_tls_perform(struct kmip_connection *conn,
|
|
struct kmip_node *request,
|
|
struct kmip_node **response,
|
|
bool debug)
|
|
{
|
|
size_t size;
|
|
int rc;
|
|
|
|
if (conn == NULL || request == NULL || response == NULL)
|
|
return -EINVAL;
|
|
|
|
*response = NULL;
|
|
|
|
/* Send out the request */
|
|
rc = kmip_encode_ttlv(request, conn->plain_tls.bio, &size, debug);
|
|
if (rc != 0) {
|
|
kmip_debug(debug, "kmip_encode_ttlv failed");
|
|
goto out;
|
|
}
|
|
if (BIO_flush(conn->plain_tls.bio) != 1) {
|
|
kmip_debug(debug, "BIO_flush failed");
|
|
goto out;
|
|
}
|
|
kmip_debug(debug, "%lu bytes sent", size);
|
|
|
|
/* receive the response */
|
|
rc = kmip_decode_ttlv(conn->plain_tls.bio, NULL, response,
|
|
KMIP_DECODE_MAX_NESTING_LEVEL, debug);
|
|
if (rc != 0 || *response == NULL) {
|
|
kmip_debug(debug, "kmip_decode_ttlv failed");
|
|
goto out;
|
|
}
|
|
|
|
rc = 0;
|
|
|
|
out:
|
|
if (rc != 0) {
|
|
if (BIO_reset(conn->plain_tls.bio) != 1)
|
|
kmip_debug(debug, "BIO_reset failed");
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Terminates a plain TLS KMIP connection.
|
|
*
|
|
* @param conn the KMIP connection to free
|
|
*/
|
|
void kmip_connection_tls_term(struct kmip_connection *conn)
|
|
{
|
|
if (conn == NULL)
|
|
return;
|
|
|
|
if (conn->plain_tls.bio != NULL) {
|
|
BIO_ssl_shutdown(conn->plain_tls.bio);
|
|
BIO_free_all(conn->plain_tls.bio);
|
|
}
|
|
if (conn->plain_tls.ssl_ctx != NULL)
|
|
SSL_CTX_free(conn->plain_tls.ssl_ctx);
|
|
|
|
conn->plain_tls.bio = NULL;
|
|
conn->plain_tls.ssl_ctx = NULL;
|
|
conn->plain_tls.ssl = NULL;
|
|
}
|