[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.
This commit is contained in:
Fabio Alessandrelli 2023-01-20 01:51:35 +01:00
parent 2afa175195
commit adba870534
47 changed files with 338 additions and 203 deletions

View File

@ -65,6 +65,45 @@ void X509Certificate::_bind_methods() {
ClassDB::bind_method(D_METHOD("load", "path"), &X509Certificate::load);
}
/// TLSOptions
Ref<TLSOptions> TLSOptions::client(Ref<X509Certificate> p_trusted_chain, const String &p_common_name_override) {
Ref<TLSOptions> 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> TLSOptions::client_unsafe(Ref<X509Certificate> p_trusted_chain) {
Ref<TLSOptions> 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> TLSOptions::server(Ref<CryptoKey> p_own_key, Ref<X509Certificate> p_own_certificate) {
Ref<TLSOptions> 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<X509Certificate>()), DEFVAL(String()));
ClassDB::bind_static_method("TLSOptions", D_METHOD("client_unsafe", "trusted_chain"), &TLSOptions::client_unsafe, DEFVAL(Ref<X509Certificate>()));
ClassDB::bind_static_method("TLSOptions", D_METHOD("server", "key", "certificate"), &TLSOptions::server);
}
/// HMACContext
void HMACContext::_bind_methods() {

View File

@ -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<X509Certificate> trusted_ca_chain;
Ref<X509Certificate> own_certificate;
Ref<CryptoKey> private_key;
protected:
static void _bind_methods();
public:
static Ref<TLSOptions> client(Ref<X509Certificate> p_trusted_chain = Ref<X509Certificate>(), const String &p_common_name_override = String());
static Ref<TLSOptions> client_unsafe(Ref<X509Certificate> p_trusted_chain);
static Ref<TLSOptions> server(Ref<CryptoKey> p_own_key, Ref<X509Certificate> p_own_certificate);
TLSVerifyMode get_verify_mode() const { return verify_mode; }
String get_common_name() const { return common_name; }
Ref<X509Certificate> get_trusted_ca_chain() const { return trusted_ca_chain; }
Ref<X509Certificate> get_own_certificate() const { return own_certificate; }
Ref<CryptoKey> get_private_key() const { return private_key; }
bool is_server() const { return server_mode; }
};
class HMACContext : public RefCounted {
GDCLASS(HMACContext, RefCounted);

View File

@ -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<X509Certificate>()));
ClassDB::bind_method(D_METHOD("setup", "server_options"), &DTLSServer::setup);
ClassDB::bind_method(D_METHOD("take_connection", "udp_peer"), &DTLSServer::take_connection);
}

View File

@ -47,7 +47,7 @@ public:
static bool is_available();
static DTLSServer *create();
virtual Error setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>()) = 0;
virtual Error setup(Ref<TLSOptions> p_options) = 0;
virtual void stop() = 0;
virtual Ref<PacketPeerDTLS> take_connection(Ref<PacketPeerUDP> p_peer) = 0;

View File

@ -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<TLSOptions>()));
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);

View File

@ -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<String> &p_headers);
virtual Error request(Method p_method, const String &p_url, const Vector<String> &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<TLSOptions> p_tls_options = Ref<TLSOptions>()) = 0;
virtual void set_connection(const Ref<StreamPeer> &p_connection) = 0;
virtual Ref<StreamPeer> get_connection() const = 0;

View File

