Repository: godotengine/godot (language: cpp)

Issue:
[4.4 beta 1] TLS Handshake Error with Godot HTTPRequest
### Tested versions

4.4 beta 1 


### System information

Windows 11 - Godot 4.4 beta 1 - Vulkan (Mobile)

### Issue description

I’m encountering a TLS handshake error when trying to make a request to https://graph.oculus.com/ using Godot's HTTPRequest.

* Requests to https://www.google.com/ work without any issues.
* Python's requests library can access the URL without any problems.
* The same https://graph.oculus.com/ URL works correctly in Postman.
* I attempted to create a certificate bundle as well, but it was unsuccessful.
* This issue appears to be specific to Godot.

CPP ERROR:

E 0:00:01:0639   StreamPeerMbedTLS::_do_handshake: TLS handshake error: -28800
  <C++ Source>   modules\mbedtls\stream_peer_mbedtls.cpp:88 @ StreamPeerMbedTLS::_do_handshake()

### Steps to reproduce

* Create simple project
* Add a HTTPRequest node
* Add script:
```
extends Node3D

@onready var http: HTTPRequest = $HTTPRequest

func _ready() -> void:
	http.request("https://graph.oculus.com/")
	print(await http.request_completed)
```
* Run


### Minimal reproduction project (MRP)

[teste-vr.zip](https://github.com/user-attachments/files/18505403/teste-vr.zip)


Discussion / hints:
I am not in the right area right now, but try running a TLS analyzer on https://graph.oculus.com and https://www.google.com/ and see what's different.
Hello @fire , below is the analysis I performed using the tool: [SSLLabs](https://www.ssllabs.com/). The following key details were observed:

---

#### **TLS Version Support**
- **Google**: Supports TLS versions starting from **TLS 1.0**.
- **Meta**: Supports TLS versions starting from **TLS 1.2**.

---

#### **Key Usage**
- **Google**: Utilizes an **RSA key** of **2048 bits** for its SSL certificate.
- **Meta**: Utilizes an **EC key** of **256 bits** for its SSL certificate.

---

#### **Cipher Suites**

**Google supports the following Cipher Suites:**

- **TLS 1.3 (No server preference)**
  - TLS_AES_128_GCM_SHA256 (0x1301): ECDH x25519 (eq. 3072 bits RSA), FS, 128-bit
  - TLS_AES_256_GCM_SHA384 (0x1302): ECDH x25519 (eq. 3072 bits RSA), FS, 256-bit
  - TLS_CHACHA20_POLY1305_SHA256 (0x1303): ECDH x25519 (eq. 3072 bits RSA), FS, 256-bit

- **TLS 1.2 (Server-preferred order)**
  - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b): ECDH x25519, FS, 128-bit
  - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca9): ECDH x25519, FS, 256-bit
  - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c): ECDH x25519, FS, 256-bit
  - Other suites (WEAK):
    - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013): FS, 128-bit
    - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014): FS, 256-bit
    - TLS_RSA_WITH_AES_128_GCM_SHA256 (0x9c): WEAK, 128-bit
    - TLS_RSA_WITH_AES_256_GCM_SHA384 (0x9d): WEAK, 256-bit

---

**Meta supports the following Cipher Suites:**

- **TLS 1.3 (Server-preferred order)**
  - TLS_AES_128_GCM_SHA256 (0x1301): ECDH x25519, FS, 128-bit
  - TLS_CHACHA20_POLY1305_SHA256 (0x1303): ECDH x25519, FS, 256-bit
  - TLS_AES_256_GCM_SHA384 (0x1302): ECDH x25519, FS, 256-bit

- **TLS 1.2 (Server-preferred order)**
  - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f): ECDH secp256r1, FS, 128-bit
  - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030): ECDH secp256r1, FS, 256-bit
  - Other suites (WEAK):
    - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013): FS, 128-bit
    - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014): FS, 256-bit

---

I can confirm the issue (on Debian 12), the error (-28800, i.e. -0x7080) is `MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE`, so it seems we are either hitting a limitation of our build flags, or this is an issue with MBEDTLS not falling back to TLS 1.2 when a 1.3 is not available (e.g., TLS Handshake record layer fragmentation).
Indeed, running in verbose mode I get:

```
thirdparty/mbedtls/library/ssl_tls13_generic.c:1552: Perform PSA-based ECDH/FFDH computation.
thirdparty/mbedtls/library/ssl_tls13_client.c:1949: Switch to handshake keys for inbound traffic
thirdparty/mbedtls/library/ssl_msg.c:5070: Ignore ChangeCipherSpec in TLS 1.3 compatibility mode
thirdparty/mbedtls/library/ssl_msg.c:3297: TLS handshake fragmentation not supported
thirdparty/mbedtls/library/ssl_msg.c:4244: mbedtls_ssl_handle_message_type() returned -28800 (-0x7080)
thirdparty/mbedtls/library/ssl_tls13_client.c:2372: mbedtls_ssl_read_record() returned -28800 (-0x7080)
```

So my suspects on TLS Handshake record layer fragmentation being the cause seems to be correct. Will investigate if we can try to fallback to TLS 1.2 manually in that scenario.
This appears to be a regression:
- Reproducible in 4.4.dev3 and later, all the way to current `master` (296de7da830fa03425edf544427543087817e649)
- Not reproducible in 4.3.stable, 4.4.dev1 and 4.4.dev2

