Add support for MS Azure MQTT

This commit is contained in:
cpq committed 2026-10-05 12:41:56 +01:00
1 parent da82df2041
commit cf9d56be79
10 files changed
+381 -145

No files matched your search

+108 -47
View File
@@ -3481,6 +3481,7 @@ struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read,
#endif #endif
// The one health record. Lives in RAM that survives a warm reset, see // The one health record. Lives in RAM that survives a warm reset, see
// MG_HEALTH_RAM and the .mg_health region in the linker script // MG_HEALTH_RAM and the .mg_health region in the linker script
struct mg_health mg_health_record MG_HEALTH_RAM; struct mg_health mg_health_record MG_HEALTH_RAM;
@@ -8071,10 +8072,10 @@ bool mg_dscp(struct mg_connection *c, uint8_t dscp) {
int tos = dscp << 2, level = IPPROTO_IP, optname = IP_TOS; int tos = dscp << 2, level = IPPROTO_IP, optname = IP_TOS;
c->dscp = (unsigned) (dscp & 63); c->dscp = (unsigned) (dscp & 63);
#if MG_ENABLE_IPV6 && !MG_ENABLE_LWIP #if MG_ENABLE_IPV6 && !MG_ENABLE_LWIP
if (c->loc.is_ip6) if (c->loc.is_ip6) level = IPPROTO_IPV6, optname = IPV6_TCLASS;
level = IPPROTO_IPV6, optname = IPV6_TCLASS;
#endif #endif
return setsockopt((MG_SOCKET_TYPE) (size_t) c->fd, level, optname, (char *) &tos, sizeof(tos)) == 0; return setsockopt((MG_SOCKET_TYPE) (size_t) c->fd, level, optname,
(char *) &tos, sizeof(tos)) == 0;
#endif #endif
#elif MG_ENABLE_TCPIP #elif MG_ENABLE_TCPIP
c->dscp = (unsigned) (dscp & 63); c->dscp = (unsigned) (dscp & 63);
@@ -8378,13 +8379,15 @@ void mg_mgr_init(struct mg_mgr *mgr) {
mgr->dns4.url = "udp://8.8.8.8:53"; mgr->dns4.url = "udp://8.8.8.8:53";
mgr->dns6.url = "udp://[2001:4860:4860::8888]:53"; mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
mg_tls_ctx_init(mgr); mg_tls_ctx_init(mgr);
MG_DEBUG(("MG_IO_SIZE: %lu, TLS: %s", MG_IO_SIZE, MG_DEBUG(("MG_IO_SIZE: %lu, TLS: %s, conn size: %zu, mgr size: %zu",
MG_IO_SIZE,
MG_TLS == MG_TLS_NONE ? "none" MG_TLS == MG_TLS_NONE ? "none"
: MG_TLS == MG_TLS_MBED ? "MbedTLS" : MG_TLS == MG_TLS_MBED ? "MbedTLS"
: MG_TLS == MG_TLS_OPENSSL ? "OpenSSL" : MG_TLS == MG_TLS_OPENSSL ? "OpenSSL"
: MG_TLS == MG_TLS_BUILTIN ? "builtin" : MG_TLS == MG_TLS_BUILTIN ? "builtin"
: MG_TLS == MG_TLS_WOLFSSL ? "WolfSSL" : MG_TLS == MG_TLS_WOLFSSL ? "WolfSSL"
: "custom")); : "custom",
sizeof(struct mg_connection), sizeof(struct mg_mgr)));
#if MG_ENABLE_MDASH #if MG_ENABLE_MDASH
mg_mdash_init(mgr); mg_mdash_init(mgr);
#endif #endif
@@ -10670,6 +10673,8 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from
// MG_EPHEMERAL_PORT_BASE to 65535 // MG_EPHEMERAL_PORT_BASE to 65535
if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM")); if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM"));
MG_DEBUG(("Builtin TCP/IP init done. Queue size: %zu, extra conn size: %zu",
ifp->recv_queue.size, mgr->extraconnsize));
} }
} }
@@ -16971,7 +16976,8 @@ struct tls_data {
uint8_t random[32]; // client random from ClientHello uint8_t random[32]; // client random from ClientHello
uint8_t session_id[32]; // client session ID between the handshake states uint8_t session_id[32]; // client session ID between the handshake states
uint8_t x25519_cli[32]; // client X25519 key between the handshake states uint8_t x25519_cli[32]; // client X25519 key between the handshake states
uint8_t x25519_sec[32]; // x25519 secret between the handshake states uint8_t p256_cli[32]; // client P-256 key between the handshake states
uint8_t x25519_sec[32]; // ECDHE secret (X25519 or P-256) between the states
bool skip_verification; // do not perform checks on server certificate bool skip_verification; // do not perform checks on server certificate
bool cert_requested; // client received a CertificateRequest bool cert_requested; // client received a CertificateRequest
@@ -16994,6 +17000,7 @@ struct tls_data {
// verify cert // verify cert
size_t pubkeysz; // size of the server public key size_t pubkeysz; // size of the server public key
uint8_t sighash[32]; // calculated signature verification hash uint8_t sighash[32]; // calculated signature verification hash
uint8_t sighash384[48]; // same, SHA-384, for ecdsa_secp384r1_sha384
struct tls_enc enc; // actual keys in use at this time struct tls_enc enc; // actual keys in use at this time
struct tls_enc app_keys; // storage during two-way auth handshake struct tls_enc app_keys; // storage during two-way auth handshake
@@ -17208,6 +17215,10 @@ static void mg_tls_generate_handshake_keys(struct mg_connection *c) {
mg_hmac_sha256(tls->enc.handshake_secret, pre_extract_secret, mg_hmac_sha256(tls->enc.handshake_secret, pre_extract_secret,
sizeof(pre_extract_secret), tls->x25519_sec, sizeof(pre_extract_secret), tls->x25519_sec,
sizeof(tls->x25519_sec)); sizeof(tls->x25519_sec));
// ECDHE keys and secret are not needed anymore, wipe for forward secrecy
mg_bzero(tls->x25519_cli, sizeof(tls->x25519_cli));
mg_bzero(tls->p256_cli, sizeof(tls->p256_cli));
mg_bzero(tls->x25519_sec, sizeof(tls->x25519_sec));
mg_tls_hexdump("hs secret", tls->enc.handshake_secret, 32); mg_tls_hexdump("hs secret", tls->enc.handshake_secret, 32);
// mg_sha256_final is not idempotent, need to copy sha256 context to calculate // mg_sha256_final is not idempotent, need to copy sha256 context to calculate
@@ -17490,8 +17501,11 @@ static int mg_tls_recv_record(struct mg_connection *c) {
return r; return r;
} }
// Hash the CertificateVerify content: SHA-256 into hash, and if hash384 is not
// NULL, SHA-384 into hash384 for ecdsa_secp384r1_sha384
static void mg_tls_calc_cert_verify_hash(struct mg_connection *c, static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
uint8_t hash[32], bool is_client) { uint8_t hash[32], uint8_t *hash384,
bool is_client) {
struct tls_data *tls = (struct tls_data *) c->tls; struct tls_data *tls = (struct tls_data *) c->tls;
uint8_t sig_content[130]; uint8_t sig_content[130];
mg_sha256_ctx sha256; mg_sha256_ctx sha256;
@@ -17511,6 +17525,7 @@ static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
mg_sha256_init(&sha256); mg_sha256_init(&sha256);
mg_sha256_update(&sha256, sig_content, sizeof(sig_content)); mg_sha256_update(&sha256, sig_content, sizeof(sig_content));
mg_sha256_final(hash, &sha256); mg_sha256_final(hash, &sha256);
if (hash384 != NULL) mg_sha384(hash384, sig_content, sizeof(sig_content));
} }
// read and parse ClientHello record // read and parse ClientHello record
@@ -17652,10 +17667,15 @@ static bool mg_tls_server_send_ext(struct mg_connection *c) {
return mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE); return mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE);
} }
// signature algorithms we actually support: // signature algorithms we actually support: ecdsa_secp256r1_sha256,
// rsa_pkcs1_sha256, rsa_pss_rsae_sha256 and ecdsa_secp256r1_sha256 // ecdsa_secp384r1_sha384 (if enabled), rsa_pss_rsae_sha256, rsa_pkcs1_sha256
static const uint8_t secp256r1_sig_algs[12] = { static const uint8_t secp256r1_sig_algs[] = {
#if MG_UECC_SUPPORTS_secp384r1
0x00, 0x0d, 0x00, 0x0a, 0x00, 0x08, 0x04, 0x03,
0x05, 0x03, 0x08, 0x04, 0x04, 0x01};
#else
0x00, 0x0d, 0x00, 0x08, 0x00, 0x06, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01}; 0x00, 0x0d, 0x00, 0x08, 0x00, 0x06, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01};
#endif
static bool mg_tls_server_send_cert_request(struct mg_connection *c) { static bool mg_tls_server_send_cert_request(struct mg_connection *c) {
struct tls_data *tls = (struct tls_data *) c->tls; struct tls_data *tls = (struct tls_data *) c->tls;
@@ -17935,7 +17955,7 @@ static bool mg_tls_send_cert_verify(struct mg_connection *c, bool is_client) {
struct tls_data *tls = (struct tls_data *) c->tls; struct tls_data *tls = (struct tls_data *) c->tls;
uint8_t hash[32] = {0}; uint8_t hash[32] = {0};
mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash, is_client); mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash, NULL, is_client);
if (tls->rsa.n.len > 0 && tls->rsa.d.len > 0) { if (tls->rsa.n.len > 0 && tls->rsa.d.len > 0) {
// RSA certificate verify packet // RSA certificate verify packet
@@ -18063,6 +18083,7 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
struct mg_iobuf *wio = &tls->send; struct mg_iobuf *wio = &tls->send;
uint8_t x25519_pub[X25519_BYTES]; uint8_t x25519_pub[X25519_BYTES];
uint8_t p256_pub[64]; // X || Y, sent as an uncompressed point 0x04 || X || Y
// - "signature algorithms we actually support", see above // - "signature algorithms we actually support", see above
// uint8_t secp256r1_sig_algs[] // uint8_t secp256r1_sig_algs[]
@@ -18075,7 +18096,7 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
0xfe, 0x00, 0x00, 0xfe}; 0xfe, 0x00, 0x00, 0xfe};
// clang-format off // clang-format off
uint8_t msg_client_hello[145] = { uint8_t msg_client_hello[216] = {
// TLS Client Hello header reported as TLS1.2 (5) // TLS Client Hello header reported as TLS1.2 (5)
0x16, 0x03, 0x03, 0x00, 0xfe, 0x16, 0x03, 0x03, 0x00, 0xfe,
// client hello, tls 1.2 (6) // client hello, tls 1.2 (6)
@@ -18096,15 +18117,17 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
0x01, 0x00, 0x01, 0x00,
// extensions + keyshare // extensions + keyshare
0x00, 0xfe, 0x00, 0xfe,
// x25519 keyshare // keyshare: x25519
0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x00, 0x33, 0x00, 0x6b, 0x00, 0x69, 0x00, 0x1d, 0x00, 0x20,
PLACEHOLDER_32B, PLACEHOLDER_32B,
// supported groups (x25519) // keyshare: secp256r1, uncompressed point
0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x41, 0x04, PLACEHOLDER_32B, PLACEHOLDER_32B,
// supported groups (x25519, secp256r1)
0x00, 0x0a, 0x00, 0x06, 0x00, 0x04, 0x00, 0x1d, 0x00, 0x17,
// supported versions (tls1.3 == 0x304) // supported versions (tls1.3 == 0x304)
0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04, 0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04,
// session ticket (none) // session ticket (none)
0x00, 0x23, 0x00, 0x00, // 144 bytes till here 0x00, 0x23, 0x00, 0x00, // 215 bytes till here
}; };
// clang-format on // clang-format on
const char *hostname = tls->hostname; const char *hostname = tls->hostname;
@@ -18117,11 +18140,11 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
// patch ClientHello with correct hostname ext length (if any) // patch ClientHello with correct hostname ext length (if any)
MG_STORE_BE16(msg_client_hello + 3, MG_STORE_BE16(msg_client_hello + 3,
hostname_extsz + 183 - 9 - 34 + sig_alg_sz); hostname_extsz + 254 - 9 - 34 + sig_alg_sz);
MG_STORE_BE16(msg_client_hello + 7, MG_STORE_BE16(msg_client_hello + 7,
hostname_extsz + 179 - 9 - 34 + sig_alg_sz); hostname_extsz + 250 - 9 - 34 + sig_alg_sz);
MG_STORE_BE16(msg_client_hello + 82, MG_STORE_BE16(msg_client_hello + 82,
hostname_extsz + 104 - 9 - 34 + sig_alg_sz); hostname_extsz + 175 - 9 - 34 + sig_alg_sz);
if (hostnamesz > 0) { if (hostnamesz > 0) {
MG_STORE_BE16(server_name_ext + 2, hostnamesz + 5); MG_STORE_BE16(server_name_ext + 2, hostnamesz + 5);
@@ -18129,16 +18152,21 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
MG_STORE_BE16(server_name_ext + 7, hostnamesz); MG_STORE_BE16(server_name_ext + 7, hostnamesz);
} }
// calculate keyshare // calculate keyshares
if (!mg_random(tls->x25519_cli, sizeof(tls->x25519_cli))) mg_error(c, "RNG"); if (!mg_random(tls->x25519_cli, sizeof(tls->x25519_cli))) mg_error(c, "RNG");
mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1); mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1);
if (!mg_uecc_make_key(p256_pub, tls->p256_cli, mg_uecc_secp256r1())) {
mg_error(c, "P-256 key");
return false; // Do not send uninitialised p256_pub
}
// fill in the gaps: random + session ID + keyshare // fill in the gaps: random + session ID + keyshares
if (!mg_random(tls->session_id, sizeof(tls->session_id))) mg_error(c, "RNG"); if (!mg_random(tls->session_id, sizeof(tls->session_id))) mg_error(c, "RNG");
if (!mg_random(tls->random, sizeof(tls->random))) mg_error(c, "RNG"); if (!mg_random(tls->random, sizeof(tls->random))) mg_error(c, "RNG");
memmove(msg_client_hello + 11, tls->random, sizeof(tls->random)); memmove(msg_client_hello + 11, tls->random, sizeof(tls->random));
memmove(msg_client_hello + 44, tls->session_id, sizeof(tls->session_id)); memmove(msg_client_hello + 44, tls->session_id, sizeof(tls->session_id));
memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub)); memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub));
memmove(msg_client_hello + 131, p256_pub, sizeof(p256_pub));
// client hello message // client hello message
if (mg_iobuf_add(wio, wio->len, msg_client_hello, sizeof(msg_client_hello)) == if (mg_iobuf_add(wio, wio->len, msg_client_hello, sizeof(msg_client_hello)) ==
@@ -18208,17 +18236,29 @@ static int mg_tls_client_recv_hello(struct mg_connection *c) {
} }
if (ext_len2 < (2 + 2 + 32)) goto fail; if (ext_len2 < (2 + 2 + 32)) goto fail;
group = MG_LOAD_BE16(ext + j + 4); group = MG_LOAD_BE16(ext + j + 4);
if (group != 0x001d) { key_exchange_len = MG_LOAD_BE16(ext + j + 6);
key_exchange = ext + j + 8;
if (key_exchange_len > ext_len2 - 4) goto fail;
if (group == 0x001d) { // x25519
if (key_exchange_len != 32 ||
mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1) <
0) {
mg_error(c, "bad key");
return -1;
}
} else if (group == 0x0017) { // secp256r1, uncompressed point 0x04 || X || Y
if (key_exchange_len != 65 || key_exchange[0] != 0x04 ||
!mg_uecc_valid_public_key(key_exchange + 1, mg_uecc_secp256r1()) ||
!mg_uecc_shared_secret(key_exchange + 1, tls->p256_cli,
tls->x25519_sec, mg_uecc_secp256r1())) {
mg_error(c, "bad key");
return -1;
}
} else {
mg_error(c, "bad key exchange group"); mg_error(c, "bad key exchange group");
return -1; return -1;
} }
key_exchange_len = MG_LOAD_BE16(ext + j + 6); // mg_tls_hexdump("c ecdhe sec", tls->x25519_sec, 32);
key_exchange = ext + j + 8;
if (key_exchange_len != 32 || mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1) < 0) {
mg_error(c, "bad key");
return -1;
}
mg_tls_hexdump("c x25519 sec", tls->x25519_sec, 32);
mg_tls_drop_record(c); mg_tls_drop_record(c);
/* generate handshake keys */ /* generate handshake keys */
mg_tls_generate_handshake_keys(c); mg_tls_generate_handshake_keys(c);
@@ -18483,7 +18523,7 @@ static int countdots(struct mg_str s) {
char *p = s.buf; char *p = s.buf;
while (len--) { while (len--) {
if (*(p++) == '.') ++count; if (*(p++) == '.') ++count;
} }
return count; return count;
} }
@@ -18560,10 +18600,12 @@ static int mg_tls_verify_cert_signature(const struct mg_tls_cert *cert,
#if MG_UECC_SUPPORTS_secp384r1 #if MG_UECC_SUPPORTS_secp384r1
if (issuer->pubkey.len == 96) { if (issuer->pubkey.len == 96) {
const uint32_t N = 48; const uint32_t N = 48;
if (a.len > N) a.value += (a.len - N), a.len = N; if (a.len > N) a.value += (a.len - N), a.len = N; // padding
if (b.len > N) b.value += (b.len - N), b.len = N; if (b.len > N) b.value += (b.len - N), b.len = N;
memmove(sig, a.value, N); memset(sig, 0, N - a.len); // short encoding
memmove(sig + N, b.value, N); memmove(sig + (N - a.len), a.value, a.len);
memset(sig + N, 0, N - b.len);
memmove(sig + N + (N - b.len), b.value, b.len);
return mg_uecc_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash, return mg_uecc_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
(unsigned) cert->tbshashsz, sig, (unsigned) cert->tbshashsz, sig,
mg_uecc_secp384r1()); mg_uecc_secp384r1());
@@ -18792,7 +18834,7 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
} }
} }
mg_tls_drop_message(c); mg_tls_drop_message(c);
mg_tls_calc_cert_verify_hash(c, tls->sighash, !is_client); mg_tls_calc_cert_verify_hash(c, tls->sighash, tls->sighash384, !is_client);
return 0; return 0;
} }
@@ -18855,11 +18897,20 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
return -1; return -1;
} }
MG_VERBOSE(("certificate verification successful (RSA)")); MG_VERBOSE(("certificate verification successful (RSA)"));
} else if (sigalg == 0x0403) { // ecdsa_secp256r1_sha256 } else if (sigalg == 0x0403 ||
// Extract certificate signature and verify it using pubkey and sighash (sigalg == 0x0503 && MG_UECC_SUPPORTS_secp384r1)) {
uint8_t sig[64]; // ecdsa_secp256r1_sha256 or ecdsa_secp384r1_sha384. Extract certificate
// signature and verify it using pubkey and sighash
bool is384 = sigalg == 0x0503;
uint32_t N = is384 ? 48 : 32; // curve size: r and s are N bytes each
int ok;
uint8_t sig[96];
struct mg_der_tlv seq, r, s; struct mg_der_tlv seq, r, s;
memset(sig, 0, 64); memset(sig, 0, sizeof(sig));
if (tls->pubkeysz != 2 * N) {
mg_error(c, "certverify scheme %04x does not match the key", sigalg);
return -1;
}
if (mg_der_to_tlv(sigbuf, siglen, &seq) < 0) { if (mg_der_to_tlv(sigbuf, siglen, &seq) < 0) {
mg_error(c, "verification message is not an ASN.1 DER sequence"); mg_error(c, "verification message is not an ASN.1 DER sequence");
return -1; return -1;
@@ -18873,15 +18924,24 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
return -1; return -1;
} }
// Integers may be padded with zeroes // Integers may be padded with zeroes
if (r.len > 32) r.value = r.value + (r.len - 32), r.len = 32; if (r.len > N) r.value = r.value + (r.len - N), r.len = N;
if (s.len > 32) s.value = s.value + (s.len - 32), s.len = 32; if (s.len > N) s.value = s.value + (s.len - N), s.len = N;
// r or s may be shorter than 32 bytes, "right-justify" (network order) // r or s may be shorter than N bytes, "right-justify" (network order)
memmove(sig + (32 - r.len), r.value, r.len); memmove(sig + (N - r.len), r.value, r.len);
memmove(sig + 32 + (32 - s.len), s.value, s.len); memmove(sig + N + (N - s.len), s.value, s.len);
if (mg_uecc_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash), sig, #if MG_UECC_SUPPORTS_secp384r1
mg_uecc_secp256r1()) != 1) { if (is384) {
ok = mg_uecc_verify(tls->pubkey, tls->sighash384,
sizeof(tls->sighash384), sig, mg_uecc_secp384r1());
} else
#endif
{
ok = mg_uecc_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash),
sig, mg_uecc_secp256r1());
}
if (ok != 1) {
mg_error(c, "failed to verify EC certificate (certverify)"); mg_error(c, "failed to verify EC certificate (certverify)");
return -1; return -1;
} }
@@ -19622,7 +19682,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
MG_INFO(("Parsed PKCS#8 RSA private key: %d bytes", (int) key.len)); MG_INFO(("Parsed PKCS#8 RSA private key: %d bytes", (int) key.len));
} else { } else {
mg_free((void *) key.buf); mg_free((void *) key.buf);
MG_INFO(("Parsed PKCS#8 EC private key")); MG_VERBOSE(("Parsed PKCS#8 EC private key"));
} }
} else { } else {
mg_free((void *) key.buf); mg_free((void *) key.buf);
@@ -19673,6 +19733,7 @@ void mg_tls_free(struct mg_connection *c) {
} }
mg_free((void *) tls->ca_der.buf); mg_free((void *) tls->ca_der.buf);
mg_free((void *) tls->rsa_key_der.buf); mg_free((void *) tls->rsa_key_der.buf);
mg_bzero((unsigned char *) tls, sizeof(*tls)); // Wipe keys and secrets
} }
mg_free(c->tls); mg_free(c->tls);
c->tls = NULL; c->tls = NULL;
+8 -6
View File
@@ -1,8 +1,8 @@
#include "net.h"
#include "dns.h" #include "dns.h"
#include "fmt.h" #include "fmt.h"
#include "log.h" #include "log.h"
#include "mdash.h" #include "mdash.h"
#include "net.h"
#include "printf.h" #include "printf.h"
#include "profile.h" #include "profile.h"
#include "timer.h" #include "timer.h"
@@ -39,10 +39,10 @@ bool mg_dscp(struct mg_connection *c, uint8_t dscp) {
int tos = dscp << 2, level = IPPROTO_IP, optname = IP_TOS; int tos = dscp << 2, level = IPPROTO_IP, optname = IP_TOS;
c->dscp = (unsigned) (dscp & 63); c->dscp = (unsigned) (dscp & 63);
#if MG_ENABLE_IPV6 && !MG_ENABLE_LWIP #if MG_ENABLE_IPV6 && !MG_ENABLE_LWIP
if (c->loc.is_ip6) if (c->loc.is_ip6) level = IPPROTO_IPV6, optname = IPV6_TCLASS;
level = IPPROTO_IPV6, optname = IPV6_TCLASS;
#endif #endif
return setsockopt((MG_SOCKET_TYPE) (size_t) c->fd, level, optname, (char *) &tos, sizeof(tos)) == 0; return setsockopt((MG_SOCKET_TYPE) (size_t) c->fd, level, optname,
(char *) &tos, sizeof(tos)) == 0;
#endif #endif
#elif MG_ENABLE_TCPIP #elif MG_ENABLE_TCPIP
c->dscp = (unsigned) (dscp & 63); c->dscp = (unsigned) (dscp & 63);
@@ -346,13 +346,15 @@ void mg_mgr_init(struct mg_mgr *mgr) {
mgr->dns4.url = "udp://8.8.8.8:53"; mgr->dns4.url = "udp://8.8.8.8:53";
mgr->dns6.url = "udp://[2001:4860:4860::8888]:53"; mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
mg_tls_ctx_init(mgr); mg_tls_ctx_init(mgr);
MG_DEBUG(("MG_IO_SIZE: %lu, TLS: %s", MG_IO_SIZE, MG_DEBUG(("MG_IO_SIZE: %lu, TLS: %s, conn size: %zu, mgr size: %zu",
MG_IO_SIZE,
MG_TLS == MG_TLS_NONE ? "none" MG_TLS == MG_TLS_NONE ? "none"
: MG_TLS == MG_TLS_MBED ? "MbedTLS" : MG_TLS == MG_TLS_MBED ? "MbedTLS"
: MG_TLS == MG_TLS_OPENSSL ? "OpenSSL" : MG_TLS == MG_TLS_OPENSSL ? "OpenSSL"
: MG_TLS == MG_TLS_BUILTIN ? "builtin" : MG_TLS == MG_TLS_BUILTIN ? "builtin"
: MG_TLS == MG_TLS_WOLFSSL ? "WolfSSL" : MG_TLS == MG_TLS_WOLFSSL ? "WolfSSL"
: "custom")); : "custom",
sizeof(struct mg_connection), sizeof(struct mg_mgr)));
#if MG_ENABLE_MDASH #if MG_ENABLE_MDASH
mg_mdash_init(mgr); mg_mdash_init(mgr);
#endif #endif
+2
View File
@@ -2265,6 +2265,8 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from
// MG_EPHEMERAL_PORT_BASE to 65535 // MG_EPHEMERAL_PORT_BASE to 65535
if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM")); if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM"));
MG_DEBUG(("Builtin TCP/IP init done. Queue size: %zu, extra conn size: %zu",
ifp->recv_queue.size, mgr->extraconnsize));
} }
} }
+98 -42
View File
@@ -107,7 +107,8 @@ struct tls_data {
uint8_t random[32]; // client random from ClientHello uint8_t random[32]; // client random from ClientHello
uint8_t session_id[32]; // client session ID between the handshake states uint8_t session_id[32]; // client session ID between the handshake states
uint8_t x25519_cli[32]; // client X25519 key between the handshake states uint8_t x25519_cli[32]; // client X25519 key between the handshake states
uint8_t x25519_sec[32]; // x25519 secret between the handshake states uint8_t p256_cli[32]; // client P-256 key between the handshake states
uint8_t x25519_sec[32]; // ECDHE secret (X25519 or P-256) between the states
bool skip_verification; // do not perform checks on server certificate bool skip_verification; // do not perform checks on server certificate
bool cert_requested; // client received a CertificateRequest bool cert_requested; // client received a CertificateRequest
@@ -130,6 +131,7 @@ struct tls_data {
// verify cert // verify cert
size_t pubkeysz; // size of the server public key size_t pubkeysz; // size of the server public key
uint8_t sighash[32]; // calculated signature verification hash uint8_t sighash[32]; // calculated signature verification hash
uint8_t sighash384[48]; // same, SHA-384, for ecdsa_secp384r1_sha384
struct tls_enc enc; // actual keys in use at this time struct tls_enc enc; // actual keys in use at this time
struct tls_enc app_keys; // storage during two-way auth handshake struct tls_enc app_keys; // storage during two-way auth handshake
@@ -344,6 +346,10 @@ static void mg_tls_generate_handshake_keys(struct mg_connection *c) {
mg_hmac_sha256(tls->enc.handshake_secret, pre_extract_secret, mg_hmac_sha256(tls->enc.handshake_secret, pre_extract_secret,
sizeof(pre_extract_secret), tls->x25519_sec, sizeof(pre_extract_secret), tls->x25519_sec,
sizeof(tls->x25519_sec)); sizeof(tls->x25519_sec));
// ECDHE keys and secret are not needed anymore, wipe for forward secrecy
mg_bzero(tls->x25519_cli, sizeof(tls->x25519_cli));
mg_bzero(tls->p256_cli, sizeof(tls->p256_cli));
mg_bzero(tls->x25519_sec, sizeof(tls->x25519_sec));
mg_tls_hexdump("hs secret", tls->enc.handshake_secret, 32); mg_tls_hexdump("hs secret", tls->enc.handshake_secret, 32);
// mg_sha256_final is not idempotent, need to copy sha256 context to calculate // mg_sha256_final is not idempotent, need to copy sha256 context to calculate
@@ -626,8 +632,11 @@ static int mg_tls_recv_record(struct mg_connection *c) {
return r; return r;
} }
// Hash the CertificateVerify content: SHA-256 into hash, and if hash384 is not
// NULL, SHA-384 into hash384 for ecdsa_secp384r1_sha384
static void mg_tls_calc_cert_verify_hash(struct mg_connection *c, static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
uint8_t hash[32], bool is_client) { uint8_t hash[32], uint8_t *hash384,
bool is_client) {
struct tls_data *tls = (struct tls_data *) c->tls; struct tls_data *tls = (struct tls_data *) c->tls;
uint8_t sig_content[130]; uint8_t sig_content[130];
mg_sha256_ctx sha256; mg_sha256_ctx sha256;
@@ -647,6 +656,7 @@ static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
mg_sha256_init(&sha256); mg_sha256_init(&sha256);
mg_sha256_update(&sha256, sig_content, sizeof(sig_content)); mg_sha256_update(&sha256, sig_content, sizeof(sig_content));
mg_sha256_final(hash, &sha256); mg_sha256_final(hash, &sha256);
if (hash384 != NULL) mg_sha384(hash384, sig_content, sizeof(sig_content));
} }
// read and parse ClientHello record // read and parse ClientHello record
@@ -788,10 +798,15 @@ static bool mg_tls_server_send_ext(struct mg_connection *c) {
return mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE); return mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE);
} }
// signature algorithms we actually support: // signature algorithms we actually support: ecdsa_secp256r1_sha256,
// rsa_pkcs1_sha256, rsa_pss_rsae_sha256 and ecdsa_secp256r1_sha256 // ecdsa_secp384r1_sha384 (if enabled), rsa_pss_rsae_sha256, rsa_pkcs1_sha256
static const uint8_t secp256r1_sig_algs[12] = { static const uint8_t secp256r1_sig_algs[] = {
#if MG_UECC_SUPPORTS_secp384r1
0x00, 0x0d, 0x00, 0x0a, 0x00, 0x08, 0x04, 0x03,
0x05, 0x03, 0x08, 0x04, 0x04, 0x01};
#else
0x00, 0x0d, 0x00, 0x08, 0x00, 0x06, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01}; 0x00, 0x0d, 0x00, 0x08, 0x00, 0x06, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01};
#endif
static bool mg_tls_server_send_cert_request(struct mg_connection *c) { static bool mg_tls_server_send_cert_request(struct mg_connection *c) {
struct tls_data *tls = (struct tls_data *) c->tls; struct tls_data *tls = (struct tls_data *) c->tls;
@@ -1071,7 +1086,7 @@ static bool mg_tls_send_cert_verify(struct mg_connection *c, bool is_client) {
struct tls_data *tls = (struct tls_data *) c->tls; struct tls_data *tls = (struct tls_data *) c->tls;
uint8_t hash[32] = {0}; uint8_t hash[32] = {0};
mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash, is_client); mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash, NULL, is_client);
if (tls->rsa.n.len > 0 && tls->rsa.d.len > 0) { if (tls->rsa.n.len > 0 && tls->rsa.d.len > 0) {
// RSA certificate verify packet // RSA certificate verify packet
@@ -1199,6 +1214,7 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
struct mg_iobuf *wio = &tls->send; struct mg_iobuf *wio = &tls->send;
uint8_t x25519_pub[X25519_BYTES]; uint8_t x25519_pub[X25519_BYTES];
uint8_t p256_pub[64]; // X || Y, sent as an uncompressed point 0x04 || X || Y
// - "signature algorithms we actually support", see above // - "signature algorithms we actually support", see above
// uint8_t secp256r1_sig_algs[] // uint8_t secp256r1_sig_algs[]
@@ -1211,7 +1227,7 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
0xfe, 0x00, 0x00, 0xfe}; 0xfe, 0x00, 0x00, 0xfe};
// clang-format off // clang-format off
uint8_t msg_client_hello[145] = { uint8_t msg_client_hello[216] = {
// TLS Client Hello header reported as TLS1.2 (5) // TLS Client Hello header reported as TLS1.2 (5)
0x16, 0x03, 0x03, 0x00, 0xfe, 0x16, 0x03, 0x03, 0x00, 0xfe,
// client hello, tls 1.2 (6) // client hello, tls 1.2 (6)
@@ -1232,15 +1248,17 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
0x01, 0x00, 0x01, 0x00,
// extensions + keyshare // extensions + keyshare
0x00, 0xfe, 0x00, 0xfe,
// x25519 keyshare // keyshare: x25519
0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x00, 0x33, 0x00, 0x6b, 0x00, 0x69, 0x00, 0x1d, 0x00, 0x20,
PLACEHOLDER_32B, PLACEHOLDER_32B,
// supported groups (x25519) // keyshare: secp256r1, uncompressed point
0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x41, 0x04, PLACEHOLDER_32B, PLACEHOLDER_32B,
// supported groups (x25519, secp256r1)
0x00, 0x0a, 0x00, 0x06, 0x00, 0x04, 0x00, 0x1d, 0x00, 0x17,
// supported versions (tls1.3 == 0x304) // supported versions (tls1.3 == 0x304)
0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04, 0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04,
// session ticket (none) // session ticket (none)
0x00, 0x23, 0x00, 0x00, // 144 bytes till here 0x00, 0x23, 0x00, 0x00, // 215 bytes till here
}; };
// clang-format on // clang-format on
const char *hostname = tls->hostname; const char *hostname = tls->hostname;
@@ -1253,11 +1271,11 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
// patch ClientHello with correct hostname ext length (if any) // patch ClientHello with correct hostname ext length (if any)
MG_STORE_BE16(msg_client_hello + 3, MG_STORE_BE16(msg_client_hello + 3,
hostname_extsz + 183 - 9 - 34 + sig_alg_sz); hostname_extsz + 254 - 9 - 34 + sig_alg_sz);
MG_STORE_BE16(msg_client_hello + 7, MG_STORE_BE16(msg_client_hello + 7,
hostname_extsz + 179 - 9 - 34 + sig_alg_sz); hostname_extsz + 250 - 9 - 34 + sig_alg_sz);
MG_STORE_BE16(msg_client_hello + 82, MG_STORE_BE16(msg_client_hello + 82,
hostname_extsz + 104 - 9 - 34 + sig_alg_sz); hostname_extsz + 175 - 9 - 34 + sig_alg_sz);
if (hostnamesz > 0) { if (hostnamesz > 0) {
MG_STORE_BE16(server_name_ext + 2, hostnamesz + 5); MG_STORE_BE16(server_name_ext + 2, hostnamesz + 5);
@@ -1265,16 +1283,21 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
MG_STORE_BE16(server_name_ext + 7, hostnamesz); MG_STORE_BE16(server_name_ext + 7, hostnamesz);
} }
// calculate keyshare // calculate keyshares
if (!mg_random(tls->x25519_cli, sizeof(tls->x25519_cli))) mg_error(c, "RNG"); if (!mg_random(tls->x25519_cli, sizeof(tls->x25519_cli))) mg_error(c, "RNG");
mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1); mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1);
if (!mg_uecc_make_key(p256_pub, tls->p256_cli, mg_uecc_secp256r1())) {
mg_error(c, "P-256 key");
return false; // Do not send uninitialised p256_pub
}
// fill in the gaps: random + session ID + keyshare // fill in the gaps: random + session ID + keyshares
if (!mg_random(tls->session_id, sizeof(tls->session_id))) mg_error(c, "RNG"); if (!mg_random(tls->session_id, sizeof(tls->session_id))) mg_error(c, "RNG");
if (!mg_random(tls->random, sizeof(tls->random))) mg_error(c, "RNG"); if (!mg_random(tls->random, sizeof(tls->random))) mg_error(c, "RNG");
memmove(msg_client_hello + 11, tls->random, sizeof(tls->random)); memmove(msg_client_hello + 11, tls->random, sizeof(tls->random));
memmove(msg_client_hello + 44, tls->session_id, sizeof(tls->session_id)); memmove(msg_client_hello + 44, tls->session_id, sizeof(tls->session_id));
memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub)); memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub));
memmove(msg_client_hello + 131, p256_pub, sizeof(p256_pub));
// client hello message // client hello message
if (mg_iobuf_add(wio, wio->len, msg_client_hello, sizeof(msg_client_hello)) == if (mg_iobuf_add(wio, wio->len, msg_client_hello, sizeof(msg_client_hello)) ==
@@ -1344,17 +1367,29 @@ static int mg_tls_client_recv_hello(struct mg_connection *c) {
} }
if (ext_len2 < (2 + 2 + 32)) goto fail; if (ext_len2 < (2 + 2 + 32)) goto fail;
group = MG_LOAD_BE16(ext + j + 4); group = MG_LOAD_BE16(ext + j + 4);
if (group != 0x001d) { key_exchange_len = MG_LOAD_BE16(ext + j + 6);
key_exchange = ext + j + 8;
if (key_exchange_len > ext_len2 - 4) goto fail;
if (group == 0x001d) { // x25519
if (key_exchange_len != 32 ||
mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1) <
0) {
mg_error(c, "bad key");
return -1;
}
} else if (group == 0x0017) { // secp256r1, uncompressed point 0x04 || X || Y
if (key_exchange_len != 65 || key_exchange[0] != 0x04 ||
!mg_uecc_valid_public_key(key_exchange + 1, mg_uecc_secp256r1()) ||
!mg_uecc_shared_secret(key_exchange + 1, tls->p256_cli,
tls->x25519_sec, mg_uecc_secp256r1())) {
mg_error(c, "bad key");
return -1;
}
} else {
mg_error(c, "bad key exchange group"); mg_error(c, "bad key exchange group");
return -1; return -1;
} }
key_exchange_len = MG_LOAD_BE16(ext + j + 6); // mg_tls_hexdump("c ecdhe sec", tls->x25519_sec, 32);
key_exchange = ext + j + 8;
if (key_exchange_len != 32 || mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1) < 0) {
mg_error(c, "bad key");
return -1;
}
mg_tls_hexdump("c x25519 sec", tls->x25519_sec, 32);
mg_tls_drop_record(c); mg_tls_drop_record(c);
/* generate handshake keys */ /* generate handshake keys */
mg_tls_generate_handshake_keys(c); mg_tls_generate_handshake_keys(c);
@@ -1619,7 +1654,7 @@ static int countdots(struct mg_str s) {
char *p = s.buf; char *p = s.buf;
while (len--) { while (len--) {
if (*(p++) == '.') ++count; if (*(p++) == '.') ++count;
} }
return count; return count;
} }
@@ -1696,10 +1731,12 @@ static int mg_tls_verify_cert_signature(const struct mg_tls_cert *cert,
#if MG_UECC_SUPPORTS_secp384r1 #if MG_UECC_SUPPORTS_secp384r1
if (issuer->pubkey.len == 96) { if (issuer->pubkey.len == 96) {
const uint32_t N = 48; const uint32_t N = 48;
if (a.len > N) a.value += (a.len - N), a.len = N; if (a.len > N) a.value += (a.len - N), a.len = N; // padding
if (b.len > N) b.value += (b.len - N), b.len = N; if (b.len > N) b.value += (b.len - N), b.len = N;
memmove(sig, a.value, N); memset(sig, 0, N - a.len); // short encoding
memmove(sig + N, b.value, N); memmove(sig + (N - a.len), a.value, a.len);
memset(sig + N, 0, N - b.len);
memmove(sig + N + (N - b.len), b.value, b.len);
return mg_uecc_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash, return mg_uecc_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
(unsigned) cert->tbshashsz, sig, (unsigned) cert->tbshashsz, sig,
mg_uecc_secp384r1()); mg_uecc_secp384r1());
@@ -1928,7 +1965,7 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
} }
} }
mg_tls_drop_message(c); mg_tls_drop_message(c);
mg_tls_calc_cert_verify_hash(c, tls->sighash, !is_client); mg_tls_calc_cert_verify_hash(c, tls->sighash, tls->sighash384, !is_client);
return 0; return 0;
} }
@@ -1991,11 +2028,20 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
return -1; return -1;
} }
MG_VERBOSE(("certificate verification successful (RSA)")); MG_VERBOSE(("certificate verification successful (RSA)"));
} else if (sigalg == 0x0403) { // ecdsa_secp256r1_sha256 } else if (sigalg == 0x0403 ||
// Extract certificate signature and verify it using pubkey and sighash (sigalg == 0x0503 && MG_UECC_SUPPORTS_secp384r1)) {
uint8_t sig[64]; // ecdsa_secp256r1_sha256 or ecdsa_secp384r1_sha384. Extract certificate
// signature and verify it using pubkey and sighash
bool is384 = sigalg == 0x0503;
uint32_t N = is384 ? 48 : 32; // curve size: r and s are N bytes each
int ok;
uint8_t sig[96];
struct mg_der_tlv seq, r, s; struct mg_der_tlv seq, r, s;
memset(sig, 0, 64); memset(sig, 0, sizeof(sig));
if (tls->pubkeysz != 2 * N) {
mg_error(c, "certverify scheme %04x does not match the key", sigalg);
return -1;
}
if (mg_der_to_tlv(sigbuf, siglen, &seq) < 0) { if (mg_der_to_tlv(sigbuf, siglen, &seq) < 0) {
mg_error(c, "verification message is not an ASN.1 DER sequence"); mg_error(c, "verification message is not an ASN.1 DER sequence");
return -1; return -1;
@@ -2009,15 +2055,24 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
return -1; return -1;
} }
// Integers may be padded with zeroes // Integers may be padded with zeroes
if (r.len > 32) r.value = r.value + (r.len - 32), r.len = 32; if (r.len > N) r.value = r.value + (r.len - N), r.len = N;
if (s.len > 32) s.value = s.value + (s.len - 32), s.len = 32; if (s.len > N) s.value = s.value + (s.len - N), s.len = N;
// r or s may be shorter than 32 bytes, "right-justify" (network order) // r or s may be shorter than N bytes, "right-justify" (network order)
memmove(sig + (32 - r.len), r.value, r.len); memmove(sig + (N - r.len), r.value, r.len);
memmove(sig + 32 + (32 - s.len), s.value, s.len); memmove(sig + N + (N - s.len), s.value, s.len);
if (mg_uecc_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash), sig, #if MG_UECC_SUPPORTS_secp384r1
mg_uecc_secp256r1()) != 1) { if (is384) {
ok = mg_uecc_verify(tls->pubkey, tls->sighash384,
sizeof(tls->sighash384), sig, mg_uecc_secp384r1());
} else
#endif
{
ok = mg_uecc_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash),
sig, mg_uecc_secp256r1());
}
if (ok != 1) {
mg_error(c, "failed to verify EC certificate (certverify)"); mg_error(c, "failed to verify EC certificate (certverify)");
return -1; return -1;
} }
@@ -2758,7 +2813,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
MG_INFO(("Parsed PKCS#8 RSA private key: %d bytes", (int) key.len)); MG_INFO(("Parsed PKCS#8 RSA private key: %d bytes", (int) key.len));
} else { } else {
mg_free((void *) key.buf); mg_free((void *) key.buf);
MG_INFO(("Parsed PKCS#8 EC private key")); MG_VERBOSE(("Parsed PKCS#8 EC private key"));
} }
} else { } else {
mg_free((void *) key.buf); mg_free((void *) key.buf);
@@ -2809,6 +2864,7 @@ void mg_tls_free(struct mg_connection *c) {
} }
mg_free((void *) tls->ca_der.buf); mg_free((void *) tls->ca_der.buf);
mg_free((void *) tls->rsa_key_der.buf); mg_free((void *) tls->rsa_key_der.buf);
mg_bzero((unsigned char *) tls, sizeof(*tls)); // Wipe keys and secrets
} }
mg_free(c->tls); mg_free(c->tls);
c->tls = NULL; c->tls = NULL;
+21 -16
View File
@@ -1,27 +1,32 @@
PROG ?= example # Program we are building REPO_ROOT ?= ../../..
DELETE = rm -rf # Command to remove files PROG ?= example
OUT ?= -o $(PROG) # Compiler argument for output file DELETE = rm -rf
SOURCES = main.c mongoose.c mongoose_mqtt.c packed_fs.c # Source code files, packed_fs.c contains ca.pem, which contains CA certs for TLS OUT ?= -o $(PROG)
CFLAGS = -W -Wall -Wextra -g -I. # Build options CFLAGS = -W -Wall -Wextra -g -I. -I$(REPO_ROOT)
SOURCES = main.c mongoose_mqtt.c $(REPO_ROOT)/mongoose.c
# Mongoose build options. See https://mongoose.ws/docs/getting-started/build-options/ # Mongoose build options. See https://mongoose.ws/docs/getting-started/build-options/
CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN CFLAGS_MONGOOSE += -DMG_TLS=MG_TLS_BUILTIN
CFLAGS_MONGOOSE += -DMG_ENABLE_LINES=1 CFLAGS_MONGOOSE += -DMG_ENABLE_LINES=1
ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe # This is for Azure - it just cannot Chacha20
PROG ?= example.exe # Use .exe suffix for the binary CFLAGS_MONGOOSE += -DMG_ENABLE_CHACHA20=0
CC = gcc # Use MinGW gcc compiler
CFLAGS += -lws2_32 # Link against Winsock library # Windows settings. Assume MinGW compiler.
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files # To use VS compiler, run: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
OUT ?= -o $(PROG) # Build output ifeq ($(OS),Windows_NT)
MAKE += WINDOWS=1 CC=$(CC) PROG ?= example.exe
CC = gcc
CFLAGS += -lws2_32
DELETE = cmd /C del /Q /F /S
OUT ?= -o $(PROG)
endif endif
all: $(PROG) # Default target. Build and run program all: $(PROG)
$(RUN) ./$(PROG) $(ARGS) $(RUN) ./$(PROG) $(ARGS)
$(PROG): $(SOURCES) # Build program from sources $(PROG): $(SOURCES)
$(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT) $(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT)
clean: # Cleanup. Delete built program and all build artifacts clean:
$(DELETE) $(PROG) *.o *.obj *.exe *.dSYM mbedtls $(DELETE) $(PROG) *.o *.obj *.exe *.dSYM mbedtls
+95 -1
View File
@@ -1 +1,95 @@
See detailed tutorial at https://mongoose.ws/tutorials/mqtt/mqtt-client/ # Mongoose MQTT client
This MQTT client implementation implements the following:
- Connects to the MQTT server specified by MQTT_SERVER_URL
- When connected, subscribes to the topic MQTT_SUBSCRIBE_TOPIC
- When it receives a message, echoes it back to MQTT_PUBLISH_TOPIC
- Timer-based reconnection logic revives the connection when it is down
- Ping server periodically. When disconnected, a last will is published
- Implements "ota.update" for OTA updates, see https://mongoose.ws/mqtt/
By default, it uses HiveMQ public broker, and can be tested with
the [HiveMQ Websocket Client](https://www.hivemq.com/demos/websocket-client/):
- Subscribe to `mg/123/#`
- Send a message to `mg/123/rx`
## Integrating into an embedded project
1. Copy `mongoose_mqtt.c` to your embedded project and add it to the build
2. Add `mg_mqtt_init(&mgr)` after `mg_mgr_init()`
3. Add `mg_mqtt_poll(&mgr)` after `mg_mgr_poll()`
## Microsoft Azure IoT Hub
1. Create IoT Hub
2. Generate device self-signed certificates:
```sh
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -pkeyopt ec_param_enc:named_curve -nodes -keyout device.key -out device.crt -days 3650 -subj "/CN=MYDEVICE"
```
3. Show certificate thumbprint:
```sh
openssl x509 -in device.crt -noout -fingerprint -sha256 | tr -d ':' | cut -d= -f2
```
4. Register a device with the thumbprint from above, and set the following:
```c
#define AZURE_HUB_NAME "HUB_NAME" // Change this
#define AZURE_DEVICE_ID "DEVICE_ID" // Change this
// Do not change this
#define MQTT_SERVER_URL "mqtts://" AZURE_HUB_NAME ".device.azure-devices.net"
#define MQTT_CLIENT_ID AZURE_DEVICE_ID
#define MQTT_USER AZURE_HUB_NAME ".azure-devices.net/" AZURE_DEVICE_ID "/?api-version=2021-04-12"
#define MQTT_PUBLISH_TOPIC "devices/" AZURE_DEVICE_ID "/messages/events/"
#define MQTT_SUBSCRIBE_TOPIC "devices/" AZURE_DEVICE_ID "/messages/devicebound/#"
```
5. Set `TLS_CA`. Visit https://mongoose.ws/tls/, enter "HUB_NAME.device.azure-devices.nett:8883" into the CA field and click on the "Get CA Certificate" button. Enable the "Show as C/C++ constant", copy-paste to your code.
6. Set `TLS_KEY` and `TLS_CRT` with the output of this commands:
```sh
sed 's/\r$//; s/._/ "&\\n"/; $!s/$/ \\/' device.key
sed 's/\r$//; s/._/ "&\\n"/; $!s/$/ \\/' device.crt
```
## Microsoft Azure Event Grid
1. Create Azure Event Grid (EG) instance
2. Generate device self-signed certificates:
```sh
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -pkeyopt ec_param_enc:named_curve -nodes -keyout device.key -out device.crt -days 3650 -subj "/CN=MYDEVICE"
```
3. Show certificate thumbprint:
```sh
openssl x509 -in device.crt -noout -fingerprint -sha256 | tr -d ':' | cut -d= -f2
```
4. Register a client on EG: choose a name, and use the thumbprint from above
5. Set URL, username and client ID:
```c
#define MQTT_SERVER_URL "mqtts://INSTANCE.REGION.ts.eventgrid.azure.net:8883"
#define MQTT_CLIENT_ID "CLIENT_NAME"
#define MQTT_USER "CLIENT_NAME"
```
6. Set `TLS_CA`. Visit https://mongoose.ws/tls/, enter "INSTANCE.REGION.ts.eventgrid.azure.net:8883" into the CA field and click on the "Get CA Certificate" button. Enable the "Show as C/C++ constant", copy-paste to your code.
7. Set `TLS_KEY` and `TLS_CRT` with the output of this commands:
```sh
sed 's/\r$//; s/._/ "&\\n"/; $!s/$/ \\/' device.key
sed 's/\r$//; s/._/ "&\\n"/; $!s/$/ \\/' device.crt
```
8. In Azure EG, go to Instance / MQTT Broker / Topic spaces, add "space1" with `mg/#` pattern
9. In Azure EG, go to Instance / MQTT Broker / Permissions bindings, add binding1 and binding2:
- $all , space1 , Publisher
- $all , space1 , Subscriber
-1
View File
@@ -1 +0,0 @@
../../../mongoose.c
-1
View File
@@ -1 +0,0 @@
../../../mongoose.h
+49 -30
View File
@@ -1,24 +1,22 @@
// Copyright (c) 2026 Cesanta Software Limited // Copyright (c) 2026 Cesanta Software Limited
// All rights reserved // All rights reserved
//
// Example MQTT client. It performs the following steps:
// 1. Connects to the MQTT server specified by MQTT_SERVER_URL
// 2. When connected, subscribes to the topic MQTT_SUBSCRIBE_TOPIC
// 3. When it receives a message, echoes it back to MQTT_PUBLISH_TOPIC
// 4. Timer-based reconnection logic revives the connection when it is down
// 5. Ping server periodically. When disconnected, a last will is published
// 6. Implements "ota.update" for OTA updates, see https://mongoose.ws/mqtt/
#include "mongoose.h" #include "mongoose.h"
#define MQTT_SERVER_URL "mqtt://broker.hivemq.com:1883" #define MQTT_SERVER_URL "mqtt://broker.hivemq.com:1883"
#define MQTT_PUBLISH_TOPIC "mg/123/tx" #define MQTT_CLIENT_ID "d3"
#define MQTT_SUBSCRIBE_TOPIC "mg/123/rx" #define MQTT_USER MQTT_CLIENT_ID
#define MQTT_PASS ""
#define MQTT_PUBLISH_TOPIC "mg/" MQTT_CLIENT_ID "/tx"
#define MQTT_SUBSCRIBE_TOPIC "mg/" MQTT_CLIENT_ID "/rx"
#define MQTT_QOS 1 #define MQTT_QOS 1
#define RECONNECT_PERIOD_MS 3000 #define MQTT_RECONNECT_PERIOD_MS 3000
#define TLS_CA ""
#define TLS_KEY ""
#define TLS_CRT ""
static struct mg_connection *s_mqtt_conn; // Client connection static struct mg_connection *s_mqtt_conn; // Client connection
static struct mg_rpc *s_rpc = NULL; // List of registered RPC methods
static void subscribe(struct mg_connection *c, struct mg_str topic) { static void subscribe(struct mg_connection *c, struct mg_str topic) {
struct mg_mqtt_opts opts = {}; struct mg_mqtt_opts opts = {};
@@ -69,7 +67,9 @@ static void mqtt_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
// c->is_hexdumping = 1; // c->is_hexdumping = 1;
} else if (ev == MG_EV_CONNECT) { } else if (ev == MG_EV_CONNECT) {
if (c->is_tls) { if (c->is_tls) {
struct mg_tls_opts opts = {.ca = mg_unpacked("/certs/ca.pem"), struct mg_tls_opts opts = {.ca = mg_str(TLS_CA),
.cert = mg_str(TLS_CRT),
.key = mg_str(TLS_KEY),
.name = mg_url_host(MQTT_SERVER_URL)}; .name = mg_url_host(MQTT_SERVER_URL)};
mg_tls_init(c, &opts); mg_tls_init(c, &opts);
} }
@@ -77,16 +77,31 @@ static void mqtt_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
// On error, log error message // On error, log error message
MG_ERROR(("%lu ERROR %s", c->id, (char *) ev_data)); MG_ERROR(("%lu ERROR %s", c->id, (char *) ev_data));
} else if (ev == MG_EV_MQTT_OPEN) { } else if (ev == MG_EV_MQTT_OPEN) {
// MQTT connect is successful int status = *(int *) ev_data;
MG_DEBUG(("%lu CONNECTED to %s", c->id, MQTT_SERVER_URL)); MG_DEBUG(("%lu CONNECT status: %d", c->id, status));
subscribe(c, mg_str(MQTT_SUBSCRIBE_TOPIC)); if (status == 0) {
subscribe(c, mg_str(MQTT_SUBSCRIBE_TOPIC));
if (mg_match(mg_str(MQTT_SERVER_URL), mg_str("#azure-devices.net"), 0)) {
// This is Azure IoT Hub. Subscribe for DPS messages
subscribe(c, mg_str("$iothub/methods/POST/#"));
}
}
} else if (ev == MG_EV_MQTT_MSG) { } else if (ev == MG_EV_MQTT_MSG) {
// When we get echo response, print it // When we get echo response, print it
char response[100];
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data; struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
mg_snprintf(response, sizeof(response), "Got %.*s -> %.*s", mm->topic.len, struct mg_str caps[5]; // caps[0] = method name, caps[2] = request id
mm->topic.buf, mm->data.len, mm->data.buf); if (mg_match(mm->topic, mg_str("$iothub/methods/POST/*/?$rid=*"), caps)) {
publish(c, mg_str(MQTT_PUBLISH_TOPIC), mg_str(response)); // Azure direct method call. Construct a stub response, "{}"
char topic[128];
mg_snprintf(topic, sizeof(topic), "$iothub/methods/res/%d/?$rid=%.*s",
200, (int) caps[2].len, caps[2].buf);
publish(c, mg_str(topic), mg_str("{}"));
} else {
char response[100];
mg_snprintf(response, sizeof(response), "Got %.*s -> %.*s", mm->topic.len,
mm->topic.buf, mm->data.len, mm->data.buf);
publish(c, mg_str(MQTT_PUBLISH_TOPIC), mg_str(response));
}
} else if (ev == MG_EV_MQTT_CMD) { } else if (ev == MG_EV_MQTT_CMD) {
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data; struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
if (mm->cmd == MQTT_CMD_PINGREQ) mg_mqtt_pong(c); if (mm->cmd == MQTT_CMD_PINGREQ) mg_mqtt_pong(c);
@@ -97,9 +112,7 @@ static void mqtt_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
} }
void mg_mqtt_init(struct mg_mgr *mgr) { void mg_mqtt_init(struct mg_mgr *mgr) {
(void) mgr; mg_rpc_add(&mgr->rpcs, mg_str("ota.update"), rpc_ota_update, NULL);
if (!s_rpc) mg_rpc_add(&s_rpc, mg_str("ota.update"), rpc_ota_update, NULL);
mg_mem_files = mg_packed_files; // Use generated packed filesystem
} }
void mg_mqtt_poll(struct mg_mgr *mgr) { void mg_mqtt_poll(struct mg_mgr *mgr) {
@@ -107,18 +120,24 @@ void mg_mqtt_poll(struct mg_mgr *mgr) {
// Reconnect if connection is closed, and send MQTT PINGs to keep // Reconnect if connection is closed, and send MQTT PINGs to keep
// the connection alive or to detect connection loss // the connection alive or to detect connection loss
if (mg_timer_expired(&timer, RECONNECT_PERIOD_MS, mg_now())) { if (mg_timer_expired(&timer, MQTT_RECONNECT_PERIOD_MS, mg_now())) {
if (s_mqtt_conn == NULL) { if (s_mqtt_conn == NULL) {
struct mg_mqtt_opts opts = {.clean = true, struct mg_mqtt_opts opts = {
.qos = MQTT_QOS, .clean = true,
.topic = mg_str(MQTT_PUBLISH_TOPIC), // .client_id = mg_str(MQTT_CLIENT_ID),
.keepalive = 5, .user = mg_str(MQTT_USER),
.version = 4, .pass = mg_str(MQTT_PASS),
.message = mg_str("bye")}; .qos = MQTT_QOS,
.keepalive = 5,
.version = 4, // MQTT 3.1.1
.topic = mg_str(MQTT_PUBLISH_TOPIC),
.message = mg_str("bye"),
};
s_mqtt_conn = s_mqtt_conn =
mg_mqtt_connect(mgr, MQTT_SERVER_URL, &opts, mqtt_ev_handler, NULL); mg_mqtt_connect(mgr, MQTT_SERVER_URL, &opts, mqtt_ev_handler, NULL);
} else { } else {
mg_mqtt_ping(s_mqtt_conn); mg_mqtt_ping(s_mqtt_conn);
// publish(s_mqtt_conn, mg_str(MQTT_PUBLISH_TOPIC), mg_str("hi"));
} }
} }
} }
-1
View File
@@ -1 +0,0 @@
../../http/http-client/packed_fs.c