@ -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<TLSOptions> 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<StreamPeer> &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<StreamPeerTLS>(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<StreamPeerTLS> tls_conn;
if (!handshaking) {
// Connect the StreamPeerTLS and start handshaking.
tls_conn = Ref<StreamPeerTLS>(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<StreamPeerTLS> tmp = connection;
tmp->poll();
if (tmp->get_status() != StreamPeerTLS::STATUS_CONNECTED) {

View File

@ -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<TLSOptions> tls_options;
Vector<uint8_t> response_str;
@ -79,7 +80,7 @@ public:
Error request(Method p_method, const String &p_url, const Vector<String> &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<TLSOptions> p_tls_options = Ref<TLSOptions>()) override;
void set_connection(const Ref<StreamPeer> &p_connection) override;
Ref<StreamPeer> get_connection() const override;
void close() override;

View File

@ -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<X509Certificate>()));
ClassDB::bind_method(D_METHOD("connect_to_peer", "packet_peer", "hostname", "client_options"), &PacketPeerDTLS::connect_to_peer, DEFVAL(Ref<TLSOptions>()));
ClassDB::bind_method(D_METHOD("get_status"), &PacketPeerDTLS::get_status);
ClassDB::bind_method(D_METHOD("disconnect_from_peer"), &PacketPeerDTLS::disconnect_from_peer);

View File

@ -53,7 +53,7 @@ public:
};
virtual void poll() = 0;
virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_validate_certs = true, const String &p_for_hostname = String(), Ref<X509Certificate> p_ca_certs = Ref<X509Certificate>()) = 0;
virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, const String &p_hostname, Ref<TLSOptions> p_options = Ref<TLSOptions>()) = 0;
virtual void disconnect_from_peer() = 0;
virtual Status get_status() const = 0;

View File

@ -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<X509Certificate>()));
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<X509Certificate>()));
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<TLSOptions>()));
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);

View File

@ -58,8 +58,8 @@ public:
bool is_blocking_handshake_enabled() const;
virtual void poll() = 0;
virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>()) = 0;
virtual Error connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs = false, const String &p_for_hostname = String(), Ref<X509Certificate> p_valid_cert = Ref<X509Certificate>()) = 0;
virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<TLSOptions> p_options) = 0;
virtual Error connect_to_stream(Ref<StreamPeer> p_base, const String &p_common_name, Ref<TLSOptions> p_options) = 0;
virtual Status get_status() const = 0;
virtual Ref<StreamPeer> get_stream() const = 0;

View File

@ -209,6 +209,7 @@ void register_core_types() {
GDREGISTER_CLASS(AESContext);
ClassDB::register_custom_instance_class<X509Certificate>();
ClassDB::register_custom_instance_class<CryptoKey>();
GDREGISTER_ABSTRACT_CLASS(TLSOptions);
ClassDB::register_custom_instance_class<HMACContext>();
ClassDB::register_custom_instance_class<Crypto>();
ClassDB::register_custom_instance_class<StreamPeerTLS>();

View File

@ -148,11 +148,9 @@
<methods>
<method name="setup">
<return type="int" enum="Error" />
<param index="0" name="key" type="CryptoKey" />
<param index="1" name="certificate" type="X509Certificate" />
<param index="2" name="chain" type="X509Certificate" default="null" />
<param index="0" name="server_options" type="TLSOptions" />
<description>
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].
</description>
</method>
<method name="take_connection">

View File

@ -30,13 +30,10 @@
<return type="int" enum="Error" />
<param index="0" name="host" type="String" />
<param index="1" name="port" type="int" default="-1" />
<param index="2" name="use_tls" type="bool" default="false" />
<param index="3" name="verify_host" type="bool" default="true" />
<param index="2" name="tls_options" type="TLSOptions" default="null" />
<description>
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].
</description>
</method>
<method name="get_response_body_length" qualifiers="const">

View File

