mirror of https://github.com/eclipse/paho.mqtt.c
Squashed commit of the following:
commit e131ec19cc4193d13310f1fa51e1d8e83d18a8a2 Merge:eae1454fd94689Author: Ian Craggs <icraggs@gmail.com> Date: Thu Feb 12 11:48:35 2026 +0000 Merge branch 'fix-ssl-handshake-want-write' of github.com:spencerbug/paho.mqtt.c into spencerbug-fix-ssl-handshake-want-write commitfd946895c7Author: Spencer Neilan <sneilan@shoppertrak.com> Date: Fri Nov 14 08:14:20 2025 -0600 #1627 Fixed failing unit tests Signed-off-by: Spencer Neilan <jneilas@jci.com> commit2d23bf0f61Author: Spencer Neilan <sneilan@shoppertrak.com> Date: Thu Nov 13 16:22:15 2025 -0600 #1627 Added unit test integrated as test-ssl-want-write-handshake in ctest suite Signed-off-by: Spencer Neilan <jneilas@jci.com> commit7ce39d2db1Author: Spencer Neilan <sneilan@shoppertrak.com> Date: Thu Nov 13 16:21:33 2025 -0600 #1627 Fix SSL handshake WANT_WRITE handling by adding to pending-write poll set. Signed-off-by: Spencer Neilan <jneilas@jci.com> commit010c5cd44aAuthor: Spencer Neilan <sneilan@shoppertrak.com> Date: Thu Nov 13 16:25:25 2025 -0600 Merge branch 'master' of https://github.com/spencerbug/paho.mqtt.c into fix-ssl-handshake-want-write commit651b71d1beMerge:5571f9bc4381a7Author: Spencer Neilan <spencer.neilan@gmail.com> Date: Thu Nov 13 16:23:30 2025 -0600 Merge pull request #1 from eclipse-paho/master Sync
This commit is contained in:
parent
092e2c4682
commit
3c7f634a30
|
|
@ -777,53 +777,72 @@ int SSLSocket_connect(SSL* ssl, SOCKET sock, const char* hostname, int verify, i
|
|||
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)
|
||||
rc = TCPSOCKET_INTERRUPTED;
|
||||
}
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x010002000) /* 1.0.2 and later */
|
||||
else if (verify)
|
||||
{
|
||||
char* peername = NULL;
|
||||
int port;
|
||||
size_t hostname_len;
|
||||
|
||||
X509* cert = SSL_get_peer_certificate(ssl);
|
||||
hostname_len = MQTTProtocol_addressPort(hostname, &port, NULL, MQTT_DEFAULT_PORT);
|
||||
|
||||
rc = X509_check_host(cert, hostname, hostname_len, 0, &peername);
|
||||
if (rc == 1)
|
||||
Log(TRACE_PROTOCOL, -1, "peername from X509_check_host is %s", peername);
|
||||
else
|
||||
Log(TRACE_PROTOCOL, -1, "X509_check_host for hostname %.*s failed, rc %d",
|
||||
(int)hostname_len, hostname, rc);
|
||||
|
||||
if (peername != NULL)
|
||||
OPENSSL_free(peername);
|
||||
|
||||
/* 0 == fail, -1 == SSL internal error, -2 == malformed input */
|
||||
if (rc == 0 || rc == -1 || rc == -2)
|
||||
{
|
||||
char* ip_addr = malloc(hostname_len + 1);
|
||||
/* cannot use = strndup(hostname, hostname_len); here because of custom Heap */
|
||||
if (ip_addr)
|
||||
switch (error)
|
||||
{
|
||||
strncpy(ip_addr, hostname, hostname_len);
|
||||
ip_addr[hostname_len] = '\0';
|
||||
case SSL_ERROR_WANT_READ:
|
||||
rc = TCPSOCKET_INTERRUPTED;
|
||||
Socket_clearPendingWrite(sock);
|
||||
break;
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
rc = TCPSOCKET_INTERRUPTED;
|
||||
Socket_addPendingWrite(sock);
|
||||
break;
|
||||
default:
|
||||
Socket_clearPendingWrite(sock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Socket_clearPendingWrite(sock);
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x010002000) /* 1.0.2 and later */
|
||||
if (verify)
|
||||
{
|
||||
char* peername = NULL;
|
||||
int port;
|
||||
size_t hostname_len;
|
||||
|
||||
rc = X509_check_ip_asc(cert, ip_addr, 0);
|
||||
Log(TRACE_MIN, -1, "rc from X509_check_ip_asc is %d", rc);
|
||||
X509* cert = SSL_get_peer_certificate(ssl);
|
||||
hostname_len = MQTTProtocol_addressPort(hostname, &port, NULL, MQTT_DEFAULT_PORT);
|
||||
|
||||
free(ip_addr);
|
||||
rc = X509_check_host(cert, hostname, hostname_len, 0, &peername);
|
||||
if (rc == 1)
|
||||
Log(TRACE_PROTOCOL, -1, "peername from X509_check_host is %s", peername);
|
||||
else
|
||||
Log(TRACE_PROTOCOL, -1, "X509_check_host for hostname %.*s failed, rc %d",
|
||||
(int)hostname_len, hostname, rc);
|
||||
|
||||
if (peername != NULL)
|
||||
OPENSSL_free(peername);
|
||||
|
||||
/* 0 == fail, -1 == SSL internal error, -2 == malformed input */
|
||||
if (rc == 0 || rc == -1 || rc == -2)
|
||||
{
|
||||
char* ip_addr = malloc(hostname_len + 1);
|
||||
/* cannot use = strndup(hostname, hostname_len); here because of custom Heap */
|
||||
if (ip_addr)
|
||||
{
|
||||
strncpy(ip_addr, hostname, hostname_len);
|
||||
ip_addr[hostname_len] = '\0';
|
||||
|
||||
rc = X509_check_ip_asc(cert, ip_addr, 0);
|
||||
Log(TRACE_MIN, -1, "rc from X509_check_ip_asc is %d", rc);
|
||||
|
||||
free(ip_addr);
|
||||
}
|
||||
|
||||
if (rc == 0 || rc == -1 || rc == -2)
|
||||
rc = SSL_FATAL;
|
||||
}
|
||||
|
||||
if (rc == 0 || rc == -1 || rc == -2)
|
||||
rc = SSL_FATAL;
|
||||
if (cert)
|
||||
X509_free(cert);
|
||||
}
|
||||
|
||||
if (cert)
|
||||
X509_free(cert);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
|
|
|
|||
|
|
@ -1207,8 +1207,53 @@ if(PAHO_WITH_SSL OR PAHO_WITH_LIBRESSL)
|
|||
PROPERTIES TIMEOUT 540
|
||||
)
|
||||
|
||||
endif()
|
||||
endif()
|
||||
ENDIF()
|
||||
|
||||
# SSL WANT_WRITE handshake test - validates proper handling of SSL_ERROR_WANT_WRITE/WANT_READ
|
||||
IF (PAHO_BUILD_STATIC)
|
||||
ADD_EXECUTABLE(
|
||||
test_ssl_want_write-static
|
||||
test_ssl_want_write.c
|
||||
)
|
||||
|
||||
TARGET_LINK_LIBRARIES(
|
||||
test_ssl_want_write-static
|
||||
paho-mqtt3cs-static
|
||||
)
|
||||
|
||||
ADD_TEST(
|
||||
NAME test-ssl-want-write-handshake-static
|
||||
COMMAND test_ssl_want_write-static "--hostname" ${MQTT_SSL_HOSTNAME} "--client_key" "${CERTDIR}/client.pem" "--server_key" "${CERTDIR}/test-root-ca.crt"
|
||||
)
|
||||
|
||||
SET_TESTS_PROPERTIES(
|
||||
test-ssl-want-write-handshake-static
|
||||
PROPERTIES TIMEOUT 60
|
||||
)
|
||||
ENDIF()
|
||||
|
||||
IF (PAHO_BUILD_SHARED)
|
||||
ADD_EXECUTABLE(
|
||||
test_ssl_want_write
|
||||
test_ssl_want_write.c
|
||||
)
|
||||
|
||||
TARGET_LINK_LIBRARIES(
|
||||
test_ssl_want_write
|
||||
paho-mqtt3cs
|
||||
)
|
||||
|
||||
ADD_TEST(
|
||||
NAME test-ssl-want-write-handshake
|
||||
COMMAND test_ssl_want_write "--hostname" ${MQTT_SSL_HOSTNAME} "--client_key" "${CERTDIR}/client.pem" "--server_key" "${CERTDIR}/test-root-ca.crt"
|
||||
)
|
||||
|
||||
SET_TESTS_PROPERTIES(
|
||||
test-ssl-want-write-handshake
|
||||
PROPERTIES TIMEOUT 60
|
||||
)
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
|
||||
if(PAHO_BUILD_STATIC)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,229 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2024
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Unit test for SSL_ERROR_WANT_WRITE/WANT_READ handling during SSL handshake
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test for SSL_ERROR_WANT_WRITE and SSL_ERROR_WANT_READ handling in SSLSocket_connect()
|
||||
*
|
||||
* This test verifies that SSL connections complete successfully even when
|
||||
* SSL_ERROR_WANT_WRITE or SSL_ERROR_WANT_READ occur during the handshake.
|
||||
* The fix ensures proper socket polling by calling Socket_addPendingWrite()
|
||||
* when WANT_WRITE occurs.
|
||||
*/
|
||||
|
||||
#include "MQTTClient.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
|
||||
#if !defined(_WINDOWS)
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
/* Test configuration */
|
||||
struct Options
|
||||
{
|
||||
char* connection;
|
||||
char* client_key_file;
|
||||
char* server_key_file;
|
||||
int verbose;
|
||||
} options =
|
||||
{
|
||||
"ssl://localhost:18883",
|
||||
NULL,
|
||||
NULL,
|
||||
1
|
||||
};
|
||||
|
||||
void usage(void)
|
||||
{
|
||||
printf("SSL WANT_WRITE Handshake Test\n");
|
||||
printf("Options:\n");
|
||||
printf("\t--hostname <hostname> - SSL server hostname (default: ssl://localhost:18883)\n");
|
||||
printf("\t--client_key <file> - Client certificate file\n");
|
||||
printf("\t--server_key <file> - Server CA certificate file\n");
|
||||
printf("\t--verbose - Enable verbose output\n");
|
||||
printf("\t--help - This help\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void getopts(int argc, char** argv)
|
||||
{
|
||||
int count = 1;
|
||||
|
||||
while (count < argc)
|
||||
{
|
||||
if (strcmp(argv[count], "--hostname") == 0)
|
||||
{
|
||||
if (++count < argc)
|
||||
options.connection = argv[count];
|
||||
else
|
||||
usage();
|
||||
}
|
||||
else if (strcmp(argv[count], "--client_key") == 0)
|
||||
{
|
||||
if (++count < argc)
|
||||
options.client_key_file = argv[count];
|
||||
else
|
||||
usage();
|
||||
}
|
||||
else if (strcmp(argv[count], "--server_key") == 0)
|
||||
{
|
||||
if (++count < argc)
|
||||
options.server_key_file = argv[count];
|
||||
else
|
||||
usage();
|
||||
}
|
||||
else if (strcmp(argv[count], "--verbose") == 0)
|
||||
{
|
||||
options.verbose = 1;
|
||||
}
|
||||
else if (strcmp(argv[count], "--help") == 0)
|
||||
{
|
||||
usage();
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Unknown option: %s\n", argv[count]);
|
||||
usage();
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Logging */
|
||||
void MyLog(int level, char* format, ...)
|
||||
{
|
||||
static char msg_buf[512];
|
||||
va_list args;
|
||||
struct timeval ts;
|
||||
struct tm timeinfo;
|
||||
|
||||
if (!options.verbose)
|
||||
return;
|
||||
|
||||
gettimeofday(&ts, NULL);
|
||||
localtime_r(&ts.tv_sec, &timeinfo);
|
||||
strftime(msg_buf, 80, "%Y%m%d %H%M%S", &timeinfo);
|
||||
sprintf(&msg_buf[strlen(msg_buf)], ".%.3lu ", ts.tv_usec / 1000L);
|
||||
|
||||
va_start(args, format);
|
||||
vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args);
|
||||
va_end(args);
|
||||
|
||||
printf("%s\n", msg_buf);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* Test tracking */
|
||||
int tests = 0;
|
||||
int failures = 0;
|
||||
|
||||
#define ASSERT(condition, message) \
|
||||
do { \
|
||||
tests++; \
|
||||
if (!(condition)) { \
|
||||
failures++; \
|
||||
MyLog(1, "FAIL: %s (line %d): %s", __func__, __LINE__, message); \
|
||||
return -1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Test: Basic SSL connection
|
||||
*
|
||||
* Verifies that SSL connections complete successfully.
|
||||
* This validates that the SSL_ERROR_WANT_WRITE/WANT_READ handling
|
||||
* in SSLSocket_connect() works correctly.
|
||||
*/
|
||||
int test_ssl_connect_basic(void)
|
||||
{
|
||||
MQTTClient client;
|
||||
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
|
||||
MQTTClient_SSLOptions ssl_opts = MQTTClient_SSLOptions_initializer;
|
||||
int rc;
|
||||
|
||||
MyLog(1, "Test: Basic SSL connection");
|
||||
|
||||
rc = MQTTClient_create(&client, options.connection, "test_ssl_want_write",
|
||||
MQTTCLIENT_PERSISTENCE_NONE, NULL);
|
||||
ASSERT(rc == MQTTCLIENT_SUCCESS, "MQTTClient_create failed");
|
||||
|
||||
conn_opts.keepAliveInterval = 20;
|
||||
conn_opts.cleansession = 1;
|
||||
|
||||
if (options.server_key_file)
|
||||
{
|
||||
ssl_opts.trustStore = options.server_key_file;
|
||||
conn_opts.ssl = &ssl_opts;
|
||||
}
|
||||
|
||||
if (options.client_key_file)
|
||||
{
|
||||
ssl_opts.keyStore = options.client_key_file;
|
||||
}
|
||||
|
||||
MyLog(1, "Connecting to %s", options.connection);
|
||||
rc = MQTTClient_connect(client, &conn_opts);
|
||||
|
||||
if (rc != MQTTCLIENT_SUCCESS)
|
||||
{
|
||||
MyLog(1, "Connection failed: rc=%d", rc);
|
||||
MyLog(1, "Note: This is expected if no SSL broker is running on %s", options.connection);
|
||||
/* Not a test failure - just means no broker available */
|
||||
}
|
||||
else
|
||||
{
|
||||
MyLog(1, "Connection succeeded");
|
||||
MQTTClient_disconnect(client, 1000);
|
||||
}
|
||||
|
||||
MQTTClient_destroy(&client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main test runner
|
||||
*/
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
getopts(argc, argv);
|
||||
|
||||
printf("SSL Connection Test\n");
|
||||
printf("===================\n");
|
||||
printf("Connection: %s\n", options.connection);
|
||||
if (options.client_key_file)
|
||||
printf("Client Key: %s\n", options.client_key_file);
|
||||
if (options.server_key_file)
|
||||
printf("Server Key: %s\n", options.server_key_file);
|
||||
printf("\n");
|
||||
|
||||
rc = test_ssl_connect_basic();
|
||||
|
||||
printf("\nTest Results: %d tests, %d failures\n", tests, failures);
|
||||
|
||||
if (failures == 0)
|
||||
printf("SUCCESS\n");
|
||||
else
|
||||
printf("FAILURE: %d test(s) failed\n", failures);
|
||||
|
||||
return (failures == 0) ? 0 : 1;
|
||||
}
|
||||
Loading…
Reference in New Issue