v4 / vlib / net / mbedtls / ssl_connection.c.v
1024 lines · 929 sloc · 31.75 KB · 6e2d911004257b2b6384e3a12ad0c050275f4ed6
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 &u8, lng usize) int {
332 host := unsafe { name.vstring_literal_with_len(int(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
383// do_handshake_loop drives the non-blocking TLS server handshake to completion
384// (or `deadline`). It never calls shutdown: on any error it just returns, leaving
385// cleanup to the caller. server_handshake wraps it for the synchronous accept
386// path (self-shutdown on error); the threaded server path (complete_handshake)
387// lets its worker defer own the single shutdown instead.
388fn (mut conn SSLConn) do_handshake_loop(deadline time.Time) ! {
389 mut ret := C.mbedtls_ssl_handshake(&conn.ssl)
390 for ret != 0 {
391 match ret {
392 C.MBEDTLS_ERR_SSL_WANT_READ {
393 conn.wait_for_read(ssl_remaining_timeout(deadline))!
394 }
395 C.MBEDTLS_ERR_SSL_WANT_WRITE {
396 conn.wait_for_write(ssl_remaining_timeout(deadline))!
397 }
398 else {
399 return error_with_code('net.mbedtls SSLListener.accept, mbedtls_ssl_handshake failed 1; handshake ret: ${ret}',
400 ret)
401 }
402 }
403
404 ret = C.mbedtls_ssl_handshake(&conn.ssl)
405 }
406}
407
408fn (mut conn SSLConn) server_handshake(timeout time.Duration) ! {
409 deadline := ssl_timeout_deadline(timeout)
410 conn.do_handshake_loop(deadline) or {
411 conn.shutdown() or {
412 $if trace_ssl ? {
413 eprintln('${@METHOD} shutdown ---> res: ${err}')
414 }
415 }
416 return err
417 }
418}
419
420// accept_with_timeout waits up to `timeout` for a new client before accepting it.
421pub fn (mut l SSLListener) accept_with_timeout(timeout time.Duration) !&SSLConn {
422 return l.accept_with_timeouts(timeout, timeout)
423}
424
425// accept_raw_with_timeout waits up to `accept_timeout` for a new client, accepts
426// the raw TCP connection and sets up its (non-blocking) SSL context, but does NOT
427// perform the TLS handshake. The returned conn is non-blocking with a non-blocking
428// bio; the caller must run conn.complete_handshake to finish negotiation. This lets
429// the threaded server accept on one thread and handshake on a worker thread.
430pub fn (mut l SSLListener) accept_raw_with_timeout(accept_timeout time.Duration) !&SSLConn {
431 wait_for(l.server_fd.fd, .read, accept_timeout)!
432 mut conn := l.accept_tcp_connection()!
433
434 C.mbedtls_ssl_init(&conn.ssl)
435 C.mbedtls_ssl_config_init(&conn.conf)
436 net.set_blocking(conn.handle, false) or {
437 conn.shutdown() or {}
438 return err
439 }
440 ret := C.mbedtls_ssl_setup(&conn.ssl, &l.conf)
441 if ret != 0 {
442 conn.shutdown() or {}
443 return error_with_code('net.mbedtls SSLListener.accept, mbedtls_ssl_setup SSL setup failed ret: ${ret}',
444 ret)
445 }
446
447 C.v_mbedtls_ssl_set_bio_nonblocking(&conn.ssl, &conn.server_fd)
448 return conn
449}
450
451// complete_handshake finishes the TLS server handshake on a conn returned by
452// accept_raw_with_timeout, waiting up to `timeout`, then restores blocking mode
453// and the blocking bio. It never calls shutdown: on any error it returns and the
454// caller owns cleanup (so the conn is shut down exactly once, by the caller).
455pub fn (mut conn SSLConn) complete_handshake(timeout time.Duration) ! {
456 deadline := ssl_timeout_deadline(timeout)
457 conn.do_handshake_loop(deadline)!
458 net.set_blocking(conn.handle, true)!
459 C.mbedtls_ssl_set_bio(&conn.ssl, &conn.server_fd, C.mbedtls_net_send, C.mbedtls_net_recv,
460 C.mbedtls_net_recv_timeout)
461}
462
463// accept_with_timeouts waits up to `accept_timeout` for a new client, then
464// waits up to `handshake_timeout` for the TLS server handshake to complete.
465pub fn (mut l SSLListener) accept_with_timeouts(accept_timeout time.Duration, handshake_timeout time.Duration) !&SSLConn {
466 mut conn := l.accept_raw_with_timeout(accept_timeout)!
467 conn.complete_handshake(handshake_timeout) or {
468 conn.shutdown() or {}
469 return err
470 }
471 return conn
472}
473
474@[params]
475pub struct SSLConnectConfig {
476pub:
477 verify string // the path to a rootca.pem file, containing trusted CA certificate(s)
478 cert string // the path to a cert.pem file, containing client certificate(s) for the request
479 cert_key string // the path to a key.pem file, containing private keys for the client certificate(s)
480 validate bool // set this to true, if you want to stop requests, when their certificates are found to be invalid
481
482 in_memory_verification bool // if true, verify, cert, and cert_key are read from memory, not from a file
483
484 get_certificate ?fn (mut SSLListener, string) !&SSLCerts
485
486 read_timeout time.Duration = default_mbedtls_client_read_timeout // the SSL client read timeout
487
488 alpn_protocols []string // the list of ALPN protocols to advertise, e.g. ['h2', 'http/1.1']; empty means no ALPN extension is sent
489}
490
491fn ssl_read_timeout_ms(timeout time.Duration) u32 {
492 if timeout <= 0 || timeout == net.infinite_timeout {
493 return 0
494 }
495 timeout_ms := timeout.milliseconds()
496 if timeout_ms > i64(max_u32) {
497 return max_u32
498 }
499 return u32(timeout_ms)
500}
501
502fn ssl_listener_read_timeout(config SSLConnectConfig) time.Duration {
503 if config.read_timeout == default_mbedtls_client_read_timeout {
504 return default_mbedtls_server_read_timeout
505 }
506 return config.read_timeout
507}
508
509fn ssl_timeout_deadline(timeout time.Duration) time.Time {
510 if timeout <= 0 || timeout == net.infinite_timeout {
511 return time.unix(0)
512 }
513 return time.now().add(timeout)
514}
515
516fn ssl_remaining_timeout(deadline time.Time) time.Duration {
517 if deadline.unix() == 0 {
518 return net.infinite_timeout
519 }
520 remaining := deadline - time.now()
521 if remaining <= 0 {
522 return time.nanosecond
523 }
524 return remaining
525}
526
527// read_timeout returns the current SSL read timeout.
528pub fn (s &SSLConn) read_timeout() time.Duration {
529 return s.read_timeout
530}
531
532// set_read_timeout sets the SSL read timeout for subsequent operations.
533pub fn (mut s SSLConn) set_read_timeout(timeout time.Duration) {
534 s.read_timeout = timeout
535 s.duration = timeout
536 C.mbedtls_ssl_conf_read_timeout(&s.conf, ssl_read_timeout_ms(timeout))
537}
538
539// new_ssl_conn returns a new SSLConn with the given config.
540pub fn new_ssl_conn(config SSLConnectConfig) !&SSLConn {
541 $if trace_ssl ? {
542 eprintln(@METHOD)
543 }
544 mut conn := &SSLConn{
545 config: config
546 duration: config.read_timeout
547 read_timeout: config.read_timeout
548 }
549 conn.init()!
550 return conn
551}
552
553// Select operation
554enum Select {
555 read
556 write
557 except
558}
559
560// close terminates the ssl connection and does cleanup
561pub fn (mut s SSLConn) close() ! {
562 s.shutdown()!
563}
564
565// shutdown terminates the ssl connection and does cleanup
566pub fn (mut s SSLConn) shutdown() ! {
567 $if trace_ssl ? {
568 eprintln(@METHOD)
569 }
570 if !s.opened {
571 return error('net.mbedtls SSLConn.shutdown, connection was not open')
572 }
573 // Mark closed before freeing so a second shutdown (e.g. a worker defer racing
574 // close_idle) is a harmless no-op rather than a double-free of the mbedtls
575 // contexts below.
576 s.opened = false
577 if unsafe { s.certs != nil } {
578 C.mbedtls_x509_crt_free(&s.certs.cacert)
579 C.mbedtls_x509_crt_free(&s.certs.client_cert)
580 C.mbedtls_pk_free(&s.certs.client_key)
581 }
582 C.mbedtls_ssl_free(&s.ssl)
583 C.mbedtls_ssl_config_free(&s.conf)
584 free_rng(mut s.ctr_drbg, mut s.entropy)
585 if s.alpn_list != unsafe { nil } {
586 unsafe {
587 C.free(s.alpn_list)
588 s.alpn_list = nil
589 }
590 }
591 if s.owns_socket {
592 net.shutdown(s.handle)
593 net.close(s.handle)!
594 }
595}
596
597// negotiated_alpn returns the ALPN protocol selected during the TLS
598// handshake (e.g. 'h2' or 'http/1.1'), or an empty string if no protocol
599// was negotiated.
600pub fn (s &SSLConn) negotiated_alpn() string {
601 // mbedtls_ssl_get_alpn_protocol returns a `const char *`; cast away const
602 // for V, since we only read from it (and copy it below).
603 p := &char(C.mbedtls_ssl_get_alpn_protocol(&s.ssl))
604 if p == unsafe { nil } {
605 return ''
606 }
607 return unsafe { cstring_to_vstring(p) }
608}
609
610// connect to server using mbedtls
611fn (mut s SSLConn) init() ! {
612 $if trace_ssl ? {
613 eprintln(@METHOD)
614 }
615 C.mbedtls_net_init(&s.server_fd)
616 C.mbedtls_ssl_init(&s.ssl)
617 C.mbedtls_ssl_config_init(&s.conf)
618 init_rng(mut s.ctr_drbg, mut s.entropy)!
619 mut ret := 0
620 ret = C.mbedtls_ssl_config_defaults(&s.conf, C.MBEDTLS_SSL_IS_CLIENT,
621 C.MBEDTLS_SSL_TRANSPORT_STREAM, C.MBEDTLS_SSL_PRESET_DEFAULT)
622 if ret != 0 {
623 return error_with_code('net.mbedtls SSLConn.init, mbedtls_ssl_config_defaults failed to set SSL configuration ret: ${ret}',
624 ret)
625 }
626 $if trace_mbedtls_timeouts ? {
627 dump(s.read_timeout)
628 }
629 s.set_read_timeout(s.read_timeout)
630
631 unsafe {
632 C.mbedtls_ssl_conf_rng(&s.conf, C.mbedtls_ctr_drbg_random, &s.ctr_drbg)
633 }
634
635 // Advertise ALPN protocols (e.g. ['h2', 'http/1.1']) when requested.
636 // mbedtls expects a NUL-terminated array of NUL-terminated C strings, and
637 // keeps the pointer without copying, so both the array and the backing
638 // strings must outlive the config. The strings live in s.config; the array
639 // is allocated here and freed in shutdown().
640 if s.config.alpn_protocols.len > 0 {
641 n := s.config.alpn_protocols.len
642 s.alpn_list = unsafe { &&char(C.malloc(isize((n + 1) * int(sizeof(voidptr))))) }
643 if s.alpn_list == unsafe { nil } {
644 return error('net.mbedtls SSLConn.init, failed to allocate ALPN list')
645 }
646 unsafe {
647 for i, proto in s.config.alpn_protocols {
648 s.alpn_list[i] = &char(proto.str)
649 }
650 s.alpn_list[n] = &char(0)
651 }
652 ret = C.mbedtls_ssl_conf_alpn_protocols(&s.conf, voidptr(s.alpn_list))
653 if ret != 0 {
654 return error_with_code('net.mbedtls SSLConn.init, mbedtls_ssl_conf_alpn_protocols failed ret: ${ret}',
655 ret)
656 }
657 }
658 if s.config.verify != '' || s.config.cert != '' || s.config.cert_key != '' {
659 s.certs = &SSLCerts{}
660 C.mbedtls_x509_crt_init(&s.certs.cacert)
661 C.mbedtls_x509_crt_init(&s.certs.client_cert)
662 C.mbedtls_pk_init(&s.certs.client_key)
663 }
664
665 if s.config.in_memory_verification {
666 if s.config.verify != '' {
667 ret = C.mbedtls_x509_crt_parse(&s.certs.cacert, s.config.verify.str,
668
669 s.config.verify.len + 1)
670 }
671 if s.config.cert != '' {
672 ret = C.mbedtls_x509_crt_parse(&s.certs.client_cert, s.config.cert.str,
673
674 s.config.cert.len + 1)
675 }
676 if s.config.cert_key != '' {
677 unsafe {
678 ret = C.mbedtls_pk_parse_key(&s.certs.client_key, s.config.cert_key.str,
679
680 s.config.cert_key.len + 1, 0, 0, C.mbedtls_ctr_drbg_random, &s.ctr_drbg)
681 }
682 }
683 } else {
684 if s.config.verify != '' {
685 ret = C.mbedtls_x509_crt_parse_file(&s.certs.cacert, &char(s.config.verify.str))
686 }
687 if s.config.cert != '' {
688 ret = C.mbedtls_x509_crt_parse_file(&s.certs.client_cert, &char(s.config.cert.str))
689 }
690 if s.config.cert_key != '' {
691 unsafe {
692 ret = C.mbedtls_pk_parse_keyfile(&s.certs.client_key, &char(s.config.cert_key.str),
693 0, C.mbedtls_ctr_drbg_random, &s.ctr_drbg)
694 }
695 }
696 }
697 if ret < 0 {
698 return error_with_code('net.mbedtls SSLConn.init, failed to set certificates, ret: ${ret}',
699 ret)
700 }
701
702 if unsafe { s.certs != nil } {
703 C.mbedtls_ssl_conf_ca_chain(&s.conf, &s.certs.cacert, 0)
704 C.mbedtls_ssl_conf_own_cert(&s.conf, &s.certs.client_cert, &s.certs.client_key)
705 }
706
707 if s.config.validate {
708 C.mbedtls_ssl_conf_authmode(&s.conf, C.MBEDTLS_SSL_VERIFY_REQUIRED)
709 } else {
710 C.mbedtls_ssl_conf_authmode(&s.conf, C.MBEDTLS_SSL_VERIFY_OPTIONAL)
711 }
712
713 ret = C.mbedtls_ssl_setup(&s.ssl, &s.conf)
714 if ret != 0 {
715 return error_with_code('net.mbedtls SSLConn.init, mbedtls_ssl_setup failed to setup SSL connection ret: ${ret}',
716 ret)
717 }
718}
719
720// connect sets up an ssl connection on an existing TCP connection
721pub fn (mut s SSLConn) connect(mut tcp_conn net.TcpConn, hostname string) ! {
722 $if trace_ssl ? {
723 eprintln('${@METHOD} hostname: ${hostname}')
724 }
725 if s.opened {
726 return error('net.mbedtls SSLConn.connect, ssl connection was already open')
727 }
728 s.handle = tcp_conn.sock.handle
729 s.set_read_timeout(tcp_conn.read_timeout())
730 mut ret := C.mbedtls_ssl_set_hostname(&s.ssl, &char(hostname.str))
731 if ret != 0 {
732 return error_with_code('net.mbedtls SSLConn.connect, mbedtls_ssl_set_hostname failed to set hostname',
733 ret)
734 }
735 s.server_fd.fd = s.handle
736 C.mbedtls_ssl_set_bio(&s.ssl, &s.server_fd, C.mbedtls_net_send, C.mbedtls_net_recv,
737 C.mbedtls_net_recv_timeout)
738 ret = C.mbedtls_ssl_handshake(&s.ssl)
739 if ret != 0 {
740 return error_with_code('net.mbedtls SSLConn.connect, mbedtls_ssl_handshake failed 2; ret: ${ret}',
741 ret)
742 }
743 s.opened = true
744}
745
746// dial opens an ssl connection on hostname:port
747pub fn (mut s SSLConn) dial(hostname string, port int) ! {
748 $if trace_ssl ? {
749 eprintln('${@METHOD} hostname: ${hostname} | port: ${port}')
750 }
751 if s.opened {
752 return error('net.mbedtls SSLConn.dial, the ssl connection was already open')
753 }
754 mut connected := false
755 defer {
756 if !connected {
757 if unsafe { s.certs != nil } {
758 C.mbedtls_x509_crt_free(&s.certs.cacert)
759 C.mbedtls_x509_crt_free(&s.certs.client_cert)
760 C.mbedtls_pk_free(&s.certs.client_key)
761 s.certs = unsafe { nil }
762 }
763 C.mbedtls_net_free(&s.server_fd)
764 C.mbedtls_ssl_free(&s.ssl)
765 C.mbedtls_ssl_config_free(&s.conf)
766 free_rng(mut s.ctr_drbg, mut s.entropy)
767 s.handle = 0
768 s.owns_socket = false
769 }
770 }
771 s.owns_socket = true
772
773 mut ret := C.mbedtls_ssl_set_hostname(&s.ssl, &char(hostname.str))
774 if ret != 0 {
775 return error_with_code('net.mbedtls SSLConn.dial, failed to set hostname', ret)
776 }
777
778 port_str := port.str()
779 ret = C.mbedtls_net_connect(&s.server_fd, &char(hostname.str), &char(port_str.str),
780 C.MBEDTLS_NET_PROTO_TCP)
781 if ret != 0 {
782 return error_with_code('net.mbedtls SSLConn.dial, failed to connect to host', ret)
783 }
784 C.mbedtls_ssl_set_bio(&s.ssl, &s.server_fd, C.mbedtls_net_send, C.mbedtls_net_recv,
785 C.mbedtls_net_recv_timeout)
786 s.handle = s.server_fd.fd
787 ret = C.mbedtls_ssl_handshake(&s.ssl)
788 if ret != 0 {
789 return error_with_code('net.mbedtls SSLConn.dial, mbedtls_ssl_handshake failed 3; ret: ${ret}',
790 ret)
791 }
792 s.opened = true
793 connected = true
794}
795
796// addr retrieves the local ip address and port number for this connection
797pub fn (s &SSLConn) addr() !net.Addr {
798 return net.addr_from_socket_handle(s.handle)
799}
800
801// peer_addr retrieves the ip address and port number used by the peer
802pub fn (s &SSLConn) peer_addr() !net.Addr {
803 return net.peer_addr_from_socket_handle(s.handle)
804}
805
806// socket_read_into_ptr reads `len` bytes into `buf`
807pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &u8, len int) !int {
808 mut res := 0
809 $if trace_ssl ? {
810 defer(fn) {
811 if len > 0 {
812 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) }}`')
813 }
814 }
815 }
816
817 deadline := ssl_timeout_deadline(s.duration)
818 // s.wait_for_read(deadline - time.now())!
819 for {
820 res = C.mbedtls_ssl_read(&s.ssl, buf_ptr, len)
821 if res > 0 {
822 return res
823 } else if res == 0 {
824 $if trace_ssl ? {
825 eprintln('${@METHOD} ---> res: io.Eof')
826 }
827 return io.Eof{}
828 } else {
829 match res {
830 C.MBEDTLS_ERR_SSL_WANT_READ {
831 s.wait_for_read(ssl_remaining_timeout(deadline)) or {
832 $if trace_ssl ? {
833 eprintln('${@METHOD} ---> res: ${err}, C.MBEDTLS_ERR_SSL_WANT_READ')
834 }
835 return err
836 }
837 }
838 C.MBEDTLS_ERR_SSL_WANT_WRITE {
839 s.wait_for_write(ssl_remaining_timeout(deadline)) or {
840 $if trace_ssl ? {
841 eprintln('${@METHOD} ---> res: ${err}, C.MBEDTLS_ERR_SSL_WANT_WRITE')
842 }
843 return err
844 }
845 }
846 C.MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET {
847 // TLS 1.3 servers can deliver tickets asynchronously while the
848 // connection is otherwise healthy. Keep reading application data.
849 continue
850 }
851 C.MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY {
852 $if trace_ssl ? {
853 eprintln('${@METHOD} ---> res: 0 C.MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY')
854 }
855 return 0
856 }
857 C.MBEDTLS_ERR_SSL_TIMEOUT {
858 $if trace_ssl ? {
859 eprintln('${@METHOD} ---> res: C.MBEDTLS_ERR_SSL_TIMEOUT')
860 }
861 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',
862 res)
863 }
864 else {
865 $if trace_ssl ? {
866 eprintln('${@METHOD} ---> res: could not read using SSL')
867 }
868 return error_with_code('net.mbedtls SSLConn.socket_read_into_ptr, could not read using SSL',
869 res)
870 }
871 }
872 }
873 }
874 // Dead code, just to satisfy the compiler:
875 return error('net.mbedtls SSLConn.socket_read_into_ptr, unknown error')
876}
877
878// read reads data from the ssl connection into `buffer`
879pub fn (mut s SSLConn) read(mut buffer []u8) !int {
880 $if trace_ssl ? {
881 eprintln('${@METHOD} buffer.len: ${buffer.len}')
882 }
883 return s.socket_read_into_ptr(&u8(buffer.data), buffer.len)
884}
885
886// write_ptr writes `len` bytes from `bytes` to the ssl connection
887pub fn (mut s SSLConn) write_ptr(bytes &u8, len int) !int {
888 mut total_sent := 0
889 $if trace_ssl ? {
890 defer(fn) {
891 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-=-=-=-')
892 }
893 }
894
895 s.last_write_sent = 0
896 deadline := ssl_timeout_deadline(s.duration)
897 unsafe {
898 mut ptr_base := bytes
899 for total_sent < len {
900 ptr := ptr_base + total_sent
901 remaining := len - total_sent
902 mut sent := C.mbedtls_ssl_write(&s.ssl, ptr, remaining)
903 if sent <= 0 {
904 // The write did not fully complete; a retryable error can leave a
905 // record partially flushed, so the sent count is no longer
906 // provable. Mark it indeterminate (a later full success below
907 // resets it to the exact length).
908 s.last_write_sent = -1
909 match sent {
910 C.MBEDTLS_ERR_SSL_WANT_READ {
911 s.wait_for_read(ssl_remaining_timeout(deadline))!
912 continue
913 }
914 C.MBEDTLS_ERR_SSL_WANT_WRITE {
915 s.wait_for_write(ssl_remaining_timeout(deadline))!
916 continue
917 }
918 else {
919 $if trace_ssl ? {
920 eprintln('${@METHOD} ---> res: could not write SSL, sent: ${sent}')
921 }
922 return error_with_code('net.mbedtls SSLConn.write_ptr, could not write using SSL',
923 sent)
924 }
925 }
926 }
927 total_sent += sent
928 s.last_write_sent = total_sent
929 }
930 }
931 return total_sent
932}
933
934// write writes data from `bytes` to the ssl connection
935pub fn (mut s SSLConn) write(bytes []u8) !int {
936 return s.write_ptr(&u8(bytes.data), bytes.len)
937}
938
939// write_string writes a string to the ssl connection
940pub fn (mut s SSLConn) write_string(str string) !int {
941 $if trace_ssl ? {
942 eprintln('${@METHOD} str: ${str}')
943 }
944 return s.write_ptr(str.str, str.len)
945}
946
947// Select waits for an io operation (specified by parameter `test`) to be available
948fn select(handle int, test Select, timeout time.Duration) !bool {
949 $if trace_ssl ? {
950 eprintln('${@METHOD} handle: ${handle}, timeout: ${timeout}')
951 }
952 set := C.fd_set{}
953 C.FD_ZERO(&set)
954 C.FD_SET(handle, &set)
955
956 is_infinite := timeout <= 0 || timeout == net.infinite_timeout
957 deadline := ssl_timeout_deadline(timeout)
958 mut remaining_time := if is_infinite { i64(0) } else { timeout.milliseconds() }
959 for is_infinite || remaining_time > 0 {
960 seconds := remaining_time / 1000
961 microseconds := (remaining_time % 1000) * 1000
962
963 tt := C.timeval{
964 tv_sec: u64(seconds)
965 tv_usec: u64(microseconds)
966 }
967 timeval_timeout := if is_infinite { &C.timeval(unsafe { nil }) } else { &tt }
968
969 mut res := -1
970 match test {
971 .read {
972 res = net.socket_error(C.select(handle + 1, &set, C.NULL, C.NULL, timeval_timeout))!
973 }
974 .write {
975 res = net.socket_error(C.select(handle + 1, C.NULL, &set, C.NULL, timeval_timeout))!
976 }
977 .except {
978 res = net.socket_error(C.select(handle + 1, C.NULL, C.NULL, &set, timeval_timeout))!
979 }
980 }
981
982 if res < 0 {
983 if C.errno == C.EINTR {
984 // errno is 4, Spurious wakeup from signal, keep waiting
985 if !is_infinite {
986 remaining_time = ssl_remaining_timeout(deadline).milliseconds()
987 }
988 continue
989 }
990 cerr := C.errno
991 return error_with_code('net.mbedtls select, failed, res: ${res}', cerr)
992 } else if res == 0 {
993 return net.err_timed_out
994 }
995
996 res = C.FD_ISSET(handle, &set)
997 $if trace_ssl ? {
998 eprintln('${@METHOD} ---> res: ${res}')
999 }
1000 return res != 0
1001 }
1002
1003 return net.err_timed_out
1004}
1005
1006// wait_for wraps the common wait code
1007fn wait_for(handle int, what Select, timeout time.Duration) ! {
1008 ready := select(handle, what, timeout)!
1009 if ready {
1010 return
1011 }
1012
1013 return net.err_timed_out
1014}
1015
1016// wait_for_write waits for a write io operation to be available
1017fn (mut s SSLConn) wait_for_write(timeout time.Duration) ! {
1018 return wait_for(s.handle, .write, timeout)
1019}
1020
1021// wait_for_read waits for a read io operation to be available
1022fn (mut s SSLConn) wait_for_read(timeout time.Duration) ! {
1023 return wait_for(s.handle, .read, timeout)
1024}
1025