@ -187,9 +187,8 @@
<return type="int" enum="Error" />
<param index="0" name="url" type="String" />
<param index="1" name="custom_headers" type="PackedStringArray" default="PackedStringArray()" />
<param index="2" name="tls_validate_domain" type="bool" default="true" />
<param index="3" name="method" type="int" enum="HTTPClient.Method" default="0" />
<param index="4" name="request_data" type="String" default="&quot;&quot;" />
<param index="2" name="method" type="int" enum="HTTPClient.Method" default="0" />
<param index="3" name="request_data" type="String" default="&quot;&quot;" />
<description>
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 @@
<return type="int" enum="Error" />
<param index="0" name="url" type="String" />
<param index="1" name="custom_headers" type="PackedStringArray" default="PackedStringArray()" />
<param index="2" name="tls_validate_domain" type="bool" default="true" />
<param index="3" name="method" type="int" enum="HTTPClient.Method" default="0" />
<param index="4" name="request_data_raw" type="PackedByteArray" default="PackedByteArray()" />
<param index="2" name="method" type="int" enum="HTTPClient.Method" default="0" />
<param index="3" name="request_data_raw" type="PackedByteArray" default="PackedByteArray()" />
<description>
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.
</description>
</method>
<method name="set_tls_options">
<return type="void" />
<param index="0" name="client_options" type="TLSOptions" />
<description>
Sets the [TLSOptions] to be used when connecting to an HTTPS server. See [method TLSOptions.client].
</description>
</method>
</methods>
<members>
<member name="accept_gzip" type="bool" setter="set_accept_gzip" getter="is_accepting_gzip" default="true">

View File

@ -14,11 +14,10 @@
<method name="connect_to_peer">
<return type="int" enum="Error" />
<param index="0" name="packet_peer" type="PacketPeerUDP" />
<param index="1" name="validate_certs" type="bool" default="true" />
<param index="2" name="for_hostname" type="String" default="&quot;&quot;" />
<param index="3" name="valid_certificate" type="X509Certificate" default="null" />
<param index="1" name="hostname" type="String" />
<param index="2" name="client_options" type="TLSOptions" default="null" />
<description>
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].
</description>
</method>
<method name="disconnect_from_peer">

View File

@ -14,22 +14,18 @@
<method name="accept_stream">
<return type="int" enum="Error" />
<param index="0" name="stream" type="StreamPeer" />
<param index="1" name="private_key" type="CryptoKey" />
<param index="2" name="certificate" type="X509Certificate" />
<param index="3" name="chain" type="X509Certificate" default="null" />
<param index="1" name="server_options" type="TLSOptions" />
<description>
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].
</description>
</method>
<method name="connect_to_stream">
<return type="int" enum="Error" />
<param index="0" name="stream" type="StreamPeer" />
<param index="1" name="validate_certs" type="bool" default="false" />
<param index="2" name="for_hostname" type="String" default="&quot;&quot;" />
<param index="3" name="valid_certificate" type="X509Certificate" default="null" />
<param index="1" name="common_name" type="String" />
<param index="2" name="client_options" type="TLSOptions" default="null" />
<description>
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].
</description>
</method>
<method name="disconnect_from_stream">

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="TLSOptions" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
TLS configuration for clients and servers.
</brief_description>
<description>
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]
</description>
<tutorials>
</tutorials>
<methods>
<method name="client" qualifiers="static">
<return type="TLSOptions" />
<param index="0" name="trusted_chain" type="X509Certificate" default="null" />
<param index="1" name="common_name_override" type="String" default="&quot;&quot;" />
<description>
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.
</description>
</method>
<method name="client_unsafe" qualifiers="static">
<return type="TLSOptions" />
<param index="0" name="trusted_chain" type="X509Certificate" default="null" />
<description>
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.
</description>
</method>
<method name="server" qualifiers="static">
<return type="TLSOptions" />
<param index="0" name="key" type="CryptoKey" />
<param index="1" name="certificate" type="X509Certificate" />
<description>
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).
</description>
</method>
</methods>
</class>

View File

@ -84,19 +84,17 @@
</method>
<method name="dtls_client_setup">
<return type="int" enum="Error" />
<param index="0" name="certificate" type="X509Certificate" />
<param index="1" name="hostname" type="String" />
<param index="2" name="verify" type="bool" default="true" />
<param index="0" name="hostname" type="String" />
<param index="1" name="client_options" type="TLSOptions" default="null" />
<description>
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].
</description>
</method>
<method name="dtls_server_setup">
<return type="int" enum="Error" />
<param index="0" name="key" type="CryptoKey" />
<param index="1" name="certificate" type="X509Certificate" />
<param index="0" name="server_options" type="TLSOptions" />
<description>
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].
</description>
</method>
<method name="flush">

View File

