diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 4d8a207b..f660bebb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -156,8 +156,6 @@ jobs: exclude: - ssl: MBEDTLS select: -DMG_ENABLE_POLL=0 - - ssl: MBEDTLS # see issue#2694 - select: -DMG_ENABLE_POLL=1 - ssl: OPENSSL select: -DMG_ENABLE_POLL=0 - ssl: WOLFSSL diff --git a/mongoose.c b/mongoose.c index 1cdd2745..70d24351 100644 --- a/mongoose.c +++ b/mongoose.c @@ -20202,6 +20202,11 @@ void mg_tls_handshake(struct mg_connection *c) { struct mg_tls *tls = (struct mg_tls *) c->tls; int rc = mbedtls_ssl_handshake(&tls->ssl); if (rc == 0) { // Success + if (tls->check_name && (mbedtls_ssl_get_verify_result(&tls->ssl) & + MBEDTLS_X509_BADCERT_CN_MISMATCH)) { + mg_error(c, "failed to verify hostname"); + return; + } // ignore MBEDTLS_X509_BADCERT_NOT_TRUSTED, no CA cert given MG_DEBUG(("%lu success", c->id)); c->is_tls_hs = 0; mg_call(c, MG_EV_TLS_HS, NULL); @@ -20223,6 +20228,7 @@ static void debug_cb(void *c, int lev, const char *s, int n, const char *s2) { void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { struct mg_tls *tls = (struct mg_tls *) mg_calloc(1, sizeof(*tls)); int rc = 0; + bool check_name = false; c->tls = tls; if (c->tls == NULL) { mg_error(c, "TLS OOM"); @@ -20253,24 +20259,30 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { mbedtls_ssl_conf_rng(&tls->conf, mg_mbed_rng, c); #endif + if (c->is_client && opts->name.buf != NULL && opts->name.len > 0 && + opts->name.buf[0] != '\0') { + char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf); + mbedtls_ssl_set_hostname(&tls->ssl, host); +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < 0x03030000 || \ + MBEDTLS_VERSION_NUMBER >= 0x04000000 + // NOTE: MBEDTLS_SSL_VERIFY_OPTIONAL is not supported for TLS1.3 on client + // side See https://github.com/Mbed-TLS/mbedtls/issues/7075 + check_name = true; +#endif + MG_DEBUG(("%lu hostname verification: %s", c->id, host)); + mg_free(host); + } else { + MG_DEBUG(("%lu skipping hostname verification", c->id)); + mbedtls_ssl_set_hostname(&tls->ssl, NULL); + } if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) { - // NOTE: MBEDTLS_SSL_VERIFY_NONE is not supported for TLS1.3 on client side - // See https://github.com/Mbed-TLS/mbedtls/issues/7075 - mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE); + mbedtls_ssl_conf_authmode(&tls->conf, check_name // see set comment above + ? MBEDTLS_SSL_VERIFY_OPTIONAL + : MBEDTLS_SSL_VERIFY_NONE); + tls->check_name = check_name; // host name set but no CA cert given } else { if (mg_load_cert(opts->ca, &tls->ca) == false) goto fail; mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL); - if (c->is_client) { - if (opts->name.buf != NULL && opts->name.buf[0] != '\0') { - char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf); - mbedtls_ssl_set_hostname(&tls->ssl, host); - MG_DEBUG(("%lu hostname verification: %s", c->id, host)); - mg_free(host); - } else { - MG_DEBUG(("%lu skipping hostname verification", c->id)); - mbedtls_ssl_set_hostname(&tls->ssl, NULL); - } - } mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED); } if (!mg_load_cert(opts->cert, &tls->cert)) goto fail; @@ -20552,6 +20564,7 @@ void mg_tls_free(struct mg_connection *c) { SSL_free(tls->ssl); SSL_CTX_free(tls->ctx); BIO_meth_free(tls->bm); + mg_free(tls->name); mg_free(tls); c->tls = NULL; } @@ -20559,6 +20572,7 @@ void mg_tls_free(struct mg_connection *c) { void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { struct mg_tls *tls = (struct mg_tls *) mg_calloc(1, sizeof(*tls)); const char *id = "mongoose"; + bool check_name = false; static unsigned char s_initialised = 0; BIO *bio = NULL; int rc; @@ -20600,6 +20614,14 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE); #endif +if (c->is_client && opts->name.buf != NULL && opts->name.len > 0 && opts->name.buf[0] != '\0') { + tls->name = mg_mprintf("%.*s", (int) opts->name.len, opts->name.buf); + if (tls->name == NULL) { + mg_error(c, "TLS OOM"); + goto fail; + } + check_name = true; +} #if MG_TLS == MG_TLS_WOLFSSL && !defined(OPENSSL_COMPATIBLE_DEFAULTS) if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) { // Older versions require that either the CA is loaded or SSL_VERIFY_NONE @@ -20608,7 +20630,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { } #endif - if (opts->ca.buf != NULL && opts->ca.buf[0] != '\0') { + if (opts->ca.buf != NULL && opts->ca.len > 0 && opts->ca.buf[0] != '\0') { SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); #if MG_TLS == MG_TLS_WOLFSSL @@ -20630,6 +20652,8 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { goto fail; } #endif + } else { + tls->check_name = check_name; // host name set but no CA cert given } if (opts->cert.buf != NULL && opts->cert.buf[0] != '\0') { @@ -20654,15 +20678,13 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { (void) SSL_set_ecdh_auto(tls->ssl, 1); #endif #if OPENSSL_VERSION_NUMBER >= 0x10100000L - if (opts->name.len > 0) { - char *s = mg_mprintf("%.*s", (int) opts->name.len, opts->name.buf); + if (tls->name != NULL) { #if MG_TLS != MG_TLS_WOLFSSL || LIBWOLFSSL_VERSION_HEX >= 0x05005002 - SSL_set1_host(tls->ssl, s); + SSL_set1_host(tls->ssl, tls->name); #else - X509_VERIFY_PARAM_set1_host(SSL_get0_param(tls->ssl), s, 0); + X509_VERIFY_PARAM_set1_host(SSL_get0_param(tls->ssl), tls->name, 0); #endif - SSL_set_tlsext_host_name(tls->ssl, s); - mg_free(s); + SSL_set_tlsext_host_name(tls->ssl, tls->name); } #endif #if MG_TLS == MG_TLS_WOLFSSL @@ -20690,6 +20712,16 @@ void mg_tls_handshake(struct mg_connection *c) { struct mg_tls *tls = (struct mg_tls *) c->tls; int rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl); if (rc == 1) { + if (tls->check_name) { // host name set but no CA cert given + X509 *cert = SSL_get_peer_certificate(tls->ssl); + bool ok = + cert != NULL && X509_check_host(cert, tls->name, 0, 0, NULL) == 1; + X509_free(cert); + if (!ok) { + mg_error(c, "failed to verify hostname"); + return; + } + } MG_DEBUG(("%lu success", c->id)); c->is_tls_hs = 0; mg_call(c, MG_EV_TLS_HS, NULL); diff --git a/mongoose.h b/mongoose.h index bf3eb537..30c3a0db 100644 --- a/mongoose.h +++ b/mongoose.h @@ -3507,13 +3507,14 @@ struct mg_tls { mbedtls_x509_crt cert; // Parsed certificate mbedtls_pk_context pk; // Private key context mbedtls_ssl_context ssl; // SSL/TLS context - mbedtls_ssl_config conf; // SSL-TLS config + mbedtls_ssl_config conf; // SSL/TLS config #ifdef MBEDTLS_SSL_SESSION_TICKETS mbedtls_ssl_ticket_context ticket; // Session tickets context #endif // https://github.com/Mbed-TLS/mbedtls/blob/3b3c652d/include/mbedtls/ssl.h#L5071C18-L5076C29 unsigned char *throttled_buf; // see #3074 size_t throttled_len; + bool check_name; // set when hostname was set, but no CA certificate given }; #endif @@ -3522,11 +3523,14 @@ struct mg_tls { #include #include +#include struct mg_tls { BIO_METHOD *bm; SSL_CTX *ctx; SSL *ssl; + char *name; // matching hostname + bool check_name; // set when hostname was set, but no CA certificate given }; #endif diff --git a/src/tls_mbed.c b/src/tls_mbed.c index b7929bc5..0a156f8f 100644 --- a/src/tls_mbed.c +++ b/src/tls_mbed.c @@ -92,6 +92,11 @@ void mg_tls_handshake(struct mg_connection *c) { struct mg_tls *tls = (struct mg_tls *) c->tls; int rc = mbedtls_ssl_handshake(&tls->ssl); if (rc == 0) { // Success + if (tls->check_name && (mbedtls_ssl_get_verify_result(&tls->ssl) & + MBEDTLS_X509_BADCERT_CN_MISMATCH)) { + mg_error(c, "failed to verify hostname"); + return; + } // ignore MBEDTLS_X509_BADCERT_NOT_TRUSTED, no CA cert given MG_DEBUG(("%lu success", c->id)); c->is_tls_hs = 0; mg_call(c, MG_EV_TLS_HS, NULL); @@ -113,6 +118,7 @@ static void debug_cb(void *c, int lev, const char *s, int n, const char *s2) { void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { struct mg_tls *tls = (struct mg_tls *) mg_calloc(1, sizeof(*tls)); int rc = 0; + bool check_name = false; c->tls = tls; if (c->tls == NULL) { mg_error(c, "TLS OOM"); @@ -143,24 +149,30 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { mbedtls_ssl_conf_rng(&tls->conf, mg_mbed_rng, c); #endif + if (c->is_client && opts->name.buf != NULL && opts->name.len > 0 && + opts->name.buf[0] != '\0') { + char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf); + mbedtls_ssl_set_hostname(&tls->ssl, host); +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < 0x03030000 || \ + MBEDTLS_VERSION_NUMBER >= 0x04000000 + // NOTE: MBEDTLS_SSL_VERIFY_OPTIONAL is not supported for TLS1.3 on client + // side See https://github.com/Mbed-TLS/mbedtls/issues/7075 + check_name = true; +#endif + MG_DEBUG(("%lu hostname verification: %s", c->id, host)); + mg_free(host); + } else { + MG_DEBUG(("%lu skipping hostname verification", c->id)); + mbedtls_ssl_set_hostname(&tls->ssl, NULL); + } if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) { - // NOTE: MBEDTLS_SSL_VERIFY_NONE is not supported for TLS1.3 on client side - // See https://github.com/Mbed-TLS/mbedtls/issues/7075 - mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE); + mbedtls_ssl_conf_authmode(&tls->conf, check_name // see set comment above + ? MBEDTLS_SSL_VERIFY_OPTIONAL + : MBEDTLS_SSL_VERIFY_NONE); + tls->check_name = check_name; // host name set but no CA cert given } else { if (mg_load_cert(opts->ca, &tls->ca) == false) goto fail; mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL); - if (c->is_client) { - if (opts->name.buf != NULL && opts->name.buf[0] != '\0') { - char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf); - mbedtls_ssl_set_hostname(&tls->ssl, host); - MG_DEBUG(("%lu hostname verification: %s", c->id, host)); - mg_free(host); - } else { - MG_DEBUG(("%lu skipping hostname verification", c->id)); - mbedtls_ssl_set_hostname(&tls->ssl, NULL); - } - } mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED); } if (!mg_load_cert(opts->cert, &tls->cert)) goto fail; diff --git a/src/tls_mbed.h b/src/tls_mbed.h index e7bce2ae..b29e7bdc 100644 --- a/src/tls_mbed.h +++ b/src/tls_mbed.h @@ -24,12 +24,13 @@ struct mg_tls { mbedtls_x509_crt cert; // Parsed certificate mbedtls_pk_context pk; // Private key context mbedtls_ssl_context ssl; // SSL/TLS context - mbedtls_ssl_config conf; // SSL-TLS config + mbedtls_ssl_config conf; // SSL/TLS config #ifdef MBEDTLS_SSL_SESSION_TICKETS mbedtls_ssl_ticket_context ticket; // Session tickets context #endif // https://github.com/Mbed-TLS/mbedtls/blob/3b3c652d/include/mbedtls/ssl.h#L5071C18-L5076C29 unsigned char *throttled_buf; // see #3074 size_t throttled_len; + bool check_name; // set when hostname was set, but no CA certificate given }; #endif diff --git a/src/tls_openssl.c b/src/tls_openssl.c index 658074da..e868872c 100644 --- a/src/tls_openssl.c +++ b/src/tls_openssl.c @@ -141,6 +141,7 @@ void mg_tls_free(struct mg_connection *c) { SSL_free(tls->ssl); SSL_CTX_free(tls->ctx); BIO_meth_free(tls->bm); + mg_free(tls->name); mg_free(tls); c->tls = NULL; } @@ -148,6 +149,7 @@ void mg_tls_free(struct mg_connection *c) { void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { struct mg_tls *tls = (struct mg_tls *) mg_calloc(1, sizeof(*tls)); const char *id = "mongoose"; + bool check_name = false; static unsigned char s_initialised = 0; BIO *bio = NULL; int rc; @@ -189,6 +191,14 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE); #endif +if (c->is_client && opts->name.buf != NULL && opts->name.len > 0 && opts->name.buf[0] != '\0') { + tls->name = mg_mprintf("%.*s", (int) opts->name.len, opts->name.buf); + if (tls->name == NULL) { + mg_error(c, "TLS OOM"); + goto fail; + } + check_name = true; +} #if MG_TLS == MG_TLS_WOLFSSL && !defined(OPENSSL_COMPATIBLE_DEFAULTS) if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) { // Older versions require that either the CA is loaded or SSL_VERIFY_NONE @@ -197,7 +207,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { } #endif - if (opts->ca.buf != NULL && opts->ca.buf[0] != '\0') { + if (opts->ca.buf != NULL && opts->ca.len > 0 && opts->ca.buf[0] != '\0') { SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); #if MG_TLS == MG_TLS_WOLFSSL @@ -219,6 +229,8 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { goto fail; } #endif + } else { + tls->check_name = check_name; // host name set but no CA cert given } if (opts->cert.buf != NULL && opts->cert.buf[0] != '\0') { @@ -243,15 +255,13 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { (void) SSL_set_ecdh_auto(tls->ssl, 1); #endif #if OPENSSL_VERSION_NUMBER >= 0x10100000L - if (opts->name.len > 0) { - char *s = mg_mprintf("%.*s", (int) opts->name.len, opts->name.buf); + if (tls->name != NULL) { #if MG_TLS != MG_TLS_WOLFSSL || LIBWOLFSSL_VERSION_HEX >= 0x05005002 - SSL_set1_host(tls->ssl, s); + SSL_set1_host(tls->ssl, tls->name); #else - X509_VERIFY_PARAM_set1_host(SSL_get0_param(tls->ssl), s, 0); + X509_VERIFY_PARAM_set1_host(SSL_get0_param(tls->ssl), tls->name, 0); #endif - SSL_set_tlsext_host_name(tls->ssl, s); - mg_free(s); + SSL_set_tlsext_host_name(tls->ssl, tls->name); } #endif #if MG_TLS == MG_TLS_WOLFSSL @@ -279,6 +289,16 @@ void mg_tls_handshake(struct mg_connection *c) { struct mg_tls *tls = (struct mg_tls *) c->tls; int rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl); if (rc == 1) { + if (tls->check_name) { // host name set but no CA cert given + X509 *cert = SSL_get_peer_certificate(tls->ssl); + bool ok = + cert != NULL && X509_check_host(cert, tls->name, 0, 0, NULL) == 1; + X509_free(cert); + if (!ok) { + mg_error(c, "failed to verify hostname"); + return; + } + } MG_DEBUG(("%lu success", c->id)); c->is_tls_hs = 0; mg_call(c, MG_EV_TLS_HS, NULL); diff --git a/src/tls_openssl.h b/src/tls_openssl.h index 44b57dc6..32c9079b 100644 --- a/src/tls_openssl.h +++ b/src/tls_openssl.h @@ -4,10 +4,13 @@ #include #include +#include struct mg_tls { BIO_METHOD *bm; SSL_CTX *ctx; SSL *ssl; + char *name; // matching hostname + bool check_name; // set when hostname was set, but no CA certificate given }; #endif diff --git a/test/unit_test.c b/test/unit_test.c index 68be4666..c5f3d9b5 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1612,9 +1612,8 @@ static void test_http_client(void) { opts.name = mg_url_host(url); // Test empty CA - // Disable mbedTLS: https://github.com/Mbed-TLS/mbedtls/issues/7075 -#if MG_TLS != MG_TLS_MBED opts.ca = mg_str(""); + opts.name = mg_url_host(url); c = mg_http_connect(&mgr, url, f3, &ok); mg_tls_init(c, &opts); ok = 0; @@ -1622,7 +1621,21 @@ static void test_http_client(void) { MG_INFO(("OK: %d", ok)); ASSERT(ok == 200); mg_mgr_poll(&mgr, 1); + // Make host validation fail + c = mg_http_connect(&mgr, url, f3, &ok); + ASSERT(c != NULL); + opts.name = mg_str("dummy"); // Set some invalid hostname value + mg_tls_init(c, &opts); + ok = 0; + for (i = 0; i < 500 && ok <= 0; i++) mg_mgr_poll(&mgr, 10); + MG_INFO(("OK: %d", ok)); +#if MG_TLS != MG_TLS_MBED || !defined(MBEDTLS_VERSION_NUMBER) || \ + MBEDTLS_VERSION_NUMBER < 0x03030000 || \ + MBEDTLS_VERSION_NUMBER >= 0x04000000 + // Ignore result for 3.3.0 <= mbedTLS < 4.0.0 : https://github.com/Mbed-TLS/mbedtls/issues/7075 + ASSERT(ok == 777); #endif + mg_mgr_poll(&mgr, 1); #endif #if MG_ENABLE_IPV6