Files
s390-tools/libkmipclient/tls.c
Ingo Franzki 486882a4df libkmipclient: Fix findings from static code checker
The Coverity static code checker found a few bugs in the libkmipclient
code, fix those.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Joerg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-21 15:50:31 +02:00

528 lines
12 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 0;
}
/**
* 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 (SSL_set1_host(conn->plain_tls.ssl, hostname) != 1) {
kmip_debug(debug, "SSL_set1_host failed");
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, 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;
}