v / vlib / net / mbedtls / ssl_connection.c.v
996 lines · 904 sloc · 30.15 KB · 75789c6f9989ff8de76988c5d312979ca5e27853
Raw
1module mbedtls
2
3import io
4import net
5import time
6
7const mbedtls_client_read_timeout_ms = $d('mbedtls_client_read_timeout_ms', 10_000)
8const mbedtls_server_read_timeout_ms = $d('mbedtls_server_read_timeout_ms', 41_000)
9const default_mbedtls_client_read_timeout = mbedtls_client_read_timeout_ms * time.millisecond
10const default_mbedtls_server_read_timeout = mbedtls_server_read_timeout_ms * time.millisecond
11
12fn init_rng(mut ctr_drbg C.mbedtls_ctr_drbg_context, mut entropy C.mbedtls_entropy_context) ! {
13 $if trace_ssl ? {
14 eprintln(@METHOD)
15 }
16 C.mbedtls_ctr_drbg_init(&ctr_drbg)
17 C.mbedtls_entropy_init(&entropy)
18 ret := C.mbedtls_ctr_drbg_seed(&ctr_drbg, C.mbedtls_entropy_func, &entropy, 0, 0)
19 if ret != 0 {
20 C.mbedtls_ctr_drbg_free(&ctr_drbg)
21 C.mbedtls_entropy_free(&entropy)
22 return error_with_code('net.mbedtls init_rng, failed to seed ssl context: ${ret}', ret)
23 }
24 // C.mbedtls_debug_set_threshold(5)
25}
26
27fn free_rng(mut ctr_drbg C.mbedtls_ctr_drbg_context, mut entropy C.mbedtls_entropy_context) {
28 C.mbedtls_ctr_drbg_free(&ctr_drbg)
29 C.mbedtls_entropy_free(&entropy)
30}
31
32// SSLCerts represents a pair of CA and client certificates + key
33pub struct SSLCerts {
34pub mut:
35 cacert C.mbedtls_x509_crt
36 client_cert C.mbedtls_x509_crt
37 client_key C.mbedtls_pk_context
38}
39
40// new_sslcerts initializes and returns a pair of SSL certificates and key
41pub fn new_sslcerts() &SSLCerts {
42 mut certs := SSLCerts{}
43 C.mbedtls_x509_crt_init(&certs.cacert)
44 C.mbedtls_x509_crt_init(&certs.client_cert)
45 C.mbedtls_pk_init(&certs.client_key)
46 return &certs
47}
48
49// new_sslcerts_in_memory creates a pair of SSL certificates, given their contents (not paths).
50pub fn new_sslcerts_in_memory(verify string, cert string, cert_key string) !&SSLCerts {
51 mut ctr_drbg := C.mbedtls_ctr_drbg_context{}
52 mut entropy := C.mbedtls_entropy_context{}
53 init_rng(mut ctr_drbg, mut entropy)!
54 defer {
55 free_rng(mut ctr_drbg, mut entropy)
56 }
57 return new_sslcerts_in_memory_with_rng(verify, cert, cert_key, &ctr_drbg)
58}
59
60fn new_sslcerts_in_memory_with_rng(verify string, cert string, cert_key string, rng &C.mbedtls_ctr_drbg_context) !&SSLCerts {
61 mut certs := new_sslcerts()
62 if verify != '' {
63 ret := C.mbedtls_x509_crt_parse(&certs.cacert, verify.str, verify.len + 1)
64 if ret != 0 {
65 return error_with_code('net.mbedtls new_sslcerts_in_memory, mbedtls_x509_crt_parse error 1 ret: ${ret}',
66 ret)
67 }
68 }
69 if cert != '' {
70 ret := C.mbedtls_x509_crt_parse(&certs.client_cert, cert.str, cert.len + 1)
71 if ret != 0 {
72 return error_with_code('net.mbedtls new_sslcerts_in_memory, mbedtls_x509_crt_parse error 2 ret: ${ret}',
73 ret)
74 }
75 }
76 if cert_key != '' {
77 unsafe {
78 ret := C.mbedtls_pk_parse_key(&certs.client_key, cert_key.str, cert_key.len + 1, 0, 0,
79 C.mbedtls_ctr_drbg_random, rng)
80 if ret != 0 {
81 return error_with_code('net.mbedtls new_sslcerts_in_memory, mbedtls_pk_parse_key error ret: ${ret}',
82 ret)
83 }
84 }
85 }
86 return certs
87}
88
89// new_sslcerts_from_file creates a new pair of SSL certificates, given their paths on the filesystem.
90pub fn new_sslcerts_from_file(verify string, cert string, cert_key string) !&SSLCerts {
91 mut ctr_drbg := C.mbedtls_ctr_drbg_context{}
92 mut entropy := C.mbedtls_entropy_context{}
93 init_rng(mut ctr_drbg, mut entropy)!
94 defer {
95 free_rng(mut ctr_drbg, mut entropy)
96 }
97 return new_sslcerts_from_file_with_rng(verify, cert, cert_key, &ctr_drbg)
98}
99
100fn new_sslcerts_from_file_with_rng(verify string, cert string, cert_key string, rng &C.mbedtls_ctr_drbg_context) !&SSLCerts {
101 mut certs := new_sslcerts()
102 if verify != '' {
103 ret := C.mbedtls_x509_crt_parse_file(&certs.cacert, &char(verify.str))
104 if ret != 0 {
105 return error_with_code('net.mbedtls new_sslcerts_from_file, mbedtls_x509_crt_parse_file error 1 ret: ${ret}',
106 ret)
107 }
108 }
109 if cert != '' {
110 ret := C.mbedtls_x509_crt_parse_file(&certs.client_cert, &char(cert.str))
111 if ret != 0 {
112 return error_with_code('net.mbedtls new_sslcerts_from_file, mbedtls_x509_crt_parse_file error 2 ret: ${ret}',
113 ret)
114 }
115 }
116 if cert_key != '' {
117 unsafe {
118 ret := C.mbedtls_pk_parse_keyfile(&certs.client_key, &char(cert_key.str), 0,
119 C.mbedtls_ctr_drbg_random, rng)
120 if ret != 0 {
121 return error_with_code('net.mbedtls new_sslcerts_from_file, mbedtls_pk_parse_keyfile error ret: ${ret}',
122 ret)
123 }
124 }
125 }
126 return certs
127}
128
129// cleanup frees the SSL certificates
130pub fn (mut c SSLCerts) cleanup() {
131 C.mbedtls_x509_crt_free(&c.cacert)
132 C.mbedtls_x509_crt_free(&c.client_cert)
133 C.mbedtls_pk_free(&c.client_key)
134}
135
136// SSLConn is the current connection
137pub struct SSLConn {
138pub:
139 config SSLConnectConfig
140pub mut:
141 server_fd C.mbedtls_net_context
142 ssl C.mbedtls_ssl_context
143 conf C.mbedtls_ssl_config
144 certs &SSLCerts = unsafe { nil }
145 ctr_drbg C.mbedtls_ctr_drbg_context
146 entropy C.mbedtls_entropy_context
147 handle int
148 duration time.Duration
149 opened bool
150 ip string
151 read_timeout time.Duration
152
153 owns_socket bool
154 // alpn_list is a NUL-terminated C array of pointers to the protocol
155 // strings in config.alpn_protocols. mbedtls stores this pointer without
156 // copying, so it must outlive the SSL config; it is freed in shutdown().
157 alpn_list &&char = unsafe { nil }
158 // last_write_sent reports the most recent write_ptr's progress for retry
159 // decisions: 0 = provably nothing was sent (safe to replay), or -1 = the
160 // count is indeterminate because a failed/retryable write may have already
161 // flushed a record to the peer (TLS cannot prove zero). On full success it
162 // equals the bytes written.
163 last_write_sent int
164}
165
166// SSLListener listens on a TCP port and accepts connection secured with TLS
167pub struct SSLListener {
168 saddr string
169 config SSLConnectConfig
170mut:
171 server_fd C.mbedtls_net_context
172 ssl C.mbedtls_ssl_context
173 conf C.mbedtls_ssl_config
174 certs &SSLCerts = unsafe { nil }
175 ctr_drbg C.mbedtls_ctr_drbg_context
176 entropy C.mbedtls_entropy_context
177 opened bool
178 // alpn_list is a NUL-terminated C array of pointers to the protocol
179 // strings in config.alpn_protocols, advertised by accepted connections.
180 // It must outlive the SSL config and is freed in shutdown().
181 alpn_list &&char = unsafe { nil }
182 // handle int
183 // duration time.Duration
184}
185
186// create a new SSLListener binding to `saddr`
187pub fn new_ssl_listener(saddr string, config SSLConnectConfig) !&SSLListener {
188 mut listener := &SSLListener{
189 saddr: saddr
190 config: config
191 }
192 listener.init()!
193 listener.opened = true
194 return listener
195}
196
197// finish the listener and clean up resources
198pub fn (mut l SSLListener) shutdown() ! {
199 $if trace_ssl ? {
200 eprintln(@METHOD)
201 }
202 if unsafe { l.certs != nil } {
203 l.certs.cleanup()
204 }
205 C.mbedtls_ssl_free(&l.ssl)
206 C.mbedtls_ssl_config_free(&l.conf)
207 free_rng(mut l.ctr_drbg, mut l.entropy)
208 if l.alpn_list != unsafe { nil } {
209 unsafe {
210 C.free(l.alpn_list)
211 l.alpn_list = nil
212 }
213 }
214 if l.opened {
215 C.mbedtls_net_free(&l.server_fd)
216 }
217}
218
219// internal function to init and bind the listener
220fn (mut l SSLListener) init() ! {
221 $if trace_ssl ? {
222 eprintln(@METHOD)
223 }
224
225 lhost, lport := net.split_address(l.saddr)!
226 if l.config.cert == '' || l.config.cert_key == '' {
227 return error('net.mbedtls SSLListener.init, no certificate or key provided')
228 }
229 if l.config.validate && l.config.verify == '' {
230 return error('net.mbedtls SSLListener.init, no root CA provided')
231 }
232 C.mbedtls_net_init(&l.server_fd)
233 C.mbedtls_ssl_init(&l.ssl)
234 C.mbedtls_ssl_config_init(&l.conf)
235 init_rng(mut l.ctr_drbg, mut l.entropy)!
236 l.certs = &SSLCerts{}
237 C.mbedtls_x509_crt_init(&l.certs.client_cert)
238 C.mbedtls_pk_init(&l.certs.client_key)
239
240 unsafe {
241 C.mbedtls_ssl_conf_rng(&l.conf, C.mbedtls_ctr_drbg_random, &l.ctr_drbg)
242 }
243
244 mut ret := 0
245
246 if l.config.in_memory_verification {
247 l.certs = new_sslcerts_in_memory_with_rng(l.config.verify, l.config.cert,
248 l.config.cert_key, &l.ctr_drbg) or {
249 return error('net.mbedtls SSLListener.init, cert failure 1, err: ${err}')
250 }
251 } else {
252 l.certs = new_sslcerts_from_file_with_rng(l.config.verify, l.config.cert,
253 l.config.cert_key, &l.ctr_drbg) or {
254 return error('net.mbedtls SSLListener.init, cert failure 2, err: ${err}')
255 }
256 }
257
258 if l.config.validate {
259 C.mbedtls_ssl_conf_authmode(&l.conf, C.MBEDTLS_SSL_VERIFY_REQUIRED)
260 }
261
262 mut bind_ip := unsafe { nil }
263 if lhost != '' {
264 bind_ip = voidptr(lhost.str)
265 }
266 bind_port := lport.str()
267
268 ret = C.mbedtls_net_bind(&l.server_fd, bind_ip, voidptr(bind_port.str), C.MBEDTLS_NET_PROTO_TCP)
269
270 if ret != 0 {
271 return error_with_code("net.mbedtls SSLListener.init, mbedtls_net_bind can't bind to ${l.saddr} error ret: ${ret}",
272 ret)
273 }
274
275 ret = C.mbedtls_ssl_config_defaults(&l.conf, C.MBEDTLS_SSL_IS_SERVER,
276 C.MBEDTLS_SSL_TRANSPORT_STREAM, C.MBEDTLS_SSL_PRESET_DEFAULT)
277 if ret != 0 {
278 return error_with_code("net.mbedtls SSLListener.init, mbedtls_ssl_config_defaults can't set config defaults ret: ${ret}",
279 ret)
280 }
281 listener_read_timeout := ssl_listener_read_timeout(l.config)
282 $if trace_mbedtls_timeouts ? {
283 dump(listener_read_timeout)
284 }
285 C.mbedtls_ssl_conf_read_timeout(&l.conf, ssl_read_timeout_ms(listener_read_timeout))
286
287 C.mbedtls_ssl_conf_ca_chain(&l.conf, &l.certs.cacert, unsafe { nil })
288 ret = C.mbedtls_ssl_conf_own_cert(&l.conf, &l.certs.client_cert, &l.certs.client_key)
289 if ret != 0 {
290 return error_with_code("net.mbedtls SSLListener.init, mbedtls_ssl_conf_own_cert can't load certificate ret: ${ret}",
291 ret)
292 }
293
294 // Advertise ALPN protocols for accepted connections to select from.
295 // See the matching client-side logic in SSLConn.init for lifetime notes.
296 if l.config.alpn_protocols.len > 0 {
297 n := l.config.alpn_protocols.len
298 l.alpn_list = unsafe { &&char(C.malloc(isize((n + 1) * int(sizeof(voidptr))))) }
299 if l.alpn_list == unsafe { nil } {
300 return error('net.mbedtls SSLListener.init, failed to allocate ALPN list')
301 }
302 unsafe {
303 for i, proto in l.config.alpn_protocols {
304 l.alpn_list[i] = &char(proto.str)
305 }
306 l.alpn_list[n] = &char(0)
307 }
308 ret = C.mbedtls_ssl_conf_alpn_protocols(&l.conf, voidptr(l.alpn_list))
309 if ret != 0 {
310 return error_with_code('net.mbedtls SSLListener.init, mbedtls_ssl_conf_alpn_protocols failed ret: ${ret}',
311 ret)
312 }
313 }
314
315 ret = C.mbedtls_ssl_setup(&l.ssl, &l.conf)
316 if ret != 0 {
317 return error_with_code("net.mbedtls SSLListener.init, mbedtls_ssl_setup can't setup ssl ret: ${ret}",
318 ret)
319 }
320
321 if get_cert_callback := l.config.get_certificate {
322 l.init_sni(get_cert_callback)
323 }
324}
325
326// setup SNI callback
327fn (mut l SSLListener) init_sni(get_cert_callback fn (mut SSLListener, string) !&SSLCerts) {
328 $if trace_ssl ? {
329 eprintln(@METHOD)
330 }
331 C.mbedtls_ssl_conf_sni(&l.conf, fn [get_cert_callback, mut l] (p_info voidptr, ssl &C.mbedtls_ssl_context, name &char, lng int) int {
332 host := unsafe { name.vstring_literal_with_len(lng) }
333 if certs := get_cert_callback(mut l, host) {
334 return C.mbedtls_ssl_set_hs_own_cert(ssl, &certs.client_cert, &certs.client_key)
335 } else {
336 return -1
337 }
338 }, &l.conf)
339}
340
341// accepts a new connection and returns a SSLConn of the connected client
342pub fn (mut l SSLListener) accept() !&SSLConn {
343 mut conn := l.accept_tcp_connection()!
344
345 C.mbedtls_ssl_init(&conn.ssl)
346 C.mbedtls_ssl_config_init(&conn.conf)
347 ret := C.mbedtls_ssl_setup(&conn.ssl, &l.conf)
348 if ret != 0 {
349 conn.shutdown() or {}
350 return error_with_code('net.mbedtls SSLListener.accept, mbedtls_ssl_setup SSL setup failed ret: ${ret}',
351 ret)
352 }
353
354 C.mbedtls_ssl_set_bio(&conn.ssl, &conn.server_fd, C.mbedtls_net_send, C.mbedtls_net_recv,
355 C.mbedtls_net_recv_timeout)
356 conn.server_handshake(net.infinite_timeout)!
357 return conn
358}
359
360fn (mut l SSLListener) accept_tcp_connection() !&SSLConn {
361 mut conn := &SSLConn{
362 config: l.config
363 duration: ssl_listener_read_timeout(l.config)
364 read_timeout: ssl_listener_read_timeout(l.config)
365 opened: true
366 }
367 ip := [16]u8{}
368 iplen := usize(0)
369
370 ret := C.mbedtls_net_accept(&l.server_fd, &conn.server_fd, &ip, 16, &iplen)
371 if ret != 0 {
372 return error_with_code("net.mbedtls SSLListener.accept, mbedtls_net_accept can't accept connection ret: ${ret}",
373 ret)
374 }
375 conn.handle = conn.server_fd.fd
376 conn.owns_socket = true
377 if iplen == 4 {
378 conn.ip = '${ip[0]}.${ip[1]}.${ip[2]}.${ip[3]}'
379 }
380 return conn
381}
382
383fn (mut conn SSLConn) server_handshake(timeout time.Duration) ! {
384 deadline := ssl_timeout_deadline(timeout)
385 mut ret := C.mbedtls_ssl_handshake(&conn.ssl)
386 for ret != 0 {
387 match ret {
388 C.MBEDTLS_ERR_SSL_WANT_READ {
389 conn.wait_for_read(ssl_remaining_timeout(deadline)) or {
390 conn.shutdown() or {}
391 return err
392 }
393 }
394 C.MBEDTLS_ERR_SSL_WANT_WRITE {
395 conn.wait_for_write(ssl_remaining_timeout(deadline)) or {
396 conn.shutdown() or {}
397 return err
398 }
399 }
400 else {
401 conn.shutdown() or {
402 $if trace_ssl ? {
403 eprintln('${@METHOD} shutdown ---> res: ${err}')
404 }
405 }
406 return error_with_code('net.mbedtls SSLListener.accept, mbedtls_ssl_handshake failed 1; handshake ret: ${ret}',
407 ret)
408 }
409 }
410
411 ret = C.mbedtls_ssl_handshake(&conn.ssl)
412 }
413}
414
415// accept_with_timeout waits up to `timeout` for a new client before accepting it.
416pub fn (mut l SSLListener) accept_with_timeout(timeout time.Duration) !&SSLConn {
417 return l.accept_with_timeouts(timeout, timeout)
418}
419
420// accept_with_timeouts waits up to `accept_timeout` for a new client, then
421// waits up to `handshake_timeout` for the TLS server handshake to complete.
422pub fn (mut l SSLListener) accept_with_timeouts(accept_timeout time.Duration, handshake_timeout time.Duration) !&SSLConn {
423 wait_for(l.server_fd.fd, .read, accept_timeout)!
424 mut conn := l.accept_tcp_connection()!
425
426 C.mbedtls_ssl_init(&conn.ssl)
427 C.mbedtls_ssl_config_init(&conn.conf)
428 net.set_blocking(conn.handle, false) or {
429 conn.shutdown() or {}
430 return err
431 }
432 ret := C.mbedtls_ssl_setup(&conn.ssl, &l.conf)
433 if ret != 0 {
434 conn.shutdown() or {}
435 return error_with_code('net.mbedtls SSLListener.accept, mbedtls_ssl_setup SSL setup failed ret: ${ret}',
436 ret)
437 }
438
439 C.v_mbedtls_ssl_set_bio_nonblocking(&conn.ssl, &conn.server_fd)
440 conn.server_handshake(handshake_timeout)!
441 net.set_blocking(conn.handle, true) or {
442 conn.shutdown() or {}
443 return err
444 }
445 C.mbedtls_ssl_set_bio(&conn.ssl, &conn.server_fd, C.mbedtls_net_send, C.mbedtls_net_recv,
446 C.mbedtls_net_recv_timeout)
447 return conn
448}
449
450@[params]
451pub struct SSLConnectConfig {
452pub:
453 verify string // the path to a rootca.pem file, containing trusted CA certificate(s)
454 cert string // the path to a cert.pem file, containing client certificate(s) for the request
455 cert_key string // the path to a key.pem file, containing private keys for the client certificate(s)
456 validate bool // set this to true, if you want to stop requests, when their certificates are found to be invalid
457
458 in_memory_verification bool // if true, verify, cert, and cert_key are read from memory, not from a file
459
460 get_certificate ?fn (mut SSLListener, string) !&SSLCerts
461
462 read_timeout time.Duration = default_mbedtls_client_read_timeout // the SSL client read timeout
463
464 alpn_protocols []string // the list of ALPN protocols to advertise, e.g. ['h2', 'http/1.1']; empty means no ALPN extension is sent
465}
466
467fn ssl_read_timeout_ms(timeout time.Duration) u32 {
468 if timeout <= 0 || timeout == net.infinite_timeout {
469 return 0
470 }
471 timeout_ms := timeout.milliseconds()
472 if timeout_ms > i64(max_u32) {
473 return max_u32
474 }
475 return u32(timeout_ms)
476}
477
478fn ssl_listener_read_timeout(config SSLConnectConfig) time.Duration {
479 if config.read_timeout == default_mbedtls_client_read_timeout {
480 return default_mbedtls_server_read_timeout
481 }
482 return config.read_timeout
483}
484
485fn ssl_timeout_deadline(timeout time.Duration) time.Time {
486 if timeout <= 0 || timeout == net.infinite_timeout {
487 return time.unix(0)
488 }
489 return time.now().add(timeout)
490}
491
492fn ssl_remaining_timeout(deadline time.Time) time.Duration {
493 if deadline.unix() == 0 {
494 return net.infinite_timeout
495 }
496 remaining := deadline - time.now()
497 if remaining <= 0 {
498 return time.nanosecond
499 }
500 return remaining
501}
502
503// read_timeout returns the current SSL read timeout.
504pub fn (s &SSLConn) read_timeout() time.Duration {
505 return s.read_timeout
506}
507
508// set_read_timeout sets the SSL read timeout for subsequent operations.
509pub fn (mut s SSLConn) set_read_timeout(timeout time.Duration) {
510 s.read_timeout = timeout
511 s.duration = timeout
512 C.mbedtls_ssl_conf_read_timeout(&s.conf, ssl_read_timeout_ms(timeout))
513}
514
515// new_ssl_conn returns a new SSLConn with the given config.
516pub fn new_ssl_conn(config SSLConnectConfig) !&SSLConn {
517 $if trace_ssl ? {
518 eprintln(@METHOD)
519 }
520 mut conn := &SSLConn{
521 config: config
522 duration: config.read_timeout
523 read_timeout: config.read_timeout
524 }
525 conn.init()!
526 return conn
527}
528
529// Select operation
530enum Select {
531 read
532 write
533 except
534}
535
536// close terminates the ssl connection and does cleanup
537pub fn (mut s SSLConn) close() ! {
538 s.shutdown()!
539}
540
541// shutdown terminates the ssl connection and does cleanup
542pub fn (mut s SSLConn) shutdown() ! {
543 $if trace_ssl ? {
544 eprintln(@METHOD)
545 }
546 if !s.opened {
547 return error('net.mbedtls SSLConn.shutdown, connection was not open')
548 }
549 if unsafe { s.certs != nil } {
550 C.mbedtls_x509_crt_free(&s.certs.cacert)
551 C.mbedtls_x509_crt_free(&s.certs.client_cert)
552 C.mbedtls_pk_free(&s.certs.client_key)
553 }
554 C.mbedtls_ssl_free(&s.ssl)
555 C.mbedtls_ssl_config_free(&s.conf)
556 free_rng(mut s.ctr_drbg, mut s.entropy)
557 if s.alpn_list != unsafe { nil } {
558 unsafe {
559 C.free(s.alpn_list)
560 s.alpn_list = nil
561 }
562 }
563 if s.owns_socket {
564 net.shutdown(s.handle)
565 net.close(s.handle)!
566 }
567}
568
569// negotiated_alpn returns the ALPN protocol selected during the TLS
570// handshake (e.g. 'h2' or 'http/1.1'), or an empty string if no protocol
571// was negotiated.
572pub fn (s &SSLConn) negotiated_alpn() string {
573 // mbedtls_ssl_get_alpn_protocol returns a `const char *`; cast away const
574 // for V, since we only read from it (and copy it below).
575 p := &char(C.mbedtls_ssl_get_alpn_protocol(&s.ssl))
576 if p == unsafe { nil } {
577 return ''
578 }
579 return unsafe { cstring_to_vstring(p) }
580}
581
582// connect to server using mbedtls
583fn (mut s SSLConn) init() ! {
584 $if trace_ssl ? {
585 eprintln(@METHOD)
586 }
587 C.mbedtls_net_init(&s.server_fd)
588 C.mbedtls_ssl_init(&s.ssl)
589 C.mbedtls_ssl_config_init(&s.conf)
590 init_rng(mut s.ctr_drbg, mut s.entropy)!
591 mut ret := 0
592 ret = C.mbedtls_ssl_config_defaults(&s.conf, C.MBEDTLS_SSL_IS_CLIENT,
593 C.MBEDTLS_SSL_TRANSPORT_STREAM, C.MBEDTLS_SSL_PRESET_DEFAULT)
594 if ret != 0 {
595 return error_with_code('net.mbedtls SSLConn.init, mbedtls_ssl_config_defaults failed to set SSL configuration ret: ${ret}',
596 ret)
597 }
598 $if trace_mbedtls_timeouts ? {
599 dump(s.read_timeout)
600 }
601 s.set_read_timeout(s.read_timeout)
602
603 unsafe {
604 C.mbedtls_ssl_conf_rng(&s.conf, C.mbedtls_ctr_drbg_random, &s.ctr_drbg)
605 }
606
607 // Advertise ALPN protocols (e.g. ['h2', 'http/1.1']) when requested.
608 // mbedtls expects a NUL-terminated array of NUL-terminated C strings, and
609 // keeps the pointer without copying, so both the array and the backing
610 // strings must outlive the config. The strings live in s.config; the array
611 // is allocated here and freed in shutdown().
612 if s.config.alpn_protocols.len > 0 {
613 n := s.config.alpn_protocols.len
614 s.alpn_list = unsafe { &&char(C.malloc(isize((n + 1) * int(sizeof(voidptr))))) }
615 if s.alpn_list == unsafe { nil } {
616 return error('net.mbedtls SSLConn.init, failed to allocate ALPN list')
617 }
618 unsafe {
619 for i, proto in s.config.alpn_protocols {
620 s.alpn_list[i] = &char(proto.str)
621 }
622 s.alpn_list[n] = &char(0)
623 }
624 ret = C.mbedtls_ssl_conf_alpn_protocols(&s.conf, voidptr(s.alpn_list))
625 if ret != 0 {
626 return error_with_code('net.mbedtls SSLConn.init, mbedtls_ssl_conf_alpn_protocols failed ret: ${ret}',
627 ret)
628 }
629 }
630 if s.config.verify != '' || s.config.cert != '' || s.config.cert_key != '' {
631 s.certs = &SSLCerts{}
632 C.mbedtls_x509_crt_init(&s.certs.cacert)
633 C.mbedtls_x509_crt_init(&s.certs.client_cert)
634 C.mbedtls_pk_init(&s.certs.client_key)
635 }
636
637 if s.config.in_memory_verification {
638 if s.config.verify != '' {
639 ret = C.mbedtls_x509_crt_parse(&s.certs.cacert, s.config.verify.str,
640
641 s.config.verify.len + 1)
642 }
643 if s.config.cert != '' {
644 ret = C.mbedtls_x509_crt_parse(&s.certs.client_cert, s.config.cert.str,
645
646 s.config.cert.len + 1)
647 }
648 if s.config.cert_key != '' {
649 unsafe {
650 ret = C.mbedtls_pk_parse_key(&s.certs.client_key, s.config.cert_key.str,
651
652 s.config.cert_key.len + 1, 0, 0, C.mbedtls_ctr_drbg_random, &s.ctr_drbg)
653 }
654 }
655 } else {
656 if s.config.verify != '' {
657 ret = C.mbedtls_x509_crt_parse_file(&s.certs.cacert, &char(s.config.verify.str))
658 }
659 if s.config.cert != '' {
660 ret = C.mbedtls_x509_crt_parse_file(&s.certs.client_cert, &char(s.config.cert.str))
661 }
662 if s.config.cert_key != '' {
663 unsafe {
664 ret = C.mbedtls_pk_parse_keyfile(&s.certs.client_key, &char(s.config.cert_key.str),
665 0, C.mbedtls_ctr_drbg_random, &s.ctr_drbg)
666 }
667 }
668 }
669 if ret < 0 {
670 return error_with_code('net.mbedtls SSLConn.init, failed to set certificates, ret: ${ret}',
671 ret)
672 }
673
674 if unsafe { s.certs != nil } {
675 C.mbedtls_ssl_conf_ca_chain(&s.conf, &s.certs.cacert, 0)
676 C.mbedtls_ssl_conf_own_cert(&s.conf, &s.certs.client_cert, &s.certs.client_key)
677 }
678
679 if s.config.validate {
680 C.mbedtls_ssl_conf_authmode(&s.conf, C.MBEDTLS_SSL_VERIFY_REQUIRED)
681 } else {
682 C.mbedtls_ssl_conf_authmode(&s.conf, C.MBEDTLS_SSL_VERIFY_OPTIONAL)
683 }
684
685 ret = C.mbedtls_ssl_setup(&s.ssl, &s.conf)
686 if ret != 0 {
687 return error_with_code('net.mbedtls SSLConn.init, mbedtls_ssl_setup failed to setup SSL connection ret: ${ret}',
688 ret)
689 }
690}
691
692// connect sets up an ssl connection on an existing TCP connection
693pub fn (mut s SSLConn) connect(mut tcp_conn net.TcpConn, hostname string) ! {
694 $if trace_ssl ? {
695 eprintln('${@METHOD} hostname: ${hostname}')
696 }
697 if s.opened {
698 return error('net.mbedtls SSLConn.connect, ssl connection was already open')
699 }
700 s.handle = tcp_conn.sock.handle
701 s.set_read_timeout(tcp_conn.read_timeout())
702 mut ret := C.mbedtls_ssl_set_hostname(&s.ssl, &char(hostname.str))
703 if ret != 0 {
704 return error_with_code('net.mbedtls SSLConn.connect, mbedtls_ssl_set_hostname failed to set hostname',
705 ret)
706 }
707 s.server_fd.fd = s.handle
708 C.mbedtls_ssl_set_bio(&s.ssl, &s.server_fd, C.mbedtls_net_send, C.mbedtls_net_recv,
709 C.mbedtls_net_recv_timeout)
710 ret = C.mbedtls_ssl_handshake(&s.ssl)
711 if ret != 0 {
712 return error_with_code('net.mbedtls SSLConn.connect, mbedtls_ssl_handshake failed 2; ret: ${ret}',
713 ret)
714 }
715 s.opened = true
716}
717
718// dial opens an ssl connection on hostname:port
719pub fn (mut s SSLConn) dial(hostname string, port int) ! {
720 $if trace_ssl ? {
721 eprintln('${@METHOD} hostname: ${hostname} | port: ${port}')
722 }
723 if s.opened {
724 return error('net.mbedtls SSLConn.dial, the ssl connection was already open')
725 }
726 mut connected := false
727 defer {
728 if !connected {
729 if unsafe { s.certs != nil } {
730 C.mbedtls_x509_crt_free(&s.certs.cacert)
731 C.mbedtls_x509_crt_free(&s.certs.client_cert)
732 C.mbedtls_pk_free(&s.certs.client_key)
733 s.certs = unsafe { nil }
734 }
735 C.mbedtls_net_free(&s.server_fd)
736 C.mbedtls_ssl_free(&s.ssl)
737 C.mbedtls_ssl_config_free(&s.conf)
738 free_rng(mut s.ctr_drbg, mut s.entropy)
739 s.handle = 0
740 s.owns_socket = false
741 }
742 }
743 s.owns_socket = true
744
745 mut ret := C.mbedtls_ssl_set_hostname(&s.ssl, &char(hostname.str))
746 if ret != 0 {
747 return error_with_code('net.mbedtls SSLConn.dial, failed to set hostname', ret)
748 }
749
750 port_str := port.str()
751 ret = C.mbedtls_net_connect(&s.server_fd, &char(hostname.str), &char(port_str.str),
752 C.MBEDTLS_NET_PROTO_TCP)
753 if ret != 0 {
754 return error_with_code('net.mbedtls SSLConn.dial, failed to connect to host', ret)
755 }
756 C.mbedtls_ssl_set_bio(&s.ssl, &s.server_fd, C.mbedtls_net_send, C.mbedtls_net_recv,
757 C.mbedtls_net_recv_timeout)
758 s.handle = s.server_fd.fd
759 ret = C.mbedtls_ssl_handshake(&s.ssl)
760 if ret != 0 {
761 return error_with_code('net.mbedtls SSLConn.dial, mbedtls_ssl_handshake failed 3; ret: ${ret}',
762 ret)
763 }
764 s.opened = true
765 connected = true
766}
767
768// addr retrieves the local ip address and port number for this connection
769pub fn (s &SSLConn) addr() !net.Addr {
770 return net.addr_from_socket_handle(s.handle)
771}
772
773// peer_addr retrieves the ip address and port number used by the peer
774pub fn (s &SSLConn) peer_addr() !net.Addr {
775 return net.peer_addr_from_socket_handle(s.handle)
776}
777
778// socket_read_into_ptr reads `len` bytes into `buf`
779pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &u8, len int) !int {
780 mut res := 0
781 $if trace_ssl ? {
782 defer(fn) {
783 if len > 0 {
784 eprintln('${@METHOD} res: ${res}: buf_ptr: ${voidptr(buf_ptr):x}, len: ${len}, hex: ${unsafe { buf_ptr.vbytes(len).hex() }} data: `${unsafe { buf_ptr.vstring_with_len(len) }}`')
785 }
786 }
787 }
788
789 deadline := ssl_timeout_deadline(s.duration)
790 // s.wait_for_read(deadline - time.now())!
791 for {
792 res = C.mbedtls_ssl_read(&s.ssl, buf_ptr, len)
793 if res > 0 {
794 return res
795 } else if res == 0 {
796 $if trace_ssl ? {
797 eprintln('${@METHOD} ---> res: io.Eof')
798 }
799 return io.Eof{}
800 } else {
801 match res {
802 C.MBEDTLS_ERR_SSL_WANT_READ {
803 s.wait_for_read(ssl_remaining_timeout(deadline)) or {
804 $if trace_ssl ? {
805 eprintln('${@METHOD} ---> res: ${err}, C.MBEDTLS_ERR_SSL_WANT_READ')
806 }
807 return err
808 }
809 }
810 C.MBEDTLS_ERR_SSL_WANT_WRITE {
811 s.wait_for_write(ssl_remaining_timeout(deadline)) or {
812 $if trace_ssl ? {
813 eprintln('${@METHOD} ---> res: ${err}, C.MBEDTLS_ERR_SSL_WANT_WRITE')
814 }
815 return err
816 }
817 }
818 C.MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET {
819 // TLS 1.3 servers can deliver tickets asynchronously while the
820 // connection is otherwise healthy. Keep reading application data.
821 continue
822 }
823 C.MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY {
824 $if trace_ssl ? {
825 eprintln('${@METHOD} ---> res: 0 C.MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY')
826 }
827 return 0
828 }
829 C.MBEDTLS_ERR_SSL_TIMEOUT {
830 $if trace_ssl ? {
831 eprintln('${@METHOD} ---> res: C.MBEDTLS_ERR_SSL_TIMEOUT')
832 }
833 return error_with_code('net.mbedtls SSLConn.socket_read_into_ptr, did not receive any data within ${s.read_timeout.milliseconds()}ms. Use conn.set_read_timeout(...) to increase the timeout',
834 res)
835 }
836 else {
837 $if trace_ssl ? {
838 eprintln('${@METHOD} ---> res: could not read using SSL')
839 }
840 return error_with_code('net.mbedtls SSLConn.socket_read_into_ptr, could not read using SSL',
841 res)
842 }
843 }
844 }
845 }
846 // Dead code, just to satisfy the compiler:
847 return error('net.mbedtls SSLConn.socket_read_into_ptr, unknown error')
848}
849
850// read reads data from the ssl connection into `buffer`
851pub fn (mut s SSLConn) read(mut buffer []u8) !int {
852 $if trace_ssl ? {
853 eprintln('${@METHOD} buffer.len: ${buffer.len}')
854 }
855 return s.socket_read_into_ptr(&u8(buffer.data), buffer.len)
856}
857
858// write_ptr writes `len` bytes from `bytes` to the ssl connection
859pub fn (mut s SSLConn) write_ptr(bytes &u8, len int) !int {
860 mut total_sent := 0
861 $if trace_ssl ? {
862 defer(fn) {
863 eprintln('${@METHOD} total_sent: ${total_sent}, bytes: ${voidptr(bytes):x}, len: ${len}, hex: ${unsafe { bytes.vbytes(len).hex() }}, data:-=-=-=-\n${unsafe { bytes.vstring_with_len(len) }}\n-=-=-=-')
864 }
865 }
866
867 s.last_write_sent = 0
868 deadline := ssl_timeout_deadline(s.duration)
869 unsafe {
870 mut ptr_base := bytes
871 for total_sent < len {
872 ptr := ptr_base + total_sent
873 remaining := len - total_sent
874 mut sent := C.mbedtls_ssl_write(&s.ssl, ptr, remaining)
875 if sent <= 0 {
876 // The write did not fully complete; a retryable error can leave a
877 // record partially flushed, so the sent count is no longer
878 // provable. Mark it indeterminate (a later full success below
879 // resets it to the exact length).
880 s.last_write_sent = -1
881 match sent {
882 C.MBEDTLS_ERR_SSL_WANT_READ {
883 s.wait_for_read(ssl_remaining_timeout(deadline))!
884 continue
885 }
886 C.MBEDTLS_ERR_SSL_WANT_WRITE {
887 s.wait_for_write(ssl_remaining_timeout(deadline))!
888 continue
889 }
890 else {
891 $if trace_ssl ? {
892 eprintln('${@METHOD} ---> res: could not write SSL, sent: ${sent}')
893 }
894 return error_with_code('net.mbedtls SSLConn.write_ptr, could not write using SSL',
895 sent)
896 }
897 }
898 }
899 total_sent += sent
900 s.last_write_sent = total_sent
901 }
902 }
903 return total_sent
904}
905
906// write writes data from `bytes` to the ssl connection
907pub fn (mut s SSLConn) write(bytes []u8) !int {
908 return s.write_ptr(&u8(bytes.data), bytes.len)
909}
910
911// write_string writes a string to the ssl connection
912pub fn (mut s SSLConn) write_string(str string) !int {
913 $if trace_ssl ? {
914 eprintln('${@METHOD} str: ${str}')
915 }
916 return s.write_ptr(str.str, str.len)
917}
918
919// Select waits for an io operation (specified by parameter `test`) to be available
920fn select(handle int, test Select, timeout time.Duration) !bool {
921 $if trace_ssl ? {
922 eprintln('${@METHOD} handle: ${handle}, timeout: ${timeout}')
923 }
924 set := C.fd_set{}
925 C.FD_ZERO(&set)
926 C.FD_SET(handle, &set)
927
928 is_infinite := timeout <= 0 || timeout == net.infinite_timeout
929 deadline := ssl_timeout_deadline(timeout)
930 mut remaining_time := if is_infinite { i64(0) } else { timeout.milliseconds() }
931 for is_infinite || remaining_time > 0 {
932 seconds := remaining_time / 1000
933 microseconds := (remaining_time % 1000) * 1000
934
935 tt := C.timeval{
936 tv_sec: u64(seconds)
937 tv_usec: u64(microseconds)
938 }
939 timeval_timeout := if is_infinite { &C.timeval(unsafe { nil }) } else { &tt }
940
941 mut res := -1
942 match test {
943 .read {
944 res = net.socket_error(C.select(handle + 1, &set, C.NULL, C.NULL, timeval_timeout))!
945 }
946 .write {
947 res = net.socket_error(C.select(handle + 1, C.NULL, &set, C.NULL, timeval_timeout))!
948 }
949 .except {
950 res = net.socket_error(C.select(handle + 1, C.NULL, C.NULL, &set, timeval_timeout))!
951 }
952 }
953
954 if res < 0 {
955 if C.errno == C.EINTR {
956 // errno is 4, Spurious wakeup from signal, keep waiting
957 if !is_infinite {
958 remaining_time = ssl_remaining_timeout(deadline).milliseconds()
959 }
960 continue
961 }
962 cerr := C.errno
963 return error_with_code('net.mbedtls select, failed, res: ${res}', cerr)
964 } else if res == 0 {
965 return net.err_timed_out
966 }
967
968 res = C.FD_ISSET(handle, &set)
969 $if trace_ssl ? {
970 eprintln('${@METHOD} ---> res: ${res}')
971 }
972 return res != 0
973 }
974
975 return net.err_timed_out
976}
977
978// wait_for wraps the common wait code
979fn wait_for(handle int, what Select, timeout time.Duration) ! {
980 ready := select(handle, what, timeout)!
981 if ready {
982 return
983 }
984
985 return net.err_timed_out
986}
987
988// wait_for_write waits for a write io operation to be available
989fn (mut s SSLConn) wait_for_write(timeout time.Duration) ! {
990 return wait_for(s.handle, .write, timeout)
991}
992
993// wait_for_read waits for a read io operation to be available
994fn (mut s SSLConn) wait_for_read(timeout time.Duration) ! {
995 return wait_for(s.handle, .read, timeout)
996}
997