1/**************************************************************************/
2/* packet_peer_mbed_dtls.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "packet_peer_mbed_dtls.h"
32
33#include "core/io/file_access.h"
34#include "core/io/stream_peer_tls.h"
35
36int PacketPeerMbedDTLS::bio_send(void *ctx, const unsigned char *buf, size_t len) {
37 if (buf == nullptr || len == 0) {
38 return 0;
39 }
40
41 PacketPeerMbedDTLS *sp = static_cast<PacketPeerMbedDTLS *>(ctx);
42
43 ERR_FAIL_COND_V(sp == nullptr, 0);
44
45 Error err = sp->base->put_packet((const uint8_t *)buf, len);
46 if (err == ERR_BUSY) {
47 return MBEDTLS_ERR_SSL_WANT_WRITE;
48 } else if (err != OK) {
49 ERR_FAIL_V(MBEDTLS_ERR_SSL_INTERNAL_ERROR);
50 }
51 return len;
52}
53
54int PacketPeerMbedDTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) {
55 if (buf == nullptr || len == 0) {
56 return 0;
57 }
58
59 PacketPeerMbedDTLS *sp = static_cast<PacketPeerMbedDTLS *>(ctx);
60
61 ERR_FAIL_COND_V(sp == nullptr, 0);
62
63 int pc = sp->base->get_available_packet_count();
64 if (pc == 0) {
65 return MBEDTLS_ERR_SSL_WANT_READ;
66 } else if (pc < 0) {
67 ERR_FAIL_V(MBEDTLS_ERR_SSL_INTERNAL_ERROR);
68 }
69
70 const uint8_t *buffer;
71 int buffer_size = 0;
72 Error err = sp->base->get_packet(&buffer, buffer_size);
73 if (err != OK) {
74 return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
75 }
76 memcpy(buf, buffer, buffer_size);
77 return buffer_size;
78}
79
80void PacketPeerMbedDTLS::_cleanup() {
81 tls_ctx->clear();
82 base = Ref<PacketPeer>();
83 status = STATUS_DISCONNECTED;
84}
85
86int PacketPeerMbedDTLS::_set_cookie() {
87 // Setup DTLS session cookie for this client
88 uint8_t client_id[18];
89 IPAddress addr = base->get_packet_address();
90 uint16_t port = base->get_packet_port();
91 memcpy(client_id, addr.get_ipv6(), 16);
92 memcpy(&client_id[16], (uint8_t *)&port, 2);
93 return mbedtls_ssl_set_client_transport_id(tls_ctx->get_context(), client_id, 18);
94}
95
96Error PacketPeerMbedDTLS::_do_handshake() {
97 int ret = 0;
98 while ((ret = mbedtls_ssl_handshake(tls_ctx->get_context())) != 0) {
99 if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
100 if (ret != MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED) {
101 ERR_PRINT("TLS handshake error: " + itos(ret));
102 TLSContextMbedTLS::print_mbedtls_error(ret);
103 }
104 _cleanup();
105 status = STATUS_ERROR;
106 return FAILED;
107 }
108 // Will retry via poll later
109 return OK;
110 }
111
112 status = STATUS_CONNECTED;
113 return OK;
114}
115
116Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, const String &p_hostname, Ref<TLSOptions> p_options) {
117 ERR_FAIL_COND_V(!p_base.is_valid() || !p_base->is_socket_connected(), ERR_INVALID_PARAMETER);
118
119 Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_DATAGRAM, p_hostname, p_options.is_valid() ? p_options : TLSOptions::client());
120 ERR_FAIL_COND_V(err != OK, err);
121
122 base = p_base;
123
124 mbedtls_ssl_set_bio(tls_ctx->get_context(), this, bio_send, bio_recv, nullptr);
125 mbedtls_ssl_set_timer_cb(tls_ctx->get_context(), &timer, mbedtls_timing_set_delay, mbedtls_timing_get_delay);
126
127 status = STATUS_HANDSHAKING;
128
129 if (_do_handshake() != OK) {
130 status = STATUS_ERROR_HOSTNAME_MISMATCH;
131 return FAILED;
132 }
133
134 return OK;
135}
136
137Error PacketPeerMbedDTLS::accept_peer(Ref<PacketPeerUDP> p_base, Ref<TLSOptions> p_options, Ref<CookieContextMbedTLS> p_cookies) {
138 ERR_FAIL_COND_V(!p_base.is_valid() || !p_base->is_socket_connected(), ERR_INVALID_PARAMETER);
139
140 Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_DATAGRAM, p_options, p_cookies);
141 ERR_FAIL_COND_V(err != OK, err);
142
143 base = p_base;
144 base->set_blocking_mode(false);
145
146 mbedtls_ssl_session_reset(tls_ctx->get_context());
147
148 int ret = _set_cookie();
149 if (ret != 0) {
150 _cleanup();
151 ERR_FAIL_V_MSG(FAILED, "Error setting DTLS client cookie");
152 }
153
154 mbedtls_ssl_set_bio(tls_ctx->get_context(), this, bio_send, bio_recv, nullptr);
155 mbedtls_ssl_set_timer_cb(tls_ctx->get_context(), &timer, mbedtls_timing_set_delay, mbedtls_timing_get_delay);
156
157 status = STATUS_HANDSHAKING;
158
159 if (_do_handshake() != OK) {
160 status = STATUS_ERROR;
161 return FAILED;
162 }
163
164 return OK;
165}
166
167Error PacketPeerMbedDTLS::put_packet(const uint8_t *p_buffer, int p_bytes) {
168 ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED);
169
170 if (p_bytes == 0) {
171 return OK;
172 }
173
174 int ret = mbedtls_ssl_write(tls_ctx->get_context(), p_buffer, p_bytes);
175 if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
176 // Non blocking io.
177 } else if (ret <= 0) {
178 TLSContextMbedTLS::print_mbedtls_error(ret);
179 _cleanup();
180 return ERR_CONNECTION_ERROR;
181 }
182
183 return OK;
184}
185
186Error PacketPeerMbedDTLS::get_packet(const uint8_t **r_buffer, int &r_bytes) {
187 ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED);
188
189 r_bytes = 0;
190
191 int ret = mbedtls_ssl_read(tls_ctx->get_context(), packet_buffer, PACKET_BUFFER_SIZE);
192 if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
193 ret = 0; // non blocking io
194 } else if (ret <= 0) {
195 if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
196 // Also send close notify back
197 disconnect_from_peer();
198 } else {
199 _cleanup();
200 status = STATUS_ERROR;
201 TLSContextMbedTLS::print_mbedtls_error(ret);
202 }
203 return ERR_CONNECTION_ERROR;
204 }
205 *r_buffer = packet_buffer;
206 r_bytes = ret;
207
208 return OK;
209}
210
211void PacketPeerMbedDTLS::poll() {
212 if (status == STATUS_HANDSHAKING) {
213 _do_handshake();
214 return;
215 } else if (status != STATUS_CONNECTED) {
216 return;
217 }
218
219 ERR_FAIL_COND(!base.is_valid());
220
221 int ret = mbedtls_ssl_read(tls_ctx->get_context(), nullptr, 0);
222
223 if (ret < 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
224 if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
225 // Also send close notify back
226 disconnect_from_peer();
227 } else {
228 _cleanup();
229 status = STATUS_ERROR;
230 TLSContextMbedTLS::print_mbedtls_error(ret);
231 }
232 }
233}
234
235int PacketPeerMbedDTLS::get_available_packet_count() const {
236 ERR_FAIL_COND_V(status != STATUS_CONNECTED, 0);
237
238 return mbedtls_ssl_get_bytes_avail(&(tls_ctx->tls)) > 0 ? 1 : 0;
239}
240
241int PacketPeerMbedDTLS::get_max_packet_size() const {
242 return 488; // 512 (UDP in Godot) - 24 (DTLS header)
243}
244
245PacketPeerMbedDTLS::PacketPeerMbedDTLS() {
246 tls_ctx.instantiate();
247}
248
249PacketPeerMbedDTLS::~PacketPeerMbedDTLS() {
250 disconnect_from_peer();
251}
252
253void PacketPeerMbedDTLS::disconnect_from_peer() {
254 if (status != STATUS_CONNECTED && status != STATUS_HANDSHAKING) {
255 return;
256 }
257
258 if (status == STATUS_CONNECTED) {
259 int ret = 0;
260 // Send SSL close notification, blocking, but ignore other errors.
261 do {
262 ret = mbedtls_ssl_close_notify(tls_ctx->get_context());
263 } while (ret == MBEDTLS_ERR_SSL_WANT_WRITE);
264 }
265
266 _cleanup();
267}
268
269PacketPeerMbedDTLS::Status PacketPeerMbedDTLS::get_status() const {
270 return status;
271}
272
273PacketPeerDTLS *PacketPeerMbedDTLS::_create_func() {
274 return memnew(PacketPeerMbedDTLS);
275}
276
277void PacketPeerMbedDTLS::initialize_dtls() {
278 _create = _create_func;
279 available = true;
280}
281
282void PacketPeerMbedDTLS::finalize_dtls() {
283 _create = nullptr;
284 available = false;
285}
286