Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "mbedtls"]
path = mbedtls
url = https://github.com/Mbed-TLS/mbedtls
9 changes: 7 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@ project(eMQTT5)
option(REDUCED_FOOTPRINT "Whether to enable reduced footprint for the client code" ON)
option(CROSSPLATFORM_SOCKET "Whether to use cross plaftform socket code (this disable SSL)" OFF)
option(ENABLE_TLS "Whether to enable TLS/SSL code (you'll need MBedTLS available)" OFF)
option(MBEDTLS_SUBMODULE "Whether MBedTLS included as submodule" OFF)
option(LOW_LATENCY "Whether to enable low latency code (at the cost of higher CPU usage)" OFF)

if (CROSSPLATFORM_SOCKET STREQUAL OFF AND ENABLE_TLS STREQUAL ON)
message(WARNING "As of 06/28/2020, MBedTLS is not correctly CMake compatible and does not generate a mbedtls-config.cmake file. You'll need to apply the patch from my branch found in pull request #3465")
find_package(mbedtls CONFIG REQUIRED)
if (MBEDTLS_SUBMODULE)
add_subdirectory(mbedtls)
else()
message(WARNING "As of 06/28/2020, MBedTLS is not correctly CMake compatible and does not generate a mbedtls-config.cmake file. You'll need to apply the patch from my branch found in pull request #3465")
find_package(mbedtls CONFIG REQUIRED)
endif()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked if the issue was solved on the MBedTLS source code. It seems you're ignoring the mbedtls-config.cmake file and directly specify the libraries in the file. This will work for a direct compilation target but I'm not sure it'll work for cross compiling since the library might be elsewhere (and heavily patched, like in esp-idf's version or Zephyr). Usually, I'm porting this library to ESP32 (in the esp-eMQTT5 repo) and I'm using mbedTLS there. I'll try this port and see if it breaks or if it works.

Copy link
Contributor Author

@Odysseus1710 Odysseus1710 Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added MBedTLS repo as a submodule so the dependencies are built from the sources directly and no packages are searched. But this can be left out her and addressed separately if wanted since it has nothing to do with the mTLS feature directly and is just helpful if you dont want mbedtls as a system requirement.

git submodule add https://github.com/Mbed-TLS/mbedtls
cd mbedtls
git checkout mbedtls-3.6
git submodule update --init --recursive

In this version the header <mbedtls/certs.h> seems to be not available anymore.

endif ()


Expand Down
4 changes: 4 additions & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ IF (${CROSSPLATFORM_SOCKET} STREQUAL "ON" AND ${ENABLE_TLS} STREQUAL "ON")
message(WARNING "Building crossplatform socket code with TLS is not supported yet, disabling TLS code")
ENDIF()

IF (ENABLE_TLS AND MBEDTLS_SUBMODULE)
target_link_libraries(eMQTT5 PUBLIC mbedcrypto mbedtls mbedx509)
ENDIF()