@ -273,10 +273,11 @@ TypedArray<ENetPacketPeer> ENetConnection::_get_peers() {
return out;
}
Error ENetConnection::dtls_server_setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert) {
Error ENetConnection::dtls_server_setup(const Ref<TLSOptions> &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<TLSOptions *>(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<X509Certificate> p_cert, const String &p_hostname, bool p_verify) {
Error ENetConnection::dtls_client_setup(const String &p_hostname, const Ref<TLSOptions> &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<TLSOptions *>(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<TLSOptions>()));
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);

View File

@ -128,8 +128,8 @@ public:
int get_local_port() const;
// Godot additions
Error dtls_server_setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert);
Error dtls_client_setup(Ref<X509Certificate> p_cert, const String &p_hostname, bool p_verify = true);
Error dtls_server_setup(const Ref<TLSOptions> &p_options);
Error dtls_client_setup(const String &p_hostname, const Ref<TLSOptions> &p_options);
void refuse_new_connections(bool p_refuse);
ENetConnection() {}

View File

@ -31,25 +31,25 @@
#include "dtls_server_mbedtls.h"
#include "packet_peer_mbed_dtls.h"
Error DTLSServerMbedTLS::setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> 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<TLSOptions> 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<PacketPeerDTLS> DTLSServerMbedTLS::take_connection(Ref<PacketPeerUDP> p_udp_peer) {
Ref<PacketPeerMbedDTLS> 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() {

View File

@ -37,16 +37,14 @@
class DTLSServerMbedTLS : public DTLSServer {
private:
static DTLSServer *_create_func();
Ref<CryptoKey> _key;
Ref<X509Certificate> _cert;
Ref<X509Certificate> _ca_chain;
Ref<CookieContextMbedTLS> _cookies;
Ref<TLSOptions> tls_options;
Ref<CookieContextMbedTLS> cookies;
public:
static void initialize();
static void finalize();
virtual Error setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>());
virtual Error setup(Ref<TLSOptions> p_options);
virtual void stop();
virtual Ref<PacketPeerDTLS> take_connection(Ref<PacketPeerUDP> p_peer);

View File

@ -114,16 +114,14 @@ Error PacketPeerMbedDTLS::_do_handshake() {
return OK;
}
Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_validate_certs, const String &p_for_hostname, Ref<X509Certificate> p_ca_certs) {
Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, const String &p_hostname, Ref<TLSOptions> 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<PacketPeerUDP> p_base, bool p_vali
return OK;
}
Error PacketPeerMbedDTLS::accept_peer(Ref<PacketPeerUDP> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain, Ref<CookieContextMbedTLS> 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<PacketPeerUDP> p_base, Ref<TLSOptions> p_options, Ref<CookieContextMbedTLS> 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;

View File

@ -64,8 +64,8 @@ protected:
public:
virtual void poll();
virtual Error accept_peer(Ref<PacketPeerUDP> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert = Ref<X509Certificate>(), Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>(), Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>());
virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_validate_certs = true, const String &p_for_hostname = String(), Ref<X509Certificate> p_ca_certs = Ref<X509Certificate>());
virtual Error accept_peer(Ref<PacketPeerUDP> p_base, Ref<TLSOptions> p_options, Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>());
virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, const String &p_hostname, Ref<TLSOptions> p_options = Ref<TLSOptions>());
virtual Status get_status() const;
virtual void disconnect_from_peer();

View File

@ -102,16 +102,13 @@ Error StreamPeerMbedTLS::_do_handshake() {
return OK;
}
Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs, const String &p_for_hostname, Ref<X509Certificate> p_ca_certs) {
Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, const String &p_common_name, Ref<TLSOptions> 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<StreamPeer> p_base, bool p_valida
return OK;
}
Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain) {
Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base, Ref<TLSOptions> 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;

View File

@ -54,8 +54,8 @@ protected:
public:
virtual void poll();
virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>());
virtual Error connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs = false, const String &p_for_hostname = String(), Ref<X509Certificate> p_valid_cert = Ref<X509Certificate>());
virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<TLSOptions> p_options);
virtual Error connect_to_stream(Ref<StreamPeer> p_base, const String &p_common_name, Ref<TLSOptions> p_options);
virtual Status get_status() const;
virtual Ref<StreamPeer> get_stream() const;

