azure-c-shared-utility/adapters/tlsio_openssl.c

1638 строки
53 KiB
C

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include <stdlib.h>
#include "openssl/ssl.h"
#include "openssl/err.h"
#include "openssl/crypto.h"
#include "openssl/opensslv.h"
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include "azure_c_shared_utility/lock.h"
#include "azure_c_shared_utility/tlsio.h"
#include "azure_c_shared_utility/tlsio_openssl.h"
#include "azure_c_shared_utility/socketio.h"
#include "azure_c_shared_utility/optimize_size.h"
#include "azure_c_shared_utility/xlogging.h"
#include "azure_c_shared_utility/crt_abstractions.h"
#include "azure_c_shared_utility/x509_openssl.h"
#include "azure_c_shared_utility/shared_util_options.h"
#include "azure_c_shared_utility/gballoc.h"
#include "azure_c_shared_utility/const_defines.h"
typedef enum TLSIO_STATE_TAG
{
TLSIO_STATE_NOT_OPEN,
TLSIO_STATE_OPENING_UNDERLYING_IO,
TLSIO_STATE_IN_HANDSHAKE,
// TLSIO_STATE_HANDSHAKE_FAILED is an ephemeral state signalling successful socket
// operation but with rejected handshake. The tlsio will never be in this state
// at the start of any of the API calls.
TLSIO_STATE_HANDSHAKE_FAILED,
TLSIO_STATE_OPEN,
TLSIO_STATE_CLOSING,
TLSIO_STATE_ERROR
} TLSIO_STATE;
typedef enum TLSIO_VERSION_TAG
{
VERSION_1_0,
VERSION_1_1,
VERSION_1_2,
} TLSIO_VERSION;
static bool is_an_opening_state(TLSIO_STATE state)
{
// TLSIO_STATE_HANDSHAKE_FAILED is deliberately not one of these states.
return state == TLSIO_STATE_OPENING_UNDERLYING_IO ||
state == TLSIO_STATE_IN_HANDSHAKE;
}
typedef int(*TLS_CERTIFICATE_VALIDATION_CALLBACK)(X509_STORE_CTX*, void*);
typedef struct TLS_IO_INSTANCE_TAG
{
XIO_HANDLE underlying_io;
ON_BYTES_RECEIVED on_bytes_received;
ON_IO_OPEN_COMPLETE on_io_open_complete;
ON_IO_CLOSE_COMPLETE on_io_close_complete;
ON_IO_ERROR on_io_error;
void* on_bytes_received_context;
void* on_io_open_complete_context;
void* on_io_close_complete_context;
void* on_io_error_context;
SSL* ssl;
SSL_CTX* ssl_context;
BIO* in_bio;
BIO* out_bio;
TLSIO_STATE tlsio_state;
char* certificate;
char* cipher_list;
const char* x509_certificate;
const char* x509_private_key;
TLSIO_VERSION tls_version;
TLS_CERTIFICATE_VALIDATION_CALLBACK tls_validation_callback;
void* tls_validation_callback_data;
} TLS_IO_INSTANCE;
struct CRYPTO_dynlock_value
{
LOCK_HANDLE lock;
};
static const char* const OPTION_UNDERLYING_IO_OPTIONS = "underlying_io_options";
#define SSL_DO_HANDSHAKE_SUCCESS 1
/*this function will clone an option given by name and value*/
static void* tlsio_openssl_CloneOption(const char* name, const void* value)
{
void* result;
if (
(name == NULL) || (value == NULL)
)
{
LogError("invalid parameter detected: const char* name=%p, const void* value=%p", name, value);
result = NULL;
}
else
{
if (strcmp(name, OPTION_UNDERLYING_IO_OPTIONS) == 0)
{
result = (void*)value;
}
else if (strcmp(name, OPTION_TRUSTED_CERT) == 0)
{
if (mallocAndStrcpy_s((char**)&result, value) != 0)
{
LogError("unable to mallocAndStrcpy_s TrustedCerts value");
result = NULL;
}
else
{
/*return as is*/
}
}
else if (strcmp(name, OPTION_OPENSSL_CIPHER_SUITE) == 0)
{
if (mallocAndStrcpy_s((char**)&result, value) != 0)
{
LogError("unable to mallocAndStrcpy_s CipherSuite value");
result = NULL;
}
else
{
/*return as is*/
}
}
else if (strcmp(name, SU_OPTION_X509_CERT) == 0)
{
if (mallocAndStrcpy_s((char**)&result, value) != 0)
{
LogError("unable to mallocAndStrcpy_s x509certificate value");
result = NULL;
}
else
{
/*return as is*/
}
}
else if (strcmp(name, SU_OPTION_X509_PRIVATE_KEY) == 0)
{
if (mallocAndStrcpy_s((char**)&result, value) != 0)
{
LogError("unable to mallocAndStrcpy_s x509privatekey value");
result = NULL;
}
else
{
/*return as is*/
}
}
else if (strcmp(name, OPTION_X509_ECC_CERT) == 0)
{
if (mallocAndStrcpy_s((char**)&result, value) != 0)
{
LogError("unable to mallocAndStrcpy_s x509EccCertificate value");
result = NULL;
}
else
{
/*return as is*/
}
}
else if (strcmp(name, OPTION_X509_ECC_KEY) == 0)
{
if (mallocAndStrcpy_s((char**)&result, value) != 0)
{
LogError("unable to mallocAndStrcpy_s x509EccKey value");
result = NULL;
}
else
{
/*return as is*/
}
}
else if (strcmp(name, OPTION_TLS_VERSION) == 0)
{
int int_value;
if (*(TLSIO_VERSION*)value == VERSION_1_0)
{
int_value = 10;
}
else if (*(TLSIO_VERSION*)value == VERSION_1_1)
{
int_value = 11;
}
else if (*(TLSIO_VERSION*)value == VERSION_1_2)
{
int_value = 12;
}
else
{
LogError("Unexpected TLS version value (%d)", *(int*)value);
int_value = -1;
}
if (int_value < 0)
{
result = NULL;
}
else
{
int* value_clone;
if ((value_clone = (int*)malloc(sizeof(int))) == NULL)
{
LogError("Failed clonning tls_version option");
}
else
{
*value_clone = int_value;
}
result = value_clone;
}
}
else if (
(strcmp(name, "tls_validation_callback") == 0) ||
(strcmp(name, "tls_validation_callback_data") == 0)
)
{
result = (void*)value;
}
else
{
LogError("not handled option : %s", name);
result = NULL;
}
}
return result;
}
/*this function destroys an option previously created*/
static void tlsio_openssl_DestroyOption(const char* name, const void* value)
{
/*since all options for this layer are actually string copies., disposing of one is just calling free*/
if (
(name == NULL) || (value == NULL)
)
{
LogError("invalid parameter detected: const char* name=%p, const void* value=%p", name, value);
}
else
{
if (
(strcmp(name, OPTION_TRUSTED_CERT) == 0) ||
(strcmp(name, OPTION_OPENSSL_CIPHER_SUITE) == 0) ||
(strcmp(name, SU_OPTION_X509_CERT) == 0) ||
(strcmp(name, SU_OPTION_X509_PRIVATE_KEY) == 0) ||
(strcmp(name, OPTION_X509_ECC_CERT) == 0) ||
(strcmp(name, OPTION_X509_ECC_KEY) == 0) ||
(strcmp(name, OPTION_TLS_VERSION) == 0)
)
{
free((void*)value);
}
else if (
(strcmp(name, "tls_validation_callback") == 0) ||
(strcmp(name, "tls_validation_callback_data") == 0)
)
{
// nothing to free.
}
else if (strcmp(name, OPTION_UNDERLYING_IO_OPTIONS) == 0)
{
OptionHandler_Destroy((OPTIONHANDLER_HANDLE)value);
}
else
{
LogError("not handled option : %s", name);
}
}
}
static OPTIONHANDLER_HANDLE tlsio_openssl_retrieveoptions(CONCRETE_IO_HANDLE handle)
{
OPTIONHANDLER_HANDLE result;
if (handle == NULL)
{
LogError("invalid parameter detected: CONCRETE_IO_HANDLE handle=%p", handle);
result = NULL;
}
else
{
result = OptionHandler_Create(tlsio_openssl_CloneOption, tlsio_openssl_DestroyOption, tlsio_openssl_setoption);
if (result == NULL)
{
LogError("unable to OptionHandler_Create");
/*return as is*/
}
else
{
/*this layer cares about the certificates and the x509 credentials*/
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)handle;
OPTIONHANDLER_HANDLE underlying_io_options;
if ((underlying_io_options = xio_retrieveoptions(tls_io_instance->underlying_io)) == NULL ||
OptionHandler_AddOption(result, OPTION_UNDERLYING_IO_OPTIONS, underlying_io_options) != OPTIONHANDLER_OK)
{
LogError("unable to save underlying_io options");
OptionHandler_Destroy(underlying_io_options);
OptionHandler_Destroy(result);
result = NULL;
}
else if (
(tls_io_instance->certificate != NULL) &&
(OptionHandler_AddOption(result, OPTION_TRUSTED_CERT, tls_io_instance->certificate) != OPTIONHANDLER_OK)
)
{
LogError("unable to save TrustedCerts option");
OptionHandler_Destroy(result);
result = NULL;
}
else if (
(tls_io_instance->cipher_list != NULL) &&
(OptionHandler_AddOption(result, OPTION_OPENSSL_CIPHER_SUITE, tls_io_instance->cipher_list) != OPTIONHANDLER_OK)
)
{
LogError("unable to save CipherSuite option");
OptionHandler_Destroy(result);
result = NULL;
}
else if (tls_io_instance->x509_certificate != NULL && (OptionHandler_AddOption(result, SU_OPTION_X509_CERT, tls_io_instance->x509_certificate) != OPTIONHANDLER_OK) )
{
LogError("unable to save x509 certificate option");
OptionHandler_Destroy(result);
result = NULL;
}
else if (tls_io_instance->x509_private_key != NULL && (OptionHandler_AddOption(result, SU_OPTION_X509_PRIVATE_KEY, tls_io_instance->x509_private_key) != OPTIONHANDLER_OK) )
{
LogError("unable to save x509 privatekey option");
OptionHandler_Destroy(result);
result = NULL;
}
else if (tls_io_instance->tls_version != 0)
{
if (OptionHandler_AddOption(result, OPTION_TLS_VERSION, &tls_io_instance->tls_version) != OPTIONHANDLER_OK)
{
LogError("unable to save tls_version option");
OptionHandler_Destroy(result);
result = NULL;
}
}
else if (tls_io_instance->tls_validation_callback != NULL)
{
#ifdef WIN32
#pragma warning(push)
#pragma warning(disable:4152)
#endif
void* ptr = tls_io_instance->tls_validation_callback;
#ifdef WIN32
#pragma warning(pop)
#endif
if (OptionHandler_AddOption(result, "tls_validation_callback", (const char*)ptr) != OPTIONHANDLER_OK)
{
LogError("unable to save tls_validation_callback option");
OptionHandler_Destroy(result);
result = NULL;
}
if (OptionHandler_AddOption(result, "tls_validation_callback_data", (const char*)tls_io_instance->tls_validation_callback_data) != OPTIONHANDLER_OK)
{
LogError("unable to save tls_validation_callback_data option");
OptionHandler_Destroy(result);
result = NULL;
}
}
else
{
/*all is fine, all interesting options have been saved*/
/*return as is*/
}
}
}
return result;
}
static const IO_INTERFACE_DESCRIPTION tlsio_openssl_interface_description =
{
tlsio_openssl_retrieveoptions,
tlsio_openssl_create,
tlsio_openssl_destroy,
tlsio_openssl_open,
tlsio_openssl_close,
tlsio_openssl_send,
tlsio_openssl_dowork,
tlsio_openssl_setoption
};
static LOCK_HANDLE * openssl_locks = NULL;
static void openssl_lock_unlock_helper(LOCK_HANDLE lock, int lock_mode, const char* file, int line)
{
#ifdef NO_LOGGING
// Avoid unused variable warning when logging not compiled in
(void)file;
(void)line;
#endif
if (lock_mode & CRYPTO_LOCK)
{
if (Lock(lock) != 0)
{
LogError("Failed to lock openssl lock (%s:%d)", file, line);
}
}
else
{
if (Unlock(lock) != 0)
{
LogError("Failed to unlock openssl lock (%s:%d)", file, line);
}
}
}
static void log_ERR_get_error(const char* message)
{
char buf[128];
AZURE_UNREFERENCED_PARAMETER(buf);
unsigned long error;
int i;
if (message != NULL)
{
LogError("%s", message);
}
error = ERR_get_error();
for (i = 0; 0 != error; i++)
{
LogError(" [%d] %s", i, ERR_error_string(error, buf));
error = ERR_get_error();
}
}
static STATIC_VAR_UNUSED struct CRYPTO_dynlock_value* openssl_dynamic_locks_create_cb(const char* file, int line)
{
#ifdef NO_LOGGING
// Avoid unused variable warning when logging not compiled in
(void)file;
(void)line;
#endif
struct CRYPTO_dynlock_value* result;
result = malloc(sizeof(struct CRYPTO_dynlock_value));
if (result == NULL)
{
LogError("Failed to allocate lock! Out of memory (%s:%d).", file, line);
}
else
{
result->lock = Lock_Init();
if (result->lock == NULL)
{
LogError("Failed to create lock for dynamic lock (%s:%d).", file, line);
free(result);
result = NULL;
}
}
return result;
}
static STATIC_VAR_UNUSED void openssl_dynamic_locks_lock_unlock_cb(int lock_mode, struct CRYPTO_dynlock_value* dynlock_value, const char* file, int line)
{
openssl_lock_unlock_helper(dynlock_value->lock, lock_mode, file, line);
}
static STATIC_VAR_UNUSED void openssl_dynamic_locks_destroy_cb(struct CRYPTO_dynlock_value* dynlock_value, const char* file, int line)
{
(void)file;
(void)line;
Lock_Deinit(dynlock_value->lock);
free(dynlock_value);
}
static void openssl_dynamic_locks_uninstall(void)
{
#if (OPENSSL_VERSION_NUMBER >= 0x00906000)
CRYPTO_set_dynlock_create_callback(NULL);
CRYPTO_set_dynlock_lock_callback(NULL);
CRYPTO_set_dynlock_destroy_callback(NULL);
#endif
}
static void openssl_dynamic_locks_install(void)
{
#if (OPENSSL_VERSION_NUMBER >= 0x00906000)
CRYPTO_set_dynlock_destroy_callback(openssl_dynamic_locks_destroy_cb);
CRYPTO_set_dynlock_lock_callback(openssl_dynamic_locks_lock_unlock_cb);
CRYPTO_set_dynlock_create_callback(openssl_dynamic_locks_create_cb);
#endif
}
static void STATIC_VAR_UNUSED openssl_static_locks_lock_unlock_cb(int lock_mode, int lock_index, const char * file, int line)
{
if (lock_index < 0 || lock_index >= CRYPTO_num_locks())
{
LogError("Bad lock index %d passed (%s:%d)", lock_index, file, line);
}
else
{
openssl_lock_unlock_helper(openssl_locks[lock_index], lock_mode, file, line);
}
}
static void openssl_static_locks_uninstall(void)
{
if (openssl_locks != NULL)
{
int i;
CRYPTO_set_locking_callback(NULL);
for (i = 0; i < CRYPTO_num_locks(); i++)
{
if (openssl_locks[i] != NULL)
{
Lock_Deinit(openssl_locks[i]);
}
}
free(openssl_locks);
openssl_locks = NULL;
}
else
{
LogError("Locks already uninstalled");
}
}
static int openssl_static_locks_install(void)
{
int result;
if (openssl_locks != NULL)
{
LogError("Locks already initialized");
result = MU_FAILURE;
}
else
{
openssl_locks = malloc(CRYPTO_num_locks() * sizeof(LOCK_HANDLE));
if (openssl_locks == NULL)
{
LogError("Failed to allocate locks");
result = MU_FAILURE;
}
else
{
int i;
for (i = 0; i < CRYPTO_num_locks(); i++)
{
openssl_locks[i] = Lock_Init();
if (openssl_locks[i] == NULL)
{
LogError("Failed to allocate lock %d", i);
break;
}
}
if (i != CRYPTO_num_locks())
{
int j;
for (j = 0; j < i; j++)
{
Lock_Deinit(openssl_locks[j]);
}
result = MU_FAILURE;
}
else
{
CRYPTO_set_locking_callback(openssl_static_locks_lock_unlock_cb);
result = 0;
}
}
}
return result;
}
static void indicate_error(TLS_IO_INSTANCE* tls_io_instance)
{
if (tls_io_instance->on_io_error == NULL)
{
LogError("NULL on_io_error.");
}
else
{
tls_io_instance->on_io_error(tls_io_instance->on_io_error_context);
}
}
static void indicate_open_complete(TLS_IO_INSTANCE* tls_io_instance, IO_OPEN_RESULT open_result)
{
if (tls_io_instance->on_io_open_complete == NULL)
{
LogError("NULL on_io_open_complete.");
}
else
{
tls_io_instance->on_io_open_complete(tls_io_instance->on_io_open_complete_context, open_result);
}
}
static int write_outgoing_bytes(TLS_IO_INSTANCE* tls_io_instance, ON_SEND_COMPLETE on_send_complete, void* callback_context)
{
int result;
size_t pending = BIO_ctrl_pending(tls_io_instance->out_bio);
if (pending == 0)
{
result = 0;
}
else
{
unsigned char* bytes_to_send = malloc(pending);
if (bytes_to_send == NULL)
{
LogError("NULL bytes_to_send.");
result = MU_FAILURE;
}
else
{
if (BIO_read(tls_io_instance->out_bio, bytes_to_send, (int)pending) != (int)pending)
{
log_ERR_get_error("BIO_read not in pending state.");
result = MU_FAILURE;
}
else
{
if (xio_send(tls_io_instance->underlying_io, bytes_to_send, pending, on_send_complete, callback_context) != 0)
{
LogError("Error in xio_send.");
result = MU_FAILURE;
}
else
{
result = 0;
}
}
free(bytes_to_send);
}
}
return result;
}
// Non-NULL tls_io_instance is guaranteed by callers.
// We are in TLSIO_STATE_IN_HANDSHAKE when entering this method.
static void send_handshake_bytes(TLS_IO_INSTANCE* tls_io_instance)
{
int hsret;
// ERR_clear_error must be called before any call that might set an
// SSL_get_error result
ERR_clear_error();
hsret = SSL_do_handshake(tls_io_instance->ssl);
if (hsret != SSL_DO_HANDSHAKE_SUCCESS)
{
int ssl_err = SSL_get_error(tls_io_instance->ssl, hsret);
if (ssl_err != SSL_ERROR_WANT_READ && ssl_err != SSL_ERROR_WANT_WRITE)
{
if (ssl_err == SSL_ERROR_SSL)
{
LogError("%s", ERR_error_string(ERR_get_error(), NULL));
}
else
{
LogError("SSL handshake failed: %d", ssl_err);
}
tls_io_instance->tlsio_state = TLSIO_STATE_HANDSHAKE_FAILED;
}
else
{
if (write_outgoing_bytes(tls_io_instance, NULL, NULL) != 0)
{
LogError("Error in write_outgoing_bytes.");
tls_io_instance->tlsio_state = TLSIO_STATE_HANDSHAKE_FAILED;
}
}
}
else
{
tls_io_instance->tlsio_state = TLSIO_STATE_OPEN;
indicate_open_complete(tls_io_instance, IO_OPEN_OK);
}
}
static void close_openssl_instance(TLS_IO_INSTANCE* tls_io_instance)
{
if (tls_io_instance->ssl != NULL)
{
SSL_free(tls_io_instance->ssl);
tls_io_instance->ssl = NULL;
}
if (tls_io_instance->ssl_context != NULL)
{
SSL_CTX_free(tls_io_instance->ssl_context);
tls_io_instance->ssl_context = NULL;
}
}
static void on_underlying_io_close_complete(void* context)
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)context;
switch (tls_io_instance->tlsio_state)
{
default:
case TLSIO_STATE_NOT_OPEN:
case TLSIO_STATE_OPEN:
case TLSIO_STATE_IN_HANDSHAKE:
case TLSIO_STATE_HANDSHAKE_FAILED:
case TLSIO_STATE_ERROR:
break;
case TLSIO_STATE_OPENING_UNDERLYING_IO:
tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;
indicate_open_complete(tls_io_instance, IO_OPEN_ERROR);
break;
case TLSIO_STATE_CLOSING:
tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;
if (tls_io_instance->on_io_close_complete != NULL)
{
tls_io_instance->on_io_close_complete(tls_io_instance->on_io_close_complete_context);
}
break;
}
close_openssl_instance(tls_io_instance);
}
static void on_underlying_io_open_complete(void* context, IO_OPEN_RESULT open_result)
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)context;
if (tls_io_instance->tlsio_state == TLSIO_STATE_OPENING_UNDERLYING_IO)
{
if (open_result == IO_OPEN_OK)
{
tls_io_instance->tlsio_state = TLSIO_STATE_IN_HANDSHAKE;
// Begin the handshake process here. It continues in on_underlying_io_bytes_received
send_handshake_bytes(tls_io_instance);
}
else
{
tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;
indicate_open_complete(tls_io_instance, IO_OPEN_ERROR);
LogError("Invalid tlsio_state. Expected state is TLSIO_STATE_OPENING_UNDERLYING_IO.");
}
}
}
static void on_underlying_io_error(void* context)
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)context;
switch (tls_io_instance->tlsio_state)
{
default:
break;
case TLSIO_STATE_OPENING_UNDERLYING_IO:
case TLSIO_STATE_IN_HANDSHAKE:
tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;
indicate_open_complete(tls_io_instance, IO_OPEN_ERROR);
break;
case TLSIO_STATE_OPEN:
indicate_error(tls_io_instance);
break;
}
}
static int decode_ssl_received_bytes(TLS_IO_INSTANCE* tls_io_instance)
{
int result = 0;
unsigned char buffer[64];
int rcv_bytes = 1;
while (rcv_bytes > 0)
{
if (tls_io_instance->ssl == NULL)
{
LogError("SSL channel closed in decode_ssl_received_bytes.");
result = MU_FAILURE;
return result;
}
rcv_bytes = SSL_read(tls_io_instance->ssl, buffer, sizeof(buffer));
if (rcv_bytes > 0)
{
if (tls_io_instance->on_bytes_received == NULL)
{
LogError("NULL on_bytes_received.");
}
else
{
tls_io_instance->on_bytes_received(tls_io_instance->on_bytes_received_context, buffer, rcv_bytes);
}
}
}
return result;
}
static void on_underlying_io_bytes_received(void* context, const unsigned char* buffer, size_t size)
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)context;
int written = BIO_write(tls_io_instance->in_bio, buffer, (int)size);
if (written != (int)size)
{
tls_io_instance->tlsio_state = TLSIO_STATE_ERROR;
indicate_error(tls_io_instance);
log_ERR_get_error("Error in BIO_write.");
}
else
{
switch (tls_io_instance->tlsio_state)
{
default:
break;
case TLSIO_STATE_IN_HANDSHAKE:
send_handshake_bytes(tls_io_instance);
break;
case TLSIO_STATE_OPEN:
if (decode_ssl_received_bytes(tls_io_instance) != 0)
{
tls_io_instance->tlsio_state = TLSIO_STATE_ERROR;
indicate_error(tls_io_instance);
LogError("Error in decode_ssl_received_bytes.");
}
break;
}
}
}
static int add_certificate_to_store(TLS_IO_INSTANCE* tls_io_instance, const char* certValue)
{
int result = 0;
if (certValue != NULL)
{
X509_STORE* cert_store = SSL_CTX_get_cert_store(tls_io_instance->ssl_context);
if (cert_store == NULL)
{
log_ERR_get_error("failure in SSL_CTX_get_cert_store.");
result = MU_FAILURE;
}
else
{
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && (OPENSSL_VERSION_NUMBER < 0x20000000L)
const BIO_METHOD* bio_method;
#else
BIO_METHOD* bio_method;
#endif
bio_method = BIO_s_mem();
if (bio_method == NULL)
{
log_ERR_get_error("failure in BIO_s_mem");
result = MU_FAILURE;
}
else
{
BIO* cert_memory_bio = BIO_new(bio_method);
if (cert_memory_bio == NULL)
{
log_ERR_get_error("failure in BIO_new");
result = MU_FAILURE;
}
else
{
int puts_result = BIO_puts(cert_memory_bio, certValue);
if (puts_result < 0)
{
log_ERR_get_error("failure in BIO_puts");
result = MU_FAILURE;
}
else
{
if ((size_t)puts_result != strlen(certValue))
{
log_ERR_get_error("mismatching legths");
result = MU_FAILURE;
}
else
{
X509* certificate;
while ((certificate = PEM_read_bio_X509(cert_memory_bio, NULL, NULL, NULL)) != NULL)
{
if (!X509_STORE_add_cert(cert_store, certificate))
{
X509_free(certificate);
log_ERR_get_error("failure in X509_STORE_add_cert");
break;
}
X509_free(certificate);
}
if (certificate == NULL)
{
result = 0;/*all is fine*/
}
else
{
/*previous while loop terminated unfortunately*/
result = MU_FAILURE;
}
}
}
BIO_free(cert_memory_bio);
}
}
}
}
return result;
}
static int create_openssl_instance(TLS_IO_INSTANCE* tlsInstance)
{
int result;
const SSL_METHOD* method = NULL;
#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || (OPENSSL_VERSION_NUMBER >= 0x20000000L)
if (tlsInstance->tls_version == VERSION_1_2)
{
method = TLSv1_2_method();
}
else if (tlsInstance->tls_version == VERSION_1_1)
{
method = TLSv1_1_method();
}
else
{
method = TLSv1_method();
}
#else
{
method = TLS_method();
}
#endif
tlsInstance->ssl_context = SSL_CTX_new(method);
if (tlsInstance->ssl_context == NULL)
{
log_ERR_get_error("Failed allocating OpenSSL context.");
result = MU_FAILURE;
}
else if ((tlsInstance->cipher_list != NULL) &&
(SSL_CTX_set_cipher_list(tlsInstance->ssl_context, tlsInstance->cipher_list)) != 1)
{
SSL_CTX_free(tlsInstance->ssl_context);
tlsInstance->ssl_context = NULL;
log_ERR_get_error("unable to set cipher list.");
result = MU_FAILURE;
}
else if (add_certificate_to_store(tlsInstance, tlsInstance->certificate) != 0)
{
SSL_CTX_free(tlsInstance->ssl_context);
tlsInstance->ssl_context = NULL;
log_ERR_get_error("unable to add_certificate_to_store.");
result = MU_FAILURE;
}
/*x509 authentication can only be build before underlying connection is realized*/
else if (
(tlsInstance->x509_certificate != NULL) &&
(tlsInstance->x509_private_key != NULL) &&
(x509_openssl_add_credentials(tlsInstance->ssl_context, tlsInstance->x509_certificate, tlsInstance->x509_private_key) != 0)
)
{
SSL_CTX_free(tlsInstance->ssl_context);
tlsInstance->ssl_context = NULL;
log_ERR_get_error("unable to use x509 authentication");
result = MU_FAILURE;
}
else
{
SSL_CTX_set_cert_verify_callback(tlsInstance->ssl_context, tlsInstance->tls_validation_callback, tlsInstance->tls_validation_callback_data);
tlsInstance->in_bio = BIO_new(BIO_s_mem());
if (tlsInstance->in_bio == NULL)
{
SSL_CTX_free(tlsInstance->ssl_context);
tlsInstance->ssl_context = NULL;
log_ERR_get_error("Failed BIO_new for in BIO.");
result = MU_FAILURE;
}
else
{
tlsInstance->out_bio = BIO_new(BIO_s_mem());
if (tlsInstance->out_bio == NULL)
{
(void)BIO_free(tlsInstance->in_bio);
SSL_CTX_free(tlsInstance->ssl_context);
tlsInstance->ssl_context = NULL;
log_ERR_get_error("Failed BIO_new for out BIO.");
result = MU_FAILURE;
}
else
{
if ((BIO_set_mem_eof_return(tlsInstance->in_bio, -1) <= 0) ||
(BIO_set_mem_eof_return(tlsInstance->out_bio, -1) <= 0))
{
(void)BIO_free(tlsInstance->in_bio);
(void)BIO_free(tlsInstance->out_bio);
SSL_CTX_free(tlsInstance->ssl_context);
tlsInstance->ssl_context = NULL;
LogError("Failed BIO_set_mem_eof_return.");
result = MU_FAILURE;
}
else
{
SSL_CTX_set_verify(tlsInstance->ssl_context, SSL_VERIFY_PEER, NULL);
// Specifies that the default locations for which CA certificates are loaded should be used.
if (SSL_CTX_set_default_verify_paths(tlsInstance->ssl_context) != 1)
{
// This is only a warning to the user. They can still specify the certificate via SetOption.
LogInfo("WARNING: Unable to specify the default location for CA certificates on this platform.");
}
tlsInstance->ssl = SSL_new(tlsInstance->ssl_context);
if (tlsInstance->ssl == NULL)
{
(void)BIO_free(tlsInstance->in_bio);
(void)BIO_free(tlsInstance->out_bio);
SSL_CTX_free(tlsInstance->ssl_context);
tlsInstance->ssl_context = NULL;
log_ERR_get_error("Failed creating OpenSSL instance.");
result = MU_FAILURE;
}
else
{
SSL_set_bio(tlsInstance->ssl, tlsInstance->in_bio, tlsInstance->out_bio);
SSL_set_connect_state(tlsInstance->ssl);
result = 0;
}
}
}
}
}
return result;
}
int tlsio_openssl_init(void)
{
(void)SSL_library_init();
SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();
if (openssl_static_locks_install() != 0)
{
LogError("Failed to install static locks in OpenSSL!");
return MU_FAILURE;
}
openssl_dynamic_locks_install();
return 0;
}
void tlsio_openssl_deinit(void)
{
openssl_dynamic_locks_uninstall();
openssl_static_locks_uninstall();
#if (OPENSSL_VERSION_NUMBER >= 0x00907000L) && (OPENSSL_VERSION_NUMBER < 0x20000000L) && (FIPS_mode_set)
FIPS_mode_set(0);
#endif
CRYPTO_set_locking_callback(NULL);
CRYPTO_set_id_callback(NULL);
ERR_free_strings();
EVP_cleanup();
#if (OPENSSL_VERSION_NUMBER < 0x10000000L)
ERR_remove_state(0);
#elif (OPENSSL_VERSION_NUMBER < 0x10100000L) || (OPENSSL_VERSION_NUMBER >= 0x20000000L)
ERR_remove_thread_state(NULL);
#endif
#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) && (OPENSSL_VERSION_NUMBER < 0x10010000L) && (SSL_COMP_free_compression_methods)
SSL_COMP_free_compression_methods();
#endif
CRYPTO_cleanup_all_ex_data();
}
CONCRETE_IO_HANDLE tlsio_openssl_create(void* io_create_parameters)
{
TLSIO_CONFIG* tls_io_config = io_create_parameters;
TLS_IO_INSTANCE* result;
if (tls_io_config == NULL)
{
result = NULL;
LogError("NULL tls_io_config.");
}
else
{
result = malloc(sizeof(TLS_IO_INSTANCE));
if (result == NULL)
{
LogError("Failed allocating TLSIO instance.");
}
else
{
SOCKETIO_CONFIG socketio_config;
const IO_INTERFACE_DESCRIPTION* underlying_io_interface;
void* io_interface_parameters;
if (tls_io_config->underlying_io_interface != NULL)
{
underlying_io_interface = tls_io_config->underlying_io_interface;
io_interface_parameters = tls_io_config->underlying_io_parameters;
}
else
{
socketio_config.hostname = tls_io_config->hostname;
socketio_config.port = tls_io_config->port;
socketio_config.accepted_socket = NULL;
underlying_io_interface = socketio_get_interface_description();
io_interface_parameters = &socketio_config;
}
if (underlying_io_interface == NULL)
{
free(result);
result = NULL;
LogError("Failed getting socket IO interface description.");
}
else
{
result->certificate = NULL;
result->cipher_list = NULL;
result->in_bio = NULL;
result->out_bio = NULL;
result->on_bytes_received = NULL;
result->on_bytes_received_context = NULL;
result->on_io_open_complete = NULL;
result->on_io_open_complete_context = NULL;
result->on_io_close_complete = NULL;
result->on_io_close_complete_context = NULL;
result->on_io_error = NULL;
result->on_io_error_context = NULL;
result->ssl = NULL;
result->ssl_context = NULL;
result->tls_validation_callback = NULL;
result->tls_validation_callback_data = NULL;
result->x509_certificate = NULL;
result->x509_private_key = NULL;
result->tls_version = VERSION_1_2;
result->underlying_io = xio_create(underlying_io_interface, io_interface_parameters);
if (result->underlying_io == NULL)
{
free(result);
result = NULL;
LogError("Failed xio_create.");
}
else
{
result->tlsio_state = TLSIO_STATE_NOT_OPEN;
}
}
}
}
return result;
}
void tlsio_openssl_destroy(CONCRETE_IO_HANDLE tls_io)
{
if (tls_io == NULL)
{
LogError("NULL tls_io.");
}
else
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
if (tls_io_instance->certificate != NULL)
{
free(tls_io_instance->certificate);
tls_io_instance->certificate = NULL;
}
if (tls_io_instance->cipher_list != NULL)
{
free(tls_io_instance->cipher_list);
tls_io_instance->cipher_list = NULL;
}
free((void*)tls_io_instance->x509_certificate);
free((void*)tls_io_instance->x509_private_key);
close_openssl_instance(tls_io_instance);
if (tls_io_instance->underlying_io != NULL)
{
xio_destroy(tls_io_instance->underlying_io);
tls_io_instance->underlying_io = NULL;
}
free(tls_io);
}
}
int tlsio_openssl_open(CONCRETE_IO_HANDLE tls_io, ON_IO_OPEN_COMPLETE on_io_open_complete, void* on_io_open_complete_context, ON_BYTES_RECEIVED on_bytes_received, void* on_bytes_received_context, ON_IO_ERROR on_io_error, void* on_io_error_context)
{
int result;
if (tls_io == NULL)
{
result = MU_FAILURE;
LogError("NULL tls_io.");
}
else
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
if (tls_io_instance->tlsio_state != TLSIO_STATE_NOT_OPEN)
{
LogError("Invalid tlsio_state. Expected state is TLSIO_STATE_NOT_OPEN.");
result = MU_FAILURE;
}
else
{
tls_io_instance->on_io_open_complete = on_io_open_complete;
tls_io_instance->on_io_open_complete_context = on_io_open_complete_context;
tls_io_instance->on_bytes_received = on_bytes_received;
tls_io_instance->on_bytes_received_context = on_bytes_received_context;
tls_io_instance->on_io_error = on_io_error;
tls_io_instance->on_io_error_context = on_io_error_context;
tls_io_instance->tlsio_state = TLSIO_STATE_OPENING_UNDERLYING_IO;
if (create_openssl_instance(tls_io_instance) != 0)
{
LogError("Failed creating the OpenSSL instance.");
tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;
result = MU_FAILURE;
}
else if (xio_open(tls_io_instance->underlying_io, on_underlying_io_open_complete, tls_io_instance,
on_underlying_io_bytes_received, tls_io_instance, on_underlying_io_error, tls_io_instance) != 0)
{
LogError("Failed opening the underlying I/O.");
close_openssl_instance(tls_io_instance);
tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;
result = MU_FAILURE;
}
else
{
result = 0;
}
}
}
return result;
}
/* Codes_SRS_TLSIO_30_009: [ The phrase "enter TLSIO_STATE_EXT_CLOSING" means the adapter shall iterate through any unsent messages in the queue and shall delete each message after calling its on_send_complete with the associated callback_context and IO_SEND_CANCELLED. ]*/
/* Codes_SRS_TLSIO_30_006: [ The phrase "enter TLSIO_STATE_EXT_CLOSED" means the adapter shall forcibly close any existing connections then call the on_io_close_complete function and pass the on_io_close_complete_context that was supplied in tlsio_close_async. ]*/
/* Codes_SRS_TLSIO_30_051: [ On success, if the underlying TLS does not support asynchronous closing, then the adapter shall enter TLSIO_STATE_EXT_CLOSED immediately after entering TLSIO_STATE_EX_CLOSING. ]*/
int tlsio_openssl_close(CONCRETE_IO_HANDLE tls_io, ON_IO_CLOSE_COMPLETE on_io_close_complete, void* callback_context)
{
int result;
/* Codes_SRS_TLSIO_30_050: [ If the tlsio_handle parameter is NULL, tlsio_close_async shall log an error and return _FAILURE_. ]*/
if (tls_io == NULL)
{
LogError("NULL tls_io.");
result = MU_FAILURE;
}
else
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
if (tls_io_instance->tlsio_state != TLSIO_STATE_ERROR && tls_io_instance->tlsio_state != TLSIO_STATE_OPEN)
{
/* Codes_RS_TLSIO_30_053: [ If the adapter is in any state other than TLSIO_STATE_EXT_OPEN or TLSIO_STATE_EXT_ERROR then tlsio_close_async shall log that tlsio_close_async has been called and then continue normally. ]*/
// LogInfo rather than LogError because this is an unusual but not erroneous situation
LogInfo("Closing tlsio from a state other than TLSIO_STATE_EXT_OPEN or TLSIO_STATE_EXT_ERROR");
}
if (is_an_opening_state(tls_io_instance->tlsio_state))
{
/* Codes_SRS_TLSIO_30_057: [ On success, if the adapter is in TLSIO_STATE_EXT_OPENING, it shall call on_io_open_complete with the on_io_open_complete_context supplied in tlsio_open_async and IO_OPEN_CANCELLED. This callback shall be made before changing the internal state of the adapter. ]*/
tls_io_instance->on_io_open_complete(tls_io_instance->on_io_open_complete_context, IO_OPEN_CANCELLED);
}
if (tls_io_instance->tlsio_state == TLSIO_STATE_OPEN)
{
// Attempt a graceful shutdown
/* Codes_SRS_TLSIO_30_056: [ On success the adapter shall enter TLSIO_STATE_EX_CLOSING. ]*/
tls_io_instance->tlsio_state = TLSIO_STATE_CLOSING;
tls_io_instance->on_io_close_complete = on_io_close_complete;
tls_io_instance->on_io_close_complete_context = callback_context;
// xio_close is guaranteed to succeed from the open state, and the callback completes the
// transition into TLSIO_STATE_NOT_OPEN
if (xio_close(tls_io_instance->underlying_io, on_underlying_io_close_complete, tls_io_instance) != 0)
{
close_openssl_instance(tls_io_instance);
tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;
}
}
else
{
// Just force the shutdown
/* Codes_SRS_TLSIO_30_056: [ On success the adapter shall enter TLSIO_STATE_EX_CLOSING. ]*/
/* Codes_SRS_TLSIO_30_051: [ On success, if the underlying TLS does not support asynchronous closing or if the adapter is not in TLSIO_STATE_EXT_OPEN, then the adapter shall enter TLSIO_STATE_EXT_CLOSED immediately after entering TLSIO_STATE_EXT_CLOSING. ]*/
// Current implementations of xio_close will fail if not in the open state, but we don't care
(void)xio_close(tls_io_instance->underlying_io, NULL, NULL);
close_openssl_instance(tls_io_instance);
tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;
}
result = 0;
}
/* Codes_SRS_TLSIO_30_054: [ On failure, the adapter shall not call on_io_close_complete. ]*/
return result;
}
int tlsio_openssl_send(CONCRETE_IO_HANDLE tls_io, const void* buffer, size_t size, ON_SEND_COMPLETE on_send_complete, void* callback_context)
{
int result;
if (tls_io == NULL)
{
LogError("NULL tls_io.");
result = MU_FAILURE;
}
else
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
if (tls_io_instance->tlsio_state != TLSIO_STATE_OPEN)
{
LogError("Invalid tlsio_state. Expected state is TLSIO_STATE_OPEN.");
result = MU_FAILURE;
}
else
{
int res;
if (tls_io_instance->ssl == NULL)
{
LogError("SSL channel closed in tlsio_openssl_send.");
result = MU_FAILURE;
return result;
}
res = SSL_write(tls_io_instance->ssl, buffer, (int)size);
if (res != (int)size)
{
log_ERR_get_error("SSL_write error.");
result = MU_FAILURE;
}
else
{
if (write_outgoing_bytes(tls_io_instance, on_send_complete, callback_context) != 0)
{
LogError("Error in write_outgoing_bytes.");
result = MU_FAILURE;
}
else
{
result = 0;
}
}
}
}
return result;
}
void tlsio_openssl_dowork(CONCRETE_IO_HANDLE tls_io)
{
if (tls_io == NULL)
{
LogError("NULL tls_io.");
}
else
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
switch (tls_io_instance->tlsio_state)
{
case TLSIO_STATE_OPENING_UNDERLYING_IO:
case TLSIO_STATE_IN_HANDSHAKE:
case TLSIO_STATE_OPEN:
/* this is needed in order to pump out bytes produces by OpenSSL for things like renegotiation */
write_outgoing_bytes(tls_io_instance, NULL, NULL);
break;
case TLSIO_STATE_NOT_OPEN:
case TLSIO_STATE_HANDSHAKE_FAILED:
case TLSIO_STATE_CLOSING:
case TLSIO_STATE_ERROR:
default:
break;
}
if (tls_io_instance->tlsio_state != TLSIO_STATE_NOT_OPEN)
{
/* Same behavior as schannel */
xio_dowork(tls_io_instance->underlying_io);
if (tls_io_instance->tlsio_state == TLSIO_STATE_HANDSHAKE_FAILED)
{
// The handshake failed so we need to close. The tlsio becomes aware of the
// handshake failure during an on_bytes_received while the underlying
// xio_dowork is pumping data out of the socket in a while loop. The tlsio can't
// close down during the callback because that would mean the xio_dowork would
// be trying to read from a closed socket. So instead, the tlsio sets its state
// to TLSIO_STATE_HANDSHAKE_FAILED during the on_bytes_received callback,
// can then gracefully shut things down here.
//
// Set the state to TLSIO_STATE_ERROR so close won't gripe about the state
tls_io_instance->tlsio_state = TLSIO_STATE_ERROR;
tlsio_openssl_close(tls_io_instance, NULL, NULL);
indicate_open_complete(tls_io_instance, IO_OPEN_ERROR);
}
}
}
}
int tlsio_openssl_setoption(CONCRETE_IO_HANDLE tls_io, const char* optionName, const void* value)
{
int result;
if (tls_io == NULL || optionName == NULL)
{
result = MU_FAILURE;
}
else
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
if (strcmp(OPTION_TRUSTED_CERT, optionName) == 0)
{
const char* cert = (const char*)value;
size_t len;
if (tls_io_instance->certificate != NULL)
{
// Free the memory if it has been previously allocated
free(tls_io_instance->certificate);
tls_io_instance->certificate = NULL;
}
// Store the certificate
len = strlen(cert);
tls_io_instance->certificate = malloc(len + 1);
if (tls_io_instance->certificate == NULL)
{
result = MU_FAILURE;
}
else
{
strcpy(tls_io_instance->certificate, cert);
result = 0;
}
// If we're previously connected then add the cert to the context
if (tls_io_instance->ssl_context != NULL)
{
result = add_certificate_to_store(tls_io_instance, cert);
}
}
else if (strcmp(OPTION_OPENSSL_CIPHER_SUITE, optionName) == 0)
{
if (tls_io_instance->cipher_list != NULL)
{
// Free the memory if it has been previously allocated
free(tls_io_instance->cipher_list);
tls_io_instance->cipher_list = NULL;
}
// Store the cipher suites
if (mallocAndStrcpy_s((char**)&tls_io_instance->cipher_list, value) != 0)
{
LogError("unable to mallocAndStrcpy_s %s", optionName);
result = MU_FAILURE;
}
else
{
result = 0;
}
}
else if (strcmp(SU_OPTION_X509_CERT, optionName) == 0 || strcmp(OPTION_X509_ECC_CERT, optionName) == 0)
{
if (tls_io_instance->x509_certificate != NULL)
{
LogError("unable to set x509 options more than once");
result = MU_FAILURE;
}
else
{
/*let's make a copy of this option*/
if (mallocAndStrcpy_s((char**)&tls_io_instance->x509_certificate, value) != 0)
{
LogError("unable to mallocAndStrcpy_s %s", optionName);
result = MU_FAILURE;
}
else
{
result = 0;
}
}
}
else if (strcmp(SU_OPTION_X509_PRIVATE_KEY, optionName) == 0 || strcmp(OPTION_X509_ECC_KEY, optionName) == 0)
{
if (tls_io_instance->x509_private_key != NULL)
{
LogError("unable to set more than once x509 options");
result = MU_FAILURE;
}
else
{
/*let's make a copy of this option*/
if (mallocAndStrcpy_s((char**)&tls_io_instance->x509_private_key, value) != 0)
{
LogError("unable to mallocAndStrcpy_s %s", optionName);
result = MU_FAILURE;
}
else
{
result = 0;
}
}
}
else if (strcmp("tls_validation_callback", optionName) == 0)
{
#ifdef WIN32
#pragma warning(push)
#pragma warning(disable:4055)
#endif // WIN32
tls_io_instance->tls_validation_callback = (TLS_CERTIFICATE_VALIDATION_CALLBACK)value;
#ifdef WIN32
#pragma warning(pop)
#endif // WIN32
if (tls_io_instance->ssl_context != NULL)
{
SSL_CTX_set_cert_verify_callback(tls_io_instance->ssl_context, tls_io_instance->tls_validation_callback, tls_io_instance->tls_validation_callback_data);
}
result = 0;
}
else if (strcmp("tls_validation_callback_data", optionName) == 0)
{
tls_io_instance->tls_validation_callback_data = (void*)value;
if (tls_io_instance->ssl_context != NULL)
{
SSL_CTX_set_cert_verify_callback(tls_io_instance->ssl_context, tls_io_instance->tls_validation_callback, tls_io_instance->tls_validation_callback_data);
}
result = 0;
}
else if (strcmp(OPTION_TLS_VERSION, optionName) == 0)
{
if (tls_io_instance->ssl_context != NULL)
{
LogError("Unable to set the tls version after the tls connection is established");
result = MU_FAILURE;
}
else
{
const int version_option = *(const int*)value;
if (version_option == 0 || version_option == 10)
{
tls_io_instance->tls_version = VERSION_1_0;
}
else if (version_option == 11)
{
tls_io_instance->tls_version = VERSION_1_1;
}
else if (version_option == 12)
{
tls_io_instance->tls_version = VERSION_1_2;
}
else
{
LogInfo("Value of TLS version option %d is not found shall default to version 1.2", version_option);
tls_io_instance->tls_version = VERSION_1_2;
}
result = 0;
}
}
else if (strcmp(optionName, OPTION_UNDERLYING_IO_OPTIONS) == 0)
{
if (OptionHandler_FeedOptions((OPTIONHANDLER_HANDLE)value, (void*)tls_io_instance->underlying_io) != OPTIONHANDLER_OK)
{
LogError("failed feeding options to underlying I/O instance");
result = MU_FAILURE;
}
else
{
result = 0;
}
}
else if (strcmp("ignore_server_name_check", optionName) == 0)
{
result = 0;
}
else
{
if (tls_io_instance->underlying_io == NULL)
{
result = MU_FAILURE;
}
else
{
result = xio_setoption(tls_io_instance->underlying_io, optionName, value);
}
}
}
return result;
}
const IO_INTERFACE_DESCRIPTION* tlsio_openssl_get_interface_description(void)
{
return &tlsio_openssl_interface_description;
}