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