From adba870534bdcdd11f0f344e66090be8e2cd9ae4 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Fri, 20 Jan 2023 01:51:35 +0100 Subject: [PATCH] [NET] Refactor TLS configuration. Use a TLSOptions configuration object which is created via static functions. - "TLSOptions.client": uses the standard CA and common name verification. - "TLSOptions.client_unsafe": uses optional CA verification (i.e. if specified) - "TLSOptions.server": is the standard server configuration (chain + key) This will allow us to expand the TLS configuration options to include e.g. mutual authentication without bloating the classes that uses StreamPeerTLS and PacketPeerDTLS as underlying peers. --- core/crypto/crypto.cpp | 39 ++++++++++++++ core/crypto/crypto.h | 34 ++++++++++++ core/io/dtls_server.cpp | 2 +- core/io/dtls_server.h | 2 +- core/io/http_client.cpp | 2 +- core/io/http_client.h | 3 +- core/io/http_client_tcp.cpp | 32 +++++------ core/io/http_client_tcp.h | 7 +-- core/io/packet_peer_dtls.cpp | 2 +- core/io/packet_peer_dtls.h | 2 +- core/io/stream_peer_tls.cpp | 4 +- core/io/stream_peer_tls.h | 4 +- core/register_core_types.cpp | 1 + doc/classes/DTLSServer.xml | 6 +-- doc/classes/HTTPClient.xml | 7 +-- doc/classes/HTTPRequest.xml | 17 +++--- doc/classes/PacketPeerDTLS.xml | 7 ++- doc/classes/StreamPeerTLS.xml | 14 ++--- doc/classes/TLSOptions.xml | 53 +++++++++++++++++++ modules/enet/doc_classes/ENetConnection.xml | 12 ++--- modules/enet/enet_connection.cpp | 14 ++--- modules/enet/enet_connection.h | 4 +- modules/mbedtls/dtls_server_mbedtls.cpp | 20 +++---- modules/mbedtls/dtls_server_mbedtls.h | 8 ++- modules/mbedtls/packet_peer_mbed_dtls.cpp | 16 +++--- modules/mbedtls/packet_peer_mbed_dtls.h | 4 +- modules/mbedtls/stream_peer_mbedtls.cpp | 14 +++-- modules/mbedtls/stream_peer_mbedtls.h | 4 +- modules/mbedtls/tls_context_mbedtls.cpp | 47 ++++++++++------ modules/mbedtls/tls_context_mbedtls.h | 10 ++-- .../doc_classes/WebSocketMultiplayerPeer.xml | 10 ++-- .../websocket/doc_classes/WebSocketPeer.xml | 5 +- modules/websocket/emws_peer.cpp | 6 +-- modules/websocket/emws_peer.h | 2 +- .../websocket/websocket_multiplayer_peer.cpp | 27 ++++------ .../websocket/websocket_multiplayer_peer.h | 8 ++- modules/websocket/websocket_peer.cpp | 2 +- modules/websocket/websocket_peer.h | 2 +- modules/websocket/wsl_peer.cpp | 14 +++-- modules/websocket/wsl_peer.h | 5 +- platform/web/export/editor_http_server.h | 2 +- platform/web/http_client_web.cpp | 10 ++-- platform/web/http_client_web.h | 2 +- scene/main/http_request.cpp | 21 +++++--- scene/main/http_request.h | 8 +-- thirdparty/enet/enet/godot_ext.h | 4 +- thirdparty/enet/godot.cpp | 22 ++++---- 47 files changed, 338 insertions(+), 203 deletions(-) create mode 100644 doc/classes/TLSOptions.xml diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp index cbb18a277f9..939c1c298fb 100644 --- a/core/crypto/crypto.cpp +++ b/core/crypto/crypto.cpp @@ -65,6 +65,45 @@ void X509Certificate::_bind_methods() { ClassDB::bind_method(D_METHOD("load", "path"), &X509Certificate::load); } +/// TLSOptions + +Ref TLSOptions::client(Ref p_trusted_chain, const String &p_common_name_override) { + Ref opts; + opts.instantiate(); + opts->trusted_ca_chain = p_trusted_chain; + opts->common_name = p_common_name_override; + opts->verify_mode = TLS_VERIFY_FULL; + return opts; +} + +Ref TLSOptions::client_unsafe(Ref p_trusted_chain) { + Ref opts; + opts.instantiate(); + opts->trusted_ca_chain = p_trusted_chain; + if (p_trusted_chain.is_null()) { + opts->verify_mode = TLS_VERIFY_NONE; + } else { + opts->verify_mode = TLS_VERIFY_CERT; + } + return opts; +} + +Ref TLSOptions::server(Ref p_own_key, Ref p_own_certificate) { + Ref opts; + opts.instantiate(); + opts->server_mode = true; + opts->own_certificate = p_own_certificate; + opts->private_key = p_own_key; + opts->verify_mode = TLS_VERIFY_NONE; + return opts; +} + +void TLSOptions::_bind_methods() { + ClassDB::bind_static_method("TLSOptions", D_METHOD("client", "trusted_chain", "common_name_override"), &TLSOptions::client, DEFVAL(Ref()), DEFVAL(String())); + ClassDB::bind_static_method("TLSOptions", D_METHOD("client_unsafe", "trusted_chain"), &TLSOptions::client_unsafe, DEFVAL(Ref())); + ClassDB::bind_static_method("TLSOptions", D_METHOD("server", "key", "certificate"), &TLSOptions::server); +} + /// HMACContext void HMACContext::_bind_methods() { diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h index 981f67883c6..999fe076d60 100644 --- a/core/crypto/crypto.h +++ b/core/crypto/crypto.h @@ -67,6 +67,40 @@ public: virtual Error save(String p_path) = 0; }; +class TLSOptions : public RefCounted { + GDCLASS(TLSOptions, RefCounted); + +public: + enum TLSVerifyMode { + TLS_VERIFY_NONE = 0, + TLS_VERIFY_CERT = 1, + TLS_VERIFY_FULL = 2, + }; + +private: + bool server_mode = false; + String common_name; + TLSVerifyMode verify_mode = TLS_VERIFY_FULL; + Ref trusted_ca_chain; + Ref own_certificate; + Ref private_key; + +protected: + static void _bind_methods(); + +public: + static Ref client(Ref p_trusted_chain = Ref(), const String &p_common_name_override = String()); + static Ref client_unsafe(Ref p_trusted_chain); + static Ref server(Ref p_own_key, Ref p_own_certificate); + + TLSVerifyMode get_verify_mode() const { return verify_mode; } + String get_common_name() const { return common_name; } + Ref get_trusted_ca_chain() const { return trusted_ca_chain; } + Ref get_own_certificate() const { return own_certificate; } + Ref get_private_key() const { return private_key; } + bool is_server() const { return server_mode; } +}; + class HMACContext : public RefCounted { GDCLASS(HMACContext, RefCounted); diff --git a/core/io/dtls_server.cpp b/core/io/dtls_server.cpp index c542e394a11..07d62d3a8d4 100644 --- a/core/io/dtls_server.cpp +++ b/core/io/dtls_server.cpp @@ -48,6 +48,6 @@ bool DTLSServer::is_available() { } void DTLSServer::_bind_methods() { - ClassDB::bind_method(D_METHOD("setup", "key", "certificate", "chain"), &DTLSServer::setup, DEFVAL(Ref())); + ClassDB::bind_method(D_METHOD("setup", "server_options"), &DTLSServer::setup); ClassDB::bind_method(D_METHOD("take_connection", "udp_peer"), &DTLSServer::take_connection); } diff --git a/core/io/dtls_server.h b/core/io/dtls_server.h index e749e6968b2..f3fbde3c152 100644 --- a/core/io/dtls_server.h +++ b/core/io/dtls_server.h @@ -47,7 +47,7 @@ public: static bool is_available(); static DTLSServer *create(); - virtual Error setup(Ref p_key, Ref p_cert, Ref p_ca_chain = Ref()) = 0; + virtual Error setup(Ref p_options) = 0; virtual void stop() = 0; virtual Ref take_connection(Ref p_peer) = 0; diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index 829abdc6140..190edbfb82d 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -138,7 +138,7 @@ PackedStringArray HTTPClient::_get_response_headers() { } void HTTPClient::_bind_methods() { - ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port", "use_tls", "verify_host"), &HTTPClient::connect_to_host, DEFVAL(-1), DEFVAL(false), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port", "tls_options"), &HTTPClient::connect_to_host, DEFVAL(-1), DEFVAL(Ref())); ClassDB::bind_method(D_METHOD("set_connection", "connection"), &HTTPClient::set_connection); ClassDB::bind_method(D_METHOD("get_connection"), &HTTPClient::get_connection); ClassDB::bind_method(D_METHOD("request_raw", "method", "url", "headers", "body"), &HTTPClient::_request_raw); diff --git a/core/io/http_client.h b/core/io/http_client.h index 853ea7f472f..9e018182e37 100644 --- a/core/io/http_client.h +++ b/core/io/http_client.h @@ -31,6 +31,7 @@ #ifndef HTTP_CLIENT_H #define HTTP_CLIENT_H +#include "core/crypto/crypto.h" #include "core/io/ip.h" #include "core/io/stream_peer.h" #include "core/io/stream_peer_tcp.h" @@ -168,7 +169,7 @@ public: Error verify_headers(const Vector &p_headers); virtual Error request(Method p_method, const String &p_url, const Vector &p_headers, const uint8_t *p_body, int p_body_size) = 0; - virtual Error connect_to_host(const String &p_host, int p_port = -1, bool p_tls = false, bool p_verify_host = true) = 0; + virtual Error connect_to_host(const String &p_host, int p_port = -1, Ref p_tls_options = Ref()) = 0; virtual void set_connection(const Ref &p_connection) = 0; virtual Ref get_connection() const = 0; diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp index 5cdb13fa06e..a36c59d11ff 100644 --- a/core/io/http_client_tcp.cpp +++ b/core/io/http_client_tcp.cpp @@ -39,29 +39,31 @@ HTTPClient *HTTPClientTCP::_create_func() { return memnew(HTTPClientTCP); } -Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_tls, bool p_verify_host) { +Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, Ref p_options) { close(); conn_port = p_port; conn_host = p_host; + tls_options = p_options; ip_candidates.clear(); - tls = p_tls; - tls_verify_host = p_verify_host; - String host_lower = conn_host.to_lower(); if (host_lower.begins_with("http://")) { conn_host = conn_host.substr(7, conn_host.length() - 7); + tls_options.unref(); } else if (host_lower.begins_with("https://")) { - tls = true; + if (tls_options.is_null()) { + tls_options = TLSOptions::client(); + } conn_host = conn_host.substr(8, conn_host.length() - 8); } + ERR_FAIL_COND_V(tls_options.is_valid() && tls_options->is_server(), ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER); if (conn_port < 0) { - if (tls) { + if (tls_options.is_valid()) { conn_port = PORT_HTTPS; } else { conn_port = PORT_HTTP; @@ -70,11 +72,11 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_tl connection = tcp_connection; - if (tls && https_proxy_port != -1) { + if (tls_options.is_valid() && https_proxy_port != -1) { proxy_client.instantiate(); // Needs proxy negotiation. server_host = https_proxy_host; server_port = https_proxy_port; - } else if (!tls && http_proxy_port != -1) { + } else if (tls_options.is_null() && http_proxy_port != -1) { server_host = http_proxy_host; server_port = http_proxy_port; } else { @@ -107,7 +109,7 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_tl void HTTPClientTCP::set_connection(const Ref &p_connection) { ERR_FAIL_COND_MSG(p_connection.is_null(), "Connection is not a reference to a valid StreamPeer object."); - if (tls) { + if (tls_options.is_valid()) { ERR_FAIL_NULL_MSG(Object::cast_to(p_connection.ptr()), "Connection is not a reference to a valid StreamPeerTLS object."); } @@ -156,7 +158,7 @@ Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector< } String uri = p_url; - if (!tls && http_proxy_port != -1) { + if (tls_options.is_null() && http_proxy_port != -1) { uri = vformat("http://%s:%d%s", conn_host, conn_port, p_url); } @@ -181,7 +183,7 @@ Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector< } } if (add_host) { - if ((tls && conn_port == PORT_HTTPS) || (!tls && conn_port == PORT_HTTP)) { + if ((tls_options.is_valid() && conn_port == PORT_HTTPS) || (tls_options.is_null() && conn_port == PORT_HTTP)) { // Don't append the standard ports. request += "Host: " + conn_host + "\r\n"; } else { @@ -316,7 +318,7 @@ Error HTTPClientTCP::poll() { return OK; } break; case StreamPeerTCP::STATUS_CONNECTED: { - if (tls && proxy_client.is_valid()) { + if (tls_options.is_valid() && proxy_client.is_valid()) { Error err = proxy_client->poll(); if (err == ERR_UNCONFIGURED) { proxy_client->set_connection(tcp_connection); @@ -357,13 +359,13 @@ Error HTTPClientTCP::poll() { return ERR_CANT_CONNECT; } break; } - } else if (tls) { + } else if (tls_options.is_valid()) { Ref tls_conn; if (!handshaking) { // Connect the StreamPeerTLS and start handshaking. tls_conn = Ref(StreamPeerTLS::create()); tls_conn->set_blocking_handshake_enabled(false); - Error err = tls_conn->connect_to_stream(tcp_connection, tls_verify_host, conn_host); + Error err = tls_conn->connect_to_stream(tcp_connection, conn_host, tls_options); if (err != OK) { close(); status = STATUS_TLS_HANDSHAKE_ERROR; @@ -421,7 +423,7 @@ Error HTTPClientTCP::poll() { case STATUS_BODY: case STATUS_CONNECTED: { // Check if we are still connected. - if (tls) { + if (tls_options.is_valid()) { Ref tmp = connection; tmp->poll(); if (tmp->get_status() != StreamPeerTLS::STATUS_CONNECTED) { diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h index 97ac2d76a7b..6060c975bcb 100644 --- a/core/io/http_client_tcp.h +++ b/core/io/http_client_tcp.h @@ -33,6 +33,8 @@ #include "http_client.h" +#include "core/crypto/crypto.h" + class HTTPClientTCP : public HTTPClient { private: Status status = STATUS_DISCONNECTED; @@ -46,11 +48,10 @@ private: String http_proxy_host; int https_proxy_port = -1; // Proxy server for https requests. String https_proxy_host; - bool tls = false; - bool tls_verify_host = false; bool blocking = false; bool handshaking = false; bool head_request = false; + Ref tls_options; Vector response_str; @@ -79,7 +80,7 @@ public: Error request(Method p_method, const String &p_url, const Vector &p_headers, const uint8_t *p_body, int p_body_size) override; - Error connect_to_host(const String &p_host, int p_port = -1, bool p_tls = false, bool p_verify_host = true) override; + Error connect_to_host(const String &p_host, int p_port = -1, Ref p_tls_options = Ref()) override; void set_connection(const Ref &p_connection) override; Ref get_connection() const override; void close() override; diff --git a/core/io/packet_peer_dtls.cpp b/core/io/packet_peer_dtls.cpp index c0998f10bc3..18bef3ff3cc 100644 --- a/core/io/packet_peer_dtls.cpp +++ b/core/io/packet_peer_dtls.cpp @@ -48,7 +48,7 @@ bool PacketPeerDTLS::is_available() { void PacketPeerDTLS::_bind_methods() { ClassDB::bind_method(D_METHOD("poll"), &PacketPeerDTLS::poll); - ClassDB::bind_method(D_METHOD("connect_to_peer", "packet_peer", "validate_certs", "for_hostname", "valid_certificate"), &PacketPeerDTLS::connect_to_peer, DEFVAL(true), DEFVAL(String()), DEFVAL(Ref())); + ClassDB::bind_method(D_METHOD("connect_to_peer", "packet_peer", "hostname", "client_options"), &PacketPeerDTLS::connect_to_peer, DEFVAL(Ref())); ClassDB::bind_method(D_METHOD("get_status"), &PacketPeerDTLS::get_status); ClassDB::bind_method(D_METHOD("disconnect_from_peer"), &PacketPeerDTLS::disconnect_from_peer); diff --git a/core/io/packet_peer_dtls.h b/core/io/packet_peer_dtls.h index 5ba1faed7c0..3990a851f74 100644 --- a/core/io/packet_peer_dtls.h +++ b/core/io/packet_peer_dtls.h @@ -53,7 +53,7 @@ public: }; virtual void poll() = 0; - virtual Error connect_to_peer(Ref p_base, bool p_validate_certs = true, const String &p_for_hostname = String(), Ref p_ca_certs = Ref()) = 0; + virtual Error connect_to_peer(Ref p_base, const String &p_hostname, Ref p_options = Ref()) = 0; virtual void disconnect_from_peer() = 0; virtual Status get_status() const = 0; diff --git a/core/io/stream_peer_tls.cpp b/core/io/stream_peer_tls.cpp index 71fadd1d306..2facf3e32e8 100644 --- a/core/io/stream_peer_tls.cpp +++ b/core/io/stream_peer_tls.cpp @@ -57,8 +57,8 @@ bool StreamPeerTLS::is_blocking_handshake_enabled() const { void StreamPeerTLS::_bind_methods() { ClassDB::bind_method(D_METHOD("poll"), &StreamPeerTLS::poll); - ClassDB::bind_method(D_METHOD("accept_stream", "stream", "private_key", "certificate", "chain"), &StreamPeerTLS::accept_stream, DEFVAL(Ref())); - ClassDB::bind_method(D_METHOD("connect_to_stream", "stream", "validate_certs", "for_hostname", "valid_certificate"), &StreamPeerTLS::connect_to_stream, DEFVAL(false), DEFVAL(String()), DEFVAL(Ref())); + ClassDB::bind_method(D_METHOD("accept_stream", "stream", "server_options"), &StreamPeerTLS::accept_stream); + ClassDB::bind_method(D_METHOD("connect_to_stream", "stream", "common_name", "client_options"), &StreamPeerTLS::connect_to_stream, DEFVAL(Ref())); ClassDB::bind_method(D_METHOD("get_status"), &StreamPeerTLS::get_status); ClassDB::bind_method(D_METHOD("get_stream"), &StreamPeerTLS::get_stream); ClassDB::bind_method(D_METHOD("disconnect_from_stream"), &StreamPeerTLS::disconnect_from_stream); diff --git a/core/io/stream_peer_tls.h b/core/io/stream_peer_tls.h index 6666107ad88..ff3ea2a7d13 100644 --- a/core/io/stream_peer_tls.h +++ b/core/io/stream_peer_tls.h @@ -58,8 +58,8 @@ public: bool is_blocking_handshake_enabled() const; virtual void poll() = 0; - virtual Error accept_stream(Ref p_base, Ref p_key, Ref p_cert, Ref p_ca_chain = Ref()) = 0; - virtual Error connect_to_stream(Ref p_base, bool p_validate_certs = false, const String &p_for_hostname = String(), Ref p_valid_cert = Ref()) = 0; + virtual Error accept_stream(Ref p_base, Ref p_options) = 0; + virtual Error connect_to_stream(Ref p_base, const String &p_common_name, Ref p_options) = 0; virtual Status get_status() const = 0; virtual Ref get_stream() const = 0; diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 700174bdae0..a374e7c0095 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -209,6 +209,7 @@ void register_core_types() { GDREGISTER_CLASS(AESContext); ClassDB::register_custom_instance_class(); ClassDB::register_custom_instance_class(); + GDREGISTER_ABSTRACT_CLASS(TLSOptions); ClassDB::register_custom_instance_class(); ClassDB::register_custom_instance_class(); ClassDB::register_custom_instance_class(); diff --git a/doc/classes/DTLSServer.xml b/doc/classes/DTLSServer.xml index 457513b8aa7..a3a0b0456cb 100644 --- a/doc/classes/DTLSServer.xml +++ b/doc/classes/DTLSServer.xml @@ -148,11 +148,9 @@ - - - + - Setup the DTLS server to use the given [param key] and provide the given [param certificate] to clients. You can pass the optional [param chain] parameter to provide additional CA chain information along with the certificate. + Setup the DTLS server to use the given [param server_options]. See [method TLSOptions.server]. diff --git a/doc/classes/HTTPClient.xml b/doc/classes/HTTPClient.xml index b3ed38d2500..b7a5cff6941 100644 --- a/doc/classes/HTTPClient.xml +++ b/doc/classes/HTTPClient.xml @@ -30,13 +30,10 @@ - - + Connects to a host. This needs to be done before any requests are sent. - The host should not have http:// prepended but will strip the protocol identifier if provided. - If no [param port] is specified (or [code]-1[/code] is used), it is automatically set to 80 for HTTP and 443 for HTTPS (if [param use_tls] is enabled). - [param verify_host] will check the TLS identity of the host if set to [code]true[/code]. + If no [param port] is specified (or [code]-1[/code] is used), it is automatically set to 80 for HTTP and 443 for HTTPS. You can pass the optional [param tls_options] parameter to customize the trusted certification authorities, or the common name verification when using HTTPS. See [method TLSOptions.client] and [method TLSOptions.client_unsafe]. diff --git a/doc/classes/HTTPRequest.xml b/doc/classes/HTTPRequest.xml index 3dbc024b14c..d403acf90c7 100644 --- a/doc/classes/HTTPRequest.xml +++ b/doc/classes/HTTPRequest.xml @@ -187,9 +187,8 @@ - - - + + Creates request on the underlying [HTTPClient]. If there is no configuration errors, it tries to connect using [method HTTPClient.connect_to_host] and passes parameters onto [method HTTPClient.request]. Returns [constant OK] if request is successfully created. (Does not imply that the server has responded), [constant ERR_UNCONFIGURED] if not in the tree, [constant ERR_BUSY] if still processing previous request, [constant ERR_INVALID_PARAMETER] if given string is not a valid URL format, or [constant ERR_CANT_CONNECT] if not using thread and the [HTTPClient] cannot connect to host. @@ -201,9 +200,8 @@ - - - + + Creates request on the underlying [HTTPClient] using a raw array of bytes for the request body. If there is no configuration errors, it tries to connect using [method HTTPClient.connect_to_host] and passes parameters onto [method HTTPClient.request]. Returns [constant OK] if request is successfully created. (Does not imply that the server has responded), [constant ERR_UNCONFIGURED] if not in the tree, [constant ERR_BUSY] if still processing previous request, [constant ERR_INVALID_PARAMETER] if given string is not a valid URL format, or [constant ERR_CANT_CONNECT] if not using thread and the [HTTPClient] cannot connect to host. @@ -227,6 +225,13 @@ The proxy server is unset if [param host] is empty or [param port] is -1. + + + + + Sets the [TLSOptions] to be used when connecting to an HTTPS server. See [method TLSOptions.client]. + + diff --git a/doc/classes/PacketPeerDTLS.xml b/doc/classes/PacketPeerDTLS.xml index db8403a56b5..19c5d0e2878 100644 --- a/doc/classes/PacketPeerDTLS.xml +++ b/doc/classes/PacketPeerDTLS.xml @@ -14,11 +14,10 @@ - - - + + - Connects a [param packet_peer] beginning the DTLS handshake using the underlying [PacketPeerUDP] which must be connected (see [method PacketPeerUDP.connect_to_host]). If [param validate_certs] is [code]true[/code], [PacketPeerDTLS] will validate that the certificate presented by the remote peer and match it with the [param for_hostname] argument. You can specify a custom [X509Certificate] to use for validation via the [param valid_certificate] argument. + Connects a [param packet_peer] beginning the DTLS handshake using the underlying [PacketPeerUDP] which must be connected (see [method PacketPeerUDP.connect_to_host]). You can optionally specify the [param client_options] to be used while verifying the TLS connections. See [method TLSOptions.client] and [method TLSOptions.client_unsafe]. diff --git a/doc/classes/StreamPeerTLS.xml b/doc/classes/StreamPeerTLS.xml index d1ddb3d4415..df33baa9008 100644 --- a/doc/classes/StreamPeerTLS.xml +++ b/doc/classes/StreamPeerTLS.xml @@ -14,22 +14,18 @@ - - - + - Accepts a peer connection as a server using the given [param private_key] and providing the given [param certificate] to the client. You can pass the optional [param chain] parameter to provide additional CA chain information along with the certificate. + Accepts a peer connection as a server using the given [param server_options]. See [method TLSOptions.server]. - - - + + - Connects to a peer using an underlying [StreamPeer] [param stream]. If [param validate_certs] is [code]true[/code], [StreamPeerTLS] will validate that the certificate presented by the peer matches the [param for_hostname]. - [b]Note:[/b] Specifying a custom [param valid_certificate] is not supported in Web exports due to browsers restrictions. + Connects to a peer using an underlying [StreamPeer] [param stream] and verifying the remote certificate is correcly signed for the given [param common_name]. You can pass the optional [param client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe]. diff --git a/doc/classes/TLSOptions.xml b/doc/classes/TLSOptions.xml new file mode 100644 index 00000000000..0917bd9bce0 --- /dev/null +++ b/doc/classes/TLSOptions.xml @@ -0,0 +1,53 @@ + + + + TLS configuration for clients and servers. + + + TLSOptions abstracts the configuration options for the [StreamPeerTLS] and [PacketPeerDTLS] classes. + Objects of this class cannot be instantiated directly, and one of the static methods [method client], [method client_unsafe], or [method server] should be used instead. + [codeblocks] + [gdscript] + # Create a TLS client configuration which uses our custom trusted CA chain. + var client_trusted_cas = load("res://my_trusted_cas.crt") + var client_tls_options = TLSOptions.client(client_trusted_cas) + + # Create a TLS server configuration. + var server_certs = load("res://my_server_cas.crt") + var server_key = load("res://my_server_key.key") + var server_tls_options = TLSOptions.server(server_certs, server_key) + [/gdscript] + [/codeblocks] + + + + + + + + + + Creates a TLS client configuration which validates certificates and their common names (fully qualified domain names). + You can specify a custom [param trusted_chain] of certification authorities (the default CA list will be used if [code]null[/code]), and optionally provide a [param common_name_override] if you expect the certificate to have a common name other then the server FQDN. + Note: On the Web plafrom, TLS verification is always enforced against the CA list of the web browser. This is considered a security feature. + + + + + + + Creates an [b]unsafe[/b] TLS client configuration where certificate validation is optional. You can optionally provide a valid [param trusted_chain], but the common name of the certififcates will never be checked. Using this configuration for purposes other than testing [b]is not recommended[/b]. + Note: On the Web plafrom, TLS verification is always enforced against the CA list of the web browser. This is considered a security feature. + + + + + + + + Creates a TLS server configuration using the provided [param key] and [param certificate]. + Note: The [param certificate] should include the full certificate chain up to the signing CA (certificates file can be concatenated using a general purpose text editor). + + + + diff --git a/modules/enet/doc_classes/ENetConnection.xml b/modules/enet/doc_classes/ENetConnection.xml index 8c84fe87d79..dc832976d99 100644 --- a/modules/enet/doc_classes/ENetConnection.xml +++ b/modules/enet/doc_classes/ENetConnection.xml @@ -84,19 +84,17 @@ - - - + + - Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet clients. Call this before [method connect_to_host] to have ENet connect using DTLS with [code]certificate[/code] and [code]hostname[/code] verification. Verification can be optionally turned off via the [code]verify[/code] parameter. + Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet clients. Call this before [method connect_to_host] to have ENet connect using DTLS validating the server certificate against [code]hostname[/code]. You can pass the optional [param client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe]. - - + - Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet servers. Call this right after [method create_host_bound] to have ENet expect peers to connect using DTLS. + Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet servers. Call this right after [method create_host_bound] to have ENet expect peers to connect using DTLS. See [method TLSOptions.server]. diff --git a/modules/enet/enet_connection.cpp b/modules/enet/enet_connection.cpp index d16e7d7c4a7..804263186f4 100644 --- a/modules/enet/enet_connection.cpp +++ b/modules/enet/enet_connection.cpp @@ -273,10 +273,11 @@ TypedArray ENetConnection::_get_peers() { return out; } -Error ENetConnection::dtls_server_setup(Ref p_key, Ref p_cert) { +Error ENetConnection::dtls_server_setup(const Ref &p_options) { #ifdef GODOT_ENET ERR_FAIL_COND_V_MSG(!host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active."); - return enet_host_dtls_server_setup(host, p_key.ptr(), p_cert.ptr()) ? FAILED : OK; + ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER); + return enet_host_dtls_server_setup(host, const_cast(p_options.ptr())) ? FAILED : OK; #else ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "ENet DTLS support not available in this build."); #endif @@ -291,10 +292,11 @@ void ENetConnection::refuse_new_connections(bool p_refuse) { #endif } -Error ENetConnection::dtls_client_setup(Ref p_cert, const String &p_hostname, bool p_verify) { +Error ENetConnection::dtls_client_setup(const String &p_hostname, const Ref &p_options) { #ifdef GODOT_ENET ERR_FAIL_COND_V_MSG(!host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active."); - return enet_host_dtls_client_setup(host, p_cert.ptr(), p_verify, p_hostname.utf8().get_data()) ? FAILED : OK; + ERR_FAIL_COND_V(p_options.is_null() || p_options->is_server(), ERR_INVALID_PARAMETER); + return enet_host_dtls_client_setup(host, p_hostname.utf8().get_data(), const_cast(p_options.ptr())) ? FAILED : OK; #else ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "ENet DTLS support not available in this build."); #endif @@ -351,8 +353,8 @@ void ENetConnection::_bind_methods() { ClassDB::bind_method(D_METHOD("channel_limit", "limit"), &ENetConnection::channel_limit); ClassDB::bind_method(D_METHOD("broadcast", "channel", "packet", "flags"), &ENetConnection::_broadcast); ClassDB::bind_method(D_METHOD("compress", "mode"), &ENetConnection::compress); - ClassDB::bind_method(D_METHOD("dtls_server_setup", "key", "certificate"), &ENetConnection::dtls_server_setup); - ClassDB::bind_method(D_METHOD("dtls_client_setup", "certificate", "hostname", "verify"), &ENetConnection::dtls_client_setup, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("dtls_server_setup", "server_options"), &ENetConnection::dtls_server_setup); + ClassDB::bind_method(D_METHOD("dtls_client_setup", "hostname", "client_options"), &ENetConnection::dtls_client_setup, DEFVAL(Ref())); ClassDB::bind_method(D_METHOD("refuse_new_connections", "refuse"), &ENetConnection::refuse_new_connections); ClassDB::bind_method(D_METHOD("pop_statistic", "statistic"), &ENetConnection::pop_statistic); ClassDB::bind_method(D_METHOD("get_max_channels"), &ENetConnection::get_max_channels); diff --git a/modules/enet/enet_connection.h b/modules/enet/enet_connection.h index 9e444911cce..481afc48bb0 100644 --- a/modules/enet/enet_connection.h +++ b/modules/enet/enet_connection.h @@ -128,8 +128,8 @@ public: int get_local_port() const; // Godot additions - Error dtls_server_setup(Ref p_key, Ref p_cert); - Error dtls_client_setup(Ref p_cert, const String &p_hostname, bool p_verify = true); + Error dtls_server_setup(const Ref &p_options); + Error dtls_client_setup(const String &p_hostname, const Ref &p_options); void refuse_new_connections(bool p_refuse); ENetConnection() {} diff --git a/modules/mbedtls/dtls_server_mbedtls.cpp b/modules/mbedtls/dtls_server_mbedtls.cpp index c54ab8ef6ee..62513929eaa 100644 --- a/modules/mbedtls/dtls_server_mbedtls.cpp +++ b/modules/mbedtls/dtls_server_mbedtls.cpp @@ -31,25 +31,25 @@ #include "dtls_server_mbedtls.h" #include "packet_peer_mbed_dtls.h" -Error DTLSServerMbedTLS::setup(Ref p_key, Ref p_cert, Ref p_ca_chain) { - ERR_FAIL_COND_V(_cookies->setup() != OK, ERR_ALREADY_IN_USE); - _key = p_key; - _cert = p_cert; - _ca_chain = p_ca_chain; +Error DTLSServerMbedTLS::setup(Ref p_options) { + ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(cookies->setup() != OK, ERR_ALREADY_IN_USE); + tls_options = p_options; return OK; } void DTLSServerMbedTLS::stop() { - _cookies->clear(); + cookies->clear(); } Ref DTLSServerMbedTLS::take_connection(Ref p_udp_peer) { Ref out; - out.instantiate(); - ERR_FAIL_COND_V(!out.is_valid(), out); + ERR_FAIL_COND_V(tls_options.is_null(), out); ERR_FAIL_COND_V(!p_udp_peer.is_valid(), out); - out->accept_peer(p_udp_peer, _key, _cert, _ca_chain, _cookies); + + out.instantiate(); + out->accept_peer(p_udp_peer, tls_options, cookies); return out; } @@ -68,7 +68,7 @@ void DTLSServerMbedTLS::finalize() { } DTLSServerMbedTLS::DTLSServerMbedTLS() { - _cookies.instantiate(); + cookies.instantiate(); } DTLSServerMbedTLS::~DTLSServerMbedTLS() { diff --git a/modules/mbedtls/dtls_server_mbedtls.h b/modules/mbedtls/dtls_server_mbedtls.h index e4612d01ef0..d5841a45faa 100644 --- a/modules/mbedtls/dtls_server_mbedtls.h +++ b/modules/mbedtls/dtls_server_mbedtls.h @@ -37,16 +37,14 @@ class DTLSServerMbedTLS : public DTLSServer { private: static DTLSServer *_create_func(); - Ref _key; - Ref _cert; - Ref _ca_chain; - Ref _cookies; + Ref tls_options; + Ref cookies; public: static void initialize(); static void finalize(); - virtual Error setup(Ref p_key, Ref p_cert, Ref p_ca_chain = Ref()); + virtual Error setup(Ref p_options); virtual void stop(); virtual Ref take_connection(Ref p_peer); diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp index 16450e151e2..e8eb32f88d1 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.cpp +++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp @@ -114,16 +114,14 @@ Error PacketPeerMbedDTLS::_do_handshake() { return OK; } -Error PacketPeerMbedDTLS::connect_to_peer(Ref p_base, bool p_validate_certs, const String &p_for_hostname, Ref p_ca_certs) { +Error PacketPeerMbedDTLS::connect_to_peer(Ref p_base, const String &p_hostname, Ref p_options) { ERR_FAIL_COND_V(!p_base.is_valid() || !p_base->is_socket_connected(), ERR_INVALID_PARAMETER); - base = p_base; - int authmode = p_validate_certs ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE; - - Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_DATAGRAM, authmode, p_ca_certs); + Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_DATAGRAM, p_hostname, p_options.is_valid() ? p_options : TLSOptions::client()); ERR_FAIL_COND_V(err != OK, err); - mbedtls_ssl_set_hostname(tls_ctx->get_context(), p_for_hostname.utf8().get_data()); + base = p_base; + mbedtls_ssl_set_bio(tls_ctx->get_context(), this, bio_send, bio_recv, nullptr); mbedtls_ssl_set_timer_cb(tls_ctx->get_context(), &timer, mbedtls_timing_set_delay, mbedtls_timing_get_delay); @@ -137,8 +135,10 @@ Error PacketPeerMbedDTLS::connect_to_peer(Ref p_base, bool p_vali return OK; } -Error PacketPeerMbedDTLS::accept_peer(Ref p_base, Ref p_key, Ref p_cert, Ref p_ca_chain, Ref p_cookies) { - Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_DATAGRAM, MBEDTLS_SSL_VERIFY_NONE, p_key, p_cert, p_cookies); +Error PacketPeerMbedDTLS::accept_peer(Ref p_base, Ref p_options, Ref p_cookies) { + ERR_FAIL_COND_V(!p_base.is_valid() || !p_base->is_socket_connected(), ERR_INVALID_PARAMETER); + + Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_DATAGRAM, p_options, p_cookies); ERR_FAIL_COND_V(err != OK, err); base = p_base; diff --git a/modules/mbedtls/packet_peer_mbed_dtls.h b/modules/mbedtls/packet_peer_mbed_dtls.h index 744ef815246..05decec7833 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.h +++ b/modules/mbedtls/packet_peer_mbed_dtls.h @@ -64,8 +64,8 @@ protected: public: virtual void poll(); - virtual Error accept_peer(Ref p_base, Ref p_key, Ref p_cert = Ref(), Ref p_ca_chain = Ref(), Ref p_cookies = Ref()); - virtual Error connect_to_peer(Ref p_base, bool p_validate_certs = true, const String &p_for_hostname = String(), Ref p_ca_certs = Ref()); + virtual Error accept_peer(Ref p_base, Ref p_options, Ref p_cookies = Ref()); + virtual Error connect_to_peer(Ref p_base, const String &p_hostname, Ref p_options = Ref()); virtual Status get_status() const; virtual void disconnect_from_peer(); diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp index 1d17fb9441e..0ea426c8b58 100644 --- a/modules/mbedtls/stream_peer_mbedtls.cpp +++ b/modules/mbedtls/stream_peer_mbedtls.cpp @@ -102,16 +102,13 @@ Error StreamPeerMbedTLS::_do_handshake() { return OK; } -Error StreamPeerMbedTLS::connect_to_stream(Ref p_base, bool p_validate_certs, const String &p_for_hostname, Ref p_ca_certs) { +Error StreamPeerMbedTLS::connect_to_stream(Ref p_base, const String &p_common_name, Ref p_options) { ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER); - base = p_base; - int authmode = p_validate_certs ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE; - - Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_STREAM, authmode, p_ca_certs); + Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_STREAM, p_common_name, p_options.is_valid() ? p_options : TLSOptions::client()); ERR_FAIL_COND_V(err != OK, err); - mbedtls_ssl_set_hostname(tls_ctx->get_context(), p_for_hostname.utf8().get_data()); + base = p_base; mbedtls_ssl_set_bio(tls_ctx->get_context(), this, bio_send, bio_recv, nullptr); status = STATUS_HANDSHAKING; @@ -124,10 +121,11 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref p_base, bool p_valida return OK; } -Error StreamPeerMbedTLS::accept_stream(Ref p_base, Ref p_key, Ref p_cert, Ref p_ca_chain) { +Error StreamPeerMbedTLS::accept_stream(Ref p_base, Ref p_options) { ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER); - Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_VERIFY_NONE, p_key, p_cert); + Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_STREAM, p_options); ERR_FAIL_COND_V(err != OK, err); base = p_base; diff --git a/modules/mbedtls/stream_peer_mbedtls.h b/modules/mbedtls/stream_peer_mbedtls.h index 8a36a7ea9a2..ec0446c380c 100644 --- a/modules/mbedtls/stream_peer_mbedtls.h +++ b/modules/mbedtls/stream_peer_mbedtls.h @@ -54,8 +54,8 @@ protected: public: virtual void poll(); - virtual Error accept_stream(Ref p_base, Ref p_key, Ref p_cert, Ref p_ca_chain = Ref()); - virtual Error connect_to_stream(Ref p_base, bool p_validate_certs = false, const String &p_for_hostname = String(), Ref p_valid_cert = Ref()); + virtual Error accept_stream(Ref p_base, Ref p_options); + virtual Error connect_to_stream(Ref p_base, const String &p_common_name, Ref p_options); virtual Status get_status() const; virtual Ref get_stream() const; diff --git a/modules/mbedtls/tls_context_mbedtls.cpp b/modules/mbedtls/tls_context_mbedtls.cpp index a01137f262b..aab082f488d 100644 --- a/modules/mbedtls/tls_context_mbedtls.cpp +++ b/modules/mbedtls/tls_context_mbedtls.cpp @@ -110,22 +110,20 @@ Error TLSContextMbedTLS::_setup(int p_endpoint, int p_transport, int p_authmode) return OK; } -Error TLSContextMbedTLS::init_server(int p_transport, int p_authmode, Ref p_pkey, Ref p_cert, Ref p_cookies) { - ERR_FAIL_COND_V(!p_pkey.is_valid(), ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!p_cert.is_valid(), ERR_INVALID_PARAMETER); +Error TLSContextMbedTLS::init_server(int p_transport, Ref p_options, Ref p_cookies) { + ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER); - Error err = _setup(MBEDTLS_SSL_IS_SERVER, p_transport, p_authmode); + // Check key and certificate(s) + pkey = p_options->get_private_key(); + certs = p_options->get_own_certificate(); + ERR_FAIL_COND_V(pkey.is_null() || certs.is_null(), ERR_INVALID_PARAMETER); + + Error err = _setup(MBEDTLS_SSL_IS_SERVER, p_transport, MBEDTLS_SSL_VERIFY_NONE); // TODO client auth. ERR_FAIL_COND_V(err != OK, err); // Locking key and certificate(s) - pkey = p_pkey; - certs = p_cert; - if (pkey.is_valid()) { - pkey->lock(); - } - if (certs.is_valid()) { - certs->lock(); - } + pkey->lock(); + certs->lock(); // Adding key and certificate int ret = mbedtls_ssl_conf_own_cert(&conf, &(certs->cert), &(pkey->pkey)); @@ -150,15 +148,32 @@ Error TLSContextMbedTLS::init_server(int p_transport, int p_authmode, Ref p_valid_cas) { - Error err = _setup(MBEDTLS_SSL_IS_CLIENT, p_transport, p_authmode); +Error TLSContextMbedTLS::init_client(int p_transport, const String &p_hostname, Ref p_options) { + ERR_FAIL_COND_V(p_options.is_null() || p_options->is_server(), ERR_INVALID_PARAMETER); + + int authmode = MBEDTLS_SSL_VERIFY_REQUIRED; + if (p_options->get_verify_mode() == TLSOptions::TLS_VERIFY_NONE) { + authmode = MBEDTLS_SSL_VERIFY_NONE; + } + + Error err = _setup(MBEDTLS_SSL_IS_CLIENT, p_transport, authmode); ERR_FAIL_COND_V(err != OK, err); + if (p_options->get_verify_mode() == TLSOptions::TLS_VERIFY_FULL) { + String cn = p_options->get_common_name(); + if (cn.is_empty()) { + cn = p_hostname; + } + mbedtls_ssl_set_hostname(&tls, cn.utf8().get_data()); + } else { + mbedtls_ssl_set_hostname(&tls, nullptr); + } + X509CertificateMbedTLS *cas = nullptr; - if (p_valid_cas.is_valid()) { + if (p_options->get_trusted_ca_chain().is_valid()) { // Locking CA certificates - certs = p_valid_cas; + certs = p_options->get_trusted_ca_chain(); certs->lock(); cas = certs.ptr(); } else { diff --git a/modules/mbedtls/tls_context_mbedtls.h b/modules/mbedtls/tls_context_mbedtls.h index 574e80e1997..f1bad6a40c4 100644 --- a/modules/mbedtls/tls_context_mbedtls.h +++ b/modules/mbedtls/tls_context_mbedtls.h @@ -71,17 +71,17 @@ public: static void print_mbedtls_error(int p_ret); Ref certs; + Ref pkey; + Ref cookies; + mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; mbedtls_ssl_context tls; mbedtls_ssl_config conf; - Ref cookies; - Ref pkey; - Error _setup(int p_endpoint, int p_transport, int p_authmode); - Error init_server(int p_transport, int p_authmode, Ref p_pkey, Ref p_cert, Ref p_cookies = Ref()); - Error init_client(int p_transport, int p_authmode, Ref p_valid_cas); + Error init_server(int p_transport, Ref p_options, Ref p_cookies = Ref()); + Error init_client(int p_transport, const String &p_hostname, Ref p_options); void clear(); mbedtls_ssl_context *get_context(); diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml index 7e896a0ca39..aaeb2025ee1 100644 --- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml +++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml @@ -13,10 +13,9 @@ - - + - Starts a new multiplayer client connecting to the given [param url]. If [param verify_tls] is [code]false[/code] certificate validation will be disabled. If specified, the [param tls_certificate] will be used to verify the TLS host. + Starts a new multiplayer client connecting to the given [param url]. TLS certificates will be verified against the hostname when connecting using the [code]wss://[/code] protocol. You can pass the optional [param tls_client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe]. [b]Note[/b]: It is recommended to specify the scheme part of the URL, i.e. the [param url] should start with either [code]ws://[/code] or [code]wss://[/code]. @@ -24,10 +23,9 @@ - - + - Starts a new multiplayer server listening on the given [param port]. You can optionally specify a [param bind_address], and provide a [param tls_key] and [param tls_certificate] to use TLS. + Starts a new multiplayer server listening on the given [param port]. You can optionally specify a [param bind_address], and provide valiid [param tls_server_options] to use TLS. See [method TLSOptions.server]. diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml index 41d166a0f51..0f8c27c4ccd 100644 --- a/modules/websocket/doc_classes/WebSocketPeer.xml +++ b/modules/websocket/doc_classes/WebSocketPeer.xml @@ -58,10 +58,9 @@ - - + - Connects to the given URL. If [param verify_tls] is [code]false[/code] certificate validation will be disabled. If specified, the [param trusted_tls_certificate] will be the only one accepted when connecting to a TLS host. + Connects to the given URL. TLS certificates will be verified against the hostname when connecting using the [code]wss://[/code] protocol. You can pass the optional [param tls_client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe]. [b]Note:[/b] To avoid mixed content warnings or errors in Web, you may have to use a [code]url[/code] that starts with [code]wss://[/code] (secure) instead of [code]ws://[/code]. When doing so, make sure to use the fully qualified domain name that matches the one defined in the server's TLS certificate. Do not connect directly via the IP address for [code]wss://[/code] connections, as it won't match with the TLS certificate. diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index 1ec557427f7..7b14a3a61d1 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -58,7 +58,8 @@ void EMWSPeer::_esws_on_close(void *p_obj, int p_code, const char *p_reason, int peer->ready_state = STATE_CLOSED; } -Error EMWSPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref p_tls_certificate) { +Error EMWSPeer::connect_to_url(const String &p_url, Ref p_tls_options) { + ERR_FAIL_COND_V(p_tls_options.is_valid() && p_tls_options->is_server(), ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(ready_state != STATE_CLOSED, ERR_ALREADY_IN_USE); _clear(); @@ -85,9 +86,6 @@ Error EMWSPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref p_cert = Ref()) override; + virtual Error connect_to_url(const String &p_url, Ref p_tls_client_options) override; virtual Error accept_stream(Ref p_stream) override; virtual void close(int p_code = 1000, String p_reason = "") override; virtual void poll() override; diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index 36b4215f8c9..389d8c56ad9 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -54,11 +54,9 @@ void WebSocketMultiplayerPeer::_clear() { connection_status = CONNECTION_DISCONNECTED; unique_id = 0; peers_map.clear(); - use_tls = false; tcp_server.unref(); pending_peers.clear(); - tls_certificate.unref(); - tls_key.unref(); + tls_server_options.unref(); if (current_packet.data != nullptr) { memfree(current_packet.data); current_packet.data = nullptr; @@ -73,8 +71,8 @@ void WebSocketMultiplayerPeer::_clear() { } void WebSocketMultiplayerPeer::_bind_methods() { - ClassDB::bind_method(D_METHOD("create_client", "url", "verify_tls", "tls_certificate"), &WebSocketMultiplayerPeer::create_client, DEFVAL(true), DEFVAL(Ref())); - ClassDB::bind_method(D_METHOD("create_server", "port", "bind_address", "tls_key", "tls_certificate"), &WebSocketMultiplayerPeer::create_server, DEFVAL("*"), DEFVAL(Ref()), DEFVAL(Ref())); + ClassDB::bind_method(D_METHOD("create_client", "url", "tls_client_options"), &WebSocketMultiplayerPeer::create_client, DEFVAL(Ref())); + ClassDB::bind_method(D_METHOD("create_server", "port", "bind_address", "tls_server_options"), &WebSocketMultiplayerPeer::create_server, DEFVAL("*"), DEFVAL(Ref())); ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebSocketMultiplayerPeer::get_peer); ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &WebSocketMultiplayerPeer::get_peer_address); @@ -179,8 +177,9 @@ int WebSocketMultiplayerPeer::get_max_packet_size() const { return get_outbound_buffer_size() - PROTO_SIZE; } -Error WebSocketMultiplayerPeer::create_server(int p_port, IPAddress p_bind_ip, Ref p_tls_key, Ref p_tls_certificate) { +Error WebSocketMultiplayerPeer::create_server(int p_port, IPAddress p_bind_ip, Ref p_options) { ERR_FAIL_COND_V(get_connection_status() != CONNECTION_DISCONNECTED, ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(p_options.is_valid() && !p_options->is_server(), ERR_INVALID_PARAMETER); _clear(); tcp_server.instantiate(); Error err = tcp_server->listen(p_port, p_bind_ip); @@ -190,20 +189,16 @@ Error WebSocketMultiplayerPeer::create_server(int p_port, IPAddress p_bind_ip, R } unique_id = 1; connection_status = CONNECTION_CONNECTED; - // TLS config - tls_key = p_tls_key; - tls_certificate = p_tls_certificate; - if (tls_key.is_valid() && tls_certificate.is_valid()) { - use_tls = true; - } + tls_server_options = p_options; return OK; } -Error WebSocketMultiplayerPeer::create_client(const String &p_url, bool p_verify_tls, Ref p_tls_certificate) { +Error WebSocketMultiplayerPeer::create_client(const String &p_url, Ref p_options) { ERR_FAIL_COND_V(get_connection_status() != CONNECTION_DISCONNECTED, ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(p_options.is_valid() && p_options->is_server(), ERR_INVALID_PARAMETER); _clear(); Ref peer = _create_peer(); - Error err = peer->connect_to_url(p_url, p_verify_tls, p_tls_certificate); + Error err = peer->connect_to_url(p_url, p_options); if (err != OK) { return err; } @@ -334,14 +329,14 @@ void WebSocketMultiplayerPeer::_poll_server() { to_remove.insert(id); // Error. continue; } - if (!use_tls) { + if (tls_server_options.is_null()) { peer.ws = _create_peer(); peer.ws->accept_stream(peer.tcp); continue; } else { if (peer.connection == peer.tcp) { Ref tls = Ref(StreamPeerTLS::create()); - Error err = tls->accept_stream(peer.tcp, tls_key, tls_certificate); + Error err = tls->accept_stream(peer.tcp, tls_server_options); if (err != OK) { to_remove.insert(id); continue; diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h index ea10e8799f7..22f1bc939b6 100644 --- a/modules/websocket/websocket_multiplayer_peer.h +++ b/modules/websocket/websocket_multiplayer_peer.h @@ -71,9 +71,7 @@ protected: Ref peer_config; HashMap pending_peers; Ref tcp_server; - bool use_tls = false; - Ref tls_certificate; - Ref tls_key; + Ref tls_server_options; ConnectionStatus connection_status = CONNECTION_DISCONNECTED; @@ -115,8 +113,8 @@ public: /* WebSocketPeer */ virtual Ref get_peer(int p_peer_id) const; - Error create_client(const String &p_url, bool p_verify_tls, Ref p_tls_certificate); - Error create_server(int p_port, IPAddress p_bind_ip, Ref p_tls_key, Ref p_tls_certificate); + Error create_client(const String &p_url, Ref p_options); + Error create_server(int p_port, IPAddress p_bind_ip, Ref p_options); void set_supported_protocols(const Vector &p_protocols); Vector get_supported_protocols() const; diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp index d10315f64cd..3c0d316bc9d 100644 --- a/modules/websocket/websocket_peer.cpp +++ b/modules/websocket/websocket_peer.cpp @@ -39,7 +39,7 @@ WebSocketPeer::~WebSocketPeer() { } void WebSocketPeer::_bind_methods() { - ClassDB::bind_method(D_METHOD("connect_to_url", "url", "verify_tls", "trusted_tls_certificate"), &WebSocketPeer::connect_to_url, DEFVAL(true), DEFVAL(Ref())); + ClassDB::bind_method(D_METHOD("connect_to_url", "url", "tls_client_options"), &WebSocketPeer::connect_to_url, DEFVAL(Ref())); ClassDB::bind_method(D_METHOD("accept_stream", "stream"), &WebSocketPeer::accept_stream); ClassDB::bind_method(D_METHOD("send", "message", "write_mode"), &WebSocketPeer::_send_bind, DEFVAL(WRITE_MODE_BINARY)); ClassDB::bind_method(D_METHOD("send_text", "message"), &WebSocketPeer::send_text); diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h index 3a1527b769b..3110e87071c 100644 --- a/modules/websocket/websocket_peer.h +++ b/modules/websocket/websocket_peer.h @@ -81,7 +81,7 @@ public: return _create(); } - virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref p_cert = Ref()) { return ERR_UNAVAILABLE; }; + virtual Error connect_to_url(const String &p_url, Ref p_options = Ref()) = 0; virtual Error accept_stream(Ref p_stream) = 0; virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) = 0; diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 9ba286d5eee..6240f2eaeaf 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -334,7 +334,7 @@ void WSLPeer::_do_client_handshake() { tls = Ref(StreamPeerTLS::create()); ERR_FAIL_COND_MSG(tls.is_null(), "SSL is not available in this build."); tls->set_blocking_handshake_enabled(false); - if (tls->connect_to_stream(tcp, verify_tls, requested_host, tls_cert) != OK) { + if (tls->connect_to_stream(tcp, requested_host, tls_options) != OK) { close(-1); return; // Error. } @@ -476,9 +476,10 @@ bool WSLPeer::_verify_server_response() { return true; } -Error WSLPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref p_cert) { +Error WSLPeer::connect_to_url(const String &p_url, Ref p_options) { ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(p_url.is_empty(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_options.is_valid() && p_options->is_server(), ERR_INVALID_PARAMETER); _clear(); @@ -506,8 +507,13 @@ Error WSLPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref tls_cert; + Ref tls_options; // Packet buffers. Vector packet_buffer; @@ -132,7 +131,7 @@ public: // WebSocketPeer virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override; - virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref p_cert = Ref()) override; + virtual Error connect_to_url(const String &p_url, Ref p_options = Ref()) override; virtual Error accept_stream(Ref p_stream) override; virtual void close(int p_code = 1000, String p_reason = "") override; virtual void poll() override; diff --git a/platform/web/export/editor_http_server.h b/platform/web/export/editor_http_server.h index ce6b0be7138..80ff779949e 100644 --- a/platform/web/export/editor_http_server.h +++ b/platform/web/export/editor_http_server.h @@ -206,7 +206,7 @@ public: tls = Ref(StreamPeerTLS::create()); peer = tls; tls->set_blocking_handshake_enabled(false); - if (tls->accept_stream(tcp, key, cert) != OK) { + if (tls->accept_stream(tcp, TLSOptions::server(key, cert)) != OK) { _clear_client(); return; } diff --git a/platform/web/http_client_web.cpp b/platform/web/http_client_web.cpp index 31f54dad9f7..3e4ba5a2ae1 100644 --- a/platform/web/http_client_web.cpp +++ b/platform/web/http_client_web.cpp @@ -37,20 +37,20 @@ void HTTPClientWeb::_parse_headers(int p_len, const char **p_headers, void *p_re } } -Error HTTPClientWeb::connect_to_host(const String &p_host, int p_port, bool p_tls, bool p_verify_host) { +Error HTTPClientWeb::connect_to_host(const String &p_host, int p_port, Ref p_tls_options) { + ERR_FAIL_COND_V(p_tls_options.is_valid() && p_tls_options->is_server(), ERR_INVALID_PARAMETER); + close(); - if (p_tls && !p_verify_host) { - WARN_PRINT("Disabling HTTPClientWeb's host verification is not supported for the Web platform, host will be verified"); - } port = p_port; - use_tls = p_tls; + use_tls = p_tls_options.is_valid(); host = p_host; String host_lower = host.to_lower(); if (host_lower.begins_with("http://")) { host = host.substr(7, host.length() - 7); + use_tls = false; } else if (host_lower.begins_with("https://")) { use_tls = true; host = host.substr(8, host.length() - 8); diff --git a/platform/web/http_client_web.h b/platform/web/http_client_web.h index 993ec6c0e25..def7837a278 100644 --- a/platform/web/http_client_web.h +++ b/platform/web/http_client_web.h @@ -86,7 +86,7 @@ public: Error request(Method p_method, const String &p_url, const Vector &p_headers, const uint8_t *p_body, int p_body_size) override; - Error connect_to_host(const String &p_host, int p_port = -1, bool p_tls = false, bool p_verify_host = true) override; + Error connect_to_host(const String &p_host, int p_port = -1, Ref p_tls_options = Ref()) override; void set_connection(const Ref &p_connection) override; Ref get_connection() const override; void close() override; diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index 46ba7e67eb7..0d53f740db1 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -33,7 +33,7 @@ #include "scene/main/timer.h" Error HTTPRequest::_request() { - return client->connect_to_host(url, port, use_tls, validate_tls); + return client->connect_to_host(url, port, use_tls ? tls_options : nullptr); } Error HTTPRequest::_parse_url(const String &p_url) { @@ -96,7 +96,7 @@ String HTTPRequest::get_header_value(const PackedStringArray &p_headers, const S return value; } -Error HTTPRequest::request(const String &p_url, const Vector &p_custom_headers, bool p_tls_validate_domain, HTTPClient::Method p_method, const String &p_request_data) { +Error HTTPRequest::request(const String &p_url, const Vector &p_custom_headers, HTTPClient::Method p_method, const String &p_request_data) { // Copy the string into a raw buffer. Vector raw_data; @@ -108,10 +108,10 @@ Error HTTPRequest::request(const String &p_url, const Vector &p_custom_h memcpy(w, charstr.ptr(), len); } - return request_raw(p_url, p_custom_headers, p_tls_validate_domain, p_method, raw_data); + return request_raw(p_url, p_custom_headers, p_method, raw_data); } -Error HTTPRequest::request_raw(const String &p_url, const Vector &p_custom_headers, bool p_tls_validate_domain, HTTPClient::Method p_method, const Vector &p_request_data_raw) { +Error HTTPRequest::request_raw(const String &p_url, const Vector &p_custom_headers, HTTPClient::Method p_method, const Vector &p_request_data_raw) { ERR_FAIL_COND_V(!is_inside_tree(), ERR_UNCONFIGURED); ERR_FAIL_COND_V_MSG(requesting, ERR_BUSY, "HTTPRequest is processing a request. Wait for completion or cancel it before attempting a new one."); @@ -127,8 +127,6 @@ Error HTTPRequest::request_raw(const String &p_url, const Vector &p_cust return err; } - validate_tls = p_tls_validate_domain; - headers = p_custom_headers; if (accept_gzip) { @@ -590,10 +588,16 @@ void HTTPRequest::_timeout() { _defer_done(RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray()); } +void HTTPRequest::set_tls_options(const Ref &p_options) { + ERR_FAIL_COND(p_options.is_null() || p_options->is_server()); + tls_options = p_options; +} + void HTTPRequest::_bind_methods() { - ClassDB::bind_method(D_METHOD("request", "url", "custom_headers", "tls_validate_domain", "method", "request_data"), &HTTPRequest::request, DEFVAL(PackedStringArray()), DEFVAL(true), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(String())); - ClassDB::bind_method(D_METHOD("request_raw", "url", "custom_headers", "tls_validate_domain", "method", "request_data_raw"), &HTTPRequest::request_raw, DEFVAL(PackedStringArray()), DEFVAL(true), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(PackedByteArray())); + ClassDB::bind_method(D_METHOD("request", "url", "custom_headers", "method", "request_data"), &HTTPRequest::request, DEFVAL(PackedStringArray()), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(String())); + ClassDB::bind_method(D_METHOD("request_raw", "url", "custom_headers", "method", "request_data_raw"), &HTTPRequest::request_raw, DEFVAL(PackedStringArray()), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(PackedByteArray())); ClassDB::bind_method(D_METHOD("cancel_request"), &HTTPRequest::cancel_request); + ClassDB::bind_method(D_METHOD("set_tls_options", "client_options"), &HTTPRequest::set_tls_options); ClassDB::bind_method(D_METHOD("get_http_client_status"), &HTTPRequest::get_http_client_status); @@ -654,6 +658,7 @@ void HTTPRequest::_bind_methods() { HTTPRequest::HTTPRequest() { client = Ref(HTTPClient::create()); + tls_options = TLSOptions::client(); timer = memnew(Timer); timer->set_one_shot(true); timer->connect("timeout", callable_mp(this, &HTTPRequest::_timeout)); diff --git a/scene/main/http_request.h b/scene/main/http_request.h index add4e9538d7..9a91171eafd 100644 --- a/scene/main/http_request.h +++ b/scene/main/http_request.h @@ -68,8 +68,8 @@ private: String url; int port = 80; Vector headers; - bool validate_tls = false; bool use_tls = false; + Ref tls_options; HTTPClient::Method method; Vector request_data; @@ -125,8 +125,8 @@ protected: static void _bind_methods(); public: - Error request(const String &p_url, const Vector &p_custom_headers = Vector(), bool p_tls_validate_domain = true, HTTPClient::Method p_method = HTTPClient::METHOD_GET, const String &p_request_data = ""); //connects to a full url and perform request - Error request_raw(const String &p_url, const Vector &p_custom_headers = Vector(), bool p_tls_validate_domain = true, HTTPClient::Method p_method = HTTPClient::METHOD_GET, const Vector &p_request_data_raw = Vector()); //connects to a full url and perform request + Error request(const String &p_url, const Vector &p_custom_headers = Vector(), HTTPClient::Method p_method = HTTPClient::METHOD_GET, const String &p_request_data = ""); //connects to a full url and perform request + Error request_raw(const String &p_url, const Vector &p_custom_headers = Vector(), HTTPClient::Method p_method = HTTPClient::METHOD_GET, const Vector &p_request_data_raw = Vector()); //connects to a full url and perform request void cancel_request(); HTTPClient::Status get_http_client_status() const; @@ -161,6 +161,8 @@ public: void set_http_proxy(const String &p_host, int p_port); void set_https_proxy(const String &p_host, int p_port); + void set_tls_options(const Ref &p_options); + HTTPRequest(); }; diff --git a/thirdparty/enet/enet/godot_ext.h b/thirdparty/enet/enet/godot_ext.h index 648f3d2f24d..06a621b790e 100644 --- a/thirdparty/enet/enet/godot_ext.h +++ b/thirdparty/enet/enet/godot_ext.h @@ -11,8 +11,8 @@ */ ENET_API void enet_address_set_ip(ENetAddress * address, const uint8_t * ip, size_t size); -ENET_API int enet_host_dtls_server_setup (ENetHost *, void *, void *); -ENET_API int enet_host_dtls_client_setup (ENetHost *, void *, uint8_t, const char *); +ENET_API int enet_host_dtls_server_setup (ENetHost *, void *); +ENET_API int enet_host_dtls_client_setup (ENetHost *, const char *, void *); ENET_API void enet_host_refuse_new_connections (ENetHost *, int); #endif // __ENET_GODOT_EXT_H__ diff --git a/thirdparty/enet/godot.cpp b/thirdparty/enet/godot.cpp index 47298dcf6ad..ea7f4957a23 100644 --- a/thirdparty/enet/godot.cpp +++ b/thirdparty/enet/godot.cpp @@ -164,16 +164,14 @@ class ENetDTLSClient : public ENetGodotSocket { bool connected = false; Ref udp; Ref dtls; - bool verify = false; + Ref tls_options; String for_hostname; - Ref cert; IPAddress local_address; public: - ENetDTLSClient(ENetUDP *p_base, Ref p_cert, bool p_verify, String p_for_hostname) { - verify = p_verify; + ENetDTLSClient(ENetUDP *p_base, String p_for_hostname, Ref p_options) { for_hostname = p_for_hostname; - cert = p_cert; + tls_options = p_options; udp.instantiate(); dtls = Ref(PacketPeerDTLS::create()); if (p_base->bound) { @@ -205,7 +203,7 @@ public: Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) { if (!connected) { udp->connect_to_host(p_ip, p_port); - if (dtls->connect_to_peer(udp, verify, for_hostname, cert)) { + if (dtls->connect_to_peer(udp, for_hostname, tls_options)) { return FAILED; } connected = true; @@ -265,7 +263,7 @@ class ENetDTLSServer : public ENetGodotSocket { IPAddress local_address; public: - ENetDTLSServer(ENetUDP *p_base, Ref p_key, Ref p_cert) { + ENetDTLSServer(ENetUDP *p_base, Ref p_options) { udp_server.instantiate(); if (p_base->bound) { uint16_t port; @@ -274,7 +272,7 @@ public: bind(local_address, port); } server = Ref(DTLSServer::create()); - server->setup(p_key, p_cert); + server->setup(p_options); } ~ENetDTLSServer() { @@ -437,22 +435,22 @@ ENetSocket enet_socket_create(ENetSocketType type) { return socket; } -int enet_host_dtls_server_setup(ENetHost *host, void *p_key, void *p_cert) { +int enet_host_dtls_server_setup(ENetHost *host, void *p_options) { ENetGodotSocket *sock = (ENetGodotSocket *)host->socket; if (!sock->can_upgrade()) { return -1; } - host->socket = memnew(ENetDTLSServer((ENetUDP *)sock, Ref((CryptoKey *)p_key), Ref((X509Certificate *)p_cert))); + host->socket = memnew(ENetDTLSServer(static_cast(sock), Ref(static_cast(p_options)))); memdelete(sock); return 0; } -int enet_host_dtls_client_setup(ENetHost *host, void *p_cert, uint8_t p_verify, const char *p_for_hostname) { +int enet_host_dtls_client_setup(ENetHost *host, const char *p_for_hostname, void *p_options) { ENetGodotSocket *sock = (ENetGodotSocket *)host->socket; if (!sock->can_upgrade()) { return -1; } - host->socket = memnew(ENetDTLSClient((ENetUDP *)sock, Ref((X509Certificate *)p_cert), p_verify, String::utf8(p_for_hostname))); + host->socket = memnew(ENetDTLSClient(static_cast(sock), String::utf8(p_for_hostname), Ref(static_cast(p_options)))); memdelete(sock); return 0; }