View File

@ -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<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert, Ref<CookieContextMbedTLS> 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<TLSOptions> p_options, Ref<CookieContextMbedTLS> 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<Crypto
return OK;
}
Error TLSContextMbedTLS::init_client(int p_transport, int p_authmode, Ref<X509CertificateMbedTLS> 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<TLSOptions> 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 {

View File

@ -71,17 +71,17 @@ public:
static void print_mbedtls_error(int p_ret);
Ref<X509CertificateMbedTLS> certs;
Ref<CryptoKeyMbedTLS> pkey;
Ref<CookieContextMbedTLS> cookies;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_context tls;
mbedtls_ssl_config conf;
Ref<CookieContextMbedTLS> cookies;
Ref<CryptoKeyMbedTLS> pkey;
Error _setup(int p_endpoint, int p_transport, int p_authmode);
Error init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert, Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>());
Error init_client(int p_transport, int p_authmode, Ref<X509CertificateMbedTLS> p_valid_cas);
Error init_server(int p_transport, Ref<TLSOptions> p_options, Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>());
Error init_client(int p_transport, const String &p_hostname, Ref<TLSOptions> p_options);
void clear();
mbedtls_ssl_context *get_context();

View File

@ -13,10 +13,9 @@
<method name="create_client">
<return type="int" enum="Error" />
<param index="0" name="url" type="String" />
<param index="1" name="verify_tls" type="bool" default="true" />
<param index="2" name="tls_certificate" type="X509Certificate" default="null" />
<param index="1" name="tls_client_options" type="TLSOptions" default="null" />
<description>
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].
</description>
</method>
@ -24,10 +23,9 @@
<return type="int" enum="Error" />
<param index="0" name="port" type="int" />
<param index="1" name="bind_address" type="String" default="&quot;*&quot;" />
<param index="2" name="tls_key" type="CryptoKey" default="null" />
<param index="3" name="tls_certificate" type="X509Certificate" default="null" />
<param index="2" name="tls_server_options" type="TLSOptions" default="null" />
<description>
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].
</description>
</method>
<method name="get_peer" qualifiers="const">

View File

@ -58,10 +58,9 @@
<method name="connect_to_url">
<return type="int" enum="Error" />
<param index="0" name="url" type="String" />
<param index="1" name="verify_tls" type="bool" default="true" />
<param index="2" name="trusted_tls_certificate" type="X509Certificate" default="null" />
<param index="1" name="tls_client_options" type="TLSOptions" default="null" />
<description>
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.
</description>
</method>

View File

