1/**************************************************************************/
2/* stream_peer_mbedtls.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 "stream_peer_mbedtls.h"
32
33#include "core/io/file_access.h"
34#include "core/io/stream_peer_tcp.h"
35
36int StreamPeerMbedTLS::bio_send(void *ctx, const unsigned char *buf, size_t len) {
37 if (buf == nullptr || len == 0) {
38 return 0;
39 }
40
41 StreamPeerMbedTLS *sp = static_cast<StreamPeerMbedTLS *>(ctx);
42
43 ERR_FAIL_COND_V(sp == nullptr, 0);
44
45 int sent;
46 Error err = sp->base->put_partial_data((const uint8_t *)buf, len, sent);
47 if (err != OK) {
48 return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
49 }
50 if (sent == 0) {
51 return MBEDTLS_ERR_SSL_WANT_WRITE;
52 }
53 return sent;
54}
55
56int StreamPeerMbedTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) {
57 if (buf == nullptr || len == 0) {
58 return 0;
59 }
60
61 StreamPeerMbedTLS *sp = static_cast<StreamPeerMbedTLS *>(ctx);
62
63 ERR_FAIL_COND_V(sp == nullptr, 0);
64
65 int got;
66 Error err = sp->base->get_partial_data((uint8_t *)buf, len, got);
67 if (err != OK) {
68 return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
69 }
70 if (got == 0) {
71 return MBEDTLS_ERR_SSL_WANT_READ;
72 }
73 return got;
74}
75
76void StreamPeerMbedTLS::_cleanup() {
77 tls_ctx->clear();
78 base = Ref<StreamPeer>();
79 status = STATUS_DISCONNECTED;
80}
81
82Error StreamPeerMbedTLS::_do_handshake() {
83 int ret = mbedtls_ssl_handshake(tls_ctx->get_context());
84 if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
85 // Handshake is still in progress, will retry via poll later.
86 return OK;
87 } else if (ret != 0) {
88 // An error occurred.
89 ERR_PRINT("TLS handshake error: " + itos(ret));
90 TLSContextMbedTLS::print_mbedtls_error(ret);
91 disconnect_from_stream();
92 status = STATUS_ERROR;
93 return FAILED;
94 }
95
96 status = STATUS_CONNECTED;
97 return OK;
98}
99
100Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, const String &p_common_name, Ref<TLSOptions> p_options) {
101 ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER);
102
103 Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_STREAM, p_common_name, p_options.is_valid() ? p_options : TLSOptions::client());
104 ERR_FAIL_COND_V(err != OK, err);
105
106 base = p_base;
107 mbedtls_ssl_set_bio(tls_ctx->get_context(), this, bio_send, bio_recv, nullptr);
108
109 status = STATUS_HANDSHAKING;
110
111 if (_do_handshake() != OK) {
112 status = STATUS_ERROR_HOSTNAME_MISMATCH;
113 return FAILED;
114 }
115
116 return OK;
117}
118
119Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base, Ref<TLSOptions> p_options) {
120 ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER);
121 ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER);
122
123 Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_STREAM, p_options);
124 ERR_FAIL_COND_V(err != OK, err);
125
126 base = p_base;
127
128 mbedtls_ssl_set_bio(tls_ctx->get_context(), this, bio_send, bio_recv, nullptr);
129
130 status = STATUS_HANDSHAKING;
131
132 if (_do_handshake() != OK) {
133 return FAILED;
134 }
135
136 status = STATUS_CONNECTED;
137 return OK;
138}
139
140Error StreamPeerMbedTLS::put_data(const uint8_t *p_data, int p_bytes) {
141 ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED);
142
143 Error err;
144 int sent = 0;
145
146 while (p_bytes > 0) {
147 err = put_partial_data(p_data, p_bytes, sent);
148
149 if (err != OK) {
150 return err;
151 }
152
153 p_data += sent;
154 p_bytes -= sent;
155 }
156
157 return OK;
158}
159
160Error StreamPeerMbedTLS::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) {
161 ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED);
162
163 r_sent = 0;
164
165 if (p_bytes == 0) {
166 return OK;
167 }
168
169 int ret = mbedtls_ssl_write(tls_ctx->get_context(), p_data, p_bytes);
170 if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
171 // Non blocking IO
172 ret = 0;
173 } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
174 // Clean close
175 disconnect_from_stream();
176 return ERR_FILE_EOF;
177 } else if (ret <= 0) {
178 TLSContextMbedTLS::print_mbedtls_error(ret);
179 disconnect_from_stream();
180 return ERR_CONNECTION_ERROR;
181 }
182
183 r_sent = ret;
184 return OK;
185}
186
187Error StreamPeerMbedTLS::get_data(uint8_t *p_buffer, int p_bytes) {
188 ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED);
189
190 Error err;
191
192 int got = 0;
193 while (p_bytes > 0) {
194 err = get_partial_data(p_buffer, p_bytes, got);
195
196 if (err != OK) {
197 return err;
198 }
199
200 p_buffer += got;
201 p_bytes -= got;
202 }
203
204 return OK;
205}
206
207Error StreamPeerMbedTLS::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) {
208 ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED);
209
210 r_received = 0;
211
212 int ret = mbedtls_ssl_read(tls_ctx->get_context(), p_buffer, p_bytes);
213 if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
214 ret = 0; // non blocking io
215 } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
216 // Clean close
217 disconnect_from_stream();
218 return ERR_FILE_EOF;
219 } else if (ret <= 0) {
220 TLSContextMbedTLS::print_mbedtls_error(ret);
221 disconnect_from_stream();
222 return ERR_CONNECTION_ERROR;
223 }
224
225 r_received = ret;
226 return OK;
227}
228
229void StreamPeerMbedTLS::poll() {
230 ERR_FAIL_COND(status != STATUS_CONNECTED && status != STATUS_HANDSHAKING);
231 ERR_FAIL_COND(!base.is_valid());
232
233 if (status == STATUS_HANDSHAKING) {
234 _do_handshake();
235 return;
236 }
237
238 // We could pass nullptr as second parameter, but some behavior sanitizers don't seem to like that.
239 // Passing a 1 byte buffer to workaround it.
240 uint8_t byte;
241 int ret = mbedtls_ssl_read(tls_ctx->get_context(), &byte, 0);
242
243 if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
244 // Nothing to read/write (non blocking IO)
245 } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
246 // Clean close (disconnect)
247 disconnect_from_stream();
248 return;
249 } else if (ret < 0) {
250 TLSContextMbedTLS::print_mbedtls_error(ret);
251 disconnect_from_stream();
252 return;
253 }
254
255 Ref<StreamPeerTCP> tcp = base;
256 if (tcp.is_valid() && tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
257 disconnect_from_stream();
258 return;
259 }
260}
261
262int StreamPeerMbedTLS::get_available_bytes() const {
263 ERR_FAIL_COND_V(status != STATUS_CONNECTED, 0);
264
265 return mbedtls_ssl_get_bytes_avail(&(tls_ctx->tls));
266}
267
268StreamPeerMbedTLS::StreamPeerMbedTLS() {
269 tls_ctx.instantiate();
270}
271
272StreamPeerMbedTLS::~StreamPeerMbedTLS() {
273 disconnect_from_stream();
274}
275
276void StreamPeerMbedTLS::disconnect_from_stream() {
277 if (status != STATUS_CONNECTED && status != STATUS_HANDSHAKING) {
278 return;
279 }
280
281 Ref<StreamPeerTCP> tcp = base;
282 if (tcp.is_valid() && tcp->get_status() == StreamPeerTCP::STATUS_CONNECTED) {
283 // We are still connected on the socket, try to send close notify.
284 mbedtls_ssl_close_notify(tls_ctx->get_context());
285 }
286
287 _cleanup();
288}
289
290StreamPeerMbedTLS::Status StreamPeerMbedTLS::get_status() const {
291 return status;
292}
293
294Ref<StreamPeer> StreamPeerMbedTLS::get_stream() const {
295 return base;
296}
297
298StreamPeerTLS *StreamPeerMbedTLS::_create_func() {
299 return memnew(StreamPeerMbedTLS);
300}
301
302void StreamPeerMbedTLS::initialize_tls() {
303 _create = _create_func;
304}
305
306void StreamPeerMbedTLS::finalize_tls() {
307 _create = nullptr;
308}
309