diff --git a/examples/aws/awsiot.c b/examples/aws/awsiot.c index bf4c57d9c..7103441b8 100644 --- a/examples/aws/awsiot.c +++ b/examples/aws/awsiot.c @@ -51,6 +51,7 @@ #include "awsiot.h" #include "examples/mqttexample.h" #include "examples/mqttnet.h" +#include "examples/mqtt_log.h" #include /* Locals */ @@ -291,11 +292,18 @@ static int mqtt_aws_tls_verify_cb(int preverify, WOLFSSL_X509_STORE_CTX* store) PRINTF(" Rejecting cert: verification must succeed under" " WOLFSSL_NO_ASN_STRICT"); return 0; +#elif defined(WOLFMQTT_ALLOW_INSECURE_TLS) + /* Development/testing override only: strict ASN parsing drops + * Starfield Services Root CA G2 (serialNumber=0), so the chain can + * legitimately fail here. Accept anyway to keep the demo running. + * MUST NOT be defined in production - it disables authentication. */ + PRINTF(" Allowing cert anyways (WOLFMQTT_ALLOW_INSECURE_TLS)"); #else - /* Strict ASN parsing drops Starfield Services Root CA G2 - * (serialNumber=0), so chain verification can legitimately - * fail here. Keep the demo running. */ - PRINTF(" Allowing cert anyways"); + /* Reject on any verification error by default. To run the AWS IoT + * demo against the live endpoint, build with WOLFSSL_NO_ASN_STRICT + * (loads the full trust bundle) or supply a trusted chain. */ + PRINTF(" Rejecting cert: verification failed"); + return 0; #endif } @@ -352,6 +360,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, { MQTTCtx* mqttCtx = (MQTTCtx*)client->ctx; byte buf[PRINT_BUFFER_SIZE+1]; + char safebuf[PRINT_BUFFER_SIZE+1]; word32 len; (void)mqttCtx; @@ -367,7 +376,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, /* Print incoming message */ PRINTF("MQTT Message: Topic %s, Qos %d, Len %u", - buf, msg->qos, msg->total_len); + mqtt_log_sanitize(safebuf, (word32)sizeof(safebuf), (char*)buf), msg->qos, msg->total_len); } /* Print message payload */ @@ -378,7 +387,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, XMEMCPY(buf, msg->buffer, len); buf[len] = '\0'; /* Make sure its null terminated */ PRINTF("Payload (%d - %d) printing %d bytes:" LINE_END "%s", - msg->buffer_pos, msg->buffer_pos + msg->buffer_len, len, buf); + msg->buffer_pos, msg->buffer_pos + msg->buffer_len, len, mqtt_log_sanitize(safebuf, (word32)sizeof(safebuf), (char*)buf)); if (msg_done) { PRINTF("MQTT Message: Done"); @@ -395,11 +404,30 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, /* The property callback is called after decoding a packet that contains at least one property. The property list is deallocated after returning from the callback. */ +/* Copy a length-delimited, broker-controlled property string into dst and + * sanitize it for safe logging. */ +static const char* awsiot_log_prop(char* dst, word32 dstLen, + const char* src, word32 srcLen) +{ + char tmp[PRINT_BUFFER_SIZE + 1]; + word32 n = srcLen; + if (n > PRINT_BUFFER_SIZE) { + n = PRINT_BUFFER_SIZE; + } + if (src != NULL && n > 0) { + XMEMCPY(tmp, src, n); + } + tmp[n] = '\0'; + return mqtt_log_sanitize(dst, dstLen, tmp); +} + static int mqtt_property_cb(MqttClient *client, MqttProp *head, void *ctx) { MqttProp *prop = head; int rc = 0; MQTTCtx* mqttCtx; + char safebuf[PRINT_BUFFER_SIZE + 1]; + char safebuf2[PRINT_BUFFER_SIZE + 1]; if ((client == NULL) || (client->ctx == NULL)) { return MQTT_CODE_ERROR_BAD_ARG; @@ -447,14 +475,17 @@ static int mqtt_property_cb(MqttClient *client, MqttProp *head, void *ctx) break; case MQTT_PROP_REASON_STR: - PRINTF("Reason String: %.*s", - prop->data_str.len, prop->data_str.str); + PRINTF("Reason String: %s", + awsiot_log_prop(safebuf, (word32)sizeof(safebuf), + prop->data_str.str, prop->data_str.len)); break; case MQTT_PROP_USER_PROP: - PRINTF("User property: key=\"%.*s\", value=\"%.*s\"", - prop->data_str.len, prop->data_str.str, - prop->data_str2.len, prop->data_str2.str); + PRINTF("User property: key=\"%s\", value=\"%s\"", + awsiot_log_prop(safebuf, (word32)sizeof(safebuf), + prop->data_str.str, prop->data_str.len), + awsiot_log_prop(safebuf2, (word32)sizeof(safebuf2), + prop->data_str2.str, prop->data_str2.len)); break; case MQTT_PROP_ASSIGNED_CLIENT_ID: diff --git a/examples/azure/azureiothub.c b/examples/azure/azureiothub.c index b80bf97e1..23491cc38 100644 --- a/examples/azure/azureiothub.c +++ b/examples/azure/azureiothub.c @@ -65,6 +65,7 @@ #include "azureiothub.h" #include "examples/mqttexample.h" #include "examples/mqttnet.h" +#include "examples/mqtt_log.h" /* Locals */ static int mStopRead = 0; @@ -134,6 +135,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, { MQTTCtx* mqttCtx = (MQTTCtx*)client->ctx; byte buf[PRINT_BUFFER_SIZE+1]; + char safebuf[PRINT_BUFFER_SIZE+1]; word32 len; (void)mqttCtx; @@ -149,7 +151,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, /* Print incoming message */ PRINTF("MQTT Message: Topic %s, Qos %d, Len %u", - buf, msg->qos, msg->total_len); + mqtt_log_sanitize(safebuf, (word32)sizeof(safebuf), (char*)buf), msg->qos, msg->total_len); } /* Print message payload */ @@ -160,7 +162,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, XMEMCPY(buf, msg->buffer, len); buf[len] = '\0'; /* Make sure its null terminated */ PRINTF("Payload (%d - %d) printing %d bytes:" LINE_END "%s", - msg->buffer_pos, msg->buffer_pos + msg->buffer_len, len, buf); + msg->buffer_pos, msg->buffer_pos + msg->buffer_len, len, mqtt_log_sanitize(safebuf, (word32)sizeof(safebuf), (char*)buf)); if (msg_done) { PRINTF("MQTT Message: Done"); diff --git a/examples/firmware/fwclient.c b/examples/firmware/fwclient.c index d90fcb509..3b9ad37e0 100644 --- a/examples/firmware/fwclient.c +++ b/examples/firmware/fwclient.c @@ -118,13 +118,29 @@ static int fw_message_process(MQTTCtx *mqttCtx, byte* buffer, word32 len) #ifdef ENABLE_FIRMWARE_SIG ecc_key eccKey; #endif - word32 check_len = sizeof(FirmwareHeader) + header->sigLen + - header->pubKeyLen + header->fwLen; + word32 remaining; - /* Verify entire message was received */ - if (len != check_len) { - PRINTF("Message header vs. actual size mismatch! %d != %d", - len, check_len); + /* Validate field sizes sequentially; a summed length check can overflow. */ + if (len < sizeof(FirmwareHeader)) { + PRINTF("Message smaller than firmware header! %u", (unsigned int)len); + return EXIT_FAILURE; + } + remaining = len - sizeof(FirmwareHeader); + if (header->sigLen > remaining) { + PRINTF("Firmware sigLen exceeds message! %u", + (unsigned int)header->sigLen); + return EXIT_FAILURE; + } + remaining -= header->sigLen; + if (header->pubKeyLen > remaining) { + PRINTF("Firmware pubKeyLen exceeds message! %u", + (unsigned int)header->pubKeyLen); + return EXIT_FAILURE; + } + remaining -= header->pubKeyLen; + if (header->fwLen != remaining) { + PRINTF("Message header vs. actual size mismatch! %u != %u", + (unsigned int)header->fwLen, (unsigned int)remaining); return EXIT_FAILURE; } @@ -171,11 +187,14 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, byte msg_new, byte msg_done) { MQTTCtx* mqttCtx = (MQTTCtx*)client->ctx; + size_t topic_len = XSTRLEN(mqttCtx->topic_name); - /* Verify this message is for the firmware topic */ + /* Verify this message is for the firmware topic. Compare lengths without + * narrowing to word16 so an overlong configured topic cannot alias a + * 16-bit received length and over-read the length-delimited topic_name. */ if (msg_new && - XSTRNCMP(msg->topic_name, mqttCtx->topic_name, - msg->topic_name_len) == 0 && + (size_t)msg->topic_name_len == topic_len && + XMEMCMP(msg->topic_name, mqttCtx->topic_name, topic_len) == 0 && !mFwBuf) { /* Allocate buffer for entire message */ diff --git a/examples/mqtt_log.h b/examples/mqtt_log.h index 06ae8a6a6..2d6d8ee23 100644 --- a/examples/mqtt_log.h +++ b/examples/mqtt_log.h @@ -40,7 +40,7 @@ extern "C" { #define MQTT_LOG_MAYBE_UNUSED #endif -/* Sanitize an untrusted string for safe logging (CWE-117 defense). +/* Sanitize an untrusted string for safe logging. * * MQTT/MQTT-SN strings such as a REGISTER topic name are controlled by the * remote peer. For MQTT-SN this peer is reachable over UDP, so an attacker who diff --git a/examples/mqttexample.c b/examples/mqttexample.c index cd667d52e..9abdd1c51 100644 --- a/examples/mqttexample.c +++ b/examples/mqttexample.c @@ -632,13 +632,24 @@ static int mqtt_tls_verify_cb(int preverify, WOLFSSL_X509_STORE_CTX* store) wolfSSL_ERR_error_string(store->error, buffer) : "none"); PRINTF(" Subject's domain name is %s", store->domain); +#ifdef WOLFMQTT_ALLOW_INSECURE_TLS + /* Development/testing override only: accept any certificate. MUST NOT be + * defined in production builds - it disables server authentication. */ if (store->error != 0) { - /* Allowing to continue */ - /* Should check certificate and return 0 if not okay */ - PRINTF(" Allowing cert anyways"); + PRINTF(" Allowing cert anyways (WOLFMQTT_ALLOW_INSECURE_TLS)"); } - return 1; +#else + /* With no CA configured there is no trust anchor to validate against + * (getting-started/demo mode), so accept and warn. When a CA is provided + * the chain-validation result is enforced so a bad certificate + * (self-signed, expired, wrong host, untrusted CA) fails the handshake. */ + if (mqttCtx != NULL && mqttCtx->ca_file == NULL) { + PRINTF(" Warning: no CA configured, skipping server authentication"); + return 1; + } + return preverify; +#endif } /* Use this callback to setup TLS certificates and verify callbacks */ diff --git a/examples/mqttnet.c b/examples/mqttnet.c index 556497bba..19450be56 100644 --- a/examples/mqttnet.c +++ b/examples/mqttnet.c @@ -856,14 +856,13 @@ mqttcurl_connect(SocketContext* sock, const char* host, word16 port, return MQTT_CODE_ERROR_CURL; } - /* Only do server host verification when not running against - * localhost broker. */ - if (XSTRCMP(host, "localhost") == 0) { - res = curl_easy_setopt(sock->curl, CURLOPT_SSL_VERIFYHOST, 0L); - } - else { - res = curl_easy_setopt(sock->curl, CURLOPT_SSL_VERIFYHOST, 2L); - } + /* Enforce server hostname verification. The cert's CN/SAN must match + * the connect host (broker_test certs carry a localhost SAN). */ +#ifdef WOLFMQTT_ALLOW_INSECURE_TLS + res = curl_easy_setopt(sock->curl, CURLOPT_SSL_VERIFYHOST, 0L); +#else + res = curl_easy_setopt(sock->curl, CURLOPT_SSL_VERIFYHOST, 2L); +#endif if (res != CURLE_OK) { PRINTF("error: curl_easy_setopt(SSL_VERIFYHOST) returned: %d", diff --git a/examples/mqttsimple/mqttsimple.c b/examples/mqttsimple/mqttsimple.c index 9b9769edc..ef23c0852 100644 --- a/examples/mqttsimple/mqttsimple.c +++ b/examples/mqttsimple/mqttsimple.c @@ -28,6 +28,7 @@ #include "wolfmqtt/mqtt_client.h" #include "examples/mqttport.h" +#include "examples/mqtt_log.h" #include "mqttsimple.h" /* Requires BSD Style Socket */ @@ -84,6 +85,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, byte msg_new, byte msg_done) { byte buf[PRINT_BUFFER_SIZE+1]; + char safebuf[PRINT_BUFFER_SIZE+1]; word32 len; (void)client; @@ -99,7 +101,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, /* Print incoming message */ PRINTF("MQTT Message: Topic %s, Qos %d, Len %u", - buf, msg->qos, msg->total_len); + mqtt_log_sanitize(safebuf, (word32)sizeof(safebuf), (char*)buf), msg->qos, msg->total_len); } /* Print message payload */ @@ -110,7 +112,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, XMEMCPY(buf, msg->buffer, len); buf[len] = '\0'; /* Make sure its null terminated */ PRINTF("Payload (%d - %d) printing %d bytes:" LINE_END "%s", - msg->buffer_pos, msg->buffer_pos + msg->buffer_len, len, buf); + msg->buffer_pos, msg->buffer_pos + msg->buffer_len, len, mqtt_log_sanitize(safebuf, (word32)sizeof(safebuf), (char*)buf)); if (msg_done) { PRINTF("MQTT Message: Done"); @@ -295,13 +297,18 @@ static int mqtt_tls_verify_cb(int preverify, WOLFSSL_X509_STORE_CTX* store) wolfSSL_ERR_error_string(store->error, buffer) : "none"); PRINTF(" Subject's domain name is %s", store->domain); +#ifdef WOLFMQTT_ALLOW_INSECURE_TLS + /* Development/testing override only: accept any certificate. MUST NOT be + * defined in production builds - it disables server authentication. */ if (store->error != 0) { - /* Allowing to continue */ - /* Should check certificate and return 0 if not okay */ - PRINTF(" Allowing cert anyways"); + PRINTF(" Allowing cert anyways (WOLFMQTT_ALLOW_INSECURE_TLS)"); } - return 1; +#else + /* Propagate wolfSSL's chain-validation result so a bad certificate + * (self-signed, expired, wrong host, untrusted CA) fails the handshake. */ + return preverify; +#endif } /* Use this callback to setup TLS certificates and verify callbacks */ diff --git a/examples/multithread/multithread.c b/examples/multithread/multithread.c index 9901a21e5..83df6bdcc 100644 --- a/examples/multithread/multithread.c +++ b/examples/multithread/multithread.c @@ -28,6 +28,7 @@ #include "multithread.h" #include "examples/mqttnet.h" +#include "examples/mqtt_log.h" #include "examples/mqttexample.h" #include @@ -165,6 +166,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, byte msg_new, byte msg_done) { byte buf[PRINT_BUFFER_SIZE+1]; + char safebuf[PRINT_BUFFER_SIZE+1]; word32 len; MQTTCtx* mqttCtx = (MQTTCtx*)client->ctx; (void)mqttCtx; @@ -181,7 +183,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, /* Print incoming message */ PRINTF("MQTT Message: Topic %s, Qos %d, Id %d, Len %u, %u, %u", - buf, msg->qos, msg->packet_id, msg->total_len, msg->buffer_len, + mqtt_log_sanitize(safebuf, (word32)sizeof(safebuf), (char*)buf), msg->qos, msg->packet_id, msg->total_len, msg->buffer_len, msg->buffer_pos); } @@ -193,7 +195,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, XMEMCPY(buf, msg->buffer, len); buf[len] = '\0'; /* Make sure its null terminated */ PRINTF("Payload (%d - %d) printing %d bytes:" LINE_END "%s", - msg->buffer_pos, msg->buffer_pos + msg->buffer_len, len, buf); + msg->buffer_pos, msg->buffer_pos + msg->buffer_len, len, mqtt_log_sanitize(safebuf, (word32)sizeof(safebuf), (char*)buf)); if (msg_done) { /* for test mode: count the number of messages received */ diff --git a/examples/websocket/net_libwebsockets.c b/examples/websocket/net_libwebsockets.c index c005acd1a..22d4bab1e 100644 --- a/examples/websocket/net_libwebsockets.c +++ b/examples/websocket/net_libwebsockets.c @@ -68,8 +68,13 @@ static int callback_mqtt(struct lws *wsi, enum lws_callback_reasons reason, else if (reason == LWS_CALLBACK_CLIENT_CONNECTION_ERROR) { net->status = -1; } - else if (reason == LWS_CALLBACK_CLOSED) { + else if (reason == LWS_CALLBACK_CLOSED || + reason == LWS_CALLBACK_CLIENT_CLOSED) { net->status = 0; + /* libwebsockets frees the wsi after this callback returns; clear the + * dangling pointer so NetWebsocket_Disconnect's `if (net->wsi)` guard + * skips lws_close_reason() on freed memory. */ + net->wsi = NULL; } else if (reason == LWS_CALLBACK_CLIENT_RECEIVE) { if (in && len > 0) { @@ -185,8 +190,13 @@ int NetWebsocket_Connect(void *ctx, const char* host, word16 port, /* Set SSL options for the connection if TLS is enabled */ if (mqttCtx && mqttCtx->use_tls) { conn_info.ssl_connection = LCCSCF_USE_SSL; - conn_info.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; - conn_info.ssl_connection |= LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK; + /* Only relax verification when no CA was supplied (dev/self-signed). + * When the operator provides a CA via -A, perform full chain and + * hostname verification rather than silently disabling it. */ + if (mqttCtx->ca_file == NULL) { + conn_info.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; + conn_info.ssl_connection |= LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK; + } } #endif /* ENABLE_MQTT_TLS */ diff --git a/examples/websocket/websocket_client.c b/examples/websocket/websocket_client.c index 46211a5dd..3db8d463b 100644 --- a/examples/websocket/websocket_client.c +++ b/examples/websocket/websocket_client.c @@ -27,6 +27,7 @@ #include "wolfmqtt/mqtt_client.h" #include "examples/mqttnet.h" #include "examples/mqttexample.h" +#include "examples/mqtt_log.h" #include "examples/websocket/net_libwebsockets.h" #include #include @@ -46,6 +47,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, byte msg_new, byte msg_done) { byte buf[PRINT_BUFFER_SIZE+1]; + char safebuf[PRINT_BUFFER_SIZE+1]; word32 len; (void)client; @@ -61,7 +63,8 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, /* Print topic */ printf("MQTT Message: Topic %s, Qos %d, Len %u", - buf, msg->qos, msg->total_len); + mqtt_log_sanitize(safebuf, (word32)sizeof(safebuf), (char*)buf), + msg->qos, msg->total_len); } /* Print message payload */ @@ -73,7 +76,8 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, buf[len] = '\0'; /* Make sure it's null terminated */ printf("Payload (%d - %d): %s\n", - msg->buffer_pos, msg->buffer_pos + msg->buffer_len, buf); + msg->buffer_pos, msg->buffer_pos + msg->buffer_len, + mqtt_log_sanitize(safebuf, (word32)sizeof(safebuf), (char*)buf)); if (msg_done) { printf("MQTT Message: Done\n"); diff --git a/examples/wiot/wiot.c b/examples/wiot/wiot.c index f426705a3..e23272aa9 100644 --- a/examples/wiot/wiot.c +++ b/examples/wiot/wiot.c @@ -35,6 +35,7 @@ #include "wiot.h" #include "examples/mqttexample.h" #include "examples/mqttnet.h" +#include "examples/mqtt_log.h" /* Locals */ static int mStopRead = 0; @@ -82,6 +83,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, byte msg_new, byte msg_done) { byte buf[PRINT_BUFFER_SIZE+1]; + char safebuf[PRINT_BUFFER_SIZE+1]; word32 len; MQTTCtx* mqttCtx = (MQTTCtx*)client->ctx; @@ -98,7 +100,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, /* Print incoming message */ PRINTF("MQTT Message: Topic %s, Qos %d, Len %u", - buf, msg->qos, msg->total_len); + mqtt_log_sanitize(safebuf, (word32)sizeof(safebuf), (char*)buf), msg->qos, msg->total_len); /* for test mode: check if TEST_MESSAGE was received */ if (mqttCtx->test_mode) { @@ -117,7 +119,7 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, XMEMCPY(buf, msg->buffer, len); buf[len] = '\0'; /* Make sure its null terminated */ PRINTF("Payload (%d - %d) printing %d bytes:" LINE_END "%s", - msg->buffer_pos, msg->buffer_pos + msg->buffer_len, len, buf); + msg->buffer_pos, msg->buffer_pos + msg->buffer_len, len, mqtt_log_sanitize(safebuf, (word32)sizeof(safebuf), (char*)buf)); if (msg_done) { PRINTF("MQTT Message: Done"); diff --git a/scripts/broker_test/ca-cert.pem b/scripts/broker_test/ca-cert.pem index bb4abe7bc..d172c8936 100644 --- a/scripts/broker_test/ca-cert.pem +++ b/scripts/broker_test/ca-cert.pem @@ -2,92 +2,92 @@ Certificate: Data: Version: 3 (0x2) Serial Number: - 6b:9b:70:c6:f1:a3:94:65:19:a1:08:58:ef:a7:8d:2b:7a:83:c1:da - Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, ST = Montana, L = Bozeman, O = Sawtooth, OU = Consulting, CN = www.wolfssl.com, emailAddress = info@wolfssl.com + b8:a8:ef:9d:53:16:38:ab + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=Montana, L=Bozeman, O=Sawtooth, OU=Consulting, CN=www.wolfssl.com/emailAddress=info@wolfssl.com Validity - Not Before: Dec 18 21:25:29 2024 GMT - Not After : Sep 14 21:25:29 2027 GMT - Subject: C = US, ST = Montana, L = Bozeman, O = Sawtooth, OU = Consulting, CN = www.wolfssl.com, emailAddress = info@wolfssl.com + Not Before: Jun 15 16:36:44 2026 GMT + Not After : Jun 12 16:36:44 2036 GMT + Subject: C=US, ST=Montana, L=Bozeman, O=Sawtooth, OU=Consulting, CN=www.wolfssl.com/emailAddress=info@wolfssl.com Subject Public Key Info: Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) + RSA Public-Key: (2048 bit) Modulus: - 00:bf:0c:ca:2d:14:b2:1e:84:42:5b:cd:38:1f:4a: - f2:4d:75:10:f1:b6:35:9f:df:ca:7d:03:98:d3:ac: - de:03:66:ee:2a:f1:d8:b0:7d:6e:07:54:0b:10:98: - 21:4d:80:cb:12:20:e7:cc:4f:de:45:7d:c9:72:77: - 32:ea:ca:90:bb:69:52:10:03:2f:a8:f3:95:c5:f1: - 8b:62:56:1b:ef:67:6f:a4:10:41:95:ad:0a:9b:e3: - a5:c0:b0:d2:70:76:50:30:5b:a8:e8:08:2c:7c:ed: - a7:a2:7a:8d:38:29:1c:ac:c7:ed:f2:7c:95:b0:95: - 82:7d:49:5c:38:cd:77:25:ef:bd:80:75:53:94:3c: - 3d:ca:63:5b:9f:15:b5:d3:1d:13:2f:19:d1:3c:db: - 76:3a:cc:b8:7d:c9:e5:c2:d7:da:40:6f:d8:21:dc: - 73:1b:42:2d:53:9c:fe:1a:fc:7d:ab:7a:36:3f:98: - de:84:7c:05:67:ce:6a:14:38:87:a9:f1:8c:b5:68: - cb:68:7f:71:20:2b:f5:a0:63:f5:56:2f:a3:26:d2: - b7:6f:b1:5a:17:d7:38:99:08:fe:93:58:6f:fe:c3: - 13:49:08:16:0b:a7:4d:67:00:52:31:67:23:4e:98: - ed:51:45:1d:b9:04:d9:0b:ec:d8:28:b3:4b:bd:ed: - 36:79 + 00:bd:af:93:11:5d:d0:f7:38:60:11:7f:4a:57:bc: + b6:9a:29:f4:7e:c8:8d:aa:1c:7c:c3:96:93:9b:38: + 1a:e5:04:c0:5d:ba:cd:65:d3:79:c5:64:0b:9d:e8: + 35:94:1f:0e:d8:96:51:99:56:76:86:ef:63:85:ea: + c1:84:9b:b7:83:1a:00:6b:6c:1b:c3:7e:1c:ce:45: + 28:7e:81:64:cd:4c:c3:cc:94:50:c9:5c:54:c8:8f: + 7b:7d:bf:55:0a:7e:33:45:19:83:cb:e3:53:9c:55: + 4a:03:ae:42:fe:90:73:0c:36:89:e4:82:bd:c4:ae: + af:d4:56:70:ce:f7:01:29:96:02:ff:68:72:52:57: + 21:3d:4b:52:36:bb:99:80:02:95:30:16:e9:d4:2f: + 6f:89:63:e7:db:d9:ad:7f:02:2d:8a:8f:f6:d3:32: + 69:38:67:89:c6:0d:4d:f1:e2:e0:c9:ef:10:96:ae: + ae:b2:46:3c:89:39:79:37:85:5f:0f:52:83:f5:cc: + 05:17:67:60:52:64:1c:99:96:4d:6f:f9:17:c6:b9: + a5:bc:6e:8f:a6:65:16:c0:db:0e:7b:62:c5:ca:cc: + 49:86:56:38:4b:b7:aa:fe:ea:45:36:09:20:5e:e9: + 52:7a:52:a0:4b:cd:19:8c:ef:c0:3b:2c:98:bb:75: + bb:ad Exponent: 65537 (0x10001) X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE X509v3 Subject Key Identifier: - 27:8E:67:11:74:C3:26:1D:3F:ED:33:63:B3:A4:D8:1D:30:E5:E8:D5 + 39:6C:89:67:B2:33:EE:AF:67:61:74:DE:9A:C2:CF:DA:4F:6D:CD:40 X509v3 Authority Key Identifier: - keyid:27:8E:67:11:74:C3:26:1D:3F:ED:33:63:B3:A4:D8:1D:30:E5:E8:D5 + keyid:39:6C:89:67:B2:33:EE:AF:67:61:74:DE:9A:C2:CF:DA:4F:6D:CD:40 DirName:/C=US/ST=Montana/L=Bozeman/O=Sawtooth/OU=Consulting/CN=www.wolfssl.com/emailAddress=info@wolfssl.com - serial:6B:9B:70:C6:F1:A3:94:65:19:A1:08:58:EF:A7:8D:2B:7A:83:C1:DA - X509v3 Basic Constraints: - CA:TRUE + serial:B8:A8:EF:9D:53:16:38:AB + + X509v3 Key Usage: critical + Certificate Sign, CRL Sign X509v3 Subject Alternative Name: DNS:example.com, IP Address:127.0.0.1 - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication Signature Algorithm: sha256WithRSAEncryption - Signature Value: - 77:3b:3d:66:74:bc:97:fe:40:16:e6:ba:a5:d5:d1:84:08:89: - 69:4f:88:0d:57:a9:ef:8c:c3:97:52:c8:bd:8b:a2:49:3b:b7: - f7:5d:1e:d6:14:7f:b2:80:33:da:a0:8a:d3:e1:2f:d5:bc:33: - 9f:ea:5a:72:24:e5:f8:b8:4b:b3:df:62:90:3b:a8:21:ef:27: - 42:75:bc:60:02:8e:37:35:99:eb:a3:28:f2:65:4c:ff:7a:f8: - 8e:cc:23:6d:e5:6a:fe:22:5a:d9:b2:4f:47:c7:e0:ae:98:ef: - 94:ac:b6:4f:61:81:29:8e:e1:79:2c:46:fc:e9:1a:c3:96:1f: - 19:93:64:2e:9f:37:72:c5:e4:93:4e:61:5f:38:8e:ae:e8:39: - 19:e6:97:a8:91:d4:23:7e:1e:d2:d0:53:ec:cc:ac:a0:1d:d0: - b7:dd:b1:b7:01:2e:96:cd:85:27:e0:e7:47:e2:c1:c1:00:f6: - 94:df:77:e7:fa:c6:ef:8a:c0:7c:67:bc:ff:a0:7c:94:3b:7d: - 86:42:af:3d:83:31:ee:2a:3b:7b:f0:2c:9e:6f:e9:c4:07:81: - 24:da:05:70:4d:dd:09:ae:9e:72:b8:21:0e:8c:b2:ab:aa:4c: - 49:10:f7:76:f9:b5:0d:6c:20:d3:df:7a:06:32:8d:29:1f:28: - 1d:8d:26:33 + 4b:52:2f:78:9d:36:de:31:d9:f9:17:25:3a:f2:b3:d5:7d:f9: + 36:87:0d:64:59:3c:21:cd:5d:58:b1:38:86:37:fd:f2:f9:77: + d7:45:f9:aa:db:81:0d:a9:62:38:14:6c:d3:26:2f:59:ed:8a: + 42:0d:ba:55:e2:93:25:b3:b9:c2:36:61:4c:de:e2:53:f3:a6: + d4:ba:d4:ca:b1:40:86:6b:60:55:8e:89:50:86:92:ca:62:62: + 36:a4:0b:e2:ef:ca:31:76:cd:dd:9f:4d:20:a6:4b:28:96:36: + fa:d5:b1:80:be:9e:df:81:2f:cf:52:98:0b:63:f9:de:fa:ea: + 41:bf:5e:43:0b:5e:55:15:a5:72:07:ab:49:c6:54:fb:c7:48: + ba:a4:31:fe:fa:e1:c7:dd:0c:c2:e8:78:10:04:a6:bd:fe:88: + 00:18:5a:d7:d4:a4:58:f8:c5:97:03:16:dc:9c:d1:ba:d1:94: + 61:87:b3:5e:45:31:40:09:21:71:ce:e9:94:fc:53:29:78:78: + a6:c6:a1:be:3e:55:6f:8e:7c:cd:8f:3f:1a:d8:d3:ac:24:57: + 34:51:2a:7d:da:a6:28:bb:e3:75:0d:8f:e3:e8:b5:32:4e:db: + 0e:c9:e3:62:5c:fd:43:2d:08:7f:25:73:83:4e:a6:fd:76:a3: + 51:d9:16:2c -----BEGIN CERTIFICATE----- -MIIE/zCCA+egAwIBAgIUa5twxvGjlGUZoQhY76eNK3qDwdowDQYJKoZIhvcNAQEL -BQAwgZQxCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdNb250YW5hMRAwDgYDVQQHDAdC -b3plbWFuMREwDwYDVQQKDAhTYXd0b290aDETMBEGA1UECwwKQ29uc3VsdGluZzEY -MBYGA1UEAwwPd3d3LndvbGZzc2wuY29tMR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdv -bGZzc2wuY29tMB4XDTI0MTIxODIxMjUyOVoXDTI3MDkxNDIxMjUyOVowgZQxCzAJ -BgNVBAYTAlVTMRAwDgYDVQQIDAdNb250YW5hMRAwDgYDVQQHDAdCb3plbWFuMREw -DwYDVQQKDAhTYXd0b290aDETMBEGA1UECwwKQ29uc3VsdGluZzEYMBYGA1UEAwwP -d3d3LndvbGZzc2wuY29tMR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdvbGZzc2wuY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvwzKLRSyHoRCW804H0ry -TXUQ8bY1n9/KfQOY06zeA2buKvHYsH1uB1QLEJghTYDLEiDnzE/eRX3Jcncy6sqQ -u2lSEAMvqPOVxfGLYlYb72dvpBBBla0Km+OlwLDScHZQMFuo6AgsfO2nonqNOCkc -rMft8nyVsJWCfUlcOM13Je+9gHVTlDw9ymNbnxW10x0TLxnRPNt2Osy4fcnlwtfa -QG/YIdxzG0ItU5z+Gvx9q3o2P5jehHwFZ85qFDiHqfGMtWjLaH9xICv1oGP1Vi+j -JtK3b7FaF9c4mQj+k1hv/sMTSQgWC6dNZwBSMWcjTpjtUUUduQTZC+zYKLNLve02 -eQIDAQABo4IBRTCCAUEwHQYDVR0OBBYEFCeOZxF0wyYdP+0zY7Ok2B0w5ejVMIHU -BgNVHSMEgcwwgcmAFCeOZxF0wyYdP+0zY7Ok2B0w5ejVoYGapIGXMIGUMQswCQYD +MIIE3TCCA8WgAwIBAgIJALio751TFjirMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD VQQGEwJVUzEQMA4GA1UECAwHTW9udGFuYTEQMA4GA1UEBwwHQm96ZW1hbjERMA8G A1UECgwIU2F3dG9vdGgxEzARBgNVBAsMCkNvbnN1bHRpbmcxGDAWBgNVBAMMD3d3 -dy53b2xmc3NsLmNvbTEfMB0GCSqGSIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbYIU -a5twxvGjlGUZoQhY76eNK3qDwdowDAYDVR0TBAUwAwEB/zAcBgNVHREEFTATggtl -eGFtcGxlLmNvbYcEfwAAATAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw -DQYJKoZIhvcNAQELBQADggEBAHc7PWZ0vJf+QBbmuqXV0YQIiWlPiA1Xqe+Mw5dS -yL2Lokk7t/ddHtYUf7KAM9qgitPhL9W8M5/qWnIk5fi4S7PfYpA7qCHvJ0J1vGAC -jjc1meujKPJlTP96+I7MI23lav4iWtmyT0fH4K6Y75Sstk9hgSmO4XksRvzpGsOW -HxmTZC6fN3LF5JNOYV84jq7oORnml6iR1CN+HtLQU+zMrKAd0LfdsbcBLpbNhSfg -50fiwcEA9pTfd+f6xu+KwHxnvP+gfJQ7fYZCrz2DMe4qO3vwLJ5v6cQHgSTaBXBN -3QmunnK4IQ6MsquqTEkQ93b5tQ1sINPfegYyjSkfKB2NJjM= +dy53b2xmc3NsLmNvbTEfMB0GCSqGSIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbTAe +Fw0yNjA2MTUxNjM2NDRaFw0zNjA2MTIxNjM2NDRaMIGUMQswCQYDVQQGEwJVUzEQ +MA4GA1UECAwHTW9udGFuYTEQMA4GA1UEBwwHQm96ZW1hbjERMA8GA1UECgwIU2F3 +dG9vdGgxEzARBgNVBAsMCkNvbnN1bHRpbmcxGDAWBgNVBAMMD3d3dy53b2xmc3Ns +LmNvbTEfMB0GCSqGSIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL2vkxFd0Pc4YBF/Sle8tpop9H7IjaocfMOW +k5s4GuUEwF26zWXTecVkC53oNZQfDtiWUZlWdobvY4XqwYSbt4MaAGtsG8N+HM5F +KH6BZM1Mw8yUUMlcVMiPe32/VQp+M0UZg8vjU5xVSgOuQv6Qcww2ieSCvcSur9RW +cM73ASmWAv9oclJXIT1LUja7mYAClTAW6dQvb4lj59vZrX8CLYqP9tMyaThnicYN +TfHi4MnvEJaurrJGPIk5eTeFXw9Sg/XMBRdnYFJkHJmWTW/5F8a5pbxuj6ZlFsDb +DntixcrMSYZWOEu3qv7qRTYJIF7pUnpSoEvNGYzvwDssmLt1u60CAwEAAaOCAS4w +ggEqMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFDlsiWeyM+6vZ2F03prCz9pP +bc1AMIHJBgNVHSMEgcEwgb6AFDlsiWeyM+6vZ2F03prCz9pPbc1AoYGapIGXMIGU +MQswCQYDVQQGEwJVUzEQMA4GA1UECAwHTW9udGFuYTEQMA4GA1UEBwwHQm96ZW1h +bjERMA8GA1UECgwIU2F3dG9vdGgxEzARBgNVBAsMCkNvbnN1bHRpbmcxGDAWBgNV +BAMMD3d3dy53b2xmc3NsLmNvbTEfMB0GCSqGSIb3DQEJARYQaW5mb0B3b2xmc3Ns +LmNvbYIJALio751TFjirMA4GA1UdDwEB/wQEAwIBBjAcBgNVHREEFTATggtleGFt +cGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAS1IveJ023jHZ+RclOvKz +1X35NocNZFk8Ic1dWLE4hjf98vl310X5qtuBDaliOBRs0yYvWe2KQg26VeKTJbO5 +wjZhTN7iU/Om1LrUyrFAhmtgVY6JUIaSymJiNqQL4u/KMXbN3Z9NIKZLKJY2+tWx +gL6e34Evz1KYC2P53vrqQb9eQwteVRWlcgerScZU+8dIuqQx/vrhx90Mwuh4EASm +vf6IABha19SkWPjFlwMW3JzRutGUYYezXkUxQAkhcc7plPxTKXh4psahvj5Vb458 +zY8/GtjTrCRXNFEqfdqmKLvjdQ2P4+i1Mk7bDsnjYlz9Qy0IfyVzg06m/XajUdkW +LA== -----END CERTIFICATE----- diff --git a/scripts/broker_test/server-cert.pem b/scripts/broker_test/server-cert.pem index 6fc3db772..950f369ca 100644 --- a/scripts/broker_test/server-cert.pem +++ b/scripts/broker_test/server-cert.pem @@ -1,185 +1,119 @@ Certificate: Data: Version: 3 (0x2) - Serial Number: 1 (0x1) - Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, ST = Montana, L = Bozeman, O = Sawtooth, OU = Consulting, CN = www.wolfssl.com, emailAddress = info@wolfssl.com + Serial Number: + fb:42:38:d1:2f:26:06:bd + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=Montana, L=Bozeman, O=Sawtooth, OU=Consulting, CN=www.wolfssl.com/emailAddress=info@wolfssl.com Validity - Not Before: Dec 18 21:25:30 2024 GMT - Not After : Sep 14 21:25:30 2027 GMT - Subject: C = US, ST = Montana, L = Bozeman, O = wolfSSL, OU = Support, CN = www.wolfssl.com, emailAddress = info@wolfssl.com + Not Before: Jun 15 16:36:44 2026 GMT + Not After : Jun 12 16:36:44 2036 GMT + Subject: C=US, ST=Montana, L=Bozeman, O=wolfSSL, OU=Support, CN=www.wolfssl.com/emailAddress=info@wolfssl.com Subject Public Key Info: Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) + RSA Public-Key: (2048 bit) Modulus: - 00:c0:95:08:e1:57:41:f2:71:6d:b7:d2:45:41:27: - 01:65:c6:45:ae:f2:bc:24:30:b8:95:ce:2f:4e:d6: - f6:1c:88:bc:7c:9f:fb:a8:67:7f:fe:5c:9c:51:75: - f7:8a:ca:07:e7:35:2f:8f:e1:bd:7b:c0:2f:7c:ab: - 64:a8:17:fc:ca:5d:7b:ba:e0:21:e5:72:2e:6f:2e: - 86:d8:95:73:da:ac:1b:53:b9:5f:3f:d7:19:0d:25: - 4f:e1:63:63:51:8b:0b:64:3f:ad:43:b8:a5:1c:5c: - 34:b3:ae:00:a0:63:c5:f6:7f:0b:59:68:78:73:a6: - 8c:18:a9:02:6d:af:c3:19:01:2e:b8:10:e3:c6:cc: - 40:b4:69:a3:46:33:69:87:6e:c4:bb:17:a6:f3:e8: - dd:ad:73:bc:7b:2f:21:b5:fd:66:51:0c:bd:54:b3: - e1:6d:5f:1c:bc:23:73:d1:09:03:89:14:d2:10:b9: - 64:c3:2a:d0:a1:96:4a:bc:e1:d4:1a:5b:c7:a0:c0: - c1:63:78:0f:44:37:30:32:96:80:32:23:95:a1:77: - ba:13:d2:97:73:e2:5d:25:c9:6a:0d:c3:39:60:a4: - b4:b0:69:42:42:09:e9:d8:08:bc:33:20:b3:58:22: - a7:aa:eb:c4:e1:e6:61:83:c5:d2:96:df:d9:d0:4f: - ad:d7 + 00:d7:6c:bd:f2:d0:bc:4f:ca:5b:57:aa:d2:e9:d3: + c0:2f:9f:b7:2c:39:15:cf:21:66:4b:d8:c3:7d:f9: + 94:46:0e:0c:73:e4:da:64:cf:d2:5d:25:6c:dc:9a: + 0b:39:36:7f:fe:31:1e:1b:77:57:66:e1:ed:a4:ac: + 91:5c:99:4a:b9:9e:77:1a:a9:e0:93:f8:c8:00:02: + 20:d0:1f:35:61:66:60:4b:20:9c:11:e4:1e:e8:ee: + 1e:3a:ef:84:ae:f4:bd:a8:97:1d:72:37:0e:c1:95: + 86:f4:fc:35:d7:fe:f1:5a:05:45:bf:12:37:ca:cb: + 0a:4a:94:cf:1d:9b:61:45:75:2f:19:83:23:01:ff: + c7:74:d6:df:51:d0:98:30:bc:6d:36:14:e9:15:64: + a2:f2:68:6a:ab:4c:b7:e5:7e:9c:64:1f:36:96:07: + e9:d0:2c:29:fc:eb:b2:7a:d1:5c:65:6e:26:1f:11: + 2c:97:22:ab:04:13:cd:9b:62:52:3e:de:c9:ff:03: + 6f:97:f7:7e:9e:b7:99:9b:78:30:3c:1a:9c:e4:ac: + e3:a9:94:78:30:bd:b6:a4:9a:cf:57:69:7f:30:b9: + f4:d0:0f:26:c1:bc:8d:82:75:cb:1a:ad:9f:c2:14: + 10:fb:49:af:78:b5:1b:f6:d0:72:83:10:b0:52:d5: + e8:43 Exponent: 65537 (0x10001) X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE X509v3 Subject Key Identifier: - B3:11:32:C9:92:98:84:E2:C9:F8:D0:3B:6E:03:42:CA:1F:0E:8E:3C + A5:12:A1:7B:37:AA:D3:17:1F:BD:DD:CB:BC:D5:64:14:82:E5:3C:43 X509v3 Authority Key Identifier: - keyid:27:8E:67:11:74:C3:26:1D:3F:ED:33:63:B3:A4:D8:1D:30:E5:E8:D5 - DirName:/C=US/ST=Montana/L=Bozeman/O=Sawtooth/OU=Consulting/CN=www.wolfssl.com/emailAddress=info@wolfssl.com - serial:6B:9B:70:C6:F1:A3:94:65:19:A1:08:58:EF:A7:8D:2B:7A:83:C1:DA - X509v3 Basic Constraints: - CA:TRUE - X509v3 Subject Alternative Name: - DNS:example.com, IP Address:127.0.0.1 + keyid:39:6C:89:67:B2:33:EE:AF:67:61:74:DE:9A:C2:CF:DA:4F:6D:CD:40 + + X509v3 Key Usage: critical + Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Subject Alternative Name: + DNS:localhost, DNS:example.com, IP Address:127.0.0.1 Signature Algorithm: sha256WithRSAEncryption - Signature Value: - 8a:f1:4e:e8:9f:59:b2:d9:13:ac:fc:42:c4:81:34:9f:6b:39: - 57:9c:e9:92:5d:41:ac:05:35:b1:26:93:4d:4a:da:f8:51:82: - d2:8d:7f:d3:5c:6e:29:80:8d:9b:02:10:2b:64:f5:d1:31:06: - fa:85:2b:8f:63:32:14:76:7a:39:15:f3:4e:dd:fd:e2:2c:90: - 15:d1:6f:73:87:ee:e6:c8:eb:ad:40:d5:e8:94:1f:a6:7e:26: - 5b:87:ba:0f:06:5a:4d:55:7a:aa:c4:09:34:8b:f7:e5:cc:d6: - b7:6c:46:6d:a1:e6:66:66:4c:4b:e5:12:31:37:54:49:64:a5: - 66:eb:e0:c6:a1:49:f8:4d:c3:d3:55:a4:05:d2:ac:fb:e1:c8: - 69:30:4b:98:fd:72:1a:ab:9f:86:eb:0d:bd:7c:a6:3d:81:d9: - 01:a7:8a:79:ab:3c:ce:e5:b6:c3:1b:ef:7d:5e:37:7b:37:7c: - 91:89:59:11:21:11:7c:05:80:e1:a8:d6:f9:35:da:1b:86:06: - 5a:32:67:6c:a9:2b:e0:31:7b:89:53:37:42:af:34:a4:53:d2: - 7c:91:50:63:3a:8e:4a:1f:a3:90:4e:7c:41:59:1d:eb:7b:a2: - 14:87:ba:76:36:a4:77:46:34:f2:55:50:f0:24:9f:83:83:da: - a6:aa:3c:c8 + 3f:31:6e:0f:da:fb:6e:01:80:61:f0:d9:90:ed:85:c2:12:f0: + 50:ae:5f:31:59:8f:36:37:f0:af:d8:02:4a:90:36:b5:c6:87: + 96:cf:5c:ba:e6:7a:f0:46:1a:eb:c2:0a:f2:50:11:67:d9:4a: + 55:98:04:41:9d:14:48:87:83:bc:9c:1b:34:4c:55:ff:75:d6: + ba:ce:6f:25:25:1f:b7:25:92:28:89:94:23:c4:62:23:6f:77: + 28:8c:59:4b:69:7c:5d:9b:98:35:b2:4d:5d:9e:d5:bc:3a:eb: + 8c:cf:81:11:5e:e3:8a:5d:c5:c8:b8:08:0a:99:75:01:e2:53: + 0c:b9:77:16:37:6b:01:dd:57:77:a5:b2:f6:a6:03:6d:73:85: + ca:54:88:4b:ae:7f:86:70:a1:c1:30:fe:b7:9a:a7:27:51:13: + f1:84:e3:2d:6c:54:ac:5f:6a:e0:e9:fc:89:59:43:e1:19:48: + 72:7a:94:5e:4f:5c:ff:ac:1c:86:97:f7:66:e6:e1:e4:8d:8b: + 7d:43:4e:ee:83:34:bc:7a:ef:c1:88:36:ad:e3:48:ae:8a:f7: + a8:64:63:90:f1:71:d4:06:93:c8:0b:24:aa:68:5f:17:6a:26: + 33:d3:48:e6:8d:e8:4d:21:fd:d7:db:68:11:11:ae:6b:a7:93: + 66:e2:ef:c9 -----BEGIN CERTIFICATE----- -MIIE6DCCA9CgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBlDELMAkGA1UEBhMCVVMx -EDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoMCFNh -d3Rvb3RoMRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29sZnNz -bC5jb20xHzAdBgkqhkiG9w0BCQEWEGluZm9Ad29sZnNzbC5jb20wHhcNMjQxMjE4 -MjEyNTMwWhcNMjcwOTE0MjEyNTMwWjCBkDELMAkGA1UEBhMCVVMxEDAOBgNVBAgM -B01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xEDAOBgNVBAoMB3dvbGZTU0wxEDAO -BgNVBAsMB1N1cHBvcnQxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTEfMB0GCSqG -SIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAMCVCOFXQfJxbbfSRUEnAWXGRa7yvCQwuJXOL07W9hyIvHyf+6hn -f/5cnFF194rKB+c1L4/hvXvAL3yrZKgX/Mpde7rgIeVyLm8uhtiVc9qsG1O5Xz/X -GQ0lT+FjY1GLC2Q/rUO4pRxcNLOuAKBjxfZ/C1loeHOmjBipAm2vwxkBLrgQ48bM -QLRpo0YzaYduxLsXpvPo3a1zvHsvIbX9ZlEMvVSz4W1fHLwjc9EJA4kU0hC5ZMMq -0KGWSrzh1Bpbx6DAwWN4D0Q3MDKWgDIjlaF3uhPSl3PiXSXJag3DOWCktLBpQkIJ -6dgIvDMgs1gip6rrxOHmYYPF0pbf2dBPrdcCAwEAAaOCAUUwggFBMB0GA1UdDgQW -BBSzETLJkpiE4sn40DtuA0LKHw6OPDCB1AYDVR0jBIHMMIHJgBQnjmcRdMMmHT/t -M2OzpNgdMOXo1aGBmqSBlzCBlDELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB01vbnRh -bmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoMCFNhd3Rvb3RoMRMwEQYDVQQL -DApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20xHzAdBgkqhkiG -9w0BCQEWEGluZm9Ad29sZnNzbC5jb22CFGubcMbxo5RlGaEIWO+njSt6g8HaMAwG -A1UdEwQFMAMBAf8wHAYDVR0RBBUwE4ILZXhhbXBsZS5jb22HBH8AAAEwHQYDVR0l -BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQCK8U7o -n1my2ROs/ELEgTSfazlXnOmSXUGsBTWxJpNNStr4UYLSjX/TXG4pgI2bAhArZPXR -MQb6hSuPYzIUdno5FfNO3f3iLJAV0W9zh+7myOutQNXolB+mfiZbh7oPBlpNVXqq -xAk0i/flzNa3bEZtoeZmZkxL5RIxN1RJZKVm6+DGoUn4TcPTVaQF0qz74chpMEuY -/XIaq5+G6w29fKY9gdkBp4p5qzzO5bbDG+99Xjd7N3yRiVkRIRF8BYDhqNb5Ndob -hgZaMmdsqSvgMXuJUzdCrzSkU9J8kVBjOo5KH6OQTnxBWR3re6IUh7p2NqR3RjTy -VVDwJJ+Dg9qmqjzI +MIIEUDCCAzigAwIBAgIJAPtCONEvJga9MA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD +VQQGEwJVUzEQMA4GA1UECAwHTW9udGFuYTEQMA4GA1UEBwwHQm96ZW1hbjERMA8G +A1UECgwIU2F3dG9vdGgxEzARBgNVBAsMCkNvbnN1bHRpbmcxGDAWBgNVBAMMD3d3 +dy53b2xmc3NsLmNvbTEfMB0GCSqGSIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbTAe +Fw0yNjA2MTUxNjM2NDRaFw0zNjA2MTIxNjM2NDRaMIGQMQswCQYDVQQGEwJVUzEQ +MA4GA1UECAwHTW9udGFuYTEQMA4GA1UEBwwHQm96ZW1hbjEQMA4GA1UECgwHd29s +ZlNTTDEQMA4GA1UECwwHU3VwcG9ydDEYMBYGA1UEAwwPd3d3LndvbGZzc2wuY29t +MR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdvbGZzc2wuY29tMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA12y98tC8T8pbV6rS6dPAL5+3LDkVzyFmS9jDffmU +Rg4Mc+TaZM/SXSVs3JoLOTZ//jEeG3dXZuHtpKyRXJlKuZ53Gqngk/jIAAIg0B81 +YWZgSyCcEeQe6O4eOu+ErvS9qJcdcjcOwZWG9Pw11/7xWgVFvxI3yssKSpTPHZth +RXUvGYMjAf/HdNbfUdCYMLxtNhTpFWSi8mhqq0y35X6cZB82lgfp0Cwp/OuyetFc +ZW4mHxEslyKrBBPNm2JSPt7J/wNvl/d+nreZm3gwPBqc5KzjqZR4ML22pJrPV2l/ +MLn00A8mwbyNgnXLGq2fwhQQ+0mveLUb9tBygxCwUtXoQwIDAQABo4GmMIGjMAkG +A1UdEwQCMAAwHQYDVR0OBBYEFKUSoXs3qtMXH73dy7zVZBSC5TxDMB8GA1UdIwQY +MBaAFDlsiWeyM+6vZ2F03prCz9pPbc1AMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE +FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwJwYDVR0RBCAwHoIJbG9jYWxob3N0ggtl +eGFtcGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAPzFuD9r7bgGAYfDZ +kO2FwhLwUK5fMVmPNjfwr9gCSpA2tcaHls9cuuZ68EYa68IK8lARZ9lKVZgEQZ0U +SIeDvJwbNExV/3XWus5vJSUftyWSKImUI8RiI293KIxZS2l8XZuYNbJNXZ7VvDrr +jM+BEV7jil3FyLgICpl1AeJTDLl3FjdrAd1Xd6Wy9qYDbXOFylSIS65/hnChwTD+ +t5qnJ1ET8YTjLWxUrF9q4On8iVlD4RlIcnqUXk9c/6wchpf3Zubh5I2LfUNO7oM0 +vHrvwYg2reNIror3qGRjkPFx1AaTyAskqmhfF2omM9NI5o3oTSH919toERGua6eT +ZuLvyQ== -----END CERTIFICATE----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 6b:9b:70:c6:f1:a3:94:65:19:a1:08:58:ef:a7:8d:2b:7a:83:c1:da - Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, ST = Montana, L = Bozeman, O = Sawtooth, OU = Consulting, CN = www.wolfssl.com, emailAddress = info@wolfssl.com - Validity - Not Before: Dec 18 21:25:29 2024 GMT - Not After : Sep 14 21:25:29 2027 GMT - Subject: C = US, ST = Montana, L = Bozeman, O = Sawtooth, OU = Consulting, CN = www.wolfssl.com, emailAddress = info@wolfssl.com - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: - 00:bf:0c:ca:2d:14:b2:1e:84:42:5b:cd:38:1f:4a: - f2:4d:75:10:f1:b6:35:9f:df:ca:7d:03:98:d3:ac: - de:03:66:ee:2a:f1:d8:b0:7d:6e:07:54:0b:10:98: - 21:4d:80:cb:12:20:e7:cc:4f:de:45:7d:c9:72:77: - 32:ea:ca:90:bb:69:52:10:03:2f:a8:f3:95:c5:f1: - 8b:62:56:1b:ef:67:6f:a4:10:41:95:ad:0a:9b:e3: - a5:c0:b0:d2:70:76:50:30:5b:a8:e8:08:2c:7c:ed: - a7:a2:7a:8d:38:29:1c:ac:c7:ed:f2:7c:95:b0:95: - 82:7d:49:5c:38:cd:77:25:ef:bd:80:75:53:94:3c: - 3d:ca:63:5b:9f:15:b5:d3:1d:13:2f:19:d1:3c:db: - 76:3a:cc:b8:7d:c9:e5:c2:d7:da:40:6f:d8:21:dc: - 73:1b:42:2d:53:9c:fe:1a:fc:7d:ab:7a:36:3f:98: - de:84:7c:05:67:ce:6a:14:38:87:a9:f1:8c:b5:68: - cb:68:7f:71:20:2b:f5:a0:63:f5:56:2f:a3:26:d2: - b7:6f:b1:5a:17:d7:38:99:08:fe:93:58:6f:fe:c3: - 13:49:08:16:0b:a7:4d:67:00:52:31:67:23:4e:98: - ed:51:45:1d:b9:04:d9:0b:ec:d8:28:b3:4b:bd:ed: - 36:79 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Subject Key Identifier: - 27:8E:67:11:74:C3:26:1D:3F:ED:33:63:B3:A4:D8:1D:30:E5:E8:D5 - X509v3 Authority Key Identifier: - keyid:27:8E:67:11:74:C3:26:1D:3F:ED:33:63:B3:A4:D8:1D:30:E5:E8:D5 - DirName:/C=US/ST=Montana/L=Bozeman/O=Sawtooth/OU=Consulting/CN=www.wolfssl.com/emailAddress=info@wolfssl.com - serial:6B:9B:70:C6:F1:A3:94:65:19:A1:08:58:EF:A7:8D:2B:7A:83:C1:DA - X509v3 Basic Constraints: - CA:TRUE - X509v3 Subject Alternative Name: - DNS:example.com, IP Address:127.0.0.1 - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - Signature Algorithm: sha256WithRSAEncryption - Signature Value: - 77:3b:3d:66:74:bc:97:fe:40:16:e6:ba:a5:d5:d1:84:08:89: - 69:4f:88:0d:57:a9:ef:8c:c3:97:52:c8:bd:8b:a2:49:3b:b7: - f7:5d:1e:d6:14:7f:b2:80:33:da:a0:8a:d3:e1:2f:d5:bc:33: - 9f:ea:5a:72:24:e5:f8:b8:4b:b3:df:62:90:3b:a8:21:ef:27: - 42:75:bc:60:02:8e:37:35:99:eb:a3:28:f2:65:4c:ff:7a:f8: - 8e:cc:23:6d:e5:6a:fe:22:5a:d9:b2:4f:47:c7:e0:ae:98:ef: - 94:ac:b6:4f:61:81:29:8e:e1:79:2c:46:fc:e9:1a:c3:96:1f: - 19:93:64:2e:9f:37:72:c5:e4:93:4e:61:5f:38:8e:ae:e8:39: - 19:e6:97:a8:91:d4:23:7e:1e:d2:d0:53:ec:cc:ac:a0:1d:d0: - b7:dd:b1:b7:01:2e:96:cd:85:27:e0:e7:47:e2:c1:c1:00:f6: - 94:df:77:e7:fa:c6:ef:8a:c0:7c:67:bc:ff:a0:7c:94:3b:7d: - 86:42:af:3d:83:31:ee:2a:3b:7b:f0:2c:9e:6f:e9:c4:07:81: - 24:da:05:70:4d:dd:09:ae:9e:72:b8:21:0e:8c:b2:ab:aa:4c: - 49:10:f7:76:f9:b5:0d:6c:20:d3:df:7a:06:32:8d:29:1f:28: - 1d:8d:26:33 -----BEGIN CERTIFICATE----- -MIIE/zCCA+egAwIBAgIUa5twxvGjlGUZoQhY76eNK3qDwdowDQYJKoZIhvcNAQEL -BQAwgZQxCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdNb250YW5hMRAwDgYDVQQHDAdC -b3plbWFuMREwDwYDVQQKDAhTYXd0b290aDETMBEGA1UECwwKQ29uc3VsdGluZzEY -MBYGA1UEAwwPd3d3LndvbGZzc2wuY29tMR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdv -bGZzc2wuY29tMB4XDTI0MTIxODIxMjUyOVoXDTI3MDkxNDIxMjUyOVowgZQxCzAJ -BgNVBAYTAlVTMRAwDgYDVQQIDAdNb250YW5hMRAwDgYDVQQHDAdCb3plbWFuMREw -DwYDVQQKDAhTYXd0b290aDETMBEGA1UECwwKQ29uc3VsdGluZzEYMBYGA1UEAwwP -d3d3LndvbGZzc2wuY29tMR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdvbGZzc2wuY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvwzKLRSyHoRCW804H0ry -TXUQ8bY1n9/KfQOY06zeA2buKvHYsH1uB1QLEJghTYDLEiDnzE/eRX3Jcncy6sqQ -u2lSEAMvqPOVxfGLYlYb72dvpBBBla0Km+OlwLDScHZQMFuo6AgsfO2nonqNOCkc -rMft8nyVsJWCfUlcOM13Je+9gHVTlDw9ymNbnxW10x0TLxnRPNt2Osy4fcnlwtfa -QG/YIdxzG0ItU5z+Gvx9q3o2P5jehHwFZ85qFDiHqfGMtWjLaH9xICv1oGP1Vi+j -JtK3b7FaF9c4mQj+k1hv/sMTSQgWC6dNZwBSMWcjTpjtUUUduQTZC+zYKLNLve02 -eQIDAQABo4IBRTCCAUEwHQYDVR0OBBYEFCeOZxF0wyYdP+0zY7Ok2B0w5ejVMIHU -BgNVHSMEgcwwgcmAFCeOZxF0wyYdP+0zY7Ok2B0w5ejVoYGapIGXMIGUMQswCQYD +MIIE3TCCA8WgAwIBAgIJALio751TFjirMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD VQQGEwJVUzEQMA4GA1UECAwHTW9udGFuYTEQMA4GA1UEBwwHQm96ZW1hbjERMA8G A1UECgwIU2F3dG9vdGgxEzARBgNVBAsMCkNvbnN1bHRpbmcxGDAWBgNVBAMMD3d3 -dy53b2xmc3NsLmNvbTEfMB0GCSqGSIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbYIU -a5twxvGjlGUZoQhY76eNK3qDwdowDAYDVR0TBAUwAwEB/zAcBgNVHREEFTATggtl -eGFtcGxlLmNvbYcEfwAAATAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw -DQYJKoZIhvcNAQELBQADggEBAHc7PWZ0vJf+QBbmuqXV0YQIiWlPiA1Xqe+Mw5dS -yL2Lokk7t/ddHtYUf7KAM9qgitPhL9W8M5/qWnIk5fi4S7PfYpA7qCHvJ0J1vGAC -jjc1meujKPJlTP96+I7MI23lav4iWtmyT0fH4K6Y75Sstk9hgSmO4XksRvzpGsOW -HxmTZC6fN3LF5JNOYV84jq7oORnml6iR1CN+HtLQU+zMrKAd0LfdsbcBLpbNhSfg -50fiwcEA9pTfd+f6xu+KwHxnvP+gfJQ7fYZCrz2DMe4qO3vwLJ5v6cQHgSTaBXBN -3QmunnK4IQ6MsquqTEkQ93b5tQ1sINPfegYyjSkfKB2NJjM= +dy53b2xmc3NsLmNvbTEfMB0GCSqGSIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbTAe +Fw0yNjA2MTUxNjM2NDRaFw0zNjA2MTIxNjM2NDRaMIGUMQswCQYDVQQGEwJVUzEQ +MA4GA1UECAwHTW9udGFuYTEQMA4GA1UEBwwHQm96ZW1hbjERMA8GA1UECgwIU2F3 +dG9vdGgxEzARBgNVBAsMCkNvbnN1bHRpbmcxGDAWBgNVBAMMD3d3dy53b2xmc3Ns +LmNvbTEfMB0GCSqGSIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL2vkxFd0Pc4YBF/Sle8tpop9H7IjaocfMOW +k5s4GuUEwF26zWXTecVkC53oNZQfDtiWUZlWdobvY4XqwYSbt4MaAGtsG8N+HM5F +KH6BZM1Mw8yUUMlcVMiPe32/VQp+M0UZg8vjU5xVSgOuQv6Qcww2ieSCvcSur9RW +cM73ASmWAv9oclJXIT1LUja7mYAClTAW6dQvb4lj59vZrX8CLYqP9tMyaThnicYN +TfHi4MnvEJaurrJGPIk5eTeFXw9Sg/XMBRdnYFJkHJmWTW/5F8a5pbxuj6ZlFsDb +DntixcrMSYZWOEu3qv7qRTYJIF7pUnpSoEvNGYzvwDssmLt1u60CAwEAAaOCAS4w +ggEqMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFDlsiWeyM+6vZ2F03prCz9pP +bc1AMIHJBgNVHSMEgcEwgb6AFDlsiWeyM+6vZ2F03prCz9pPbc1AoYGapIGXMIGU +MQswCQYDVQQGEwJVUzEQMA4GA1UECAwHTW9udGFuYTEQMA4GA1UEBwwHQm96ZW1h +bjERMA8GA1UECgwIU2F3dG9vdGgxEzARBgNVBAsMCkNvbnN1bHRpbmcxGDAWBgNV +BAMMD3d3dy53b2xmc3NsLmNvbTEfMB0GCSqGSIb3DQEJARYQaW5mb0B3b2xmc3Ns +LmNvbYIJALio751TFjirMA4GA1UdDwEB/wQEAwIBBjAcBgNVHREEFTATggtleGFt +cGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAS1IveJ023jHZ+RclOvKz +1X35NocNZFk8Ic1dWLE4hjf98vl310X5qtuBDaliOBRs0yYvWe2KQg26VeKTJbO5 +wjZhTN7iU/Om1LrUyrFAhmtgVY6JUIaSymJiNqQL4u/KMXbN3Z9NIKZLKJY2+tWx +gL6e34Evz1KYC2P53vrqQb9eQwteVRWlcgerScZU+8dIuqQx/vrhx90Mwuh4EASm +vf6IABha19SkWPjFlwMW3JzRutGUYYezXkUxQAkhcc7plPxTKXh4psahvj5Vb458 +zY8/GtjTrCRXNFEqfdqmKLvjdQ2P4+i1Mk7bDsnjYlz9Qy0IfyVzg06m/XajUdkW +LA== -----END CERTIFICATE----- diff --git a/scripts/broker_test/server-key.pem b/scripts/broker_test/server-key.pem index d1627f4d4..2fb62f35f 100644 --- a/scripts/broker_test/server-key.pem +++ b/scripts/broker_test/server-key.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAwJUI4VdB8nFtt9JFQScBZcZFrvK8JDC4lc4vTtb2HIi8fJ/7 -qGd//lycUXX3isoH5zUvj+G9e8AvfKtkqBf8yl17uuAh5XIuby6G2JVz2qwbU7lf -P9cZDSVP4WNjUYsLZD+tQ7ilHFw0s64AoGPF9n8LWWh4c6aMGKkCba/DGQEuuBDj -xsxAtGmjRjNph27Euxem8+jdrXO8ey8htf1mUQy9VLPhbV8cvCNz0QkDiRTSELlk -wyrQoZZKvOHUGlvHoMDBY3gPRDcwMpaAMiOVoXe6E9KXc+JdJclqDcM5YKS0sGlC -Qgnp2Ai8MyCzWCKnquvE4eZhg8XSlt/Z0E+t1wIDAQABAoIBAQCa0DQPUmIFUAHv -n+1kbsLE2hryhNeSEEiSxOlq64t1bMZ5OPLJckqGZFSVd8vDmp231B2kAMieTuTd -x7pnFsF0vKnWlI8rMBr77d8hBSPZSjm9mGtlmrjcxH3upkMVLj2+HSJgKnMw1T7Y -oqyGQy7E9WReP4l1DxHYUSVOn9iqo85gs+KK2X4b8GTKmlsFC1uqy+XjP24yIgXz -0PrvdFKB4l90073/MYNFdfpjepcu1rYZxpIm5CgGUFAOeC6peA0Ul7QS2DFAq6EB -QcIw+AdfFuRhd9Jg8p+N6PS662PeKpeB70xs5lU0USsoNPRTHMRYCj+7r7X3SoVD -LTzxWFiBAoGBAPIsVHY5I2PJEDK3k62vvhl1loFk5rW4iUJB0W3QHBv4G6xpyzY8 -ZH3c9Bm4w2CxV0hfUk9ZOlV/MsAZQ1A/rs5vF/MOn0DKTq0VO8l56cBZOHNwnAp8 -yTpIMqfYSXUKhcLC/RVz2pkJKmmanwpxv7AEpox6Wm9IWlQ7xrFTF9/nAoGBAMuT -3ncVXbdcXHzYkKmYLdZpDmOzo9ymzItqpKISjI57SCyySzfcBhh96v52odSh6T8N -zRtfr1+elltbD6F8r7ObkNtXczrtsCNErkFPHwdCEyNMy/r0FKTV9542fFufqDzB -hV900jkt/9CE3/uzIHoumxeu5roLrl9TpFLtG8SRAoGBAOyY2rvV/vlSSn0CVUlv -VW5SL4SjK7OGYrNU0mNS2uOIdqDvixWl0xgUcndex6MEH54ZYrUbG57D8rUy+UzB -qusMJn3UX0pRXKRFBnBEp1bA1CIUdp7YY1CJkNPiv4GVkjFBhzkaQwsYpVMfORpf -H0O8h2rfbtMiAP4imHBOGhkpAoGBAIpBVihRnl/Ungs7mKNU8mxW1KrpaTOFJAza -1AwtxL9PAmk4fNTm3Ezt1xYRwz4A58MmwFEC3rt1nG9WnHrzju/PisUr0toGakTJ -c/5umYf4W77xfOZltU9s8MnF/xbKixsX4lg9ojerAby/QM5TjI7t7+5ZneBj5nxe -9Y5L8TvBAoGATUX5QIzFW/QqGoq08hysa+kMVja3TnKW1eWK0uL/8fEYEz2GCbjY -dqfJHHFSlDBD4PF4dP1hG0wJzOZoKnGtHN9DvFbbpaS+NXCkXs9P/ABVmTo9I89n -WvUi+LUp0EQR6zUuRr79jhiyX6i/GTKh9dwD5nyaHwx8qbAOITc78bA= +MIIEowIBAAKCAQEA12y98tC8T8pbV6rS6dPAL5+3LDkVzyFmS9jDffmURg4Mc+Ta +ZM/SXSVs3JoLOTZ//jEeG3dXZuHtpKyRXJlKuZ53Gqngk/jIAAIg0B81YWZgSyCc +EeQe6O4eOu+ErvS9qJcdcjcOwZWG9Pw11/7xWgVFvxI3yssKSpTPHZthRXUvGYMj +Af/HdNbfUdCYMLxtNhTpFWSi8mhqq0y35X6cZB82lgfp0Cwp/OuyetFcZW4mHxEs +lyKrBBPNm2JSPt7J/wNvl/d+nreZm3gwPBqc5KzjqZR4ML22pJrPV2l/MLn00A8m +wbyNgnXLGq2fwhQQ+0mveLUb9tBygxCwUtXoQwIDAQABAoIBAB5OihqTCyseiOM4 +gRusUqlgiuCJ12ugg0fAYyBh8F46s73KQH9WTX4VOc9/THzsEe2s81Nh4sXymwBD +1t90IXMjvBXgOFwY7+owYnVlLplZUcq/97T8puyWM2KPN9d1Twlc2SOsY0MQyj/F +C74re8DyZPGQmpDJJ9V+QfUkWvbuV03r51QSXWW1l5LdAlnf0X8nZG/btKD3QLEv +qP7s0f7LpCGmIi49fdpmlKC5PEYJKShd0d3d04U47LVixhHO4Y+YCpznA6B3CcRR +0ofAiHHJV9bqp6n8GSV/8z5zHukQQocNJgHiGg3hv5SVnqtYLAkGKgtkCMLzMQoT +gB+r4yECgYEA+sJbVtG2RuI9FhtK/g8S1I3CU6AfEoJVPc+YGv9USvAopKr2Yb6e +NcTWjtkVyq1WBpTPjsrgdlHHqdSvGUg+nGgEE519+qjNS7oVbo/M5zScpdij1d7h +lkMJMnI5j3cvWWVo6II+k4SPTwaebFwNrgQt35+/OApMxTNX3OpTZh8CgYEA2+1V +ocNmAS5zNict1CMoObot1VcPBNAlyaq3jQ5uQ1vRK+06Ev912coB11nSwPDrKkVQ +jEI3SH35dJDZVQvP8+EknXq1B86GZY39KnkEvGXvUlsIcOEGzHv4qJnul/relP57 +rToMBxkBUkEBq/xvsQJgQKTs49dNeoU9Q0WGUV0CgYEAvXGoX+b/tn2leNYVyerJ +ZxvR/Cu0Td95VsFHQN41aIgXrJAco6vHCwgysKkA9aYOn9o9FLvg6ILQPVYZExip +dLCAo/EQBBcTQmrLVkP9oY716bXJ0QIZm9P5VstFAUYh13/tyfrcG8bCHgn6FhNV +omo13gRqCoR9i15GuvetGjECgYBbIRjekqztyGWNBucChB7i7LaZNB3RDL7btZWh +KV2fI6ik3wO1Y41d1Uq2BU5DDJElTtt1guqNa0W2e17S0rY0hBI7/uCMf5NI+XDx +7Ht95W8pXPc+br/2c1gx5Lbs2tLoQhrVSLw7JK1be/xJW6ycWDOyFaTjNA+yuQsN +mP1zMQKBgBm6ile0Rxa34x0NwU0bpDslVFo60YBap8To2bf/Bf3+hLcDw4MyPrkI +FeELGPHBNIhB6WIEQrToCqjpZct5Ke5g6RLczBneqdkij6xmYyBSyU+5ZUV17CVf +sZb3Pvh5CKDu5/sKUXboWSHuPOwuxgpE24MOmeFahqizsbvQnxs+ -----END RSA PRIVATE KEY----- diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index d917a68c5..525e1ca5a 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -116,6 +116,72 @@ static void MqttBroker_ForceZero(void* mem, word32 len) #define WBLOG_DBG(b, ...) WBLOG(b, BROKER_LOG_DEBUG, __VA_ARGS__) #endif +#ifndef WOLFMQTT_BROKER_NO_LOG +#define BROKER_LOG_SAN_SZ 128 /* per-string scratch size */ +#define BROKER_LOG_SAN_POOL 4 /* distinct buffers per log statement */ + +/* Sanitize a peer-controlled string (topic, filter, client_id, cert CN, ...) + * before it reaches a PRINTF log sink. Control bytes (< 0x20 and + * DEL 0x7f) become printable escapes so a remote peer cannot inject forged log + * lines (CR/LF) or hijack the operator terminal (ANSI ESC). The result is + * returned from a small rotating pool of static buffers so several sanitized + * arguments can appear in one log statement; the broker log path is single + * threaded (as is PRINTF itself). Output is NUL-terminated and truncated to + * fit. NULL src yields "(null)". */ +static const char* BrokerLog_Sanitize(const char* src) +{ + static const char hex_digits[] = "0123456789abcdef"; + static char pool[BROKER_LOG_SAN_POOL][BROKER_LOG_SAN_SZ]; + static int pool_idx = 0; + char* dst = pool[pool_idx]; + word32 di = 0; + + pool_idx = (pool_idx + 1) % BROKER_LOG_SAN_POOL; + + if (src == NULL) { + src = "(null)"; + } + + while (*src != '\0') { + byte c = (byte)*src++; + char rep[4]; + word32 repLen = 0; + word32 j; + + switch (c) { + case '\r': rep[0] = '\\'; rep[1] = 'r'; repLen = 2; break; + case '\n': rep[0] = '\\'; rep[1] = 'n'; repLen = 2; break; + case '\t': rep[0] = '\\'; rep[1] = 't'; repLen = 2; break; + case '\v': rep[0] = '\\'; rep[1] = 'v'; repLen = 2; break; + case 0x1b: rep[0] = '\\'; rep[1] = 'e'; repLen = 2; break; + default: + if (c < 0x20 || c == 0x7f) { + rep[0] = '\\'; + rep[1] = 'x'; + rep[2] = hex_digits[(c >> 4) & 0x0f]; + rep[3] = hex_digits[c & 0x0f]; + repLen = 4; + } + else { + rep[0] = (char)c; + repLen = 1; + } + break; + } + + if (di + repLen + 1 > BROKER_LOG_SAN_SZ) { + break; + } + for (j = 0; j < repLen; j++) { + dst[di++] = rep[j]; + } + } + + dst[di] = '\0'; + return dst; +} +#endif /* !WOLFMQTT_BROKER_NO_LOG */ + /* Buffer size accessors - unify static/dynamic code paths */ #ifdef WOLFMQTT_STATIC_MEMORY #define BROKER_CLIENT_TX_SZ(bc) BROKER_TX_BUF_SZ @@ -941,7 +1007,24 @@ static int callback_broker_mqtt(struct lws *wsi, (void)user; - if (reason == LWS_CALLBACK_ESTABLISHED) { + if (reason == LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION) { + /* CSWSH defense: when an Origin allowlist is configured, reject a + * browser-supplied Origin that does not match exactly. Requests with + * no Origin header (native clients) are not browser-originated and are + * allowed through. */ + if (broker != NULL && broker->ws_allowed_origin != NULL) { + char origin[256]; + int olen = lws_hdr_copy(wsi, origin, (int)sizeof(origin), + WSI_TOKEN_ORIGIN); + if (olen > 0 && + XSTRCMP(origin, broker->ws_allowed_origin) != 0) { + WBLOG_ERR(broker, "broker: ws origin rejected: %s", + BrokerLog_Sanitize(origin)); + return -1; + } + } + } + else if (reason == LWS_CALLBACK_ESTABLISHED) { bc = BrokerClient_AddWs(broker, wsi); if (bc == NULL) { WBLOG_ERR(broker, "broker: ws accept rejected (alloc)"); @@ -1091,8 +1174,13 @@ static int BrokerWsNetRead(void* context, byte* buf, int buf_len, if (ret < (int)ws->rx_len) { XMEMMOVE(ws->rx_buffer, ws->rx_buffer + ret, ws->rx_len - ret); ws->rx_len -= ret; + /* Scrub the vacated tail so consumed CONNECT credentials do not + * linger in the WS staging buffer. */ + BROKER_FORCE_ZERO(ws->rx_buffer + ws->rx_len, (word32)ret); } else { + /* Scrub the whole consumed buffer (may hold username/password). */ + BROKER_FORCE_ZERO(ws->rx_buffer, (word32)ws->rx_len); ws->rx_len = 0; } @@ -1105,6 +1193,7 @@ static int BrokerWsNetWrite(void* context, const byte* buf, int buf_len, BrokerClient* bc = (BrokerClient*)context; BrokerWsCtx* ws; int attempts = 0; + int prev_processing; (void)timeout_ms; @@ -1132,12 +1221,19 @@ static int BrokerWsNetWrite(void* context, const byte* buf, int buf_len, XMEMCPY(ws->tx_pending + LWS_PRE, buf, buf_len); ws->tx_len = (size_t)buf_len; - /* Request writable callback and service until data is flushed */ + /* Request writable callback and service until data is flushed. Mark this + * context busy across the spin so a peer-initiated LWS_CALLBACK_CLOSED for + * this wsi (e.g. a subscriber whose peer closes during publish fan-out) + * takes the deferred-remove path instead of freeing ws and bc out from + * under this function. Mirrors the publisher guard in BrokerClient_Process. */ lws_callback_on_writable((struct lws*)ws->wsi); + prev_processing = ws->processing; + ws->processing = 1; while (ws->tx_pending != NULL && ws->status > 0 && attempts < 100) { lws_service(lws_get_context((struct lws*)ws->wsi), 0); attempts++; } + ws->processing = prev_processing; if (ws->tx_pending != NULL) { /* Data was not flushed - connection may be in bad state */ @@ -1208,6 +1304,8 @@ static int BrokerWsNetDisconnect(void* context) ws->tx_pending = NULL; } ws->tx_len = 0; + /* Scrub any unconsumed staged bytes (may hold credentials) before free. */ + BROKER_FORCE_ZERO(ws->rx_buffer, (word32)sizeof(ws->rx_buffer)); ws->rx_len = 0; ws->status = 0; @@ -1679,7 +1777,7 @@ static void BrokerClient_DrainOutQueue(BrokerClient* bc) if (enc_rc <= 0) { WBLOG_ERR(bc->broker, "broker: drain encode failed sock=%d topic=%s rc=%d", - (int)bc->sock, cur->topic, enc_rc); + (int)bc->sock, BrokerLog_Sanitize(cur->topic), enc_rc); /* Drop just this entry and continue. Encoding failure for * a single message is not fatal to the connection. */ if (prev == NULL) { @@ -1725,13 +1823,13 @@ static void BrokerClient_DrainOutQueue(BrokerClient* bc) * stop the drain here. */ WBLOG_ERR(bc->broker, "broker: drain write failed sock=%d topic=%s rc=%d", - (int)bc->sock, cur->topic, wr_rc); + (int)bc->sock, BrokerLog_Sanitize(cur->topic), wr_rc); return; } } WBLOG_DBG(bc->broker, "broker: drain send sock=%d topic=%s qos=%d packet_id=%u dup=%d", - (int)bc->sock, cur->topic, (int)cur->qos, + (int)bc->sock, BrokerLog_Sanitize(cur->topic), (int)cur->qos, (unsigned)cur->packet_id, (int)cur->retransmit_dup); cur->retransmit_dup = 0; @@ -2085,6 +2183,7 @@ static void BrokerClient_Remove(MqttBroker* broker, BrokerClient* bc) #ifndef WOLFMQTT_STATIC_MEMORY BrokerClient* cur; BrokerClient* prev = NULL; + int found = 0; #endif if (broker == NULL || bc == NULL) { @@ -2101,13 +2200,21 @@ static void BrokerClient_Remove(MqttBroker* broker, BrokerClient* bc) else { broker->clients = cur->next; } + found = 1; break; } prev = cur; cur = cur->next; } -#endif + /* Only free when bc was actually unlinked. A re-entrant close callback + * (e.g. WebSocket LWS_CALLBACK_CLOSED during a takeover fan-out) can have + * already removed and freed bc; freeing again here would double-free. */ + if (found) { + BrokerClient_Free(bc); + } +#else BrokerClient_Free(bc); +#endif } /* -------------------------------------------------------------------------- */ @@ -2215,8 +2322,8 @@ static int BrokerOrphan_EvictOldest(MqttBroker* broker) } WBLOG_INFO(broker, "broker: evicting oldest orphan client_id=%s (cap reached)", - BROKER_STR_VALID(oldest->client_id) ? oldest->client_id - : "(null)"); + BrokerLog_Sanitize(BROKER_STR_VALID(oldest->client_id) + ? oldest->client_id : "(null)")); BrokerOrphan_DropFull(broker, oldest); return 1; } @@ -2343,7 +2450,7 @@ static BrokerOrphanSession* BrokerOrphan_Take(MqttBroker* broker, broker->orphan_session_count++; WBLOG_INFO(broker, "broker: orphan session created client_id=%s queued=%d", - o->client_id, o->out_q_count); + BrokerLog_Sanitize(o->client_id), o->out_q_count); #ifdef WOLFMQTT_BROKER_PERSIST /* Re-persist the session record stamped with the orphan time so the v5 @@ -2423,12 +2530,12 @@ static int BrokerOrphan_Reclaim(MqttBroker* broker, BrokerClient* new_bc) if (retx > 0) { WBLOG_INFO(broker, "broker: orphan reclaim queued retransmit=%d client_id=%s", - retx, new_bc->client_id); + retx, BrokerLog_Sanitize(new_bc->client_id)); } } WBLOG_INFO(broker, "broker: orphan reclaimed client_id=%s queued=%d", - new_bc->client_id, new_bc->out_q_count); + BrokerLog_Sanitize(new_bc->client_id), new_bc->out_q_count); #ifdef WOLFMQTT_BROKER_PERSIST /* The reclaimed queue is now in a LIVE BrokerClient. Persisted * records for this client_id are no longer authoritative - the @@ -2489,7 +2596,8 @@ static void BrokerOrphan_Enqueue(MqttBroker* broker, BrokerOrphanSession* o, if (e == NULL) { WBLOG_ERR(broker, "broker: orphan enqueue alloc failed client_id=%s", - BROKER_STR_VALID(o->client_id) ? o->client_id : "(null)"); + BrokerLog_Sanitize( + BROKER_STR_VALID(o->client_id) ? o->client_id : "(null)")); return; } e->qos = qos; @@ -2512,8 +2620,9 @@ static void BrokerOrphan_Enqueue(MqttBroker* broker, BrokerOrphanSession* o, #endif WBLOG_DBG(broker, "broker: orphan enqueue client_id=%s topic=%s qos=%d count=%d", - BROKER_STR_VALID(o->client_id) ? o->client_id : "(null)", - topic, (int)qos, o->out_q_count); + BrokerLog_Sanitize( + BROKER_STR_VALID(o->client_id) ? o->client_id : "(null)"), + BrokerLog_Sanitize(topic), (int)qos, o->out_q_count); } /* Free every orphan (used by MqttBroker_Free and by wipe paths). */ @@ -2585,7 +2694,8 @@ static void BrokerSubs_OrphanClient(MqttBroker* broker, BrokerClient* bc) if (BrokerOrphan_Take(broker, bc) == NULL) { WBLOG_ERR(broker, "broker: orphan take failed client_id=%s - removing %d subs", - BROKER_STR_VALID(bc->client_id) ? bc->client_id : "(null)", + BrokerLog_Sanitize( + BROKER_STR_VALID(bc->client_id) ? bc->client_id : "(null)"), count); BrokerSubs_RemoveClient(broker, bc); return; @@ -2612,7 +2722,8 @@ static void BrokerSubs_OrphanClient(MqttBroker* broker, BrokerClient* bc) #endif WBLOG_INFO(broker, "broker: orphaned %d subs for client_id=%s (session persist)", - count, BROKER_STR_VALID(bc->client_id) ? bc->client_id : "(null)"); + count, BrokerLog_Sanitize( + BROKER_STR_VALID(bc->client_id) ? bc->client_id : "(null)")); } static void BrokerSubs_RemoveClient(MqttBroker* broker, BrokerClient* bc) @@ -2654,6 +2765,7 @@ static void BrokerSubs_RemoveClient(MqttBroker* broker, BrokerClient* bc) cur = next; } #endif + bc->sub_count = 0; #ifdef WOLFMQTT_BROKER_PERSIST /* Clean-session disconnect drops the persistent record. For @@ -2690,7 +2802,7 @@ static int BrokerSubs_Add(MqttBroker* broker, BrokerClient* bc, XMEMCMP(broker->subs[i].filter, filter, filter_len) == 0) { broker->subs[i].qos = qos; WBLOG_INFO(broker, "broker: sub update sock=%d filter=%s qos=%d", - (int)bc->sock, broker->subs[i].filter, qos); + (int)bc->sock, BrokerLog_Sanitize(broker->subs[i].filter), qos); return MQTT_CODE_SUCCESS; } } @@ -2701,13 +2813,21 @@ static int BrokerSubs_Add(MqttBroker* broker, BrokerClient* bc, XMEMCMP(cur->filter, filter, filter_len) == 0) { cur->qos = qos; WBLOG_INFO(broker, "broker: sub update sock=%d filter=%s qos=%d", - (int)bc->sock, cur->filter, qos); + (int)bc->sock, BrokerLog_Sanitize(cur->filter), qos); return MQTT_CODE_SUCCESS; } cur = cur->next; } #endif + /* Per-client cap: prevent a single client from occupying the whole shared + * subscription table and denying SUBSCRIBE to other clients. */ + if (bc->sub_count >= BROKER_MAX_SUBS_PER_CLIENT) { + WBLOG_ERR(broker, "broker: sub cap reached sock=%d (max %d)", + (int)bc->sock, BROKER_MAX_SUBS_PER_CLIENT); + return MQTT_CODE_ERROR_MEMORY; + } + #ifdef WOLFMQTT_STATIC_MEMORY for (i = 0; i < BROKER_MAX_SUBS; i++) { if (!broker->subs[i].in_use) { @@ -2774,8 +2894,9 @@ static int BrokerSubs_Add(MqttBroker* broker, BrokerClient* bc, } } #endif + bc->sub_count++; WBLOG_INFO(broker, "broker: sub add sock=%d filter=%s qos=%d", - (int)bc->sock, sub->filter, qos); + (int)bc->sock, BrokerLog_Sanitize(sub->filter), qos); } return rc; } @@ -2798,8 +2919,11 @@ static void BrokerSubs_Remove(MqttBroker* broker, BrokerClient* bc, (word16)XSTRLEN(s->filter) == filter_len && XMEMCMP(s->filter, filter, filter_len) == 0) { WBLOG_INFO(broker, "broker: sub remove sock=%d filter=%s", - (int)bc->sock, s->filter); + (int)bc->sock, BrokerLog_Sanitize(s->filter)); XMEMSET(s, 0, sizeof(BrokerSub)); + if (bc->sub_count > 0) { + bc->sub_count--; + } return; } } @@ -2818,12 +2942,15 @@ static void BrokerSubs_Remove(MqttBroker* broker, BrokerClient* bc, broker->subs = next; } WBLOG_INFO(broker, "broker: sub remove sock=%d filter=%s", - (int)bc->sock, cur->filter); + (int)bc->sock, BrokerLog_Sanitize(cur->filter)); WOLFMQTT_FREE(cur->filter); if (cur->client_id) { WOLFMQTT_FREE(cur->client_id); } WOLFMQTT_FREE(cur); + if (bc->sub_count > 0) { + bc->sub_count--; + } return; } prev = cur; @@ -3008,8 +3135,11 @@ static int BrokerSubs_ReassociateClient(MqttBroker* broker, } #endif if (count > 0) { + /* The new client now owns these subs; reflect them in its cap count + * so a reconnect cannot be used to exceed BROKER_MAX_SUBS_PER_CLIENT. */ + new_bc->sub_count += count; WBLOG_INFO(broker, "broker: reassociated %d subs for client_id=%s", - count, client_id); + count, BrokerLog_Sanitize(client_id)); } return count; } @@ -3018,6 +3148,64 @@ static int BrokerSubs_ReassociateClient(MqttBroker* broker, /* Retained message management */ /* -------------------------------------------------------------------------- */ #ifdef WOLFMQTT_BROKER_RETAINED +/* Free retained messages whose v5 Message Expiry Interval has elapsed so they + * stop occupying a slot / the retained_count cap. Delivery also reaps expired + * entries, but a publisher can hit the cap with only-expired entries before any + * subscription triggers that path. */ +static void BrokerRetained_ReapExpired(MqttBroker* broker, + WOLFMQTT_BROKER_TIME_T now) +{ +#ifdef WOLFMQTT_STATIC_MEMORY + int i; + for (i = 0; i < BROKER_MAX_RETAINED; i++) { + BrokerRetainedMsg* rm = &broker->retained[i]; + /* now >= store_time guards the unsigned subtraction so a backward + * clock step reads as "not expired" instead of wrapping huge. */ + if (rm->in_use && rm->expiry_sec > 0 && + now >= rm->store_time && + (now - rm->store_time) >= rm->expiry_sec) { + WBLOG_DBG(broker, "broker: retained expired topic=%s", + BrokerLog_Sanitize(rm->topic)); + XMEMSET(rm, 0, sizeof(BrokerRetainedMsg)); + } + } +#else + BrokerRetainedMsg* cur; + BrokerRetainedMsg* prev = NULL; + + /* A delivery loop may hold node pointers; defer to its post-loop reap. */ + if (broker->retained_delivering > 0) { + return; + } + cur = broker->retained; + while (cur != NULL) { + BrokerRetainedMsg* next = cur->next; + if (cur->expiry_sec > 0 && + now >= cur->store_time && + (now - cur->store_time) >= cur->expiry_sec) { + WBLOG_DBG(broker, "broker: retained expired topic=%s", + BrokerLog_Sanitize(cur->topic)); + if (prev != NULL) { + prev->next = next; + } + else { + broker->retained = next; + } + if (cur->topic != NULL) WOLFMQTT_FREE(cur->topic); + if (cur->payload != NULL) WOLFMQTT_FREE(cur->payload); + WOLFMQTT_FREE(cur); + if (broker->retained_count > 0) { + broker->retained_count--; + } + } + else { + prev = cur; + } + cur = next; + } +#endif +} + static int BrokerRetained_Store(MqttBroker* broker, const char* topic, const byte* payload, word32 payload_len, MqttQoS qos, word32 expiry_sec) { @@ -3034,6 +3222,10 @@ static int BrokerRetained_Store(MqttBroker* broker, const char* topic, if (broker == NULL || topic == NULL) { return MQTT_CODE_ERROR_BAD_ARG; } + + /* Drop expired entries first so they do not hold the cap against a new + * retained message. */ + BrokerRetained_ReapExpired(broker, WOLFMQTT_BROKER_GET_TIME_S()); #ifndef WOLFMQTT_STATIC_MEMORY cur = broker->retained; #endif @@ -3082,6 +3274,9 @@ static int BrokerRetained_Store(MqttBroker* broker, const char* topic, while (cur) { if (cur->topic != NULL && XSTRCMP(cur->topic, topic) == 0) { msg = cur; + /* Re-publishing this topic cancels a deferred delete, otherwise a + * later delivery would reap the freshly stored message. */ + msg->pending_delete = 0; break; } cur = cur->next; @@ -3089,11 +3284,19 @@ static int BrokerRetained_Store(MqttBroker* broker, const char* topic, if (msg == NULL) { /* Allocate new node + topic */ int tlen = (int)XSTRLEN(topic); - msg = (BrokerRetainedMsg*)WOLFMQTT_MALLOC( - sizeof(BrokerRetainedMsg)); - if (msg == NULL) { + /* Cap the dynamic retained list so a client publishing RETAIN=1 to + * many distinct topics cannot grow it without bound and exhaust the + * heap; the static path is already bounded by BROKER_MAX_RETAINED. */ + if (broker->retained_count >= BROKER_MAX_RETAINED) { rc = MQTT_CODE_ERROR_MEMORY; } + if (rc == MQTT_CODE_SUCCESS) { + msg = (BrokerRetainedMsg*)WOLFMQTT_MALLOC( + sizeof(BrokerRetainedMsg)); + if (msg == NULL) { + rc = MQTT_CODE_ERROR_MEMORY; + } + } if (rc == MQTT_CODE_SUCCESS) { XMEMSET(msg, 0, sizeof(*msg)); msg->topic = (char*)WOLFMQTT_MALLOC((size_t)tlen + 1); @@ -3129,6 +3332,7 @@ static int BrokerRetained_Store(MqttBroker* broker, const char* topic, if (is_new) { msg->next = broker->retained; broker->retained = msg; + broker->retained_count++; } } else if (is_new && msg != NULL) { @@ -3145,7 +3349,8 @@ static int BrokerRetained_Store(MqttBroker* broker, const char* topic, msg->qos = qos; WBLOG_DBG(broker, "broker: retained store topic=%s len=%u qos=%d " "expiry=%u", - topic, (unsigned)payload_len, (int)qos, (unsigned)expiry_sec); + BrokerLog_Sanitize(topic), (unsigned)payload_len, (int)qos, + (unsigned)expiry_sec); #ifdef WOLFMQTT_BROKER_PERSIST (void)BrokerPersist_PutRetained(broker, msg); #endif @@ -3170,7 +3375,8 @@ static void BrokerRetained_Delete(MqttBroker* broker, const char* topic) for (i = 0; i < BROKER_MAX_RETAINED; i++) { if (broker->retained[i].in_use && XSTRCMP(broker->retained[i].topic, topic) == 0) { - WBLOG_DBG(broker, "broker: retained delete topic=%s", topic); + WBLOG_DBG(broker, "broker: retained delete topic=%s", + BrokerLog_Sanitize(topic)); XMEMSET(&broker->retained[i], 0, sizeof(BrokerRetainedMsg)); found = 1; break; @@ -3181,7 +3387,17 @@ static void BrokerRetained_Delete(MqttBroker* broker, const char* topic) while (cur) { BrokerRetainedMsg* next = cur->next; if (cur->topic != NULL && XSTRCMP(cur->topic, topic) == 0) { - WBLOG_DBG(broker, "broker: retained delete topic=%s", topic); + WBLOG_DBG(broker, "broker: retained delete topic=%s", + BrokerLog_Sanitize(topic)); + if (broker->retained_delivering > 0) { + /* A delivery loop is iterating this list (possibly re-entered + * via a WebSocket fan-out). Freeing now would invalidate that + * loop's saved next pointer; flag for deferred reap + * by the delivery loop instead. */ + cur->pending_delete = 1; + found = 1; + break; + } if (prev) { prev->next = next; } @@ -3193,6 +3409,9 @@ static void BrokerRetained_Delete(MqttBroker* broker, const char* topic) WOLFMQTT_FREE(cur->payload); } WOLFMQTT_FREE(cur); + if (broker->retained_count > 0) { + broker->retained_count--; + } found = 1; break; } @@ -3235,6 +3454,7 @@ static void BrokerRetained_FreeAll(MqttBroker* broker) cur = next; } broker->retained = NULL; + broker->retained_count = 0; #endif } #endif /* WOLFMQTT_BROKER_RETAINED */ @@ -3385,7 +3605,8 @@ static int BrokerPendingWill_Add(MqttBroker* broker, BrokerClient* bc) pw->retain = bc->will_retain; pw->publish_time = now + (WOLFMQTT_BROKER_TIME_T)bc->will_delay_sec; WBLOG_DBG(broker, "broker: will deferred sock=%d client_id=%s delay=%u", - (int)bc->sock, bc->client_id, (unsigned)bc->will_delay_sec); + (int)bc->sock, BrokerLog_Sanitize(bc->client_id), + (unsigned)bc->will_delay_sec); } return rc; } @@ -3408,7 +3629,8 @@ static void BrokerPendingWill_Cancel(MqttBroker* broker, for (i = 0; i < BROKER_MAX_PENDING_WILLS; i++) { if (broker->pending_wills[i].in_use && XSTRCMP(broker->pending_wills[i].client_id, client_id) == 0) { - WBLOG_DBG(broker, "broker: will cancelled client_id=%s", client_id); + WBLOG_DBG(broker, "broker: will cancelled client_id=%s", + BrokerLog_Sanitize(client_id)); XMEMSET(&broker->pending_wills[i], 0, sizeof(BrokerPendingWill)); return; @@ -3420,7 +3642,8 @@ static void BrokerPendingWill_Cancel(MqttBroker* broker, BrokerPendingWill* next = pw->next; if (pw->client_id != NULL && XSTRCMP(pw->client_id, client_id) == 0) { - WBLOG_DBG(broker, "broker: will cancelled client_id=%s", client_id); + WBLOG_DBG(broker, "broker: will cancelled client_id=%s", + BrokerLog_Sanitize(client_id)); if (prev) { prev->next = next; } @@ -3504,7 +3727,8 @@ static int BrokerPendingWill_Process(MqttBroker* broker) } if (now >= pw->publish_time) { WBLOG_DBG(broker, "broker: LWT deferred publish client_id=%s topic=%s " - "len=%u", pw->client_id, pw->topic, + "len=%u", BrokerLog_Sanitize(pw->client_id), + BrokerLog_Sanitize(pw->topic), (unsigned)pw->payload_len); BrokerClient_PublishWillImmediate(broker, pw->topic, pw->payload, pw->payload_len, pw->qos, pw->retain); @@ -3518,16 +3742,30 @@ static int BrokerPendingWill_Process(MqttBroker* broker) BrokerPendingWill* next = pw->next; if (now >= pw->publish_time) { WBLOG_DBG(broker, "broker: LWT deferred publish client_id=%s topic=%s " - "len=%u", pw->client_id, pw->topic, + "len=%u", BrokerLog_Sanitize(pw->client_id), + BrokerLog_Sanitize(pw->topic), (unsigned)pw->payload_len); BrokerClient_PublishWillImmediate(broker, pw->topic, pw->payload, pw->payload_len, pw->qos, pw->retain); if (prev) { prev->next = next; } - else { + else if (broker->pending_wills == pw) { broker->pending_wills = next; } + else { + /* The fan-out above re-entered BrokerPendingWill_Add (a WS + * close), prepending a node, so pw is no longer the head. + * Unlink pw via its real predecessor instead of clobbering the + * new head with the stale saved next. */ + BrokerPendingWill* p = broker->pending_wills; + while (p != NULL && p->next != pw) { + p = p->next; + } + if (p != NULL) { + p->next = next; + } + } if (pw->client_id) WOLFMQTT_FREE(pw->client_id); if (pw->topic) { BROKER_FORCE_ZERO(pw->topic, XSTRLEN(pw->topic) + 1); @@ -3568,16 +3806,25 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, } now = WOLFMQTT_BROKER_GET_TIME_S(); +#ifndef WOLFMQTT_STATIC_MEMORY + /* Mark a delivery in progress so a re-entrant BrokerRetained_Delete (via a + * WebSocket fan-out close) defers its free instead of invalidating the + * loop's saved next pointer. */ + broker->retained_delivering++; +#endif + #ifdef WOLFMQTT_STATIC_MEMORY for (i = 0; i < BROKER_MAX_RETAINED; i++) { BrokerRetainedMsg* rm = &broker->retained[i]; if (!rm->in_use || rm->topic[0] == '\0') { continue; } - /* Skip expired messages */ + /* Skip expired messages (now >= store_time guards clock rollback) */ if (rm->expiry_sec > 0 && + now >= rm->store_time && (now - rm->store_time) >= rm->expiry_sec) { - WBLOG_DBG(broker, "broker: retained expired topic=%s", rm->topic); + WBLOG_DBG(broker, "broker: retained expired topic=%s", + BrokerLog_Sanitize(rm->topic)); XMEMSET(rm, 0, sizeof(BrokerRetainedMsg)); continue; } @@ -3602,7 +3849,8 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, BROKER_CLIENT_TX_SZ(bc), &out_pub, 0); if (enc_rc > 0) { WBLOG_DBG(broker, "broker: retained deliver sock=%d topic=%s " - "len=%u qos=%d", (int)bc->sock, rm->topic, + "len=%u qos=%d", (int)bc->sock, + BrokerLog_Sanitize(rm->topic), (unsigned)rm->payload_len, (int)eff_qos); (void)MqttPacket_Write(&bc->client, bc->tx_buf, enc_rc); } @@ -3612,10 +3860,22 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, rm = broker->retained; while (rm) { BrokerRetainedMsg* rm_next = rm->next; - /* Skip and remove expired messages */ - if (rm->expiry_sec > 0 && - (now - rm->store_time) >= rm->expiry_sec) { - WBLOG_DBG(broker, "broker: retained expired topic=%s", rm->topic); + /* Reap deferred-delete and expired nodes. Freeing is only safe at the + * outermost delivery depth; a nested re-entrant (WS fan-out) delivery + * may hold rm in an enclosing loop's saved rm_next, so at deeper depths + * mark the node and defer to the retained_delivering==0 post-loop reap. */ + if (rm->pending_delete || + (rm->expiry_sec > 0 && + now >= rm->store_time && + (now - rm->store_time) >= rm->expiry_sec)) { + if (broker->retained_delivering > 1) { + rm->pending_delete = 1; + rm_prev = rm; + rm = rm_next; + continue; + } + WBLOG_DBG(broker, "broker: retained expired topic=%s", + BrokerLog_Sanitize(rm->topic)); if (rm_prev) { rm_prev->next = rm_next; } @@ -3625,6 +3885,9 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, if (rm->topic) WOLFMQTT_FREE(rm->topic); if (rm->payload) WOLFMQTT_FREE(rm->payload); WOLFMQTT_FREE(rm); + if (broker->retained_count > 0) { + broker->retained_count--; + } rm = rm_next; continue; } @@ -3649,7 +3912,8 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, BROKER_CLIENT_TX_SZ(bc), &out_pub, 0); if (enc_rc > 0) { WBLOG_DBG(broker, "broker: retained deliver sock=%d topic=%s " - "len=%u qos=%d", (int)bc->sock, rm->topic, + "len=%u qos=%d", (int)bc->sock, + BrokerLog_Sanitize(rm->topic), (unsigned)rm->payload_len, (int)eff_qos); (void)MqttPacket_Write(&bc->client, bc->tx_buf, enc_rc); } @@ -3658,6 +3922,40 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, rm = rm_next; } #endif + +#ifndef WOLFMQTT_STATIC_MEMORY + if (broker->retained_delivering > 0) { + broker->retained_delivering--; + } + /* When the outermost delivery finishes, reap nodes a re-entrant delete + * marked after this loop had already passed them, so they stop counting + * against BROKER_MAX_RETAINED. Safe now: no delivery loop holds pointers. */ + if (broker->retained_delivering == 0) { + BrokerRetainedMsg* p = broker->retained; + BrokerRetainedMsg* pprev = NULL; + while (p != NULL) { + BrokerRetainedMsg* pnext = p->next; + if (p->pending_delete) { + if (pprev) { + pprev->next = pnext; + } + else { + broker->retained = pnext; + } + if (p->topic) WOLFMQTT_FREE(p->topic); + if (p->payload) WOLFMQTT_FREE(p->payload); + WOLFMQTT_FREE(p); + if (broker->retained_count > 0) { + broker->retained_count--; + } + } + else { + pprev = p; + } + p = pnext; + } + } +#endif } #endif /* WOLFMQTT_BROKER_RETAINED */ @@ -3677,11 +3975,18 @@ static void BrokerClient_PublishWill(MqttBroker* broker, BrokerClient* bc) BrokerClient_ClearWill(bc); return; /* will deferred, not published now */ } - /* If add failed (out of slots), publish immediately as fallback */ + /* Out of pending-will slots: fall back to immediate publication, but + * surface it - a silent fallback lets slot exhaustion erase the Will + * Delay grace window invisibly to the operator. */ + WBLOG_ERR(broker, + "broker: pending-will pool full, publishing LWT immediately " + "(delay=%u lost) sock=%d", (unsigned)bc->will_delay_sec, + (int)bc->sock); } WBLOG_DBG(broker, "broker: LWT publish sock=%d topic=%s len=%u", - (int)bc->sock, bc->will_topic, (unsigned)bc->will_payload_len); + (int)bc->sock, BrokerLog_Sanitize(bc->will_topic), + (unsigned)bc->will_payload_len); BrokerClient_PublishWillImmediate(broker, bc->will_topic, bc->will_payload, bc->will_payload_len, bc->will_qos, @@ -3698,6 +4003,7 @@ static void BrokerClient_PublishWillImmediate(MqttBroker* broker, int i; #else BrokerSub* sub; + BrokerSub* next_sub = NULL; #endif if (broker == NULL || topic == NULL) { @@ -3727,6 +4033,11 @@ static void BrokerClient_PublishWillImmediate(MqttBroker* broker, #else sub = broker->subs; while (sub) { + /* Snapshot the successor before any MqttPacket_Write: a WS fan-out + * write can drive an lws_service spin whose re-entrant CLOSED frees + * this client's BrokerSub nodes, so reading sub->next afterwards + * would dereference a freed node. */ + next_sub = sub->next; #endif if (sub->client != NULL && sub->client->protocol_level != 0 && BROKER_STR_VALID(sub->filter) && @@ -3756,7 +4067,7 @@ static void BrokerClient_PublishWillImmediate(MqttBroker* broker, } } #ifndef WOLFMQTT_STATIC_MEMORY - sub = sub->next; + sub = next_sub; #endif } } @@ -4196,11 +4507,128 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, WBLOG_INFO(broker, "broker: CONNECT proto=%u clean=%d will=%d client_id=%s", mc.protocol_level, mc.clean_session, mc.enable_lwt, - BROKER_STR_VALID(bc->client_id) ? bc->client_id : "(null)"); + BrokerLog_Sanitize( + BROKER_STR_VALID(bc->client_id) ? bc->client_id : "(null)")); /* Client ID uniqueness and clean session handling */ bc->clean_session = mc.clean_session; + /* Validate credentials BEFORE any session-state mutation. Storing the + * username/password and running the credential gate here ensures an + * unauthenticated peer that guesses a client_id cannot disconnect the + * victim, fire its LWT, or hijack/destroy its persisted subscriptions: + * the duplicate-takeover and orphan-reassociation logic below only runs + * once auth has passed. */ +#ifdef WOLFMQTT_BROKER_AUTH +#ifdef WOLFMQTT_STATIC_MEMORY + bc->username[0] = '\0'; + bc->password[0] = '\0'; +#endif + bc->password_len = 0; + if (mc.username) { + word16 ulen = 0; + if (MqttDecode_Num((byte*)mc.username - MQTT_DATA_LEN_SIZE, + &ulen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { + #ifdef WOLFMQTT_STATIC_MEMORY + if (ulen >= BROKER_MAX_USERNAME_LEN) { + WBLOG_ERR(broker, + "broker: username too long (%u >= %d) sock=%d", + (unsigned)ulen, BROKER_MAX_USERNAME_LEN, + (int)bc->sock); + #ifdef WOLFMQTT_V5 + if (mc.protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + ack.return_code = MQTT_REASON_BAD_USER_OR_PASS; + } + else + #endif + { + ack.return_code = + MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; + } + goto send_connack; + } + #endif + BROKER_STORE_STR_SENSITIVE(bc->username, mc.username, ulen, + BROKER_MAX_USERNAME_LEN); + } + } + if (mc.password) { + word16 plen = 0; + if (MqttDecode_Num((byte*)mc.password - MQTT_DATA_LEN_SIZE, + &plen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { + #ifdef WOLFMQTT_STATIC_MEMORY + if (plen >= BROKER_MAX_PASSWORD_LEN) { + WBLOG_ERR(broker, + "broker: password too long (%u >= %d) sock=%d", + (unsigned)plen, BROKER_MAX_PASSWORD_LEN, + (int)bc->sock); + #ifdef WOLFMQTT_V5 + if (mc.protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + ack.return_code = MQTT_REASON_BAD_USER_OR_PASS; + } + else + #endif + { + ack.return_code = + MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; + } + goto send_connack; + } + #endif + /* [MQTT-3.1.3.5] Password is Binary Data and may legally + * contain 0x00. The binary-sensitive store records the + * actual length in bc->password_len so wipe and compare + * paths don't fall back to XSTRLEN truncation. */ + BROKER_STORE_BIN_SENSITIVE(bc->password, bc->password_len, + mc.password, plen, BROKER_MAX_PASSWORD_LEN); + } + } + if (broker->auth_user || broker->auth_pass) { + int auth_ok = 1; + if (broker->auth_user && ( + #ifndef WOLFMQTT_STATIC_MEMORY + bc->username == NULL || + #endif + bc->username[0] == '\0' || + BrokerStrCompare(broker->auth_user, bc->username, + BROKER_MAX_USERNAME_LEN) != 0)) { + auth_ok = 0; + } + if (broker->auth_pass && ( + #ifndef WOLFMQTT_STATIC_MEMORY + bc->password == NULL || + #endif + bc->password_len == 0 || + BrokerBufCompare((const byte*)broker->auth_pass, + (int)XSTRLEN(broker->auth_pass), + (const byte*)bc->password, (int)bc->password_len, + BROKER_MAX_PASSWORD_LEN) != 0)) { + auth_ok = 0; + } + if (!auth_ok) { + WBLOG_ERR(broker, "broker: auth failed sock=%d user=%s", + (int)bc->sock, + #ifdef WOLFMQTT_STATIC_MEMORY + BrokerLog_Sanitize(bc->username[0] ? bc->username : "(null)")); + #else + BrokerLog_Sanitize((bc->username && bc->username[0]) + ? bc->username : "(null)")); + #endif + #ifdef WOLFMQTT_V5 + if (mc.protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + ack.return_code = MQTT_REASON_BAD_USER_OR_PASS; + } + else + #endif + { + ack.return_code = + MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; + } + goto send_connack; + } + } +#endif /* WOLFMQTT_BROKER_AUTH */ + if (BROKER_STR_VALID(bc->client_id)) { BrokerClient* old; @@ -4210,7 +4638,17 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, old = BrokerClient_FindByClientId(broker, bc->client_id, bc); if (old != NULL) { WBLOG_INFO(broker, "broker: duplicate client_id=%s, disconnecting " - "old sock=%d", bc->client_id, (int)old->sock); + "old sock=%d", BrokerLog_Sanitize(bc->client_id), + (int)old->sock); +#ifdef ENABLE_MQTT_WEBSOCKET + /* Guard old across its takeover fan-out: a re-entrant + * LWS_CALLBACK_CLOSED for old's dropped socket must take the + * deferred-remove path instead of freeing old mid-takeover, which + * would UAF (BrokerSubs_RemoveClient) and double-free below. */ + if (old->ws_ctx != NULL) { + ((BrokerWsCtx*)old->ws_ctx)->processing = 1; + } +#endif /* Publish old client's will on takeover */ #ifdef WOLFMQTT_V5 if (old->protocol_level < MQTT_CONNECT_PROTOCOL_LEVEL_5) { @@ -4223,6 +4661,11 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, } #else BrokerClient_PublishWill(broker, old); +#endif +#ifdef ENABLE_MQTT_WEBSOCKET + if (old->ws_ctx != NULL) { + ((BrokerWsCtx*)old->ws_ctx)->processing = 0; + } #endif if (!mc.clean_session) { /* Reassociate old client's subs to new client */ @@ -4328,142 +4771,39 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, MqttProp* prop = BrokerProps_Find(mc.lwt_msg->props, MQTT_PROP_WILL_DELAY_INTERVAL); if (prop != NULL) { - bc->will_delay_sec = prop->data_int; + /* Clamp to a sane maximum so a client advertising a huge + * delay (e.g. UINT32_MAX) cannot monopolize a pending-will + * slot indefinitely. */ + if (prop->data_int > BROKER_MAX_WILL_DELAY_SEC) { + bc->will_delay_sec = BROKER_MAX_WILL_DELAY_SEC; + } + else { + bc->will_delay_sec = prop->data_int; + } } } #endif bc->has_will = 1; WBLOG_DBG(broker, "broker: LWT stored sock=%d topic=%s qos=%d retain=%d " - "len=%u delay=%u", (int)bc->sock, bc->will_topic, + "len=%u delay=%u", (int)bc->sock, BrokerLog_Sanitize(bc->will_topic), bc->will_qos, bc->will_retain, (unsigned)bc->will_payload_len, (unsigned)bc->will_delay_sec); } #endif /* WOLFMQTT_BROKER_WILL */ - /* Store credentials */ -#ifdef WOLFMQTT_BROKER_AUTH -#ifdef WOLFMQTT_STATIC_MEMORY - bc->username[0] = '\0'; - bc->password[0] = '\0'; -#endif - bc->password_len = 0; - if (mc.username) { - word16 ulen = 0; - if (MqttDecode_Num((byte*)mc.username - MQTT_DATA_LEN_SIZE, - &ulen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { - #ifdef WOLFMQTT_STATIC_MEMORY - if (ulen >= BROKER_MAX_USERNAME_LEN) { - WBLOG_ERR(broker, - "broker: username too long (%u >= %d) sock=%d", - (unsigned)ulen, BROKER_MAX_USERNAME_LEN, - (int)bc->sock); - #ifdef WOLFMQTT_V5 - if (mc.protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { - ack.return_code = MQTT_REASON_BAD_USER_OR_PASS; - } - else - #endif - { - ack.return_code = - MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; - } - goto send_connack; - } - #endif - BROKER_STORE_STR_SENSITIVE(bc->username, mc.username, ulen, - BROKER_MAX_USERNAME_LEN); - } - } - if (mc.password) { - word16 plen = 0; - if (MqttDecode_Num((byte*)mc.password - MQTT_DATA_LEN_SIZE, - &plen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { - #ifdef WOLFMQTT_STATIC_MEMORY - if (plen >= BROKER_MAX_PASSWORD_LEN) { - WBLOG_ERR(broker, - "broker: password too long (%u >= %d) sock=%d", - (unsigned)plen, BROKER_MAX_PASSWORD_LEN, - (int)bc->sock); - #ifdef WOLFMQTT_V5 - if (mc.protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { - ack.return_code = MQTT_REASON_BAD_USER_OR_PASS; - } - else - #endif - { - ack.return_code = - MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; - } - goto send_connack; - } - #endif - /* [MQTT-3.1.3.5] Password is Binary Data and may legally - * contain 0x00. The binary-sensitive store records the - * actual length in bc->password_len so wipe and compare - * paths don't fall back to XSTRLEN truncation. */ - BROKER_STORE_BIN_SENSITIVE(bc->password, bc->password_len, - mc.password, plen, BROKER_MAX_PASSWORD_LEN); - } - } -#endif /* WOLFMQTT_BROKER_AUTH */ - - /* Check auth before sending CONNACK. [MQTT-3.2.2-2]: when the - * accepted CleanSession=0 connection finds stored session state, - * Session Present MUST be 1; otherwise it MUST be 0. The flag is - * cleared again below for any path that overrides return_code to a - * non-zero refusal - [MQTT-3.2.2-4] requires Session Present=0 on a - * refused CONNACK. */ + /* Credentials were already validated above, before any session-state + * mutation. [MQTT-3.2.2-2]: when the accepted CleanSession=0 connection + * finds stored session state, Session Present MUST be 1; otherwise it + * MUST be 0. The flag is cleared again below for any path that overrides + * return_code to a non-zero refusal - [MQTT-3.2.2-4] requires Session + * Present=0 on a refused CONNACK. */ ack.flags = session_present ? MQTT_CONNECT_ACK_FLAG_SESSION_PRESENT : 0; ack.return_code = MQTT_CONNECT_ACK_CODE_ACCEPTED; #ifdef WOLFMQTT_V5 ack.props = NULL; #endif -#ifdef WOLFMQTT_BROKER_AUTH - if (broker->auth_user || broker->auth_pass) { - int auth_ok = 1; - if (broker->auth_user && ( - #ifndef WOLFMQTT_STATIC_MEMORY - bc->username == NULL || - #endif - bc->username[0] == '\0' || - BrokerStrCompare(broker->auth_user, bc->username, - BROKER_MAX_USERNAME_LEN) != 0)) { - auth_ok = 0; - } - if (broker->auth_pass && ( - #ifndef WOLFMQTT_STATIC_MEMORY - bc->password == NULL || - #endif - bc->password_len == 0 || - BrokerBufCompare((const byte*)broker->auth_pass, - (int)XSTRLEN(broker->auth_pass), - (const byte*)bc->password, (int)bc->password_len, - BROKER_MAX_PASSWORD_LEN) != 0)) { - auth_ok = 0; - } - if (!auth_ok) { - WBLOG_ERR(broker, "broker: auth failed sock=%d user=%s", (int)bc->sock, - #ifdef WOLFMQTT_STATIC_MEMORY - bc->username[0] ? bc->username : "(null)"); - #else - (bc->username && bc->username[0]) ? bc->username : "(null)"); - #endif - #ifdef WOLFMQTT_V5 - if (mc.protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { - ack.return_code = MQTT_REASON_BAD_USER_OR_PASS; - } - else - #endif - { - ack.return_code = - MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; - } - } - } -#endif /* WOLFMQTT_BROKER_AUTH */ - #ifdef WOLFMQTT_V5 if (bc->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5 && ack.return_code == MQTT_CONNECT_ACK_CODE_ACCEPTED) { @@ -4659,6 +4999,14 @@ static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len, } if (sub_rc != MQTT_CODE_SUCCESS) { granted_qos = (MqttQoS)fail_code; + #ifdef WOLFMQTT_V5 + /* A capacity rejection (per-client cap or full table) maps to + * the v5 Quota Exceeded reason so the client sees why. */ + if (bc->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5 && + sub_rc == MQTT_CODE_ERROR_MEMORY) { + granted_qos = (MqttQoS)MQTT_REASON_QUOTA_EXCEEDED; + } + #endif } #ifdef WOLFMQTT_BROKER_RETAINED else { @@ -4783,6 +5131,9 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, MqttPublishResp resp; byte* payload = NULL; char* topic = NULL; +#if defined(WOLFMQTT_V5) && defined(WOLFMQTT_BROKER_RETAINED) + int retain_rc = MQTT_CODE_SUCCESS; +#endif #if WOLFMQTT_MAX_QOS >= 2 int qos2_duplicate = 0; #endif @@ -4801,6 +5152,33 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, return rc; } +#ifdef WOLFMQTT_V5 + /* [MQTT-3.3.4-6] A PUBLISH sent from a client to the server MUST NOT carry + * a Subscription Identifier; reject as a Protocol Error instead of + * forwarding the foreign id to subscribers. */ + if (bc->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5 && + pub.props != NULL && + BrokerProps_Find(pub.props, MQTT_PROP_SUBSCRIPTION_ID) != NULL) { + WBLOG_ERR(broker, "broker: client PUBLISH carried SUBSCRIPTION_ID " + "sock=%d", (int)bc->sock); + (void)BrokerSend_Disconnect(bc, MQTT_REASON_PROTOCOL_ERR); + rc = MQTT_CODE_ERROR_MALFORMED_DATA; + goto publish_cleanup; + } +#endif + + /* The decoder only captured pub.buffer_len bytes of the payload; if that is + * short of the declared pub.total_len the message exceeded the broker + * receive buffer. Reject it rather than fanning out a packet whose + * Remaining Length overstates the bytes we actually hold. */ + if (pub.total_len > 0 && pub.buffer_len < pub.total_len) { + WBLOG_ERR(broker, + "broker: PUBLISH payload exceeds buffer (have %u of %u) sock=%d", + (unsigned)pub.buffer_len, (unsigned)pub.total_len, (int)bc->sock); + rc = MQTT_CODE_ERROR_OUT_OF_BUFFER; + goto publish_cleanup; + } + /* [MQTT-3.3.2-2] PUBLISH topic name wildcard / [MQTT-4.7.3-1] * empty-topic checks now live in MqttDecode_Publish via * MqttPacket_TopicNameValid, which has already returned @@ -4872,17 +5250,32 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, #ifdef WOLFMQTT_STATIC_MEMORY word16 tlen = pub.topic_name_len; if (tlen >= BROKER_MAX_TOPIC_LEN) { - tlen = BROKER_MAX_TOPIC_LEN - 1; + /* Reject rather than truncate: a truncated topic can match a + * different subscriber filter than the wire topic (filter/auth + * bypass) and collide retained-message keys. */ + WBLOG_ERR(broker, + "broker: PUBLISH topic too long len=%u max=%d sock=%d", + (unsigned)tlen, BROKER_MAX_TOPIC_LEN, (int)bc->sock); + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + goto publish_cleanup; } XMEMCPY(topic_buf, pub.topic_name, tlen); topic_buf[tlen] = '\0'; topic = topic_buf; #else topic = (char*)WOLFMQTT_MALLOC(pub.topic_name_len + 1); - if (topic != NULL) { - XMEMCPY(topic, pub.topic_name, pub.topic_name_len); - topic[pub.topic_name_len] = '\0'; + if (topic == NULL) { + /* Without the topic copy, retained-store and fan-out are skipped; + * returning here prevents the QoS 1/2 ACK encoder below from + * falsely reporting SUCCESS for a message that was never + * delivered. */ + WBLOG_ERR(broker, "broker: PUBLISH topic alloc failed sock=%d", + (int)bc->sock); + rc = MQTT_CODE_ERROR_MEMORY; + goto publish_cleanup; } + XMEMCPY(topic, pub.topic_name, pub.topic_name_len); + topic[pub.topic_name_len] = '\0'; #endif } /* Use payload pointer directly from decoded packet - rx_buf is not @@ -4919,6 +5312,9 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, WBLOG_ERR(broker, "Retained store failed: %s", MqttClient_ReturnCodeToString(ret_rc)); } +#ifdef WOLFMQTT_V5 + retain_rc = ret_rc; +#endif } } } @@ -4935,6 +5331,7 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, int i; #else BrokerSub* sub = broker->subs; + BrokerSub* next_sub = NULL; #endif /* Fan out to matching subscribers */ #ifdef WOLFMQTT_STATIC_MEMORY @@ -4943,9 +5340,15 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, if (!sub->in_use) continue; #else while (sub) { + /* Snapshot the successor before any MqttPacket_Write: a fan-out + * write can drive an lws_service spin that frees this client's + * BrokerSub nodes re-entrantly (LWS_CALLBACK_CLOSED), so reading + * sub->next afterwards would dereference a freed node. */ + next_sub = sub->next; #endif if (sub->client != NULL && sub->client->protocol_level != 0 && + sub->client->connected && BROKER_STR_VALID(sub->filter) && BrokerTopicMatch(sub->filter, topic)) { MqttQoS eff_qos; @@ -4956,6 +5359,7 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, * Sub-encoder failure is logged but not propagated. */ { int sub_rc; + int wr; MqttPublish out_pub; XMEMSET(&out_pub, 0, sizeof(out_pub)); out_pub.topic_name = topic; @@ -4967,6 +5371,7 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, out_pub.duplicate = 0; out_pub.buffer = payload; out_pub.total_len = pub.total_len; + out_pub.buffer_len = pub.buffer_len; #ifdef WOLFMQTT_V5 out_pub.protocol_level = sub->client->protocol_level; if (sub->client->protocol_level >= @@ -4981,9 +5386,23 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, "broker: PUBLISH fwd sock=%d -> sock=%d " "topic=%s qos=%d len=%u", (int)bc->sock, (int)sub->client->sock, - topic, eff_qos, (unsigned)pub.total_len); - (void)MqttPacket_Write(&sub->client->client, + BrokerLog_Sanitize(topic), eff_qos, + (unsigned)pub.total_len); + wr = MqttPacket_Write(&sub->client->client, sub->client->tx_buf, sub_rc); + /* Static fan-out has no per-subscriber resume queue, so + * a partial write leaves this subscriber's stream + * desynced and unrecoverable. Tear down its socket and + * clear connected; the main loop reaps it on the next + * read error, and the match guard above then skips this + * client's other matching subscriptions. */ + if (wr != sub_rc && + sub->client->sock != BROKER_SOCKET_INVALID) { + broker->net.close(broker->net.ctx, + sub->client->sock); + sub->client->sock = BROKER_SOCKET_INVALID; + sub->client->connected = 0; + } } else { WBLOG_ERR(broker, @@ -5026,7 +5445,8 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, "broker: PUBLISH enq sock=%d -> sock=%d " "topic=%s qos=%d len=%u", (int)bc->sock, (int)sub->client->sock, - topic, eff_qos, (unsigned)pub.total_len); + BrokerLog_Sanitize(topic), eff_qos, + (unsigned)pub.total_len); BrokerClient_DrainOutQueue(sub->client); } } @@ -5056,7 +5476,7 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, } } } - sub = sub->next; + sub = next_sub; #endif } } @@ -5066,7 +5486,14 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, resp.packet_id = pub.packet_id; #ifdef WOLFMQTT_V5 resp.protocol_level = bc->protocol_level; + /* A retained-store failure (cap reached / OOM) must not be ACKed as + * success: tell the publisher the quota was exceeded [MQTT-3.4.2]. */ +#ifdef WOLFMQTT_BROKER_RETAINED + resp.reason_code = (retain_rc != MQTT_CODE_SUCCESS) + ? MQTT_REASON_QUOTA_EXCEEDED : MQTT_REASON_SUCCESS; +#else resp.reason_code = MQTT_REASON_SUCCESS; +#endif resp.props = NULL; #endif rc = MqttEncode_PublishResp(bc->tx_buf, BROKER_CLIENT_TX_SZ(bc), @@ -5204,12 +5631,16 @@ static void BrokerClient_AbnormalClose(MqttBroker* broker, BrokerClient* bc) * [MQTT-4.13]/[MQTT-4.8.0-1] mandate connection close on malformed * packets. * - Server-side resource exhaustion (allocator failure, per-client cap - * reached) - the connection must be torn down so resources release. */ + * reached) - the connection must be torn down so resources release. + * - v5 property protocol errors (too many properties, duplicate singleton) + * are malformed packets [MQTT-4.13.1]; the client must be disconnected. */ static int BrokerRcIsFatal(int rc) { return (rc == MQTT_CODE_ERROR_MALFORMED_DATA || rc == MQTT_CODE_ERROR_PACKET_TYPE || rc == MQTT_CODE_ERROR_PACKET_ID || + rc == MQTT_CODE_ERROR_PROPERTY || + rc == MQTT_CODE_ERROR_PROPERTY_MISMATCH || rc == MQTT_CODE_ERROR_MEMORY || rc == MQTT_CODE_ERROR_OUT_OF_BUFFER); } @@ -5222,6 +5653,19 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) int rc; int activity = 0; +#ifdef ENABLE_MQTT_WEBSOCKET + /* A peer-initiated WS close during another client's fan-out write defers + * this client's removal via pending_remove, but no end-of-dispatch + * consumer runs on its own stack. Reap it here before any read so it is + * not re-closed by BrokerClient_AbnormalClose, which would publish its + * Will a second time. Will and subscriptions were handled at close. */ + if (bc->ws_ctx != NULL && + ((BrokerWsCtx*)bc->ws_ctx)->pending_remove) { + BrokerClient_Remove(broker, bc); + return 0; + } +#endif + #ifdef ENABLE_MQTT_TLS /* Complete TLS handshake before processing MQTT packets */ if (!bc->tls_handshake_done) { @@ -5244,7 +5688,8 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) char* cn = wolfSSL_X509_get_subjectCN(peer); (void)cn; /* may be unused if logging disabled */ WBLOG_INFO(broker, "broker: TLS client cert sock=%d CN=%s", - (int)bc->sock, cn ? cn : "(unknown)"); + (int)bc->sock, + BrokerLog_Sanitize(cn ? cn : "(unknown)")); wolfSSL_X509_free(peer); } } @@ -5460,6 +5905,30 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) BrokerClient_AbnormalClose(broker, bc); return 0; } + #if defined(WOLFMQTT_V5) && defined(WOLFMQTT_BROKER_WILL) + /* [MQTT-3.14.4-3] A v5 DISCONNECT with Reason Code 0x04 + * (Disconnect with Will Message) asks the broker to publish + * the Will rather than discard it. */ + if (bc->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5 && + bc->client.packet.remain_len > 0) { + MqttDisconnect disc; + XMEMSET(&disc, 0, sizeof(disc)); + disc.protocol_level = bc->protocol_level; + if (MqttDecode_Disconnect(bc->rx_buf, rc, &disc) >= 0 && + disc.reason_code == + MQTT_REASON_DISCONNECT_W_WILL_MSG) { + BrokerClient_PublishWill(broker, bc); + } + else { + BrokerClient_ClearWill(bc); + } + /* Free any decoded v5 DISCONNECT properties. */ + if (disc.props != NULL) { + (void)MqttProps_Free(disc.props); + } + } + else + #endif BrokerClient_ClearWill(bc); /* normal disconnect */ /* Session persistence: keep subs if clean_session=0 */ if (bc->clean_session) { @@ -5502,7 +5971,7 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) /* Check keepalive timeout (MQTT spec 3.1.2.10: 1.5x keep alive) */ if (bc->keep_alive_sec > 0) { WOLFMQTT_BROKER_TIME_T now = WOLFMQTT_BROKER_GET_TIME_S(); - if ((now - bc->last_rx) > + if (now >= bc->last_rx && (now - bc->last_rx) > (WOLFMQTT_BROKER_TIME_T)(bc->keep_alive_sec * 3 / 2)) { WBLOG_ERR(broker, "broker: keepalive timeout sock=%d", (int)bc->sock); #ifdef WOLFMQTT_V5 @@ -5520,6 +5989,22 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) return 0; } } + else if (!bc->connected) { + /* Pre-CONNECT idle timeout. A freshly accepted client has + * keep_alive_sec == 0 until CONNECT completes, so it is not covered by + * the keepalive check above; evict it once it has been idle past the + * deadline (last_rx is the accept time until the first full packet) + * so half-open sockets cannot squat the client table. */ + WOLFMQTT_BROKER_TIME_T now = WOLFMQTT_BROKER_GET_TIME_S(); + if (now >= bc->last_rx && (now - bc->last_rx) > + (WOLFMQTT_BROKER_TIME_T)BROKER_CONNECT_TIMEOUT_SEC) { + WBLOG_ERR(broker, "broker: pre-CONNECT idle timeout sock=%d", + (int)bc->sock); + BrokerSubs_RemoveClient(broker, bc); + BrokerClient_Remove(broker, bc); + return 0; + } + } return activity; } @@ -5843,6 +6328,16 @@ int MqttBroker_Start(MqttBroker* broker) #ifdef ENABLE_MQTT_WEBSOCKET if (broker->use_websocket) { + #ifdef WOLFMQTT_BROKER_NO_INSECURE + /* TLS-only build: a plaintext WebSocket listener would silently bypass + * the policy the plain-TCP listener enforces. Require WSS. */ + if (broker->ws_tls_cert == NULL) { + WBLOG_ERR(broker, "broker: plaintext WebSocket listener refused in " + "TLS-only build (WOLFMQTT_BROKER_NO_INSECURE); set ws_tls_cert " + "for WSS"); + return MQTT_CODE_ERROR_BAD_ARG; + } + #endif rc = BrokerWs_Init(broker); if (rc != MQTT_CODE_SUCCESS) { WBLOG_ERR(broker, "broker: WebSocket init failed rc=%d", rc); diff --git a/src/mqtt_broker_persist.c b/src/mqtt_broker_persist.c index d9e21195b..f25e722c3 100644 --- a/src/mqtt_broker_persist.c +++ b/src/mqtt_broker_persist.c @@ -1251,6 +1251,7 @@ static int wmqb_decode_and_insert_retained(MqttBroker* broker, word64 store_time; word32 expiry; byte qos; + WOLFMQTT_BROKER_TIME_T now; rc = wmqb_read_header(blob, blob_len, BROKER_PERSIST_NS_RETAINED, &body_len); @@ -1271,6 +1272,17 @@ static int wmqb_decode_and_insert_retained(MqttBroker* broker, return MQTT_CODE_ERROR_MALFORMED_DATA; } + /* Skip records whose expiry already elapsed so they do not consume a + * retained slot / the cap at restore. Counted as skipped, not loaded. + * now >= store_time guards the unsigned subtraction against a backward + * clock step (mirrors the orphan-session restore sweep below). */ + now = WOLFMQTT_BROKER_GET_TIME_S(); + if (expiry > 0 && + now >= (WOLFMQTT_BROKER_TIME_T)store_time && + (now - (WOLFMQTT_BROKER_TIME_T)store_time) >= expiry) { + return 1; + } + #ifdef WOLFMQTT_STATIC_MEMORY { int i; @@ -1311,6 +1323,14 @@ static int wmqb_decode_and_insert_retained(MqttBroker* broker, #else { BrokerRetainedMsg* m; + /* Enforce the same dynamic cap on restore that BrokerRetained_Store + * applies, otherwise a restart would reset retained_count to 0 and + * let a client add another BROKER_MAX_RETAINED topics (heap DoS). + * Return non-zero so the iterator counts it as skipped (not loaded) + * rather than silently hiding the dropped record. */ + if (broker->retained_count >= BROKER_MAX_RETAINED) { + return 1; + } m = (BrokerRetainedMsg*)WOLFMQTT_MALLOC(sizeof(*m)); if (m == NULL) { return MQTT_CODE_ERROR_MEMORY; @@ -1346,6 +1366,7 @@ static int wmqb_decode_and_insert_retained(MqttBroker* broker, m->expiry_sec = expiry; m->next = broker->retained; broker->retained = m; + broker->retained_count++; } #endif return 0; diff --git a/src/mqtt_client.c b/src/mqtt_client.c index f5bc6fac9..7c0ce4ddb 100644 --- a/src/mqtt_client.c +++ b/src/mqtt_client.c @@ -2506,6 +2506,10 @@ int MqttClient_Subscribe(MqttClient *client, MqttSubscribe *subscribe) int MqttClient_Unsubscribe(MqttClient *client, MqttUnsubscribe *unsubscribe) { int rc; +#ifdef WOLFMQTT_V5 + int i; + word16 reason_count; +#endif /* Validate required arguments */ if (client == NULL || unsubscribe == NULL) { @@ -2595,6 +2599,25 @@ int MqttClient_Unsubscribe(MqttClient *client, MqttUnsubscribe *unsubscribe) #endif #ifdef WOLFMQTT_V5 + /* Detect broker rejection. A v5 UNSUBACK carries one reason code per + * topic filter; any code with the high bit set (>= 0x80) means the + * broker refused to remove that subscription, so the caller must not + * assume the filter is gone. Mirrors the SUBSCRIBE rejection path. */ + if (rc == MQTT_CODE_SUCCESS && + unsubscribe->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5 && + unsubscribe->ack.reason_codes != NULL) { + reason_count = unsubscribe->ack.reason_code_count; + if (reason_count > (word16)unsubscribe->topic_count) { + reason_count = (word16)unsubscribe->topic_count; + } + for (i = 0; i < (int)reason_count; i++) { + if (unsubscribe->ack.reason_codes[i] & 0x80) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_UNSUBSCRIBE_REJECTED); + break; + } + } + } + if (unsubscribe->ack.props != NULL) { /* Release the allocated properties */ MqttClient_PropsFree(unsubscribe->ack.props); @@ -2891,6 +2914,21 @@ int MqttClient_Auth(MqttClient *client, MqttAuth* auth) } #endif + /* Scrub the decoded AUTH response from rx_buf. Its properties (e.g. the + * MQTT_PROP_AUTH_DATA SASL blob) point into rx_buf and would otherwise + * linger until the next read overwrites them. MqttClient_WaitType above + * already delivered and freed auth->props, so the bytes are consumed + * before this scrub and the caller has no live pointer into rx_buf. */ +#ifdef WOLFMQTT_MULTITHREAD + /* Hold lockRecv so the scrub cannot race a concurrent read into rx_buf. */ + if (wm_SemLock(&client->lockRecv) == 0) { + CLIENT_FORCE_ZERO(client->rx_buf, client->rx_buf_len); + wm_SemUnlock(&client->lockRecv); + } +#else + CLIENT_FORCE_ZERO(client->rx_buf, client->rx_buf_len); +#endif + /* reset state */ auth->stat.write = MQTT_MSG_BEGIN; @@ -3162,6 +3200,8 @@ const char* MqttClient_ReturnCodeToString(int return_code) return "Error (Broker refused connection)"; case MQTT_CODE_ERROR_SUBSCRIBE_REJECTED: return "Error (Broker rejected subscription)"; + case MQTT_CODE_ERROR_UNSUBSCRIBE_REJECTED: + return "Error (Broker rejected unsubscribe)"; #if defined(ENABLE_MQTT_CURL) case MQTT_CODE_ERROR_CURL: return "Error (libcurl)"; diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 66eba6ce9..fc541e495 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -832,6 +832,8 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, { int rc = 0; int total, tmp; + int prop_count = 0; + word32 seen_lo = 0, seen_hi = 0; /* singleton-property duplicate guard */ MqttProp* cur_prop; byte* buf = pbuf; @@ -839,6 +841,12 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, while (((int)prop_len > 0) && (rc >= 0)) { + /* Bound the number of properties a single message may carry so a peer + * cannot saturate the shared property pool. */ + if (++prop_count > MQTT_MAX_PROPS) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PROPERTY); + break; + } /* Allocate a structure and add to head. */ cur_prop = MqttProps_Add(props); if (cur_prop == NULL) { @@ -873,6 +881,30 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, break; } + /* [MQTT v5 2.2.2.2] Every property except User Property and + * Subscription Identifier MUST appear at most once; a duplicate is a + * Protocol Error. */ + if (cur_prop->type != MQTT_PROP_USER_PROP && + cur_prop->type != MQTT_PROP_SUBSCRIPTION_ID) { + word32 bit; + if (cur_prop->type < 32) { + bit = (word32)1 << cur_prop->type; + if (seen_lo & bit) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PROPERTY); + break; + } + seen_lo |= bit; + } + else { + bit = (word32)1 << (cur_prop->type - 32); + if (seen_hi & bit) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PROPERTY); + break; + } + seen_hi |= bit; + } + } + switch (gPropMatrix[cur_prop->type].data) { case MQTT_DATA_TYPE_BYTE: @@ -902,6 +934,11 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, buf += tmp; total += tmp; prop_len -= (word32)tmp; + /* [MQTT-3.3.2-7] A Topic Alias of 0 is a Protocol Error. */ + if (cur_prop->type == MQTT_PROP_TOPIC_ALIAS && + cur_prop->data_short == 0) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } break; } case MQTT_DATA_TYPE_INT: @@ -942,6 +979,14 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, buf += tmp; total += tmp; prop_len -= (word32)tmp; + /* [MQTT-3.3.2-14] A Response Topic is a Topic Name and + * MUST NOT contain wildcard characters. */ + if (cur_prop->type == MQTT_PROP_RESP_TOPIC && + !MqttPacket_TopicNameValid(cur_prop->data_str.str, + cur_prop->data_str.len, + MQTT_CONNECT_PROTOCOL_LEVEL_5)) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } } else { /* Invalid length */ @@ -964,6 +1009,12 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, buf += tmp; total += tmp; prop_len -= (word32)tmp; + /* [MQTT-3.8.2.1.2] A Subscription Identifier of 0 is + * reserved and a Protocol Error. */ + if (cur_prop->type == MQTT_PROP_SUBSCRIPTION_ID && + cur_prop->data_int == 0) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } break; } case MQTT_DATA_TYPE_BINARY: @@ -1896,6 +1947,14 @@ int MqttEncode_Publish(byte *tx_buf, int tx_buf_len, MqttPublish *publish, if (payload_len > (tx_buf_len - (header_len + variable_len))) { payload_len = (tx_buf_len - (header_len + variable_len)); } + /* Never copy more than the bytes actually present in publish->buffer. + * total_len carries the wire-declared size, which can exceed the + * decoded buffer_len; clamping here prevents an OOB read past the + * source buffer during fan-out. */ + if (publish->buffer_len > 0 && + publish->buffer_len < (word32)payload_len) { + payload_len = (int)publish->buffer_len; + } if (tx_payload != NULL) { XMEMCPY(tx_payload, publish->buffer, payload_len); } @@ -1990,6 +2049,8 @@ int MqttDecode_Publish(byte *rx_buf, int rx_buf_len, MqttPublish *publish) if (publish->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { word32 props_len = 0; int tmp; + MqttProp* prop_iter; + byte has_topic_alias = 0; /* Decode Length of Properties */ if (rx_buf_len < (rx_payload - rx_buf)) { @@ -2015,6 +2076,23 @@ int MqttDecode_Publish(byte *rx_buf, int rx_buf_len, MqttPublish *publish) else { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); } + + /* [MQTT-3.3.2-8] v5 section 3.3.2.3.4: a zero-length Topic Name is + * only valid when a Topic Alias property supplies the topic. */ + if (publish->topic_name_len == 0) { + for (prop_iter = publish->props; prop_iter != NULL; + prop_iter = prop_iter->next) { + if (prop_iter->type == MQTT_PROP_TOPIC_ALIAS) { + has_topic_alias = 1; + break; + } + } + if (!has_topic_alias) { + MqttProps_Free(publish->props); + publish->props = NULL; + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } + } } #endif @@ -2192,6 +2270,11 @@ int MqttDecode_PublishResp(byte* rx_buf, int rx_buf_len, byte type, publish_resp->props = NULL; if (publish_resp->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { if (remain_len > MQTT_DATA_LEN_SIZE) { + /* remain_len is attacker-controlled; confirm the reason-code + * byte was received before reading past the buffer */ + if (rx_buf_len < (rx_payload - rx_buf) + 1) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } /* Decode the Reason Code */ publish_resp->reason_code = *rx_payload++; } @@ -2425,26 +2508,30 @@ int MqttDecode_Subscribe(byte *rx_buf, int rx_buf_len, MqttSubscribe *subscribe) byte options; word16 filter_len = 0; if (subscribe->topic_count >= MAX_MQTT_TOPICS) { - return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + tmp = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + break; } topic = &subscribe->topics[subscribe->topic_count]; tmp = MqttDecode_String(rx_payload, &topic->topic_filter, &filter_len, (word32)(rx_end - rx_payload)); if (tmp < 0) { - return tmp; + break; } if (rx_payload + tmp > rx_end) { - return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + tmp = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + break; } /* [MQTT-4.7.3-1] / [MQTT-4.7.1-2] / [MQTT-4.7.1-3] Reject * empty filters and malformed wildcard placement. */ if (!MqttPacket_TopicFilterValid(topic->topic_filter, filter_len)) { - return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + tmp = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + break; } rx_payload += tmp; if (rx_payload >= rx_end) { - return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + tmp = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + break; } options = *rx_payload++; /* MQTT 3.1.1 section 3.8.3.1: bits 2-7 of the SUBSCRIBE options byte @@ -2461,7 +2548,8 @@ int MqttDecode_Subscribe(byte *rx_buf, int rx_buf_len, MqttSubscribe *subscribe) if ((options & 0xC0) != 0 || (options & 0x03) > MQTT_QOS_2 || ((options >> 4) & 0x03) == 0x03) { - return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + tmp = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + break; } } else @@ -2469,7 +2557,8 @@ int MqttDecode_Subscribe(byte *rx_buf, int rx_buf_len, MqttSubscribe *subscribe) { if ((options & 0xFC) != 0 || (options & 0x03) > MQTT_QOS_2) { - return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + tmp = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + break; } } topic->qos = (MqttQoS)(options & 0x03); @@ -2479,8 +2568,18 @@ int MqttDecode_Subscribe(byte *rx_buf, int rx_buf_len, MqttSubscribe *subscribe) /* [MQTT-3.8.3-3] The payload of a SUBSCRIBE packet MUST contain at * least one Topic Filter / QoS pair. v5 section 3.8.3 carries the same * minimum-cardinality requirement. */ - if (subscribe->topic_count == 0) { - return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + if (tmp >= 0 && subscribe->topic_count == 0) { + tmp = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } + if (tmp < 0) { + #ifdef WOLFMQTT_V5 + /* Free any v5 properties decoded above so a malformed topic + * filter cannot leak the property list; the broker caller only + * frees props on the success path. */ + MqttProps_Free(subscribe->props); + subscribe->props = NULL; + #endif + return tmp; } } @@ -2783,23 +2882,26 @@ int MqttDecode_Unsubscribe(byte *rx_buf, int rx_buf_len, MqttUnsubscribe *unsubs MqttTopic *topic; word16 filter_len = 0; if (unsubscribe->topic_count >= MAX_MQTT_TOPICS) { - return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + tmp = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + break; } topic = &unsubscribe->topics[unsubscribe->topic_count]; tmp = MqttDecode_String(rx_payload, &topic->topic_filter, &filter_len, (word32)(rx_end - rx_payload)); if (tmp < 0) { - return tmp; + break; } if (rx_payload + tmp > rx_end) { - return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + tmp = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + break; } /* [MQTT-4.7.3-1] / [MQTT-4.7.1-2] / [MQTT-4.7.1-3]: an * UNSUBSCRIBE Topic Filter must obey the same syntax rules * as a SUBSCRIBE Topic Filter. */ if (!MqttPacket_TopicFilterValid(topic->topic_filter, filter_len)) { - return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + tmp = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + break; } rx_payload += tmp; unsubscribe->topic_count++; @@ -2808,8 +2910,18 @@ int MqttDecode_Unsubscribe(byte *rx_buf, int rx_buf_len, MqttUnsubscribe *unsubs /* [MQTT-3.10.3-2] The Payload of an UNSUBSCRIBE packet MUST * contain at least one Topic Filter. v5 section 3.10.3 carries the same * minimum-cardinality requirement. */ - if (unsubscribe->topic_count == 0) { - return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + if (tmp >= 0 && unsubscribe->topic_count == 0) { + tmp = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } + if (tmp < 0) { + #ifdef WOLFMQTT_V5 + /* Free any v5 properties decoded above so a malformed topic + * filter cannot leak the property list; the broker caller only + * frees props on the success path. */ + MqttProps_Free(unsubscribe->props); + unsubscribe->props = NULL; + #endif + return tmp; } } @@ -2838,6 +2950,14 @@ int MqttDecode_UnsubscribeAck(byte *rx_buf, int rx_buf_len, return header_len; } + /* Reject a truncated read: a broker advertising a Remaining Length larger + * than the bytes actually received would otherwise make reason_code_count + * (derived from remain_len) point past the buffer, so the caller's + * rejection scan reads out of bounds. */ + if (rx_buf_len < header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + /* Validate remain_len (need at least packet_id) */ if (remain_len < MQTT_DATA_LEN_SIZE) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); @@ -2857,26 +2977,29 @@ int MqttDecode_UnsubscribeAck(byte *rx_buf, int rx_buf_len, #ifdef WOLFMQTT_V5 unsubscribe_ack->props = NULL; if (unsubscribe_ack->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + /* Bound properties by the packet end, not rx_buf_len: a caller + * buffer may hold bytes from the next packet, and consuming them + * here would push rx_payload past this packet and underflow + * reason_code_count to a huge word16. */ + byte* packet_end = &rx_buf[header_len + remain_len]; + if (remain_len > MQTT_DATA_LEN_SIZE) { word32 props_len = 0; int props_tmp; /* Decode Length of Properties */ - if (rx_buf_len < (rx_payload - rx_buf)) { - return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); - } props_tmp = MqttDecode_Vbi(rx_payload, &props_len, - (word32)(rx_buf_len - (rx_payload - rx_buf))); + (word32)(packet_end - rx_payload)); if (props_tmp < 0) return props_tmp; - if (props_len <= (word32)(rx_buf_len - (rx_payload - rx_buf))) { + if (props_len <= (word32)(packet_end - (rx_payload + props_tmp))) { rx_payload += props_tmp; if (props_len > 0) { /* Decode the Properties */ props_tmp = MqttDecode_Props(MQTT_PACKET_TYPE_UNSUBSCRIBE_ACK, &unsubscribe_ack->props, rx_payload, - (word32)(rx_buf_len - (rx_payload - rx_buf)), + (word32)(packet_end - rx_payload), props_len); if (props_tmp < 0) return props_tmp; @@ -2888,8 +3011,13 @@ int MqttDecode_UnsubscribeAck(byte *rx_buf, int rx_buf_len, } } - /* Reason codes are stored in the payload */ + /* Reason codes are stored in the payload, one per topic filter */ + if (rx_payload > packet_end) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } unsubscribe_ack->reason_codes = rx_payload; + unsubscribe_ack->reason_code_count = + (word16)(packet_end - rx_payload); } #endif } @@ -3190,6 +3318,11 @@ int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, MqttDisconnect *disc) disc->props = NULL; if (remain_len > 0) { + /* remain_len is attacker-controlled; confirm the reason-code byte + * was received before reading past the buffer */ + if (rx_buf_len < (rx_payload - rx_buf) + 1) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } /* Decode variable header */ disc->reason_code = *rx_payload++; @@ -3324,6 +3457,11 @@ int MqttDecode_Auth(byte *rx_buf, int rx_buf_len, MqttAuth *auth) auth->props = NULL; if (remain_len > 0) { + /* remain_len is attacker-controlled; confirm the reason-code byte + * was received before reading past the buffer */ + if (rx_buf_len < (rx_payload - rx_buf) + 1) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } /* Decode variable header */ auth->reason_code = *rx_payload++; if ((auth->reason_code == MQTT_REASON_SUCCESS) || @@ -3538,8 +3676,11 @@ int MqttPacket_Write(MqttClient *client, byte* tx_buf, int tx_buf_len) { int rc; #ifdef WOLFMQTT_V5 - if ((client->packet_sz_max > 0) && (tx_buf_len > - (int)client->packet_sz_max)) + /* Compare unsigned: packet_sz_max is word32 and a malicious CONNACK can + * set it above INT_MAX, where the old (int) cast turned it negative and + * wedged every write. */ + if ((client->packet_sz_max > 0) && (tx_buf_len > 0) && + ((word32)tx_buf_len > client->packet_sz_max)) { rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_SERVER_PROP); } diff --git a/src/mqtt_sn_packet.c b/src/mqtt_sn_packet.c index 3205e2377..4236c3961 100644 --- a/src/mqtt_sn_packet.c +++ b/src/mqtt_sn_packet.c @@ -198,7 +198,7 @@ int SN_Decode_Header(byte *rx_buf, int rx_buf_len, /* Bytes the declared packet leaves for the MsgId at * rx_buf + id_offset. Bound the read by total_len (the declared * packet length, already validated <= rx_buf_len above), not by - * rx_buf_len: this keeps the read inside the buffer (CWE-125) and + * rx_buf_len: this keeps the read inside the buffer and * additionally rejects a frame whose declared length stops short * of the MsgId rather than reading adjacent bytes. Evaluate as a * signed int and reject before the unsigned cast below, so a short @@ -1332,7 +1332,7 @@ int SN_Decode_Publish(byte *rx_buf, int rx_buf_len, SN_Publish *publish) * MsgId so the matching PUBACK/PUBREC can be correlated. Reject MsgId=0 * here; otherwise SN_Client_HandlePacket would emit a response carrying * MsgId=0 that no conformant gateway can match, leaving its retransmit - * timer to replay the same message (CWE-20). Mirrors the standard MQTT + * timer to replay the same message. Mirrors the standard MQTT * decoder guard in mqtt_packet.c. QoS 0 and QoS -1 (MQTT_QOS_3, the * connectionless publish) send no response and legitimately use MsgId=0, * so they are intentionally excluded. */ diff --git a/src/mqtt_socket.c b/src/mqtt_socket.c index b2617f4bf..e6866203c 100644 --- a/src/mqtt_socket.c +++ b/src/mqtt_socket.c @@ -435,6 +435,12 @@ int MqttSocket_Connect(MqttClient *client, const char* host, word16 port, /* Setup the WolfSSL library */ rc = wolfSSL_Init(); + if (rc == WOLFSSL_SUCCESS) { + /* Record that this client incremented wolfSSL's global init + * refcount so only it calls wolfSSL_Cleanup() on disconnect, + * never tearing down wolfSSL for sibling clients. */ + MqttClient_Flags(client, 0, MQTT_CLIENT_FLAG_WOLFSSL_INIT); + } /* Issue callback to allow setup of the wolfSSL_CTX and cert verification settings */ @@ -571,7 +577,13 @@ int MqttSocket_Disconnect(MqttClient *client) wolfSSL_CTX_free(client->tls.ctx); client->tls.ctx = NULL; } - wolfSSL_Cleanup(); + /* Only balance a wolfSSL_Init() this client actually made; otherwise a + * non-TLS disconnect could drive the global refcount to 0 and tear + * wolfSSL down for other live clients in the same process. */ + if (MqttClient_Flags(client, 0, 0) & MQTT_CLIENT_FLAG_WOLFSSL_INIT) { + wolfSSL_Cleanup(); + MqttClient_Flags(client, MQTT_CLIENT_FLAG_WOLFSSL_INIT, 0); + } #endif MqttClient_Flags(client, (MQTT_CLIENT_FLAG_IS_TLS | MQTT_CLIENT_FLAG_IS_DTLS), 0); diff --git a/tests/test_broker_connect.c b/tests/test_broker_connect.c index ef3f6dd03..5f33468a3 100644 --- a/tests/test_broker_connect.c +++ b/tests/test_broker_connect.c @@ -589,6 +589,64 @@ TEST(connect_v311_binary_password_exact_match_accepted) MqttBroker_Stop(&broker); MqttBroker_Free(&broker); } + +/* An unauthenticated CONNECT must not mutate another + * client's session. A victim authenticates and stays connected; an attacker + * then reuses the victim's client_id with a wrong password. The broker must + * reject the attacker at the credential gate BEFORE the duplicate-takeover + * path, so the victim is never disconnected. Pre-fix, takeover ran before + * auth and closed the victim - g_clients[0].closed is the load-bearing + * assertion. */ +TEST(connect_unauth_client_id_does_not_take_over_victim) +{ + MqttBroker broker; + MqttBrokerNet net; + int i; + /* Victim: client_id "vic", user "user", pass "pass", CleanSession=1. */ + static const byte victim[] = { + 0x10, 0x1B, + 0x00, 0x04, 'M', 'Q', 'T', 'T', + 0x04, 0xC2, 0x00, 0x3C, + 0x00, 0x03, 'v', 'i', 'c', + 0x00, 0x04, 'u', 's', 'e', 'r', + 0x00, 0x04, 'p', 'a', 's', 's' + }; + /* Attacker: same client_id "vic", user "user", WRONG pass "bad". */ + static const byte attacker[] = { + 0x10, 0x1A, + 0x00, 0x04, 'M', 'Q', 'T', 'T', + 0x04, 0xC2, 0x00, 0x3C, + 0x00, 0x03, 'v', 'i', 'c', + 0x00, 0x04, 'u', 's', 'e', 'r', + 0x00, 0x03, 'b', 'a', 'd' + }; + + install_mock_net(&net); + XMEMSET(&broker, 0, sizeof(broker)); + ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Init(&broker, &net)); + broker.auth_user = "user"; + broker.auth_pass = "pass"; + ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Start(&broker)); + + reset_mock_clients(2); + mock_client_input_append(0, victim, sizeof(victim)); + mock_client_input_append(1, attacker, sizeof(attacker)); + for (i = 0; i < 16; i++) { + MqttBroker_Step(&broker); + } + + /* Victim authenticated and must remain connected. */ + ASSERT_TRUE(g_clients[0].out_len >= 4); + ASSERT_EQ(MQTT_CONNECT_ACK_CODE_ACCEPTED, g_clients[0].out_buf[3]); + ASSERT_FALSE(g_clients[0].closed); + /* Attacker rejected on auth, not via session takeover. */ + ASSERT_TRUE(g_clients[1].out_len >= 4); + ASSERT_EQ(MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD, + g_clients[1].out_buf[3]); + + MqttBroker_Stop(&broker); + MqttBroker_Free(&broker); +} #endif /* WOLFMQTT_BROKER_AUTH */ #ifdef WOLFMQTT_V5 @@ -1562,6 +1620,267 @@ TEST(broker_unhandled_packet_type_closes) MqttBroker_Free(&broker); } +/* [MQTT-3.1.0-1]: a client's first packet MUST be CONNECT. The broker's + * pre-dispatch guard closes any client that sends another packet type + * first. A PUBLISH from a never-connected client must be dropped and the + * client closed - it must NOT fan out to subscribers. A deletion of the + * guard would let BrokerHandle_Publish run on the unauthenticated client, + * so the subscriber receiving zero PUBLISH packets is the load-bearing + * assertion. */ +TEST(broker_publish_before_connect_closes) +{ + MqttBroker broker; + MqttBrokerNet net; + int i; + /* Subscriber: CONNECT then SUBSCRIBE to "t" (qos 0). */ + static const byte sub_connect[] = { + 0x10, 0x0D, + 0x00, 0x04, 'M', 'Q', 'T', 'T', + 0x04, 0x02, 0x00, 0x3C, + 0x00, 0x01, 'S' + }; + static const byte sub_subscribe[] = { + 0x82, 0x06, + 0x00, 0x01, /* packet_id */ + 0x00, 0x01, 't', + 0x00 + }; + /* Attacker: PUBLISH "t"/"x" as the very first packet, no CONNECT. */ + static const byte pub_no_connect[] = { + 0x30, 0x04, + 0x00, 0x01, 't', + 'x' + }; + + install_mock_net(&net); + XMEMSET(&broker, 0, sizeof(broker)); + ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Init(&broker, &net)); + ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Start(&broker)); + + reset_mock_clients(2); + mock_client_input_append(0, sub_connect, sizeof(sub_connect)); + mock_client_input_append(0, sub_subscribe, sizeof(sub_subscribe)); + mock_client_input_append(1, pub_no_connect, sizeof(pub_no_connect)); + for (i = 0; i < 16; i++) { + MqttBroker_Step(&broker); + } + + /* Attacker connection closed for violating [MQTT-3.1.0-1]. */ + ASSERT_TRUE(g_clients[1].closed); + /* Subscriber must have received no fan-out from the pre-CONNECT PUBLISH. */ + ASSERT_EQ(0, count_packets_of_type(g_clients[0].out_buf, + g_clients[0].out_len, MQTT_PACKET_TYPE_PUBLISH)); + + MqttBroker_Stop(&broker); + MqttBroker_Free(&broker); +} + +#if defined(WOLFMQTT_BROKER_RETAINED) && !defined(WOLFMQTT_STATIC_MEMORY) +/* The dynamic retained-message list must be bounded. A client that + * publishes RETAIN=1 to more than BROKER_MAX_RETAINED distinct topics must not + * grow the list past the cap - pre-fix it grew without bound, enabling + * heap-exhaustion DoS. */ +TEST(broker_retained_list_capped) +{ + MqttBroker broker; + MqttBrokerNet net; + int i; + byte pub[8]; + static const byte connect[] = { + 0x10, 0x0F, + 0x00, 0x04, 'M', 'Q', 'T', 'T', + 0x04, 0x02, 0x00, 0x3C, + 0x00, 0x03, 'p', 'u', 'b' + }; + + install_mock_net(&net); + XMEMSET(&broker, 0, sizeof(broker)); + ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Init(&broker, &net)); + ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Start(&broker)); + + reset_mock_clients(1); + mock_client_input_append(0, connect, sizeof(connect)); + /* Publish RETAIN=1 (QoS 0) to BROKER_MAX_RETAINED + 5 distinct topics. */ + for (i = 0; i < BROKER_MAX_RETAINED + 5; i++) { + pub[0] = 0x31; /* PUBLISH, retain=1 */ + pub[1] = 0x06; /* remain = 6 */ + pub[2] = 0x00; pub[3] = 0x03; /* topic len 3 */ + pub[4] = 'r'; + pub[5] = (byte)('0' + (i / 10)); + pub[6] = (byte)('0' + (i % 10)); + pub[7] = 'x'; /* payload */ + mock_client_input_append(0, pub, sizeof(pub)); + } + for (i = 0; i < BROKER_MAX_RETAINED + 12; i++) { + MqttBroker_Step(&broker); + } + + /* The list is capped, not grown to BROKER_MAX_RETAINED + 5. */ + ASSERT_EQ(BROKER_MAX_RETAINED, broker.retained_count); + + MqttBroker_Stop(&broker); + MqttBroker_Free(&broker); +} + +/* A backward clock step must not make live retained messages look expired. + * Entries stamped in the "future" (store_time > now) would, with an unguarded + * unsigned subtraction, wrap to a huge elapsed value and be wrongly reaped; + * the now >= store_time guard keeps them. Test time is pinned to 0, so a node + * stamped at tick 1 models a clock that has since rolled back to 0. */ +TEST(broker_retained_clock_rollback_not_expired) +{ + MqttBroker broker; + MqttBrokerNet net; + BrokerRetainedMsg* rm; + int i; + byte pub[8]; + static const byte connect[] = { + 0x10, 0x0F, + 0x00, 0x04, 'M', 'Q', 'T', 'T', + 0x04, 0x02, 0x00, 0x3C, + 0x00, 0x03, 'p', 'u', 'b' + }; + + install_mock_net(&net); + XMEMSET(&broker, 0, sizeof(broker)); + ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Init(&broker, &net)); + ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Start(&broker)); + + reset_mock_clients(1); + mock_client_input_append(0, connect, sizeof(connect)); + /* Fill the cap with distinct retained topics. */ + for (i = 0; i < BROKER_MAX_RETAINED; i++) { + pub[0] = 0x31; /* PUBLISH, retain=1 */ + pub[1] = 0x06; + pub[2] = 0x00; pub[3] = 0x03; + pub[4] = 'r'; + pub[5] = (byte)('0' + (i / 10)); + pub[6] = (byte)('0' + (i % 10)); + pub[7] = 'x'; + mock_client_input_append(0, pub, sizeof(pub)); + } + for (i = 0; i < BROKER_MAX_RETAINED + 4; i++) { + MqttBroker_Step(&broker); + } + ASSERT_EQ(BROKER_MAX_RETAINED, broker.retained_count); + + /* Stamp every entry in the future relative to the pinned now=0 clock. */ + for (rm = broker.retained; rm != NULL; rm = rm->next) { + rm->store_time = 1; + rm->expiry_sec = 1; + } + + /* A new retained topic triggers the reap path. The future-stamped entries + * must be kept (not falsely expired), so the cap still rejects the new + * topic and the count is unchanged. */ + pub[0] = 0x31; + pub[1] = 0x06; + pub[2] = 0x00; pub[3] = 0x03; + pub[4] = 'n'; pub[5] = '0'; pub[6] = '0'; + pub[7] = 'y'; + mock_client_input_append(0, pub, sizeof(pub)); + for (i = 0; i < 4; i++) { + MqttBroker_Step(&broker); + } + + ASSERT_EQ(BROKER_MAX_RETAINED, broker.retained_count); + + MqttBroker_Stop(&broker); + MqttBroker_Free(&broker); +} +#endif /* WOLFMQTT_BROKER_RETAINED && !WOLFMQTT_STATIC_MEMORY */ + +#ifndef WOLFMQTT_STATIC_MEMORY +/* A single client cannot occupy more than BROKER_MAX_SUBS_PER_CLIENT + * slots in the shared subscription table; excess SUBSCRIBEs are refused so + * other clients are not denied service. */ +TEST(broker_per_client_subscription_cap) +{ + MqttBroker broker; + MqttBrokerNet net; + int i; + byte sub[10]; + static const byte connect[] = { + 0x10, 0x0F, + 0x00, 0x04, 'M', 'Q', 'T', 'T', + 0x04, 0x02, 0x00, 0x3C, + 0x00, 0x03, 's', 'u', 'b' + }; + + install_mock_net(&net); + XMEMSET(&broker, 0, sizeof(broker)); + ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Init(&broker, &net)); + ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Start(&broker)); + + reset_mock_clients(1); + mock_client_input_append(0, connect, sizeof(connect)); + /* Subscribe to BROKER_MAX_SUBS_PER_CLIENT + 3 distinct filters. */ + for (i = 0; i < BROKER_MAX_SUBS_PER_CLIENT + 3; i++) { + sub[0] = 0x82; /* SUBSCRIBE */ + sub[1] = 0x08; /* remain = 8 */ + sub[2] = (byte)((i + 1) >> 8); /* packet_id hi */ + sub[3] = (byte)((i + 1) & 0xFF); /* packet_id lo */ + sub[4] = 0x00; sub[5] = 0x03; /* filter len 3 */ + sub[6] = 'f'; + sub[7] = (byte)('0' + (i / 10)); + sub[8] = (byte)('0' + (i % 10)); + sub[9] = 0x00; /* options: QoS 0 */ + mock_client_input_append(0, sub, sizeof(sub)); + } + for (i = 0; i < BROKER_MAX_SUBS_PER_CLIENT + 12; i++) { + MqttBroker_Step(&broker); + } + + /* Capped, not grown to BROKER_MAX_SUBS_PER_CLIENT + 3. */ + ASSERT_TRUE(broker.clients != NULL); + ASSERT_EQ(BROKER_MAX_SUBS_PER_CLIENT, broker.clients->sub_count); + + MqttBroker_Stop(&broker); + MqttBroker_Free(&broker); +} +#endif /* !WOLFMQTT_STATIC_MEMORY */ + +#ifdef WOLFMQTT_V5 +/* [MQTT-3.3.4-6] A client PUBLISH carrying a Subscription Identifier is a + * Protocol Error; the broker must reject and close, not forward the foreign + * id to subscribers. */ +TEST(broker_publish_with_subscription_id_closes) +{ + MqttBroker broker; + MqttBrokerNet net; + int i; + static const byte connect[] = { + 0x10, 0x0E, + 0x00, 0x04, 'M', 'Q', 'T', 'T', + 0x05, 0x02, 0x00, 0x3C, + 0x00, /* props len = 0 */ + 0x00, 0x01, 'P' + }; + static const byte publish[] = { + 0x30, 0x06, + 0x00, 0x01, 't', + 0x02, 0x0B, 0x05 /* props: SUBSCRIPTION_ID = 5 */ + }; + + install_mock_net(&net); + XMEMSET(&broker, 0, sizeof(broker)); + ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Init(&broker, &net)); + ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Start(&broker)); + + reset_mock_clients(1); + mock_client_input_append(0, connect, sizeof(connect)); + mock_client_input_append(0, publish, sizeof(publish)); + for (i = 0; i < 16; i++) { + MqttBroker_Step(&broker); + } + + ASSERT_TRUE(g_client_closed); + + MqttBroker_Stop(&broker); + MqttBroker_Free(&broker); +} +#endif /* WOLFMQTT_V5 */ + /* [MQTT-2.3.1-1] / [MQTT-4.13]: a SUBSCRIBE packet with Packet * Identifier = 0 is malformed and the broker MUST close the connection. * MqttDecode_Subscribe returns MQTT_CODE_ERROR_PACKET_ID; this test @@ -2423,6 +2742,7 @@ int main(int argc, char** argv) #ifdef WOLFMQTT_BROKER_AUTH RUN_TEST(connect_v311_binary_password_with_embedded_nul_refused); RUN_TEST(connect_v311_binary_password_exact_match_accepted); + RUN_TEST(connect_unauth_client_id_does_not_take_over_victim); #endif #ifdef WOLFMQTT_V5 RUN_TEST(connect_v5_emptyid_assigned_id_emitted); @@ -2446,6 +2766,17 @@ int main(int argc, char** argv) #endif RUN_TEST(disconnect_invalid_fixed_header_flags_fires_will); RUN_TEST(broker_unhandled_packet_type_closes); + RUN_TEST(broker_publish_before_connect_closes); +#if defined(WOLFMQTT_BROKER_RETAINED) && !defined(WOLFMQTT_STATIC_MEMORY) + RUN_TEST(broker_retained_list_capped); + RUN_TEST(broker_retained_clock_rollback_not_expired); +#endif +#ifndef WOLFMQTT_STATIC_MEMORY + RUN_TEST(broker_per_client_subscription_cap); +#endif +#ifdef WOLFMQTT_V5 + RUN_TEST(broker_publish_with_subscription_id_closes); +#endif RUN_TEST(broker_subscribe_packet_id_zero_closes); RUN_TEST(connack_session_present_set_on_resumed_session); RUN_TEST(connack_session_present_set_on_takeover); diff --git a/tests/test_mqtt_client.c b/tests/test_mqtt_client.c index 5f395c36a..58d739336 100644 --- a/tests/test_mqtt_client.c +++ b/tests/test_mqtt_client.c @@ -384,6 +384,114 @@ TEST(connect_clears_tx_buf_credentials) } } +/* Serves a pre-staged response packet (e.g. a SUBACK) one chunk per read so a + * full client request/response round-trip can run against the mock net. */ +static byte g_canned_buf[64]; +static int g_canned_len; +static int g_canned_pos; + +static int mock_net_read_canned(void *context, byte* buf, int buf_len, + int timeout_ms) +{ + int n; + (void)context; (void)timeout_ms; + n = g_canned_len - g_canned_pos; + if (n <= 0) { + return 0; /* exhausted -> MQTT_CODE_CONTINUE under nonblock */ + } + if (n > buf_len) { + n = buf_len; + } + XMEMCPY(buf, g_canned_buf + g_canned_pos, n); + g_canned_pos += n; + return n; +} + +/* A broker that rejects a subscription returns a SUBACK whose + * per-topic return code has the high bit set (0x80 in v3.1.1, any reason + * code >= 0x80 in v5). MqttClient_Subscribe must surface this as + * MQTT_CODE_ERROR_SUBSCRIBE_REJECTED rather than MQTT_CODE_SUCCESS, else the + * application waits forever for messages on a filter the broker never + * installed. This pins the detection that previously had no test. */ +TEST(subscribe_broker_rejection_returns_subscribe_rejected) +{ + int rc; + int i; + MqttSubscribe subscribe; + MqttTopic topics[1]; + /* SUBACK v3.1.1: type=0x90, remain=3, packet_id=42, return_code=0x80. */ + static const byte suback[] = { 0x90, 0x03, 0x00, 0x2A, 0x80 }; + + rc = test_init_client(); + ASSERT_EQ(MQTT_CODE_SUCCESS, rc); +#ifdef WOLFMQTT_V5 + test_client.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_4; +#endif + + test_net.write = mock_net_write_accept; + test_net.read = mock_net_read_canned; + XMEMCPY(g_canned_buf, suback, sizeof(suback)); + g_canned_len = (int)sizeof(suback); + g_canned_pos = 0; + + XMEMSET(&subscribe, 0, sizeof(subscribe)); + XMEMSET(topics, 0, sizeof(topics)); + topics[0].topic_filter = "test/topic"; + topics[0].qos = MQTT_QOS_0; + subscribe.packet_id = 42; + subscribe.topic_count = 1; + subscribe.topics = topics; + + rc = MQTT_CODE_CONTINUE; + for (i = 0; i < 10 && rc == MQTT_CODE_CONTINUE; i++) { + rc = MqttClient_Subscribe(&test_client, &subscribe); + } + + ASSERT_EQ(MQTT_CODE_ERROR_SUBSCRIBE_REJECTED, rc); + ASSERT_EQ(MQTT_SUBSCRIBE_ACK_CODE_FAILURE, + subscribe.topics[0].return_code); +} + +#ifdef WOLFMQTT_V5 +/* A v5 UNSUBACK whose per-topic reason code has the high bit set means the + * broker refused the unsubscribe; MqttClient_Unsubscribe must surface that as + * MQTT_CODE_ERROR_UNSUBSCRIBE_REJECTED rather than success. */ +TEST(unsubscribe_broker_rejection_returns_unsubscribe_rejected) +{ + int rc; + int i; + MqttUnsubscribe unsub; + MqttTopic topics[1]; + /* v5 UNSUBACK: type=0xB0, remain=4, packet_id=43, props_len=0, + * reason=0x87 (NOT_AUTHORIZED). */ + static const byte unsuback[] = { 0xB0, 0x04, 0x00, 0x2B, 0x00, 0x87 }; + + rc = test_init_client(); + ASSERT_EQ(MQTT_CODE_SUCCESS, rc); + test_client.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + + test_net.write = mock_net_write_accept; + test_net.read = mock_net_read_canned; + XMEMCPY(g_canned_buf, unsuback, sizeof(unsuback)); + g_canned_len = (int)sizeof(unsuback); + g_canned_pos = 0; + + XMEMSET(&unsub, 0, sizeof(unsub)); + XMEMSET(topics, 0, sizeof(topics)); + topics[0].topic_filter = "test/topic"; + unsub.packet_id = 43; + unsub.topic_count = 1; + unsub.topics = topics; + + rc = MQTT_CODE_CONTINUE; + for (i = 0; i < 10 && rc == MQTT_CODE_CONTINUE; i++) { + rc = MqttClient_Unsubscribe(&test_client, &unsub); + } + + ASSERT_EQ(MQTT_CODE_ERROR_UNSUBSCRIBE_REJECTED, rc); +} +#endif /* WOLFMQTT_V5 */ + /* ============================================================================ * MqttClient_Disconnect Tests * ============================================================================ */ @@ -786,6 +894,10 @@ void run_mqtt_client_tests(void) /* MqttClient_Subscribe tests */ RUN_TEST(subscribe_null_client); RUN_TEST(subscribe_null_subscribe); + RUN_TEST(subscribe_broker_rejection_returns_subscribe_rejected); +#ifdef WOLFMQTT_V5 + RUN_TEST(unsubscribe_broker_rejection_returns_unsubscribe_rejected); +#endif /* MqttClient_Unsubscribe tests */ RUN_TEST(unsubscribe_null_client); diff --git a/tests/test_mqtt_packet.c b/tests/test_mqtt_packet.c index f873e94ea..18245ccf7 100644 --- a/tests/test_mqtt_packet.c +++ b/tests/test_mqtt_packet.c @@ -772,6 +772,28 @@ TEST(encode_publish_qos1_valid) ASSERT_TRUE(rc > 0); } +/* The encoder must clamp the copied payload to buffer_len so a + * total_len larger than the bytes actually present cannot read past the + * source buffer (the broker fan-out OOB read). */ +TEST(encode_publish_clamps_payload_to_buffer_len) +{ + byte tx_buf[256]; + byte payload[100]; + MqttPublish pub; + int rc; + + XMEMSET(&pub, 0, sizeof(pub)); + XMEMSET(payload, 'x', sizeof(payload)); + pub.topic_name = "t"; + pub.qos = MQTT_QOS_0; + pub.buffer = payload; + pub.buffer_len = 10; /* only 10 valid bytes present */ + pub.total_len = 100; /* wire-declared size exceeds buffer_len */ + rc = MqttEncode_Publish(tx_buf, (int)sizeof(tx_buf), &pub, 0); + ASSERT_TRUE(rc > 0); + ASSERT_EQ(10, (int)pub.buffer_pos); +} + /* Verify the fixed-header flag bits (retain/QoS/dup) are actually emitted. * Covers deletion mutations of the retain / qos / duplicate branches in * MqttEncode_FixedHeader. */ @@ -1209,12 +1231,12 @@ TEST(decode_publish_topic_contains_u0000_rejected) } #ifdef WOLFMQTT_V5 -/* MQTT v5 section 3.3.2.3.4: a zero-length Topic Name is permitted (paired - * with a Topic Alias property at the application layer). Wire shape: - * PUBLISH | QoS 0, remain=4, topic_len=0, props_len=0, payload "x". */ -TEST(decode_publish_v5_empty_topic_accepted) +/* MQTT v5 section 3.3.2.3.4: a zero-length Topic Name is permitted only when + * paired with a Topic Alias property. Wire: PUBLISH QoS 0, remain=7, + * topic_len=0, props_len=3, TOPIC_ALIAS(35)=1, payload "x". */ +TEST(decode_publish_v5_empty_topic_with_alias_accepted) { - byte buf[] = { 0x30, 0x04, 0x00, 0x00, 0x00, 'x' }; + byte buf[] = { 0x30, 0x07, 0x00, 0x00, 0x03, 0x23, 0x00, 0x01, 'x' }; MqttPublish pub; int rc; @@ -1223,6 +1245,120 @@ TEST(decode_publish_v5_empty_topic_accepted) rc = MqttDecode_Publish(buf, (int)sizeof(buf), &pub); ASSERT_TRUE(rc > 0); ASSERT_EQ(0, pub.topic_name_len); + MqttProps_Free(pub.props); +} + +/* [MQTT-3.3.2-8] A zero-length Topic Name with no Topic Alias property is a + * Protocol Error. Wire: PUBLISH QoS 0, remain=4, topic_len=0, props_len=0. */ +TEST(decode_publish_v5_empty_topic_no_alias_rejected) +{ + byte buf[] = { 0x30, 0x04, 0x00, 0x00, 0x00, 'x' }; + MqttPublish pub; + int rc; + + XMEMSET(&pub, 0, sizeof(pub)); + pub.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + rc = MqttDecode_Publish(buf, (int)sizeof(buf), &pub); + ASSERT_EQ(MQTT_CODE_ERROR_MALFORMED_DATA, rc); + ASSERT_NULL(pub.props); +} + +/* [MQTT-3.8.2.1.2] A Subscription Identifier of 0 is reserved and a Protocol + * Error. Wire: PUBLISH QoS 0, topic "t", props_len=2, SUBSCRIPTION_ID(11)=0. */ +TEST(decode_publish_v5_subscription_id_zero_rejected) +{ + byte buf[] = { 0x30, 0x07, 0x00, 0x01, 't', 0x02, 0x0B, 0x00, 'x' }; + MqttPublish pub; + int rc; + + XMEMSET(&pub, 0, sizeof(pub)); + pub.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + rc = MqttDecode_Publish(buf, (int)sizeof(buf), &pub); + ASSERT_EQ(MQTT_CODE_ERROR_MALFORMED_DATA, rc); + ASSERT_NULL(pub.props); +} + +/* [MQTT-3.3.2-7] A Topic Alias of 0 is a Protocol Error. Wire: PUBLISH QoS 0, + * topic "t", props_len=3, TOPIC_ALIAS(35)=0. */ +TEST(decode_publish_v5_topic_alias_zero_rejected) +{ + byte buf[] = { 0x30, 0x08, 0x00, 0x01, 't', 0x03, 0x23, 0x00, 0x00, 'x' }; + MqttPublish pub; + int rc; + + XMEMSET(&pub, 0, sizeof(pub)); + pub.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + rc = MqttDecode_Publish(buf, (int)sizeof(buf), &pub); + ASSERT_EQ(MQTT_CODE_ERROR_MALFORMED_DATA, rc); + ASSERT_NULL(pub.props); +} + +/* [MQTT-3.3.2-14] A Response Topic is a Topic Name and MUST NOT contain + * wildcards. Wire: PUBLISH QoS 0, topic "t", props_len=6, RESP_TOPIC(8)="a/#". */ +TEST(decode_publish_v5_response_topic_wildcard_rejected) +{ + byte buf[] = { 0x30, 0x0B, 0x00, 0x01, 't', 0x06, + 0x08, 0x00, 0x03, 'a', '/', '#', 'x' }; + MqttPublish pub; + int rc; + + XMEMSET(&pub, 0, sizeof(pub)); + pub.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + rc = MqttDecode_Publish(buf, (int)sizeof(buf), &pub); + ASSERT_EQ(MQTT_CODE_ERROR_MALFORMED_DATA, rc); + ASSERT_NULL(pub.props); +} + +/* A single message may not carry more than the internal + * MQTT_MAX_PROPS (default 30) properties; otherwise a peer can saturate the + * shared property pool. 40 User Property entries exceeds that cap. The cap + * returns MQTT_CODE_ERROR_PROPERTY, distinct from the MQTT_CODE_ERROR_MEMORY a + * pool-exhaustion would have produced before the fix. */ +TEST(decode_publish_v5_property_count_capped) +{ + byte buf[300]; + MqttPublish pub; + int rc, i, pos; + /* 40 User Property entries, 7 bytes each (k=v). */ + word32 nprops = 40; + word32 props_len = nprops * 7; + word32 remain = 3 + 2 + props_len; /* topic(3) + props_vbi(2) + props */ + + pos = 0; + buf[pos++] = 0x30; /* PUBLISH QoS 0 */ + buf[pos++] = (byte)((remain & 0x7F) | 0x80); /* remain VBI low */ + buf[pos++] = (byte)(remain >> 7); /* remain VBI high */ + buf[pos++] = 0x00; buf[pos++] = 0x01; buf[pos++] = 't'; /* topic "t" */ + buf[pos++] = (byte)((props_len & 0x7F) | 0x80); /* props_len VBI low */ + buf[pos++] = (byte)(props_len >> 7); /* props_len VBI high */ + for (i = 0; i < (int)nprops; i++) { + buf[pos++] = 0x26; /* User Property */ + buf[pos++] = 0x00; buf[pos++] = 0x01; buf[pos++] = 'k'; + buf[pos++] = 0x00; buf[pos++] = 0x01; buf[pos++] = 'v'; + } + + XMEMSET(&pub, 0, sizeof(pub)); + pub.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + rc = MqttDecode_Publish(buf, pos, &pub); + ASSERT_EQ(MQTT_CODE_ERROR_PROPERTY, rc); + ASSERT_NULL(pub.props); +} + +/* [MQTT v5 2.2.2.2] A singleton property (here TOPIC_ALIAS) appearing twice is + * a Protocol Error. Wire: PUBLISH QoS 0, topic "t", props_len=6, two + * TOPIC_ALIAS(35)=1 entries. */ +TEST(decode_publish_v5_duplicate_singleton_prop_rejected) +{ + byte buf[] = { 0x30, 0x0A, 0x00, 0x01, 't', 0x06, + 0x23, 0x00, 0x01, 0x23, 0x00, 0x01 }; + MqttPublish pub; + int rc; + + XMEMSET(&pub, 0, sizeof(pub)); + pub.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + rc = MqttDecode_Publish(buf, (int)sizeof(buf), &pub); + ASSERT_EQ(MQTT_CODE_ERROR_PROPERTY, rc); + ASSERT_NULL(pub.props); } #endif /* WOLFMQTT_V5 */ @@ -3052,6 +3188,34 @@ TEST(decode_subscribe_v5_empty_payload_rejected) rc = MqttDecode_Subscribe(rx_buf, (int)sizeof(rx_buf), &sub); ASSERT_EQ(MQTT_CODE_ERROR_MALFORMED_DATA, rc); } + +/* A v5 SUBSCRIBE whose Properties block decodes cleanly but whose topic + * filter is malformed must not leak the decoded property list. The decoder + * fails on the bad filter after allocating the User Property; without the + * cleanup the broker caller returns before freeing props. Catches the + * structural invariant: sub.props == NULL on error. */ +TEST(decode_subscribe_v5_props_freed_on_bad_filter) +{ + byte rx_buf[] = { + 0x82, 0x0F, /* SUBSCRIBE, remain_len = 15 */ + 0x00, 0x01, /* packet_id */ + 0x07, /* props_len VBI = 7 */ + 0x26, 0x00, 0x01, 'k', 0x00, 0x01, 'v', /* User Property k=v */ + 0x00, 0x02, 'a', '#', /* bad filter "a#" */ + 0x00 /* options */ + }; + MqttSubscribe sub; + MqttTopic topic_arr[1]; + int rc; + + XMEMSET(&sub, 0, sizeof(sub)); + XMEMSET(topic_arr, 0, sizeof(topic_arr)); + sub.topics = topic_arr; + sub.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + rc = MqttDecode_Subscribe(rx_buf, (int)sizeof(rx_buf), &sub); + ASSERT_EQ(MQTT_CODE_ERROR_MALFORMED_DATA, rc); + ASSERT_NULL(sub.props); +} #endif /* WOLFMQTT_V5 */ /* [MQTT-4.7.3-1] / [MQTT-4.7.1-2] / [MQTT-4.7.1-3] Topic Filter syntax @@ -3368,6 +3532,31 @@ TEST(decode_unsubscribe_v5_empty_payload_rejected) rc = MqttDecode_Unsubscribe(rx_buf, (int)sizeof(rx_buf), &unsub); ASSERT_EQ(MQTT_CODE_ERROR_MALFORMED_DATA, rc); } + +/* A v5 UNSUBSCRIBE whose Properties block decodes cleanly but whose topic + * filter is malformed must not leak the decoded property list. Mirrors the + * SUBSCRIBE case: catches the invariant unsub.props == NULL on error. */ +TEST(decode_unsubscribe_v5_props_freed_on_bad_filter) +{ + byte rx_buf[] = { + 0xA2, 0x0E, /* UNSUBSCRIBE, remain_len = 14 */ + 0x00, 0x01, /* packet_id */ + 0x07, /* props_len VBI = 7 */ + 0x26, 0x00, 0x01, 'k', 0x00, 0x01, 'v', /* User Property k=v */ + 0x00, 0x02, 'a', '#' /* bad filter "a#" */ + }; + MqttUnsubscribe unsub; + MqttTopic topic_arr[1]; + int rc; + + XMEMSET(&unsub, 0, sizeof(unsub)); + XMEMSET(topic_arr, 0, sizeof(topic_arr)); + unsub.topics = topic_arr; + unsub.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + rc = MqttDecode_Unsubscribe(rx_buf, (int)sizeof(rx_buf), &unsub); + ASSERT_EQ(MQTT_CODE_ERROR_MALFORMED_DATA, rc); + ASSERT_NULL(unsub.props); +} #endif /* WOLFMQTT_V5 */ /* UNSUBSCRIBE shares the same Topic Filter syntax rules as SUBSCRIBE @@ -3801,6 +3990,22 @@ TEST(decode_puback_v5_with_reason_code_accepted) ASSERT_EQ(0, resp.reason_code); MqttProps_Free(resp.props); } + +/* Remaining Length advertises a Reason Code but the supplied buffer stops + * right after the Packet Identifier. The decoder must reject the short frame + * instead of reading the reason-code byte out of bounds. */ +TEST(decode_puback_v5_reason_code_past_buf_rejected) +{ + byte buf[4] = { 0x40, 0x03, 0x00, 0x05 }; + MqttPublishResp resp; + int rc; + + XMEMSET(&resp, 0, sizeof(resp)); + resp.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + rc = MqttDecode_PublishResp(buf, (int)sizeof(buf), + MQTT_PACKET_TYPE_PUBLISH_ACK, &resp); + ASSERT_EQ(MQTT_CODE_ERROR_OUT_OF_BUFFER, rc); +} #endif /* WOLFMQTT_V5 */ /* ============================================================================ @@ -3930,6 +4135,72 @@ TEST(decode_unsuback_malformed_remain_len_one) ASSERT_EQ(MQTT_CODE_ERROR_MALFORMED_DATA, rc); } +/* A broker advertising a Remaining Length larger than the bytes actually + * received must be rejected, not decoded - otherwise reason_code_count would + * span past the buffer and the client's rejection scan reads out of bounds. */ +TEST(decode_unsuback_truncated_remain_len_rejected) +{ + /* Remaining Length = 10, but only 3 bytes follow the 2-byte header. */ + byte buf[] = { 0xB0, 0x0A, 0x00, 0x01, 0x00 }; + MqttUnsubscribeAck ack; + int rc; + + XMEMSET(&ack, 0, sizeof(ack)); +#ifdef WOLFMQTT_V5 + ack.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; +#endif + rc = MqttDecode_UnsubscribeAck(buf, (int)sizeof(buf), &ack); + ASSERT_EQ(MQTT_CODE_ERROR_OUT_OF_BUFFER, rc); +} + +#ifdef WOLFMQTT_V5 +/* A v5 UNSUBACK carries one reason code per topic filter after the + * properties block. The decoder must expose the count and bytes so the + * client can detect a rejection (high-bit code), mirroring SUBSCRIBE. */ +TEST(decode_unsuback_v5_reason_codes) +{ + byte buf[] = { + 0xB0, 0x05, /* UNSUBACK, remain_len = 5 */ + 0x00, 0x01, /* packet_id */ + 0x00, /* props_len = 0 */ + 0x00, 0x87 /* success, NOT_AUTHORIZED */ + }; + MqttUnsubscribeAck ack; + int rc; + + XMEMSET(&ack, 0, sizeof(ack)); + ack.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + rc = MqttDecode_UnsubscribeAck(buf, (int)sizeof(buf), &ack); + ASSERT_TRUE(rc > 0); + ASSERT_EQ(2, ack.reason_code_count); + ASSERT_TRUE(ack.reason_codes != NULL); + ASSERT_EQ(0x00, ack.reason_codes[0]); + ASSERT_EQ(0x87, ack.reason_codes[1]); +} + +/* A v5 UNSUBACK whose property length runs past its own Remaining Length must + * be rejected even when the caller buffer holds trailing bytes from the next + * packet: consuming them would push past the packet and underflow + * reason_code_count. props_len=3 but only one packet byte follows it. */ +TEST(decode_unsuback_v5_props_past_remain_len_rejected) +{ + byte buf[] = { + 0xB0, 0x04, /* UNSUBACK, remain_len = 4 */ + 0x00, 0x01, /* packet_id */ + 0x03, /* props_len = 3 (overruns packet) */ + 0x1F, /* last byte inside Remaining Length */ + 0x00, 0x00 /* trailing bytes from next packet */ + }; + MqttUnsubscribeAck ack; + int rc; + + XMEMSET(&ack, 0, sizeof(ack)); + ack.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + rc = MqttDecode_UnsubscribeAck(buf, (int)sizeof(buf), &ack); + ASSERT_EQ(MQTT_CODE_ERROR_OUT_OF_BUFFER, rc); +} +#endif /* WOLFMQTT_V5 */ + /* ============================================================================ * MqttDecode_Ping (PINGRESP) and MqttDecode_Disconnect length validation * @@ -4042,6 +4313,20 @@ TEST(decode_disconnect_v5_with_reason_code_accepted) ASSERT_EQ(0, disc.reason_code); MqttProps_Free(disc.props); } + +/* Remaining Length advertises a Reason Code but the supplied buffer is only + * the fixed header. The decoder must reject the short frame instead of + * reading the reason-code byte out of bounds. */ +TEST(decode_disconnect_v5_reason_code_past_buf_rejected) +{ + byte buf[2] = { 0xE0, 0x01 }; + MqttDisconnect disc; + int rc; + + XMEMSET(&disc, 0, sizeof(disc)); + rc = MqttDecode_Disconnect(buf, (int)sizeof(buf), &disc); + ASSERT_EQ(MQTT_CODE_ERROR_OUT_OF_BUFFER, rc); +} #endif /* WOLFMQTT_V5 */ /* ============================================================================ @@ -4493,6 +4778,23 @@ TEST(auth_v5_invalid_reason_code_rejected) dec_len = MqttDecode_Auth(buf, 4, &dec); ASSERT_EQ(MQTT_CODE_ERROR_MALFORMED_DATA, dec_len); } + +/* Remaining Length advertises a Reason Code but the supplied buffer is only + * the fixed header. The decoder must reject the short frame instead of + * reading the reason-code byte out of bounds. */ +TEST(decode_auth_v5_reason_code_past_buf_rejected) +{ + byte buf[2]; + MqttAuth dec; + int dec_len; + + buf[0] = (byte)(MQTT_PACKET_TYPE_AUTH << 4); + buf[1] = 0x01; /* Remaining Length claims a reason-code byte */ + + XMEMSET(&dec, 0, sizeof(dec)); + dec_len = MqttDecode_Auth(buf, (int)sizeof(buf), &dec); + ASSERT_EQ(MQTT_CODE_ERROR_OUT_OF_BUFFER, dec_len); +} #endif /* WOLFMQTT_V5 */ /* ============================================================================ @@ -4575,6 +4877,7 @@ void run_mqtt_packet_tests(void) RUN_TEST(encode_publish_qos2_packet_id_zero); RUN_TEST(encode_publish_qos0_packet_id_zero_ok); RUN_TEST(encode_publish_qos1_valid); + RUN_TEST(encode_publish_clamps_payload_to_buffer_len); RUN_TEST(encode_publish_qos1_retain_flags_in_header); RUN_TEST(encode_publish_qos2_duplicate_flags_in_header); RUN_TEST(encode_publish_qos0_no_flags_in_header); @@ -4601,7 +4904,13 @@ void run_mqtt_packet_tests(void) RUN_TEST(decode_publish_wildcard_plus_topic_rejected); RUN_TEST(decode_publish_topic_contains_u0000_rejected); #ifdef WOLFMQTT_V5 - RUN_TEST(decode_publish_v5_empty_topic_accepted); + RUN_TEST(decode_publish_v5_empty_topic_with_alias_accepted); + RUN_TEST(decode_publish_v5_empty_topic_no_alias_rejected); + RUN_TEST(decode_publish_v5_subscription_id_zero_rejected); + RUN_TEST(decode_publish_v5_topic_alias_zero_rejected); + RUN_TEST(decode_publish_v5_response_topic_wildcard_rejected); + RUN_TEST(decode_publish_v5_property_count_capped); + RUN_TEST(decode_publish_v5_duplicate_singleton_prop_rejected); #endif RUN_TEST(decode_publish_qos1_packet_id_zero_rejected); RUN_TEST(decode_publish_qos2_packet_id_zero_rejected); @@ -4710,6 +5019,7 @@ void run_mqtt_packet_tests(void) RUN_TEST(decode_subscribe_empty_payload_rejected); #ifdef WOLFMQTT_V5 RUN_TEST(decode_subscribe_v5_empty_payload_rejected); + RUN_TEST(decode_subscribe_v5_props_freed_on_bad_filter); #endif #ifdef WOLFMQTT_V5 RUN_TEST(decode_subscribe_v5_options_byte_qos_extracted); @@ -4726,6 +5036,7 @@ void run_mqtt_packet_tests(void) RUN_TEST(decode_unsubscribe_bad_plus_placement_rejected); #ifdef WOLFMQTT_V5 RUN_TEST(decode_unsubscribe_v5_empty_payload_rejected); + RUN_TEST(decode_unsubscribe_v5_props_freed_on_bad_filter); #endif #endif @@ -4760,6 +5071,7 @@ void run_mqtt_packet_tests(void) RUN_TEST(decode_puback_null_resp_extra_payload_rejected); #ifdef WOLFMQTT_V5 RUN_TEST(decode_puback_v5_with_reason_code_accepted); + RUN_TEST(decode_puback_v5_reason_code_past_buf_rejected); #endif /* MqttEncode_PublishResp fixed-header QoS bits */ @@ -4772,6 +5084,11 @@ void run_mqtt_packet_tests(void) RUN_TEST(decode_unsuback_valid); RUN_TEST(decode_unsuback_malformed_remain_len_zero); RUN_TEST(decode_unsuback_malformed_remain_len_one); + RUN_TEST(decode_unsuback_truncated_remain_len_rejected); +#ifdef WOLFMQTT_V5 + RUN_TEST(decode_unsuback_v5_reason_codes); + RUN_TEST(decode_unsuback_v5_props_past_remain_len_rejected); +#endif /* MqttDecode_Ping (PINGRESP) length validation */ RUN_TEST(decode_pingresp_valid); @@ -4786,6 +5103,7 @@ void run_mqtt_packet_tests(void) #ifdef WOLFMQTT_V5 RUN_TEST(decode_disconnect_v5_invalid_fixed_header_flags_rejected); RUN_TEST(decode_disconnect_v5_with_reason_code_accepted); + RUN_TEST(decode_disconnect_v5_reason_code_past_buf_rejected); #endif /* Fixed-header reserved-flag validation [MQTT-2.2.2-2] */ @@ -4814,6 +5132,7 @@ void run_mqtt_packet_tests(void) RUN_TEST(auth_v5_reauth_decodes_without_error); RUN_TEST(auth_v5_success_remaining_length_zero); RUN_TEST(auth_v5_invalid_reason_code_rejected); + RUN_TEST(decode_auth_v5_reason_code_past_buf_rejected); #endif TEST_SUITE_END(); diff --git a/tests/test_mqtt_sn.c b/tests/test_mqtt_sn.c index d162c6e0f..21f1ab075 100644 --- a/tests/test_mqtt_sn.c +++ b/tests/test_mqtt_sn.c @@ -48,7 +48,7 @@ /* Log-sanitization helper shared by the SN example clients. Exercised here * because the MQTT-SN REGISTER topic name decoded above is aliased straight * from the receive buffer (gateway-/attacker-controlled over UDP) and is fed - * to a PRINTF sink by sn_reg_callback; this helper is the CWE-117 defense. */ + * to a PRINTF sink by sn_reg_callback; this helper is the log defense. */ #include "examples/mqtt_log.h" /* PRINT_BUFFER_SIZE is the payload clamp the SN example clients apply before @@ -546,7 +546,7 @@ TEST(sn_register_no_room_for_terminator_rejected) TEST(sn_register_roundtrip_short_form) { - /* Report 3831: encode then decode a normal REGISTER and confirm every + /* Encode then decode a normal REGISTER and confirm every * field survives the round trip. "sensors/temp" is 12 bytes, so * total_len = 12 + 6 = 18 (<= 255 -> short, single-byte length). The decode * buffer is larger than the packet so the in-place NUL terminator fits. */ @@ -574,7 +574,7 @@ TEST(sn_register_roundtrip_short_form) TEST(sn_register_roundtrip_ind_form) { - /* Report 3831: same round trip but with an extended-length (IND) encoding. + /* Same round trip but with an extended-length (IND) encoding. * A 260-byte topic gives total_len = 260 + 6 = 266 (> 255), so the encoder * switches to the 3-byte length header (IND + 2 length bytes) and * total_len becomes 268. Confirms the decoder honors the IND form and that @@ -606,9 +606,9 @@ TEST(sn_register_roundtrip_ind_form) } /* ============================================================================ - * mqtt_log_sanitize (CWE-117 log-injection defense) + * mqtt_log_sanitize (log-injection defense) * - * Report 5663: the MQTT-SN REGISTER topic name decoded by SN_Decode_Register is + * The MQTT-SN REGISTER topic name decoded by SN_Decode_Register is * aliased straight from the UDP receive buffer with no control-character * filtering, then logged verbatim by sn_reg_callback's PRINTF. A spoofed * gateway can therefore inject forged log lines (CR/LF) or hijack the terminal @@ -1149,7 +1149,7 @@ TEST(sn_encode_publish_null_args_rejected) TEST(sn_decode_publish_reserved_topic_type_rejected) { - /* Report 4659 PoC: flags=0x03 sets the reserved topic id type 0b11. The + /* flags=0x03 sets the reserved topic id type 0b11. The * pre-fix decoder returned 7 (success) with topic_type=3. */ byte buf[7] = { 0x07, 0x0C, 0x03, 0x00, 0x01, 0x00, 0x00 }; SN_Publish publish; @@ -1239,7 +1239,7 @@ TEST(sn_decode_publish_null_args_rejected) TEST(sn_decode_publish_qos1_zero_packet_id_rejected) { - /* Report 4248 PoC: flags=0x20 -> QoS1, MsgId=0x0000. */ + /* flags=0x20 -> QoS1, MsgId=0x0000. */ byte buf[7] = { 0x07, 0x0C, 0x20, 0x00, 0x01, 0x00, 0x00 }; SN_Publish publish; int rc; diff --git a/tests/test_mqtt_sn_client.c b/tests/test_mqtt_sn_client.c index ed42b8192..55d491c82 100644 --- a/tests/test_mqtt_sn_client.c +++ b/tests/test_mqtt_sn_client.c @@ -649,7 +649,7 @@ TEST(sn_willmsgupd_payload_scrubbed_on_write_error) } /* ============================================================================ - * SN subscribe pending-response lifecycle tests (CWE-416 regression, #5864) + * SN subscribe pending-response lifecycle tests * * SN_Client_Subscribe registers &subscribe->pendResp in client->firstPendResp * (MULTITHREAD) and must remove it exactly once the SUBACK arrives. Under @@ -717,7 +717,7 @@ TEST(sn_subscribe_rejected) } /* ============================================================================ - * SN publish pending-response lifecycle tests (CWE-416 regression, #5146) + * SN publish pending-response lifecycle tests * * For QoS 1/2 SN_Client_Publish registers &publish->pendResp in * client->firstPendResp (MULTITHREAD) and must remove it exactly once the @@ -922,7 +922,7 @@ TEST(sn_willmsgupd_payload_scrubbed_nonblock) sn_will_msg_update_scrub_check(1 /* one CONTINUE per frame */); } -/* The headline #5864 regression. With a CONTINUE armed before the SUBACK the +/* The headline regression. With a CONTINUE armed before the SUBACK the * subscribe must converge to SUCCESS and remove its pending response exactly * once. Under MULTITHREAD it also pins the dangling-pointer contract directly: * after the first in-flight CONTINUE the entry IS linked and points back into @@ -1024,7 +1024,7 @@ TEST(sn_subscribe_rejected_nonblock) ASSERT_NO_PENDRESP(); } -/* The headline #5146 regression. With a CONTINUE armed before the PUBACK the +/* The headline regression. With a CONTINUE armed before the PUBACK the * QoS 1 publish must converge to SUCCESS and remove its pending response exactly * once. Under MULTITHREAD it also pins the dangling-pointer contract directly: * after the first in-flight CONTINUE the entry IS linked and points back into diff --git a/wolfmqtt/mqtt_broker.h b/wolfmqtt/mqtt_broker.h index 316e1427e..a076b1509 100644 --- a/wolfmqtt/mqtt_broker.h +++ b/wolfmqtt/mqtt_broker.h @@ -93,6 +93,21 @@ #ifndef BROKER_MAX_SUBS #define BROKER_MAX_SUBS 32 #endif +/* Per-client subscription cap so one client cannot occupy the whole shared + * subscription table and deny other clients. */ +#ifndef BROKER_MAX_SUBS_PER_CLIENT + /* Fair share of the shared table, but floored at 8 (and never above the + * table size) so the default does not silently reject a client that + * subscribes to a normal handful of topics. Override to tighten the + * anti-DoS cap or to raise it for subscription-heavy clients. */ + #if (BROKER_MAX_SUBS / BROKER_MAX_CLIENTS) >= 8 + #define BROKER_MAX_SUBS_PER_CLIENT (BROKER_MAX_SUBS / BROKER_MAX_CLIENTS) + #elif BROKER_MAX_SUBS < 8 + #define BROKER_MAX_SUBS_PER_CLIENT BROKER_MAX_SUBS + #else + #define BROKER_MAX_SUBS_PER_CLIENT 8 + #endif +#endif #ifndef BROKER_MAX_CLIENT_ID_LEN #define BROKER_MAX_CLIENT_ID_LEN 64 #endif @@ -120,6 +135,18 @@ #ifndef BROKER_MAX_PENDING_WILLS #define BROKER_MAX_PENDING_WILLS 4 #endif +/* Upper bound (seconds) the broker accepts for a v5 Will Delay Interval. + * Caps how long a deferred-will slot can be monopolized so a few clients + * advertising near-UINT32_MAX delays cannot permanently exhaust the pool. */ +#ifndef BROKER_MAX_WILL_DELAY_SEC + #define BROKER_MAX_WILL_DELAY_SEC 3600 +#endif +/* Seconds a freshly accepted client has to complete a CONNECT before the + * broker evicts it, so half-open pre-CONNECT sockets cannot exhaust the client + * table (Slowloris / slot exhaustion). */ +#ifndef BROKER_CONNECT_TIMEOUT_SEC + #define BROKER_CONNECT_TIMEOUT_SEC 10 +#endif /* Maximum concurrent inbound QoS 2 packet IDs awaiting PUBREL per client. * Used to dedup duplicate PUBLISHes per [MQTT-4.3.3] (Method B). 16 covers * any reasonable client; a misbehaving client that exceeds this gets a @@ -468,6 +495,7 @@ typedef struct BrokerClient { WOLFMQTT_BROKER_TIME_T last_rx; byte clean_session; byte connected; /* set after successful CONNECT handshake */ + int sub_count; /* active subscriptions owned by this client */ #ifdef WOLFMQTT_BROKER_WILL byte has_will; word16 will_payload_len; @@ -564,6 +592,7 @@ typedef struct BrokerRetainedMsg { WOLFMQTT_BROKER_TIME_T store_time; /* when stored (seconds) */ word32 expiry_sec; /* v5 message expiry (0=none) */ MqttQoS qos; /* [MQTT-3.3.1-5] stored QoS */ + byte pending_delete; /* deferred free during delivery */ } BrokerRetainedMsg; #endif /* WOLFMQTT_BROKER_RETAINED */ @@ -631,6 +660,8 @@ typedef struct MqttBroker { BrokerSub* subs; #ifdef WOLFMQTT_BROKER_RETAINED BrokerRetainedMsg* retained; + int retained_count; + int retained_delivering; /* re-entrancy guard for delete */ #endif #ifdef WOLFMQTT_BROKER_WILL BrokerPendingWill* pending_wills; @@ -643,6 +674,10 @@ typedef struct MqttBroker { const char *ws_tls_cert; const char *ws_tls_key; const char *ws_tls_ca; + /* Optional exact Origin allowlist for browser WebSocket connections. When + * non-NULL, a request whose HTTP Origin header is present and does not + * match is rejected (CSWSH defense). NULL = no Origin enforcement. */ + const char *ws_allowed_origin; #endif #ifdef WOLFMQTT_BROKER_PERSIST /* Pointer (not embedded struct) so the broker stays small when no diff --git a/wolfmqtt/mqtt_client.h b/wolfmqtt/mqtt_client.h index 416be714c..c03a04646 100644 --- a/wolfmqtt/mqtt_client.h +++ b/wolfmqtt/mqtt_client.h @@ -103,7 +103,8 @@ typedef int (*MqttPublishCb)(MqttPublish* publish); enum MqttClientFlags { MQTT_CLIENT_FLAG_IS_CONNECTED = 0x01 << 0, MQTT_CLIENT_FLAG_IS_TLS = 0x01 << 1, - MQTT_CLIENT_FLAG_IS_DTLS = 0x01 << 2 + MQTT_CLIENT_FLAG_IS_DTLS = 0x01 << 2, + MQTT_CLIENT_FLAG_WOLFSSL_INIT = 0x01 << 3 /* this client called wolfSSL_Init */ }; /*! \brief Sets flags in the MqttClient structure. To be used from the application before calling MqttClient_NetConnect. diff --git a/wolfmqtt/mqtt_types.h b/wolfmqtt/mqtt_types.h index 1ce8a94b5..cfcc4a73e 100644 --- a/wolfmqtt/mqtt_types.h +++ b/wolfmqtt/mqtt_types.h @@ -211,6 +211,10 @@ enum MqttPacketResponseCodes { see each MqttTopic.return_code for the per-filter result. */ + MQTT_CODE_ERROR_UNSUBSCRIBE_REJECTED = -20, /* Broker rejected one or more + topic filters in an + UNSUBSCRIBE; see each reason + code in MqttUnsubscribeAck. */ MQTT_CODE_CONTINUE = -101, MQTT_CODE_STDIN_WAKE = -102,