@ -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<X509Certificate> p_tls_certificate) {
Error EMWSPeer::connect_to_url(const String &p_url, Ref<TLSOptions> 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<X509C
if (handshake_headers.size()) {
WARN_PRINT_ONCE("Custom headers are not supported in Web platform.");
}
if (p_tls_certificate.is_valid()) {
WARN_PRINT_ONCE("Custom SSL certificates are not supported in Web platform.");
}
requested_url = scheme + host;

View File

@ -86,7 +86,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<X509Certificate> p_cert = Ref<X509Certificate>()) override;
virtual Error connect_to_url(const String &p_url, Ref<TLSOptions> p_tls_client_options) override;
virtual Error accept_stream(Ref<StreamPeer> p_stream) override;
virtual void close(int p_code = 1000, String p_reason = "") override;
virtual void poll() override;

View File

@ -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<X509Certificate>()));
ClassDB::bind_method(D_METHOD("create_server", "port", "bind_address", "tls_key", "tls_certificate"), &WebSocketMultiplayerPeer::create_server, DEFVAL("*"), DEFVAL(Ref<CryptoKey>()), DEFVAL(Ref<X509Certificate>()));
ClassDB::bind_method(D_METHOD("create_client", "url", "tls_client_options"), &WebSocketMultiplayerPeer::create_client, DEFVAL(Ref<TLSOptions>()));
ClassDB::bind_method(D_METHOD("create_server", "port", "bind_address", "tls_server_options"), &WebSocketMultiplayerPeer::create_server, DEFVAL("*"), DEFVAL(Ref<TLSOptions>()));
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<CryptoKey> p_tls_key, Ref<X509Certificate> p_tls_certificate) {
Error WebSocketMultiplayerPeer::create_server(int p_port, IPAddress p_bind_ip, Ref<TLSOptions> 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<X509Certificate> p_tls_certificate) {
Error WebSocketMultiplayerPeer::create_client(const String &p_url, Ref<TLSOptions> 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<WebSocketPeer> 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<StreamPeerTLS> tls = Ref<StreamPeerTLS>(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;

View File

@ -71,9 +71,7 @@ protected:
Ref<WebSocketPeer> peer_config;
HashMap<int, PendingPeer> pending_peers;
Ref<TCPServer> tcp_server;
bool use_tls = false;
Ref<X509Certificate> tls_certificate;
Ref<CryptoKey> tls_key;
Ref<TLSOptions> tls_server_options;
ConnectionStatus connection_status = CONNECTION_DISCONNECTED;
@ -115,8 +113,8 @@ public:
/* WebSocketPeer */
virtual Ref<WebSocketPeer> get_peer(int p_peer_id) const;
Error create_client(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_tls_certificate);
Error create_server(int p_port, IPAddress p_bind_ip, Ref<CryptoKey> p_tls_key, Ref<X509Certificate> p_tls_certificate);
Error create_client(const String &p_url, Ref<TLSOptions> p_options);
Error create_server(int p_port, IPAddress p_bind_ip, Ref<TLSOptions> p_options);
void set_supported_protocols(const Vector<String> &p_protocols);
Vector<String> get_supported_protocols() const;

View File

@ -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<X509Certificate>()));
ClassDB::bind_method(D_METHOD("connect_to_url", "url", "tls_client_options"), &WebSocketPeer::connect_to_url, DEFVAL(Ref<TLSOptions>()));
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);

View File

@ -81,7 +81,7 @@ public:
return _create();
}
virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref<X509Certificate> p_cert = Ref<X509Certificate>()) { return ERR_UNAVAILABLE; };
virtual Error connect_to_url(const String &p_url, Ref<TLSOptions> p_options = Ref<TLSOptions>()) = 0;
virtual Error accept_stream(Ref<StreamPeer> p_stream) = 0;
virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) = 0;

View File

@ -334,7 +334,7 @@ void WSLPeer::_do_client_handshake() {
tls = Ref<StreamPeerTLS>(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<X509Certificate> p_cert) {
Error WSLPeer::connect_to_url(const String &p_url, Ref<TLSOptions> 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<X509Ce
requested_url = p_url;
requested_host = host;
verify_tls = p_verify_tls;
tls_cert = p_cert;
if (p_options.is_valid()) {
tls_options = p_options;
} else {
tls_options = TLSOptions::client();
}
tcp.instantiate();
resolver.start(host, port);

View File

@ -102,8 +102,7 @@ private:
// WebSocket configuration.
bool use_tls = true;
bool verify_tls = true;
Ref<X509Certificate> tls_cert;
Ref<TLSOptions> tls_options;
// Packet buffers.
Vector<uint8_t> 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<X509Certificate> p_cert = Ref<X509Certificate>()) override;
virtual Error connect_to_url(const String &p_url, Ref<TLSOptions> p_options = Ref<TLSOptions>()) override;
virtual Error accept_stream(Ref<StreamPeer> p_stream) override;
virtual void close(int p_code = 1000, String p_reason = "") override;
virtual void poll() override;

View File

@ -206,7 +206,7 @@ public:
tls = Ref<StreamPeerTLS>(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;
}

View File

@ -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<TLSOptions> 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);

View File

@ -86,7 +86,7 @@ public:
Error request(Method p_method, const String &p_url, const Vector<String> &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<TLSOptions> p_tls_options = Ref<TLSOptions>()) override;
void set_connection(const Ref<StreamPeer> &p_connection) override;
Ref<StreamPeer> get_connection() const override;
void close() override;

View File

@ -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<String> &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<String> &p_custom_headers, HTTPClient::Method p_method, const String &p_request_data) {
// Copy the string into a raw buffer.
Vector<uint8_t> raw_data;
@ -108,10 +108,10 @@ Error HTTPRequest::request(const String &p_url, const Vector<String> &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<String> &p_custom_headers, bool p_tls_validate_domain, HTTPClient::Method p_method, const Vector<uint8_t> &p_request_data_raw) {
Error HTTPRequest::request_raw(const String &p_url, const Vector<String> &p_custom_headers, HTTPClient::Method p_method, const Vector<uint8_t> &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<String> &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<TLSOptions> &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>(HTTPClient::create());
tls_options = TLSOptions::client();
timer = memnew(Timer);
timer->set_one_shot(true);
timer->connect("timeout", callable_mp(this, &HTTPRequest::_timeout));

View File

@ -68,8 +68,8 @@ private:
String url;
int port = 80;
Vector<String> headers;
bool validate_tls = false;
bool use_tls = false;
Ref<TLSOptions> tls_options;
HTTPClient::Method method;
Vector<uint8_t> request_data;
@ -125,8 +125,8 @@ protected:
static void _bind_methods();
public:
Error request(const String &p_url, const Vector<String> &p_custom_headers = Vector<String>(), 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<String> &p_custom_headers = Vector<String>(), bool p_tls_validate_domain = true, HTTPClient::Method p_method = HTTPClient::METHOD_GET, const Vector<uint8_t> &p_request_data_raw = Vector<uint8_t>()); //connects to a full url and perform request
Error request(const String &p_url, const Vector<String> &p_custom_headers = Vector<String>(), 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<String> &p_custom_headers = Vector<String>(), HTTPClient::Method p_method = HTTPClient::METHOD_GET, const Vector<uint8_t> &p_request_data_raw = Vector<uint8_t>()); //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<TLSOptions> &p_options);
HTTPRequest();
};

View File

@ -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__

View File

@ -164,16 +164,14 @@ class ENetDTLSClient : public ENetGodotSocket {
bool connected = false;
Ref<PacketPeerUDP> udp;
Ref<PacketPeerDTLS> dtls;
bool verify = false;
Ref<TLSOptions> tls_options;
String for_hostname;
Ref<X509Certificate> cert;
IPAddress local_address;
public:
ENetDTLSClient(ENetUDP *p_base, Ref<X509Certificate> p_cert, bool p_verify, String p_for_hostname) {
verify = p_verify;
ENetDTLSClient(ENetUDP *p_base, String p_for_hostname, Ref<TLSOptions> p_options) {
for_hostname = p_for_hostname;
cert = p_cert;
tls_options = p_options;
udp.instantiate();
dtls = Ref<PacketPeerDTLS>(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<CryptoKey> p_key, Ref<X509Certificate> p_cert) {
ENetDTLSServer(ENetUDP *p_base, Ref<TLSOptions> p_options) {
udp_server.instantiate();
if (p_base->bound) {
uint16_t port;
@ -274,7 +272,7 @@ public:
bind(local_address, port);
}
server = Ref<DTLSServer>(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>((CryptoKey *)p_key), Ref<X509Certificate>((X509Certificate *)p_cert)));
host->socket = memnew(ENetDTLSServer(static_cast<ENetUDP *>(sock), Ref<TLSOptions>(static_cast<TLSOptions *>(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>((X509Certificate *)p_cert), p_verify, String::utf8(p_for_hostname)));
host->socket = memnew(ENetDTLSClient(static_cast<ENetUDP *>(sock), String::utf8(p_for_hostname), Ref<TLSOptions>(static_cast<TLSOptions *>(p_options))));
memdelete(sock);
return 0;
}