target_compile_definitions(eMQTT5 PUBLIC _DEBUG=$<CONFIG:Debug>
MinimalFootPrint=$<STREQUAL:${REDUCED_FOOTPRINT},ON>
MQTTOnlyBSDSocket=$<STREQUAL:${CROSSPLATFORM_SOCKET},OFF>
Expand Down
16 changes: 11 additions & 5 deletions lib/include/Network/Clients/MQTT.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,18 +339,24 @@ namespace Network
/** Default constructor
@param clientID A client identifier if you need to provide one. If empty or null, the broker will assign one
@param callback A pointer to a MessageReceived callback object. The method might be called from any thread/task
@param storage A pointer to a PacketStorage implementation (used for QoS retransmission) that's owned.
If null a default one will be used that stores packet in a ring buffer (allocating memory for it).
You can use "new PacketStorage()" here to skip any memory allocation but the client won't be 100% compliant here,
it'll just fail to retransmit any QoS packet after resuming from a connection loss.
@param brokerCert If provided, contains a view on the DER encoded broker's certificate to validate against.
If provided and empty, any certificate will be accepted (not recommanded).
No copy is made so please make sure the pointed data is valid while this client is valid.
If you don't have a PEM encoded certificate, use this command to save the server's certificate to a .PEM file
$ echo | openssl s_client -servername your.server.com -connect your.server.com:8883 2>/dev/null | openssl x509 > cert.pem
If you have a PEM encoded certificate, use this code to convert it to (33% smaller) DER format
$ openssl x509 -in cert.pem -outform der -out cert.der
@param storage A pointer to a PacketStorage implementation (used for QoS retransmission) that's owned.
If null a default one will be used that stores packet in a ring buffer (allocating memory for it).
You can use "new PacketStorage()" here to skip any memory allocation but the client won't be 100% compliant here,
it'll just fail to retransmit any QoS packet after resuming from a connection loss. */
MQTTv5(const char * clientID, MessageReceived * callback, const DynamicBinDataView * brokerCert = 0, PacketStorage * storage = 0);
@param clientCert If provided, contains a view on the DER encoded client's certificate to provide on connection.
Required for two-way / mutual TLS.
@param clientKey If provided, contains a view on the client's private key
Required for two-way / mutual TLS. */

MQTTv5(const char * clientID, MessageReceived * callback, PacketStorage * storage = 0, const DynamicBinDataView * brokerCert = 0,
const DynamicBinDataView * clientCert = 0, const DynamicBinDataView * clientKey = 0);
/** Default destructor */
~MQTTv5();

Expand Down
61 changes: 46 additions & 15 deletions lib/src/Network/Clients/MQTTClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
#include <atomic>
#if MQTTUseTLS == 1
// We need MBedTLS code
#include <mbedtls/certs.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/entropy.h>
#include <mbedtls/error.h>
Expand Down Expand Up @@ -392,8 +391,12 @@ namespace Network { namespace Client {
{
typedef MQTTv5::ErrorType ErrorType;

/** The DER encoded certificate (if provided) */
/** The DER encoded server certificate (if provided) */
const Protocol::MQTT::Common::DynamicBinDataView * brokerCert;
/** The DER encoded client certificate (if provided) */
const Protocol::MQTT::Common::DynamicBinDataView * clientCert;
/** The client private key (if provided) */
const Protocol::MQTT::Common::DynamicBinDataView * clientKey;
/** This client unique identifier */
Protocol::MQTT::Common::DynamicString clientID;
/** The message received callback to use */
Expand Down Expand Up @@ -440,8 +443,10 @@ namespace Network { namespace Client {
return ++publishCurrentId;
}

ImplBase(const char * clientID, MessageReceived * callback, const Protocol::MQTT::Common::DynamicBinDataView * brokerCert, PacketStorage * storage)
: brokerCert(brokerCert), clientID(clientID), cb(callback), lastCommunication(0), publishCurrentId(0), keepAlive(300),
ImplBase(const char * clientID, MessageReceived * callback, PacketStorage * storage, const Protocol::MQTT::Common::DynamicBinDataView * brokerCert,
const Protocol::MQTT::Common::DynamicBinDataView * clientCert, const Protocol::MQTT::Common::DynamicBinDataView * clientKey)
: brokerCert(brokerCert), clientCert(clientCert), clientKey(clientKey), clientID(clientID), cb(callback),
lastCommunication(0), publishCurrentId(0), keepAlive(300),
#if MQTTUseUnsubscribe == 1
unsubscribeId(0), lastUnsubscribeError(ErrorType::WaitingForResult),
#endif
Expand Down Expand Up @@ -1041,8 +1046,9 @@ namespace Network { namespace Client {
/** The default timeout in milliseconds */
uint32 timeoutMs;

Impl(const char * clientID, MessageReceived * callback, const DynamicBinDataView * brokerCert, PacketStorage * storage)
: ImplBase(clientID, callback, brokerCert, storage), socket(0), timeoutMs(3000) {}
Impl(const char * clientID, MessageReceived * callback, const DynamicBinDataView * brokerCert,
const DynamicBinDataView * clientCert, const DynamicBinDataView * clientKey, PacketStorage * storage)
: ImplBase(clientID, callback, brokerCert, clientCert, clientKey, storage), socket(0), timeoutMs(3000) {}
~Impl() { delete0(socket); }

Time::TimeOut getTimeout() const { return timeoutMs; }
Expand Down Expand Up @@ -1177,7 +1183,7 @@ namespace Network { namespace Client {
int socket;
struct timeval & timeoutMs;

MQTTVirtual int connect(const char * host, uint16 port, const MQTTv5::DynamicBinDataView *)
MQTTVirtual int connect(const char * host, uint16 port, const MQTTv5::DynamicBinDataView *, const MQTTv5::DynamicBinDataView *, const MQTTv5::DynamicBinDataView *)
{
socket = ::socket(AF_INET, SOCK_STREAM, 0);
if (socket == -1) return -2;
Expand Down Expand Up @@ -1276,10 +1282,14 @@ namespace Network { namespace Client {
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
mbedtls_x509_crt cacert;
mbedtls_x509_crt owncert;
mbedtls_pk_context pkey;
mbedtls_net_context net;

private:
bool buildConf(const MQTTv5::DynamicBinDataView * brokerCert)
bool buildConf(const MQTTv5::DynamicBinDataView * brokerCert,
const MQTTv5::DynamicBinDataView * clientCert = nullptr,
const MQTTv5::DynamicBinDataView * clientKey = nullptr)
{
if (brokerCert)
{ // Use given root certificate (if you have a recent version of mbedtls, you could use mbedtls_x509_crt_parse_der_nocopy instead to skip a useless copy here)
Expand All @@ -1302,6 +1312,19 @@ namespace Network { namespace Client {
if (::mbedtls_ctr_drbg_seed(&entropySource, ::mbedtls_entropy_func, &entropy, NULL, 0))
return false;

// Load client cert and key (for mTLS)
if (clientCert && clientKey)
{
if (::mbedtls_x509_crt_parse_der(&owncert, clientCert->data, clientCert->length))
return false;

if (::mbedtls_pk_parse_key(&pkey, clientKey->data, clientKey->length, nullptr, 0, ::mbedtls_ctr_drbg_random, &entropySource))
return false;

if (::mbedtls_ssl_conf_own_cert(&conf, &owncert, &pkey))
return false;
}

if (::mbedtls_ssl_setup(&ssl, &conf))
return false;

Expand All @@ -1314,13 +1337,16 @@ namespace Network { namespace Client {
mbedtls_ssl_init(&ssl);
mbedtls_ssl_config_init(&conf);
mbedtls_x509_crt_init(&cacert);
mbedtls_x509_crt_init(&owncert);
mbedtls_pk_init(&pkey);
mbedtls_ctr_drbg_init(&entropySource);
mbedtls_entropy_init(&entropy);
}

int connect(const char * host, uint16 port, const MQTTv5::DynamicBinDataView * brokerCert)
int connect(const char * host, uint16 port, const MQTTv5::DynamicBinDataView * brokerCert,
const MQTTv5::DynamicBinDataView * clientCert, const MQTTv5::DynamicBinDataView * clientKey)
{
int ret = BaseSocket::connect(host, port, 0);
int ret = BaseSocket::connect(host, port, 0, 0, 0);
if (ret) return ret;

// MBedTLS doesn't deal with natural socket timeout correctly, so let's fix that
Expand All @@ -1330,7 +1356,7 @@ namespace Network { namespace Client {

net.fd = socket;

if (!buildConf(brokerCert)) return -8;
if (!buildConf(brokerCert, clientCert, clientKey)) return -8;
if (::mbedtls_ssl_set_hostname(&ssl, host)) return -9;

// Set the method the SSL engine is using to fetch/send data to the other side
Expand Down Expand Up @@ -1396,6 +1422,8 @@ namespace Network { namespace Client {
{
mbedtls_ssl_close_notify(&ssl);
mbedtls_x509_crt_free(&cacert);
mbedtls_x509_crt_free(&owncert);
mbedtls_pk_free(&pkey);
mbedtls_entropy_free(&entropy);
mbedtls_ssl_config_free(&conf);
mbedtls_ctr_drbg_free(&entropySource);
Expand All @@ -1413,8 +1441,9 @@ namespace Network { namespace Client {
/** The default timeout in milliseconds */
struct timeval timeoutMs;

Impl(const char * clientID, MessageReceived * callback, const DynamicBinDataView * brokerCert, PacketStorage * storage)
: ImplBase(clientID, callback, brokerCert, storage), socket(0), timeoutMs({3, 0}) {}
Impl(const char * clientID, MessageReceived * callback, PacketStorage * storage, const DynamicBinDataView * brokerCert,
const DynamicBinDataView * clientCert, const DynamicBinDataView * clientKey)
: ImplBase(clientID, callback, storage, brokerCert, clientCert, clientKey), socket(0), timeoutMs({3, 0}) {}
~Impl() { delete0(socket); }

uint32 getTimeout() const { return timeoutInMs(timeoutMs); }
Expand All @@ -1436,7 +1465,7 @@ namespace Network { namespace Client {
withTLS ? new MBTLSSocket(timeoutMs) :
#endif
new BaseSocket(timeoutMs);
return socket ? socket->connect(host, port, brokerCert) : -1;
return socket ? socket->connect(host, port, brokerCert, clientCert, clientKey) : -1;
}

int sendImpl(const char * buffer, const int size)
Expand All @@ -1447,7 +1476,9 @@ namespace Network { namespace Client {
};
#endif

MQTTv5::MQTTv5(const char * clientID, MessageReceived * callback, const DynamicBinDataView * brokerCert, PacketStorage * storage) : impl(new Impl(clientID, callback, brokerCert, storage)) {}
MQTTv5::MQTTv5(const char * clientID, MessageReceived * callback, PacketStorage * storage,
const DynamicBinDataView * brokerCert, const DynamicBinDataView * clientCert, const DynamicBinDataView * clientKey)
: impl(new Impl(clientID, callback, storage, brokerCert, clientCert, clientKey)) {}
MQTTv5::~MQTTv5() { delete0(impl); }


Expand Down
1 change: 1 addition & 0 deletions mbedtls
Submodule mbedtls added at c765c8
45 changes: 39 additions & 6 deletions tests/MQTTc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ int main(int argc, const char ** argv)
String password;
String clientID;
String subscribe;
String certFile;
String serverCertFile;
String clientCertFile;
String clientKeyFile;
unsigned keepAlive = 300;
bool dumpComm = false;
bool retainPublishedMessage = false;
Expand All @@ -127,7 +129,9 @@ int main(int argc, const char ** argv)
Arguments::declare(retainPublishedMessage, "Retain published message", "retain");
Arguments::declare(setQoS, "Quality of service for publishing or subscribing", "qos");
Arguments::declare(subscribe, "The subscription topic", "subscribe", "sub");
Arguments::declare(certFile, "Expected broker certificate in DER format", "der");
Arguments::declare(serverCertFile, "Expected broker certificate in DER format", "serverder");
Arguments::declare(clientCertFile, "Expected client certificate in DER format", "clientder");
Arguments::declare(clientKeyFile, "Expected client private key", "clientkey");

Arguments::declare(dumpComm, "Dump communication", "verbose");

Expand All @@ -150,15 +154,44 @@ int main(int argc, const char ** argv)
MessageReceiver receiver;

#if MQTTUseTLS == 1
Network::Client::MQTTv5::DynamicBinDataView* pBrokerCertView = nullptr;
Protocol::MQTT::Common::DynamicBinaryData brokerCert;
if (certFile)
Protocol::MQTT::Common::DynamicBinDataView brokerCertView;
if (serverCertFile)
{
// Load the certificate if provided
String certContent = readFile(certFile);
String certContent = readFile(serverCertFile);
brokerCert = Protocol::MQTT::Common::DynamicBinaryData(certContent.getLength(), (const uint8*)certContent);
brokerCertView = Protocol::MQTT::Common::DynamicBinDataView(brokerCert);
pBrokerCertView = &brokerCertView;
}
Protocol::MQTT::Common::DynamicBinDataView certView(brokerCert);
Network::Client::MQTTv5 client(clientID, &receiver, certFile ? &certView : (Network::Client::MQTTv5::DynamicBinDataView*)0);

Network::Client::MQTTv5::DynamicBinDataView* pClientCertView = nullptr;
Protocol::MQTT::Common::DynamicBinaryData clientCert;
Protocol::MQTT::Common::DynamicBinDataView clientCertView;
if (clientCertFile)
{
String certContent = readFile(clientCertFile);
clientCert = Protocol::MQTT::Common::DynamicBinaryData(certContent.getLength(), (const uint8*)certContent);
clientCertView = Protocol::MQTT::Common::DynamicBinDataView(clientCert);
pClientCertView = &clientCertView;
}

Network::Client::MQTTv5::DynamicBinDataView* pClientKeyView = nullptr;
Protocol::MQTT::Common::DynamicBinaryData clientKey;
Protocol::MQTT::Common::DynamicBinDataView clientKeyView;
if (clientKeyFile)
{
String keyContent = readFile(clientKeyFile);
// mbedtls_pk_parse_key refuses to parse non null-terminated strings
auto len = keyContent.getLength();
keyContent.insertChars(len, 1, 0);
clientKey = Protocol::MQTT::Common::DynamicBinaryData(keyContent.getLength(), (const uint8*)keyContent);
clientKeyView = Protocol::MQTT::Common::DynamicBinDataView(clientKey);
pClientKeyView = &clientKeyView;
}

Network::Client::MQTTv5 client(clientID, &receiver, nullptr, pBrokerCertView, pClientCertView, pClientKeyView);
#else
Network::Client::MQTTv5 client(clientID, &receiver);
#endif
Expand Down