So it was introduced in 4.4.dev3. Most likely from #96394 as @Faless already identified.
For reference, this is the mbedTLS issue -> https://github.com/Mbed-TLS/mbedtls/issues/1840
And this https://github.com/Mbed-TLS/mbedtls/pull/9872 seems to be the most up to date PR implementing the missing feature (sadly the 3.x backport is not yet provided).

I also have a branch implementing "retrying" the TLS connection using 1.2 in that scenario ( https://github.com/Faless/godot/tree/tls/fallback_to_tls12 ) but it's very hacky, and I don't particularly like the solution tbh (it forces us to keep track of a bunch of settings to re-attempt the TCP reconnection).

I've been thinking about an alternative adding a global setting, and a TLSOption one, to force the maximum version of TLS to 1.2 instead, and let the devs handle this if necessary until mbedTLS implements the feature (hopefully soon?).

Fix patch:
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index b27665548f4b..79bf8975411b 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -2204,6 +2204,10 @@
 			The CA certificates bundle to use for TLS connections. If this is set to a non-empty value, this will [i]override[/i] Godot's default [url=https://github.com/godotengine/godot/blob/master/thirdparty/certs/ca-certificates.crt]Mozilla certificate bundle[/url]. If left empty, the default certificate bundle will be used.
 			If in doubt, leave this setting empty.
 		</member>
+		<member name="network/tls/enable_tls_v1.3" type="bool" setter="" getter="" default="false">
+			If [code]true[/code], enable TLSv1.3 negotiation.
+			[b]Note:[/b] This is experimental, and may cause connections to fail in some cases (notably, if the remote server uses TLS handshake fragmentation).
+		</member>
 		<member name="physics/2d/default_angular_damp" type="float" setter="" getter="" default="1.0">
 			The default rotational motion damping in 2D. Damping is used to gradually slow down physical objects over time. RigidBodies will fall back to this value when combining their own damping values and no area damping value is present.
 			Suggested values are in the range [code]0[/code] to [code]30[/code]. At value [code]0[/code] objects will keep moving with the same velocity. Greater values will stop the object faster. A value equal to or greater than the physics tick rate ([member physics/common/physics_ticks_per_second]) will bring the object to a stop in one iteration.
diff --git a/modules/mbedtls/register_types.cpp b/modules/mbedtls/register_types.cpp
index bf65dfb0b76a..c0eca6ee18e0 100644
--- a/modules/mbedtls/register_types.cpp
+++ b/modules/mbedtls/register_types.cpp
@@ -35,6 +35,8 @@
 #include "packet_peer_mbed_dtls.h"
 #include "stream_peer_mbedtls.h"
 
+#include "core/config/project_settings.h"
+
 #if MBEDTLS_VERSION_MAJOR >= 3
 #include <psa/crypto.h>
 #endif
@@ -50,6 +52,8 @@ void initialize_mbedtls_module(ModuleInitializationLevel p_level) {
 		return;
 	}
 
+	GLOBAL_DEF("network/tls/enable_tls_v1.3", false);
+
 #if MBEDTLS_VERSION_MAJOR >= 3
 	int status = psa_crypto_init();
 	ERR_FAIL_COND_MSG(status != PSA_SUCCESS, "Failed to initialize psa crypto. The mbedTLS modules will not work.");
diff --git a/modules/mbedtls/tls_context_mbedtls.cpp b/modules/mbedtls/tls_context_mbedtls.cpp
index f5c196596eb2..33c4cfd5450e 100644
--- a/modules/mbedtls/tls_context_mbedtls.cpp
+++ b/modules/mbedtls/tls_context_mbedtls.cpp
@@ -30,6 +30,8 @@
 
 #include "tls_context_mbedtls.h"
 
+#include "core/config/project_settings.h"
+
 static void my_debug(void *ctx, int level,
 		const char *file, int line,
 		const char *str) {
@@ -144,6 +146,11 @@ Error TLSContextMbedTLS::init_server(int p_transport, Ref<TLSOptions> p_options,
 		cookies = p_cookies;
 		mbedtls_ssl_conf_dtls_cookies(&conf, mbedtls_ssl_cookie_write, mbedtls_ssl_cookie_check, &(cookies->cookie_ctx));
 	}
+
+	if (Engine::get_singleton()->is_editor_hint() || !(bool)GLOBAL_GET("network/tls/enable_tls_v1.3")) {
+		mbedtls_ssl_conf_max_tls_version(&conf, MBEDTLS_SSL_VERSION_TLS1_2);
+	}
+
 	mbedtls_ssl_setup(&tls, &conf);
 	return OK;
 }
@@ -187,6 +194,10 @@ Error TLSContextMbedTLS::init_client(int p_transport, const String &p_hostname,
 		}
 	}
 
+	if (Engine::get_singleton()->is_editor_hint() || !(bool)GLOBAL_GET("network/tls/enable_tls_v1.3")) {
+		mbedtls_ssl_conf_max_tls_version(&conf, MBEDTLS_SSL_VERSION_TLS1_2);
+	}
+
 	// Set valid CAs
 	mbedtls_ssl_conf_ca_chain(&conf, &(cas->cert), nullptr);
 	mbedtls_ssl_setup(&tls, &conf);


Test patch: