mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
With OpenSSL 4.0.0 function SSL_set1_host() is deprecated and should be replaced by SSL_set1_ipaddr() and SSL_set1_dnsname(). Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
534 lines
13 KiB
C
534 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, 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;
|
|
}
|