From 3481a7212132fe074f6a4f1941995a90440d7ffa Mon Sep 17 00:00:00 2001 From: Tomasz Maczkowski Date: Mon, 30 Sep 2024 15:41:08 +0200 Subject: [PATCH] Implemented OpenSSL providers support Signed-off-by: Tomasz Maczkowski --- CMakeLists.txt | 9 ++- src/MQTTAsync.c | 18 +++++- src/MQTTAsync.h | 11 +++- src/MQTTClient.c | 20 ++++++- src/MQTTClient.h | 9 ++- src/MQTTProtocolClient.c | 9 ++- src/SSLSocket.c | 120 ++++++++++++++++++++++++++++++++++++--- 7 files changed, 175 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 86f1f7f14..8a8b49f53 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ cmake_minimum_required(VERSION 3.5) -project("Eclipse Paho C" +project("Eclipse Paho C" VERSION 1.3.13 LANGUAGES C ) @@ -29,7 +29,7 @@ set(CMAKE_SCRIPTS "${CMAKE_SOURCE_DIR}/cmake") set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") ## Project Version -## Previously we read in the version from these files, but now we use the +## Previously we read in the version from these files, but now we use the ## CMake project setting. We just make sure the files and CMake match. file(READ version.major PAHO_VERSION_MAJOR) file(READ version.minor PAHO_VERSION_MINOR) @@ -64,6 +64,7 @@ option(PAHO_ENABLE_TESTING "Build tests and run" TRUE) option(PAHO_ENABLE_CPACK "Enable CPack" TRUE) option(PAHO_HIGH_PERFORMANCE "Disable tracing and heap tracking" FALSE) option(PAHO_USE_SELECT "Revert to select system call instead of poll" FALSE) +option(PAHO_SSL_PROVIDERS "Enable provider option for OpenSSL" FALSE) if(PAHO_HIGH_PERFORMANCE) add_definitions(-DHIGH_PERFORMANCE=1) @@ -73,6 +74,10 @@ if(PAHO_USE_SELECT) add_definitions(-DUSE_SELECT=1) endif() +if(PAHO_SSL_PROVIDERS) + add_definitions(-DOPENSSL_PROVIDERS=1) +endif() + if(PAHO_WITH_LIBUUID) add_definitions(-DUSE_LIBUUID=1) endif() diff --git a/src/MQTTAsync.c b/src/MQTTAsync.c index c381402e2..d5aa7fc07 100644 --- a/src/MQTTAsync.c +++ b/src/MQTTAsync.c @@ -599,7 +599,7 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) } if (options->struct_version != 0 && options->ssl) /* check validity of SSL options structure */ { - if (strncmp(options->ssl->struct_id, "MQTS", 4) != 0 || options->ssl->struct_version < 0 || options->ssl->struct_version > 5) + if (strncmp(options->ssl->struct_id, "MQTS", 4) != 0 || options->ssl->struct_version < 0 || options->ssl->struct_version > 6) { rc = MQTTASYNC_BAD_STRUCTURE; goto exit; @@ -757,6 +757,11 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) if (m->c->sslopts->CApath) free((void*)m->c->sslopts->CApath); } + if (m->c->sslopts->struct_version >= 6) + { + if (m->c->sslopts->providerName) + free((void*)m->c->sslopts->providerName); + } free((void*)m->c->sslopts); m->c->sslopts = NULL; } @@ -806,6 +811,17 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) m->c->sslopts->protos = (const unsigned char*)MQTTStrdup((const char*)options->ssl->protos); m->c->sslopts->protos_len = options->ssl->protos_len; } + if (m->c->sslopts->struct_version >= 6) + { + if (options->ssl->providerName) { +# if OPENSSL_PROVIDERS + m->c->sslopts->providerName = MQTTStrdup(options->ssl->providerName); +# else // OPENSSL_PROVIDERS + rc = MQTTASYNC_SSL_NOT_SUPPORTED; + goto exit; +# endif // OPENSSL_PROVIDERS + } + } } #else if (options->struct_version != 0 && options->ssl) diff --git a/src/MQTTAsync.h b/src/MQTTAsync.h index 3d597ce08..457e22e27 100644 --- a/src/MQTTAsync.h +++ b/src/MQTTAsync.h @@ -491,7 +491,7 @@ typedef struct #define MQTTAsync_connectData_initializer {{'M', 'Q', 'C', 'D'}, 0, NULL, {0, NULL}} /** - * This is a callback function which will allow the client application to update the + * This is a callback function which will allow the client application to update the * connection data. * @param data The connection data which can be modified by the application. * @return Return a non-zero value to update the connect data, zero to keep the same data. @@ -1073,12 +1073,13 @@ typedef struct /** The eyecatcher for this structure. Must be MQTS */ char struct_id[4]; - /** The version number of this structure. Must be 0, 1, 2, 3, 4 or 5. + /** The version number of this structure. Must be [0-6]. * 0 means no sslVersion * 1 means no verify, CApath * 2 means no ssl_error_context, ssl_error_cb * 3 means no ssl_psk_cb, ssl_psk_context, disableDefaultTrustStore * 4 means no protos, protos_len + * 5 means no providerName */ int struct_version; @@ -1177,9 +1178,13 @@ typedef struct * Exists only if struct_version >= 5 */ unsigned int protos_len; + + /** OpenSSL provider to used, NULL if disabled. */ + const char* providerName; + } MQTTAsync_SSLOptions; -#define MQTTAsync_SSLOptions_initializer { {'M', 'Q', 'T', 'S'}, 5, NULL, NULL, NULL, NULL, NULL, 1, MQTT_SSL_VERSION_DEFAULT, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0 } +#define MQTTAsync_SSLOptions_initializer { {'M', 'Q', 'T', 'S'}, 6, NULL, NULL, NULL, NULL, NULL, 1, MQTT_SSL_VERSION_DEFAULT, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, NULL } /** Utility structure where name/value pairs are needed */ typedef struct diff --git a/src/MQTTClient.c b/src/MQTTClient.c index b80b0c082..21ae73fed 100644 --- a/src/MQTTClient.c +++ b/src/MQTTClient.c @@ -1285,7 +1285,7 @@ static MQTTResponse MQTTClient_connectURIVersion(MQTTClient handle, MQTTClient_c setSocketForSSLrc = SSLSocket_setSocketForSSL(&m->c->net, m->c->sslopts, serverURI, hostname_len); - if (setSocketForSSLrc != MQTTCLIENT_SUCCESS) + if (1 == setSocketForSSLrc) { if (m->c->session != NULL) if ((rc = SSL_set_session(m->c->net.ssl, m->c->session)) != 1) @@ -1618,6 +1618,11 @@ static MQTTResponse MQTTClient_connectURI(MQTTClient handle, MQTTClient_connectO if (m->c->sslopts->CApath) free((void*)m->c->sslopts->CApath); } + if (m->c->sslopts->struct_version >= 6) + { + if (m->c->sslopts->providerName) + free((void*)m->c->sslopts->providerName); + } free(m->c->sslopts); m->c->sslopts = NULL; } @@ -1666,6 +1671,17 @@ static MQTTResponse MQTTClient_connectURI(MQTTClient handle, MQTTClient_connectO m->c->sslopts->protos = options->ssl->protos; m->c->sslopts->protos_len = options->ssl->protos_len; } + if (m->c->sslopts->struct_version >= 6) + { + if (options->ssl->providerName) { +# if OPENSSL_PROVIDERS + m->c->sslopts->providerName = MQTTStrdup(options->ssl->providerName); +# else // OPENSSL_PROVIDERS + rc.reasonCode = MQTTCLIENT_SSL_NOT_SUPPORTED; + goto exit; +# endif // OPENSSL_PROVIDERS + } + } } #endif @@ -1818,7 +1834,7 @@ MQTTResponse MQTTClient_connectAll(MQTTClient handle, MQTTClient_connectOptions* #if defined(OPENSSL) if (options->struct_version != 0 && options->ssl) /* check validity of SSL options structure */ { - if (strncmp(options->ssl->struct_id, "MQTS", 4) != 0 || options->ssl->struct_version < 0 || options->ssl->struct_version > 5) + if (strncmp(options->ssl->struct_id, "MQTS", 4) != 0 || options->ssl->struct_version < 0 || options->ssl->struct_version > 6) { rc.reasonCode = MQTTCLIENT_BAD_STRUCTURE; goto exit; diff --git a/src/MQTTClient.h b/src/MQTTClient.h index 38aa1836e..2b383745c 100644 --- a/src/MQTTClient.h +++ b/src/MQTTClient.h @@ -675,12 +675,13 @@ typedef struct /** The eyecatcher for this structure. Must be MQTS */ char struct_id[4]; - /** The version number of this structure. Must be 0, 1, 2, 3, 4 or 5. + /** The version number of this structure. Must be [0-6]. * 0 means no sslVersion * 1 means no verify, CApath * 2 means no ssl_error_context, ssl_error_cb * 3 means no ssl_psk_cb, ssl_psk_context, disableDefaultTrustStore * 4 means no protos, protos_len + * 5 means no providerName */ int struct_version; @@ -779,9 +780,13 @@ typedef struct * Exists only if struct_version >= 5 */ unsigned int protos_len; + + /** OpenSSL provider to used, NULL if disabled. */ + const char* providerName; + } MQTTClient_SSLOptions; -#define MQTTClient_SSLOptions_initializer { {'M', 'Q', 'T', 'S'}, 5, NULL, NULL, NULL, NULL, NULL, 1, MQTT_SSL_VERSION_DEFAULT, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0 } +#define MQTTClient_SSLOptions_initializer { {'M', 'Q', 'T', 'S'}, 5, NULL, NULL, NULL, NULL, NULL, 1, MQTT_SSL_VERSION_DEFAULT, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0 , NULL} /** * MQTTClient_libraryInfo is used to store details relating to the currently used diff --git a/src/MQTTProtocolClient.c b/src/MQTTProtocolClient.c index e4b8f16c7..2dc5c1376 100644 --- a/src/MQTTProtocolClient.c +++ b/src/MQTTProtocolClient.c @@ -345,7 +345,7 @@ int MQTTProtocol_handlePublishes(void* pack, SOCKET sock) if (publish->header.bits.qos == 1) { Protocol_processPublication(publish, client, 1); - + if (socketHasPendingWrites) rc = MQTTProtocol_queueAck(client, PUBACK, publish->msgId); else @@ -981,8 +981,13 @@ void MQTTProtocol_freeClient(Clients* client) if (client->sslopts->protos) free((void*)client->sslopts->protos); } + if (client->sslopts->struct_version >= 6) + { + if(client->sslopts->providerName) + free((void*)client->sslopts->providerName); + } free(client->sslopts); - client->sslopts = NULL; + client->sslopts = NULL; } #endif /* don't free the client structure itself... this is done elsewhere */ diff --git a/src/SSLSocket.c b/src/SSLSocket.c index a4941b60c..13e5ad9dd 100644 --- a/src/SSLSocket.c +++ b/src/SSLSocket.c @@ -44,6 +44,16 @@ #include #include +#if OPENSSL_PROVIDERS +# include +# include + +# if (OPENSSL_VERSION_NUMBER < 0x030000000lu) +# error "OpenSSL providers are only usabe with OpenSSL version 3.x.x." +# endif // (OPENSSL_VERSION_NUMBER < 0x030000000lu) + +#endif // OPENSSL_PROVIDERS + extern Sockets mod_s; static int SSLSocket_error(char* aString, SSL* ssl, SOCKET sock, int rc, int (*cb)(const char *str, size_t len, void *u), void* u); @@ -619,16 +629,108 @@ int SSLSocket_createContext(networkHandles* net, MQTTClient_SSLOptions* opts) SSL_CTX_set_default_passwd_cb_userdata(net->ctx, (void*)opts->privateKeyPassword); } +#if OPENSSL_PROVIDERS /* support for ASN.1 == DER format? DER can contain only one certificate? */ - rc = SSL_CTX_use_PrivateKey_file(net->ctx, opts->privateKey, SSL_FILETYPE_PEM); - if (opts->privateKey == opts->keyStore) - opts->privateKey = NULL; + if(opts->providerName && (opts->providerName[0] != '\0')) + { + // Load desired OpenSSL provider, e.g. tpm2. + OSSL_PROVIDER *provider = OSSL_PROVIDER_load(NULL, opts->providerName); + if(!provider) + { + rc = 0; + goto free_ctx; + } + + // Perform providers Self-Test. + rc = OSSL_PROVIDER_self_test(provider); + if(rc != 1) + { + OSSL_PROVIDER_unload(provider); + goto free_ctx; + } + + // Open STORE context with given providers handle. + OSSL_STORE_CTX *storeCtx = OSSL_STORE_open(opts->privateKey, NULL, NULL, NULL, NULL); + if (!storeCtx) { + OSSL_PROVIDER_unload(provider); + rc = 0; + goto free_ctx; + } + + rc = OSSL_STORE_expect(storeCtx, OSSL_STORE_INFO_PKEY); + if (rc != 1) { + OSSL_STORE_close(storeCtx); + OSSL_PROVIDER_unload(provider); + rc = 0; + goto free_ctx; + } + + EVP_PKEY *pkey = NULL; + while (pkey == NULL && !OSSL_STORE_eof(storeCtx)) { + // Load STORE context and get it's type. + OSSL_STORE_INFO *info = OSSL_STORE_load(storeCtx); + + // This can happen when, for example, we're reaching stuff that's not there + // or there is a thing that we don't expect. + // If happens, then we just loop until EOF. + if (info == NULL) { + continue; + } + + const int type = OSSL_STORE_INFO_get_type(info); + if (type == OSSL_STORE_INFO_PKEY) { + // If type is private key, fetch it. + pkey = OSSL_STORE_INFO_get1_PKEY(info); + } else { + break; + } + + OSSL_STORE_INFO_free(info); + } + + // If private key is invalid - exit. + if (!pkey) { + OSSL_STORE_close(storeCtx); + OSSL_PROVIDER_unload(provider); + rc = 0; + goto free_ctx; + } + + // Load private key handle to ctx. + rc = SSL_CTX_use_PrivateKey(net->ctx, pkey); + if (rc != 1) + { + if (opts->struct_version >= 3) + SSLSocket_error("SSL_CTX_use_PrivateKey", NULL, net->socket, rc, opts->ssl_error_cb, opts->ssl_error_context); + else + SSLSocket_error("SSL_CTX_use_PrivateKey", NULL, net->socket, rc, NULL, NULL); + goto free_ctx; + } + } + else +#endif // OPENSSL_PROVIDERS + { + rc = SSL_CTX_use_PrivateKey_file(net->ctx, opts->privateKey, SSL_FILETYPE_PEM); + if (opts->privateKey == opts->keyStore) + opts->privateKey = NULL; + if (rc != 1) + { + if (opts->struct_version >= 3) + SSLSocket_error("SSL_CTX_use_PrivateKey_file", NULL, net->socket, rc, opts->ssl_error_cb, opts->ssl_error_context); + else + SSLSocket_error("SSL_CTX_use_PrivateKey_file", NULL, net->socket, rc, NULL, NULL); + goto free_ctx; + } + } + + // Check if private kay matches certificate. + rc = SSL_CTX_check_private_key(net->ctx); if (rc != 1) { if (opts->struct_version >= 3) - SSLSocket_error("SSL_CTX_use_PrivateKey_file", NULL, net->socket, rc, opts->ssl_error_cb, opts->ssl_error_context); + SSLSocket_error("SSL_CTX_check_private_key", NULL, net->socket, rc, opts->ssl_error_cb, opts->ssl_error_context); else - SSLSocket_error("SSL_CTX_use_PrivateKey_file", NULL, net->socket, rc, NULL, NULL); + SSLSocket_error("SSL_CTX_check_private_key", NULL, net->socket, rc, NULL, NULL); goto free_ctx; } } @@ -721,8 +823,9 @@ int SSLSocket_setSocketForSSL(networkHandles* net, MQTTClient_SSLOptions* opts, SSL_CTX_set_info_callback(net->ctx, SSL_CTX_info_callback); SSL_CTX_set_msg_callback(net->ctx, SSL_CTX_msg_callback); - if (opts->enableServerCertAuth) + if (opts->enableServerCertAuth) { SSL_CTX_set_verify(net->ctx, SSL_VERIFY_PEER, NULL); + } net->ssl = SSL_new(net->ctx); @@ -773,8 +876,7 @@ int SSLSocket_connect(SSL* ssl, SOCKET sock, const char* hostname, int verify, i rc = SSL_connect(ssl); if (rc != 1) { - int error; - error = SSLSocket_error("SSL_connect", ssl, sock, rc, cb, u); + int error = SSLSocket_error("SSL_connect", ssl, sock, rc, cb, u); if (error == SSL_FATAL) rc = error; if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) @@ -1042,7 +1144,7 @@ int SSLSocket_putdatas(SSL* ssl, SOCKET socket, char* buf0, size_t buf0len, Pack free(bufs.buffers[i]); bufs.buffers[i] = NULL; } - } + } } exit: FUNC_EXIT_RC(rc);