v4 / vlib / net / openssl / ssl_connection.c.v
583 lines · 526 sloc · 16.86 KB · a58d3959a7ba248437ef2cd8de736013d675ba73
Raw
1module openssl
2
3import io
4import net
5import time
6import os
7
8// SSLConn is the current connection
9pub struct SSLConn {
10pub:
11 config SSLConnectConfig
12pub mut:
13 sslctx &C.SSL_CTX = unsafe { nil }
14 ssl &C.SSL = unsafe { nil }
15 handle int
16 duration time.Duration
17
18 owns_socket bool
19 // last_write_sent reports the most recent write_ptr's progress for retry
20 // decisions: 0 = provably nothing was sent (safe to replay), or -1 = the
21 // count is indeterminate because a failed/retryable write may have already
22 // flushed complete records to the peer (TLS cannot prove zero). On full
23 // success it equals the bytes written.
24 last_write_sent int
25}
26
27@[params]
28pub struct SSLConnectConfig {
29pub:
30 verify string // the path to a rootca.pem file, containing trusted CA certificate(s)
31 cert string // the path to a cert.pem file, containing client certificate(s) for the request
32 cert_key string // the path to a key.pem file, containing private keys for the client certificate(s)
33 validate bool // set this to true, if you want to stop requests, when their certificates are found to be invalid
34
35 in_memory_verification bool // if true, verify, cert, and cert_key are read from memory, not from a file
36
37 alpn_protocols []string // the list of ALPN protocols to advertise, e.g. ['h2', 'http/1.1']; empty means no ALPN extension is sent
38}
39
40// new_ssl_conn instance an new SSLCon struct
41pub fn new_ssl_conn(config SSLConnectConfig) !&SSLConn {
42 $if trace_ssl ? {
43 eprintln(@METHOD)
44 }
45 mut conn := &SSLConn{
46 config: config
47 sslctx: unsafe { nil }
48 ssl: unsafe { nil }
49 handle: 0
50 }
51 conn.init() or { return err }
52 return conn
53}
54
55// Select operation
56enum Select {
57 read
58 write
59 except
60}
61
62fn ssl_timeout_deadline(timeout time.Duration) time.Time {
63 if timeout <= 0 || timeout == net.infinite_timeout {
64 return time.unix(0)
65 }
66 return time.now().add(timeout)
67}
68
69fn ssl_remaining_timeout(deadline time.Time) time.Duration {
70 if deadline.unix() == 0 {
71 return net.infinite_timeout
72 }
73 return deadline - time.now()
74}
75
76// close closes the ssl connection and does cleanup
77pub fn (mut s SSLConn) close() ! {
78 s.shutdown()!
79}
80
81// negotiated_alpn returns the ALPN protocol selected during the TLS
82// handshake (e.g. 'h2' or 'http/1.1'), or an empty string if no protocol
83// was negotiated.
84pub fn (s &SSLConn) negotiated_alpn() string {
85 mut data := &u8(unsafe { nil })
86 mut length := u32(0)
87 C.v_net_openssl_get0_alpn_selected(voidptr(s.ssl), voidptr(&data), &length)
88 if length == 0 || data == unsafe { nil } {
89 return ''
90 }
91 // data points into OpenSSL-owned memory and is not NUL-terminated; copy it.
92 return unsafe { data.vbytes(int(length)).bytestr() }
93}
94
95// shutdown closes the ssl connection and does cleanup
96pub fn (mut s SSLConn) shutdown() ! {
97 $if trace_ssl ? {
98 eprintln(@METHOD)
99 }
100
101 if s.ssl != unsafe { nil } {
102 deadline := ssl_timeout_deadline(s.duration)
103 mut shutdown_done := false
104
105 for !shutdown_done {
106 // Clear the thread's OpenSSL error queue so ssl_error()/SSL_get_error()
107 // below reflect only this call and not a stale entry from an earlier
108 // operation, which could be misread as a fatal SSL_ERROR_SSL.
109 C.ERR_clear_error()
110 res := C.SSL_shutdown(voidptr(s.ssl))
111 if res == 1 {
112 shutdown_done = true
113 break
114 }
115
116 // res == 0 means our close_notify was sent, but the peer's has not
117 // been received yet, so another SSL_shutdown() call is needed to
118 // finish the bidirectional shutdown; res < 0 signals an error or a
119 // retryable condition. In both cases SSL_get_error() tells us whether
120 // the socket must first become readable/writable before retrying.
121 // Routing res == 0 through ssl_error() instead of looping immediately
122 // avoids busy-spinning on non-blocking sockets.
123 err_res := ssl_error(res, s.ssl) or { break }
124
125 match err_res {
126 .ssl_error_want_read {
127 s.wait_for_read(ssl_remaining_timeout(deadline)) or { break }
128 }
129 .ssl_error_want_write {
130 s.wait_for_write(ssl_remaining_timeout(deadline)) or { break }
131 }
132 else {
133 break
134 }
135 }
136 }
137
138 // Always free SSL object first.
139 if s.ssl != unsafe { nil } {
140 unsafe { C.SSL_free(voidptr(s.ssl)) }
141 s.ssl = unsafe { nil }
142 }
143 }
144
145 if s.sslctx != unsafe { nil } {
146 C.SSL_CTX_free(s.sslctx)
147 s.sslctx = unsafe { nil }
148 }
149
150 if s.owns_socket {
151 net.shutdown(s.handle)
152 net.close(s.handle)!
153 }
154}
155
156fn (mut s SSLConn) init() ! {
157 $if trace_ssl ? {
158 eprintln(@METHOD)
159 }
160 s.sslctx = unsafe { C.SSL_CTX_new(C.SSLv23_client_method()) }
161 if s.sslctx == 0 {
162 return error('net.openssl Could not get ssl context')
163 }
164
165 if s.config.validate {
166 C.SSL_CTX_set_verify_depth(s.sslctx, 4)
167 C.SSL_CTX_set_options(s.sslctx, C.SSL_OP_NO_COMPRESSION)
168 }
169
170 s.ssl = unsafe { &C.SSL(C.SSL_new(s.sslctx)) }
171 if s.ssl == 0 {
172 return error('net.openssl Could not create OpenSSL instance')
173 }
174
175 mut res := 0
176
177 // Advertise ALPN protocols (e.g. ['h2', 'http/1.1']) when requested.
178 // OpenSSL expects the length-prefixed wire format: each protocol is a
179 // single length byte followed by that many bytes of protocol name.
180 if s.config.alpn_protocols.len > 0 {
181 mut wire := []u8{cap: 64}
182 for proto in s.config.alpn_protocols {
183 if proto.len == 0 || proto.len > 255 {
184 return error('net.openssl SSLConn.init, invalid ALPN protocol "${proto}"')
185 }
186 wire << u8(proto.len)
187 wire << proto.bytes()
188 }
189 // Returns 0 on success (opposite of most OpenSSL calls); non-zero also
190 // means ALPN is unavailable on OpenSSL versions older than 1.0.2.
191 if C.v_net_openssl_set_alpn_protos(voidptr(s.ssl), wire.data, u32(wire.len)) != 0 {
192 return error('net.openssl SSLConn.init, failed to set ALPN protocols (requires OpenSSL >= 1.0.2)')
193 }
194 }
195
196 if s.config.validate {
197 mut verify := s.config.verify
198 mut cert := s.config.cert
199 mut cert_key := s.config.cert_key
200 if s.config.in_memory_verification {
201 now := time.now().unix().str()
202 verify = os.temp_dir() + '/v_verify' + now
203 cert = os.temp_dir() + '/v_cert' + now
204 cert_key = os.temp_dir() + '/v_cert_key' + now
205 if s.config.verify != '' {
206 os.write_file(verify, s.config.verify)!
207 }
208 if s.config.cert != '' {
209 os.write_file(cert, s.config.cert)!
210 }
211 if s.config.cert_key != '' {
212 os.write_file(cert_key, s.config.cert_key)!
213 }
214 }
215 if s.config.verify != '' {
216 res = C.SSL_CTX_load_verify_locations(voidptr(s.sslctx), &char(verify.str), 0)
217 if s.config.validate && res != 1 {
218 return error('net.openssl SSLConn.init, SSL_CTX_load_verify_locations failed')
219 }
220 }
221 if s.config.cert != '' {
222 res = C.SSL_CTX_use_certificate_file(voidptr(s.sslctx), &char(cert.str),
223 C.SSL_FILETYPE_PEM)
224 if s.config.validate && res != 1 {
225 return error('net.openssl SSLConn.init, SSL_CTX_use_certificate_file failed, res: ${res}')
226 }
227 }
228 if s.config.cert_key != '' {
229 res = C.SSL_CTX_use_PrivateKey_file(voidptr(s.sslctx), &char(cert_key.str),
230 C.SSL_FILETYPE_PEM)
231 if s.config.validate && res != 1 {
232 return error('net.openssl SSLConn.init, SSL_CTX_use_PrivateKey_file failed, res: ${res}')
233 }
234 }
235
236 preferred_ciphers := 'HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4'
237 res = C.SSL_set_cipher_list(voidptr(s.ssl), &char(preferred_ciphers.str))
238 if s.config.validate && res != 1 {
239 println('net.openssl: set cipher failed')
240 }
241 }
242}
243
244// connect to server using OpenSSL
245pub fn (mut s SSLConn) connect(mut tcp_conn net.TcpConn, hostname string) ! {
246 $if trace_ssl ? {
247 eprintln('${@METHOD} hostname: ${hostname}')
248 }
249 s.handle = tcp_conn.sock.handle
250 s.duration = tcp_conn.read_timeout()
251 mut res := C.SSL_set_tlsext_host_name(voidptr(s.ssl), voidptr(hostname.str))
252 if res != 1 {
253 return error('net.openssl SSLConn.connect, could not set host name')
254 }
255 if C.SSL_set_fd(voidptr(s.ssl), tcp_conn.sock.handle) != 1 {
256 return error('net.openssl SSLConn.connect, could not assign ssl to socket.')
257 }
258 s.complete_connect()!
259}
260
261// dial opens an ssl connection on hostname:port
262pub fn (mut s SSLConn) dial(hostname string, port int) ! {
263 $if trace_ssl ? {
264 eprintln('${@METHOD} hostname: ${hostname} | port: ${port}')
265 }
266 mut tcp_conn := net.dial_tcp('${hostname}:${port}') or { return err }
267 mut connected := false
268 defer {
269 if !connected {
270 tcp_conn.close() or {}
271 if s.ssl != 0 {
272 unsafe { C.SSL_free(voidptr(s.ssl)) }
273 s.ssl = unsafe { nil }
274 }
275 if s.sslctx != 0 {
276 C.SSL_CTX_free(s.sslctx)
277 s.sslctx = unsafe { nil }
278 }
279 s.handle = 0
280 s.owns_socket = false
281 }
282 }
283 s.owns_socket = true
284 s.connect(mut tcp_conn, hostname) or { return err }
285 connected = true
286}
287
288fn (mut s SSLConn) complete_connect() ! {
289 $if trace_ssl ? {
290 eprintln(@METHOD)
291 }
292
293 deadline := ssl_timeout_deadline(s.duration)
294 for {
295 // Clear the error queue so SSL_get_error() reflects only this call.
296 C.ERR_clear_error()
297 mut res := C.SSL_connect(voidptr(s.ssl))
298 if res == 1 {
299 break
300 }
301
302 err_res := ssl_error(res, s.ssl)!
303 if err_res == .ssl_error_want_read {
304 s.wait_for_read(ssl_remaining_timeout(deadline))!
305 continue
306 }
307 if err_res == .ssl_error_want_write {
308 s.wait_for_write(ssl_remaining_timeout(deadline))!
309 continue
310 }
311 return error('net.openssl SSLConn.complete_connect, could not connect using SSL. (${err_res}),err')
312 }
313
314 if s.config.validate {
315 mut pcert := &C.X509(unsafe { nil })
316 for {
317 // Clear the error queue so SSL_get_error() reflects only this call.
318 C.ERR_clear_error()
319 mut res := C.SSL_do_handshake(voidptr(s.ssl))
320 if res == 1 {
321 break
322 }
323
324 err_res := ssl_error(res, s.ssl)!
325 if err_res == .ssl_error_want_read {
326 s.wait_for_read(ssl_remaining_timeout(deadline))!
327 continue
328 } else if err_res == .ssl_error_want_write {
329 s.wait_for_write(ssl_remaining_timeout(deadline))!
330 continue
331 }
332 return error('net.openssl SSLConn.complete_connect, could not validate SSL certificate. (${err_res}),err')
333 }
334 pcert = C.v_net_openssl_get1_peer_certificate(s.ssl)
335 defer {
336 if pcert != 0 {
337 C.X509_free(pcert)
338 }
339 }
340 res := C.SSL_get_verify_result(voidptr(s.ssl))
341 if res != C.X509_V_OK {
342 return error('net.openssl SSLConn.complete_connect, failed SSL handshake (OpenSSL SSL_get_verify_result = ${res})')
343 }
344 }
345}
346
347// read_timeout returns the current SSL read timeout.
348pub fn (s &SSLConn) read_timeout() time.Duration {
349 return s.duration
350}
351
352// set_read_timeout sets the read timeout used for subsequent SSL reads.
353// A value of 0, or net.infinite_timeout means "wait forever".
354pub fn (mut s SSLConn) set_read_timeout(timeout time.Duration) {
355 s.duration = timeout
356}
357
358// addr retrieves the local ip address and port number for this connection
359pub fn (s &SSLConn) addr() !net.Addr {
360 return net.addr_from_socket_handle(s.handle)
361}
362
363// peer_addr retrieves the ip address and port number used by the peer
364pub fn (s &SSLConn) peer_addr() !net.Addr {
365 return net.peer_addr_from_socket_handle(s.handle)
366}
367
368pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &u8, len int) !int {
369 mut res := 0
370 $if trace_ssl ? {
371 defer(fn) {
372 if len > 0 {
373 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) }}`')
374 }
375 }
376 }
377
378 deadline := ssl_timeout_deadline(s.duration)
379 // s.wait_for_read(deadline - time.now())!
380 for {
381 // Clear the error queue so SSL_get_error() reflects only this call.
382 C.ERR_clear_error()
383 res = C.SSL_read(voidptr(s.ssl), buf_ptr, len)
384 if res > 0 {
385 return res
386 } else if res == 0 {
387 $if trace_ssl ? {
388 eprintln('${@METHOD} ---> res: io.Eof')
389 }
390 return io.Eof{}
391 } else {
392 err_res := ssl_error(res, s.ssl)!
393 match err_res {
394 .ssl_error_want_read {
395 s.wait_for_read(ssl_remaining_timeout(deadline)) or {
396 $if trace_ssl ? {
397 eprintln('${@METHOD} ---> res: ${err} .ssl_error_want_read')
398 }
399 return err
400 }
401 }
402 .ssl_error_want_write {
403 s.wait_for_write(ssl_remaining_timeout(deadline)) or {
404 $if trace_ssl ? {
405 eprintln('${@METHOD} ---> res: ${err} .ssl_error_want_write')
406 }
407 return err
408 }
409 }
410 .ssl_error_zero_return {
411 $if trace_ssl ? {
412 eprintln('${@METHOD} ---> res: 0 .ssl_error_zero_return')
413 }
414 return 0
415 }
416 else {
417 $if trace_ssl ? {
418 eprintln('${@METHOD} ---> res: could not read, err_res: ${err_res}')
419 }
420 return error('net.openssl SSLConn.socket_read_into_ptr, could not read. (${err_res})')
421 }
422 }
423 }
424 }
425
426 // Dead code, to satisfy the compiler
427 return error('net.openssl SSLConn.socket_read_into_ptr, unknown error')
428}
429
430pub fn (mut s SSLConn) read(mut buffer []u8) !int {
431 $if trace_ssl ? {
432 eprintln('${@METHOD} buffer.len: ${buffer.len}')
433 }
434 return s.socket_read_into_ptr(&u8(buffer.data), buffer.len)
435}
436
437// write_ptr writes `len` bytes from `bytes` to the ssl connection
438pub fn (mut s SSLConn) write_ptr(bytes &u8, len int) !int {
439 mut total_sent := 0
440 $if trace_ssl ? {
441 defer(fn) {
442 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-=-=-=-')
443 }
444 }
445
446 s.last_write_sent = 0
447 deadline := ssl_timeout_deadline(s.duration)
448 unsafe {
449 mut ptr_base := bytes
450 for total_sent < len {
451 ptr := ptr_base + total_sent
452 remaining := len - total_sent
453 // Clear the error queue so SSL_get_error() reflects only this call.
454 C.ERR_clear_error()
455 mut sent := C.SSL_write(voidptr(s.ssl), ptr, remaining)
456 if sent <= 0 {
457 // SSL_write did not fully complete: OpenSSL may already have
458 // flushed one or more complete records (which the peer can
459 // decrypt and act on) before returning a retryable error, so the
460 // sent count is no longer provable. Mark it indeterminate; a
461 // later full success below resets it to the exact length.
462 s.last_write_sent = -1
463 err_res := ssl_error(sent, s.ssl)!
464 if err_res == .ssl_error_want_read {
465 s.wait_for_read(ssl_remaining_timeout(deadline))!
466 continue
467 } else if err_res == .ssl_error_want_write {
468 s.wait_for_write(ssl_remaining_timeout(deadline))!
469 continue
470 } else if err_res == .ssl_error_zero_return {
471 $if trace_ssl ? {
472 eprintln('${@METHOD} ---> res: ssl write on closed connection .ssl_error_zero_return')
473 }
474 return error('net.openssl SSLConn.write_ptr, ssl write on closed connection') // TODO: error_with_code close
475 }
476 $if trace_ssl ? {
477 eprintln('${@METHOD} ---> res: could not write SSL, err_res: ${err_res}')
478 }
479 return error_with_code('net.openssl SSLConn.write_ptr, could not write. (${err_res}),err',
480 int(err_res))
481 }
482 total_sent += sent
483 s.last_write_sent = total_sent
484 }
485 }
486 return total_sent
487}
488
489// write writes data from `bytes` to the ssl connection
490pub fn (mut s SSLConn) write(bytes []u8) !int {
491 return s.write_ptr(&u8(bytes.data), bytes.len)!
492}
493
494// write_string writes a string to the ssl connection
495pub fn (mut s SSLConn) write_string(str string) !int {
496 $if trace_ssl ? {
497 eprintln('${@METHOD} str: ${str}')
498 }
499 return s.write_ptr(str.str, str.len)
500}
501
502// Select waits for an io operation (specified by parameter `test`) to be available
503fn select(handle int, test Select, timeout time.Duration) !bool {
504 $if trace_ssl ? {
505 eprintln('${@METHOD} handle: ${handle}, timeout: ${timeout}')
506 }
507 set := C.fd_set{}
508 C.FD_ZERO(&set)
509 C.FD_SET(handle, &set)
510
511 is_infinite := timeout <= 0 || timeout == net.infinite_timeout
512 deadline := ssl_timeout_deadline(timeout)
513 mut remaining_time := if is_infinite { i64(0) } else { timeout.milliseconds() }
514 for is_infinite || remaining_time > 0 {
515 seconds := remaining_time / 1000
516 microseconds := (remaining_time % 1000) * 1000
517
518 tt := C.timeval{
519 tv_sec: u64(seconds)
520 tv_usec: u64(microseconds)
521 }
522 timeval_timeout := if is_infinite {
523 &C.timeval(unsafe { nil })
524 } else {
525 &tt
526 }
527
528 mut res := -1
529 match test {
530 .read {
531 res = net.socket_error(C.select(handle + 1, &set, C.NULL, C.NULL, timeval_timeout))!
532 }
533 .write {
534 res = net.socket_error(C.select(handle + 1, C.NULL, &set, C.NULL, timeval_timeout))!
535 }
536 .except {
537 res = net.socket_error(C.select(handle + 1, C.NULL, C.NULL, &set, timeval_timeout))!
538 }
539 }
540
541 if res < 0 {
542 if C.errno == C.EINTR {
543 // errno is 4, Spurious wakeup from signal, keep waiting
544 if !is_infinite {
545 remaining_time = ssl_remaining_timeout(deadline).milliseconds()
546 }
547 continue
548 }
549 cerr := C.errno
550 return error_with_code('net.openssl select, failed: ${res}', cerr)
551 } else if res == 0 {
552 return net.err_timed_out
553 }
554
555 res = C.FD_ISSET(handle, &set)
556 $if trace_ssl ? {
557 eprintln('${@METHOD} ---> res: ${res}')
558 }
559 return res != 0
560 }
561
562 return net.err_timed_out
563}
564
565// wait_for wraps the common wait code
566fn wait_for(handle int, what Select, timeout time.Duration) ! {
567 ready := select(handle, what, timeout)!
568 if ready {
569 return
570 }
571
572 return net.err_timed_out
573}
574
575// wait_for_write waits for a write io operation to be available
576fn (mut s SSLConn) wait_for_write(timeout time.Duration) ! {
577 return wait_for(s.handle, .write, timeout)
578}
579
580// wait_for_read waits for a read io operation to be available
581fn (mut s SSLConn) wait_for_read(timeout time.Duration) ! {
582 return wait_for(s.handle, .read, timeout)
583}
584